Как уменьшить количество цветов в изображении с OpenCV? - PullRequest
20 голосов
/ 06 мая 2011

У меня есть набор файлов изображений, и я хочу уменьшить их количество до 64. Как я могу сделать это с OpenCV?

Мне это нужно, чтобы я мог работать с 64-гистограмма изображения.Я внедряю методы CBIR

Что мне нужно, так это квантование цвета в 4-битную палитру.

Ответы [ 7 ]

11 голосов
/ 21 декабря 2013

Эта тема была хорошо освещена в Руководстве по программированию приложений OpenCV 2 Computer Vision :

image

Глава 2 показывает несколько операций сокращения, одна из которых продемонстрирована здесь, в C ++:

#include <iostream>
#include <vector>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>


void colorReduce(cv::Mat& image, int div=64)
{    
    int nl = image.rows;                    // number of lines
    int nc = image.cols * image.channels(); // number of elements per line

    for (int j = 0; j < nl; j++)
    {
        // get the address of row j
        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++)
        {
            // process each pixel
            data[i] = data[i] / div * div + div / 2;
        }
    }
}

int main(int argc, char* argv[])
{   
    // Load input image (colored, 3-channel, BGR)
    cv::Mat input = cv::imread(argv[1]);
    if (input.empty())
    {
        std::cout << "!!! Failed imread()" << std::endl;
        return -1;
    } 

    colorReduce(input);

    cv::imshow("Color Reduction", input);   
    cv::imwrite("output.jpg", input);   
    cv::waitKey(0);

    return 0;
}

Ниже вы можете найти входное изображение (слева) и выходное этой операции (справа):

image image

10 голосов
/ 23 ноября 2013

Вы могли бы рассмотреть K-средства, но в этом случае это, скорее всего, будет очень медленно.Лучшим подходом может быть сделать это «вручную» самостоятельно.Допустим, у вас есть изображение типа CV_8UC3, то есть изображение, где каждый пиксель представлен 3 значениями RGB от 0 до 255 (Vec3b).Вы можете «сопоставить» эти 256 значений только с 4 конкретными значениями, что даст 4 x 4 x 4 = 64 возможных цветов.

У меня был набор данных, в котором мне нужно было убедиться, что dark = black, свет = белый и уменьшите количество цветов между ними.Вот что я сделал (C ++):

inline uchar reduceVal(const uchar val)
{
    if (val < 64) return 0;
    if (val < 128) return 64;
    return 255;
}

void processColors(Mat& img)
{
    uchar* pixelPtr = img.data;
    for (int i = 0; i < img.rows; i++)
    {
        for (int j = 0; j < img.cols; j++)
        {
            const int pi = i*img.cols*3 + j*3;
            pixelPtr[pi + 0] = reduceVal(pixelPtr[pi + 0]); // B
            pixelPtr[pi + 1] = reduceVal(pixelPtr[pi + 1]); // G
            pixelPtr[pi + 2] = reduceVal(pixelPtr[pi + 2]); // R
        }
    }
}

, в результате чего [0,64) становится 0, [64,128) -> 64 и [128,255) -> 255, давая 27цвета:

enter image description here enter image description here

Мне кажется, это аккуратно, совершенно ясно и быстрее, чем что-либо еще упомянутое в других ответах.

Вы могли бы также рассмотретьуменьшив эти значения до одного из кратных некоторого числа, скажем:

inline uchar reduceVal(const uchar val)
{
    if (val < 192) return uchar(val / 64.0 + 0.5) * 64;
    return 255;
}

, что даст набор из 5 возможных значений: {0, 64, 128, 192, 255}, то есть 125 цветов.

9 голосов
/ 16 апреля 2012

Есть много способов сделать это.Методы, предложенные jeff7, в порядке, но есть некоторые недостатки:

  • метод 1 имеет параметры N и M, которые вы должны выбрать, и вы также должны преобразовать его в другое цветовое пространство.
  • ответ по методу 2 может быть очень медленным, так как вы должны вычислить гистограмму бинов с разрешением 16,7 млн ​​и отсортировать ее по частоте (чтобы получить 64 более высоких значения частоты)

Мне нравится использовать алгоритм, основанный на Наиболее значимые биты для использования в цвете RGB и преобразования его в 64-цветное изображение.Если вы используете C / OpenCV, вы можете использовать что-то похожее на приведенную ниже функцию.

Если вы работаете с изображениями серого уровня, я рекомендовал использовать функцию LUT () OpenCV 2.3, посколькубыстрее.Существует учебник о том, как использовать LUT для уменьшения количества цветов.См .: Учебное пособие: Как сканировать изображения, справочные таблицы ... Однако я нахожу это более сложным, если вы работаете с изображениями RGB.

void reduceTo64Colors(IplImage *img, IplImage *img_quant) {
    int i,j;
    int height   = img->height;   
    int width    = img->width;    
    int step     = img->widthStep;

    uchar *data = (uchar *)img->imageData;
    int step2 = img_quant->widthStep;
    uchar *data2 = (uchar *)img_quant->imageData;

    for (i = 0; i < height ; i++)  {
        for (j = 0; j < width; j++)  {

          // operator XXXXXXXX & 11000000 equivalent to  XXXXXXXX AND 11000000 (=192)
          // operator 01000000 >> 2 is a 2-bit shift to the right = 00010000 
          uchar C1 = (data[i*step+j*3+0] & 192)>>2;
          uchar C2 = (data[i*step+j*3+1] & 192)>>4;
          uchar C3 = (data[i*step+j*3+2] & 192)>>6;

          data2[i*step2+j] = C1 | C2 | C3; // merges the 2 MSB of each channel
        }     
    }
}
4 голосов
/ 11 октября 2016

Ответы, предложенные здесь, действительно хороши. Я думал, что я тоже добавлю свою идею. Я придерживаюсь формулировки многих комментариев, в которых говорится, что 64 цвета могут быть представлены 2 битами каждого канала в изображении RGB.

Функция в коде ниже принимает в качестве входных данных изображение и количество битов, необходимых для квантования. Он использует битовые манипуляции для «отбрасывания» битов LSB и сохранения только необходимого количества битов. Результатом является гибкий метод, который может квантовать изображение на любое количество битов.

#include "include\opencv\cv.h"
#include "include\opencv\highgui.h"

// quantize the image to numBits 
cv::Mat quantizeImage(const cv::Mat& inImage, int numBits)
{
    cv::Mat retImage = inImage.clone();

    uchar maskBit = 0xFF;

    // keep numBits as 1 and (8 - numBits) would be all 0 towards the right
    maskBit = maskBit << (8 - numBits);

    for(int j = 0; j < retImage.rows; j++)
        for(int i = 0; i < retImage.cols; i++)
        {
            cv::Vec3b valVec = retImage.at<cv::Vec3b>(j, i);
            valVec[0] = valVec[0] & maskBit;
            valVec[1] = valVec[1] & maskBit;
            valVec[2] = valVec[2] & maskBit;
            retImage.at<cv::Vec3b>(j, i) = valVec;
        }

        return retImage;
}


int main ()
{
    cv::Mat inImage;
    inImage = cv::imread("testImage.jpg");
    char buffer[30];
    for(int i = 1; i <= 8; i++)
    {
        cv::Mat quantizedImage = quantizeImage(inImage, i);
        sprintf(buffer, "%d Bit Image", i);
        cv::imshow(buffer, quantizedImage);

        sprintf(buffer, "%d Bit Image.png", i);
        cv::imwrite(buffer, quantizedImage);
    }

    cv::waitKey(0);
    return 0;
}

Вот изображение, которое используется в вызове функции выше:

enter image description here

Изображение квантовано на 2 бита для каждого канала RGB (всего 64 цвета):

enter image description here

3 бита для каждого канала:

enter image description here

4 бита ...

enter image description here

3 голосов
/ 07 мая 2011

Существует алгоритм кластеризации K-средних, который уже доступен в библиотеке OpenCV.Короче говоря, он определяет, какие центроиды являются лучшими вокруг которых кластеризовать ваши данные для определенного пользователем значения k (= нет кластеров).Так что в вашем случае вы могли бы найти центроиды, вокруг которых можно кластеризовать ваши значения пикселей для заданного значения k = 64.Подробности есть, если вы гуглите. Здесь краткое введение в k-means.

Нечто похожее на то, что вы, вероятно, пытаетесь, было задано здесь на SO с использованием k-средних, надеюсь, это поможет.

Другой подход - использование Фильтр среднего смещения пирамиды функция в OpenCV.Он дает несколько «сплющенные» изображения, т. Е. Количество цветов меньше, поэтому он может вам помочь.

1 голос
/ 06 мая 2011

Предполагая, что вы хотите использовать одинаковые 64 цвета для всех изображений (т. Е. Палитра не оптимизирована для каждого изображения), есть как минимум пара вариантов, о которых я могу подумать:

1) Преобразовать в цветовое пространство Lab или YCrCb и квантовать, используя N битов для яркости и M битов для каждого цветового канала, N должно быть больше, чем M.

2) Вычислите трехмерную гистограмму значений цвета для всех ваших тренировочных изображений, затем выберите 64 цвета с самыми большими значениями ячейки. Квантовайте свои изображения, назначая каждому пикселю цвет ближайшего бина из обучающего набора.

Метод 1 является наиболее общим и простым в реализации, тогда как метод 2 может быть лучше адаптирован к вашему конкретному набору данных.

Обновление: Например, 32 цвета - это 5 битов, поэтому назначьте 3 бита для канала яркости и 1 бит для каждого цветового канала. Чтобы сделать это квантование, выполните целочисленное деление канала яркости на 2 ^ 8/2 ^ 3 = 32 и каждого цветового канала на 2 ^ 8/2 ^ 1 = 128. Теперь есть только 8 разных значений яркости и 2 разных цветовых канала. каждый. Объедините эти значения в одно целое число, выполняя битовое смещение или математическое вычисление (квантованное значение цвета = яркость * 4 + цвет1 * 2 + цвет2);

0 голосов
/ 29 декабря 2012

Почему бы вам просто не сделать умножение / деление матрицы? Значения будут автоматически округлены.

псевдокод:

конвертировать ваши каналы в неподписанные символы (CV_8UC3),
Поделить на Всего цветов / желаемых цветов. Мат = Мат / (256/64). Десятичные точки будет усечено.
Умножьте на тот же номер. Мат = коврик * 4

Готово. Каждый канал теперь содержит только 64 цвета.

...