Определение доминирующих цветов на изображении — очень полезная техника, например для выбора палитры веб-сайта, элементов UI и т.п.
Существуют и online-сервисы, решающие подобную задачу самыми разными методами.
Однако, попробуем решить данную задачку самостоятельно при помощи OpenCV.
Рассмотрим два способа:
— определение отношения пикселя к заданному набору цветов (в пространстве HSV)
— использовании кластеризации методом k-средних (k-means).
Первый способ довольно прост — мы просто конвертируем картинку в цветовое пространство HSV, а затем перебираем все пиксели изображения и по его значениям H, S и V при помощи функции getPixelColorType() определяем к какому цвету из заданого перечисления:
enum {cBLACK=0, cWHITE, cGREY, cRED, cORANGE, cYELLOW, cGREEN, cAQUA, cBLUE, cPURPLE, NUM_COLOR_TYPES};
относится цвет.
При этом, число таких пикселей подсчитывается. Сохраняются и RGB-составляющие, для последующего усреднения.
Исходный код:
// // определение преобладающих цветов на изображении // через пространство HSV // + накопление данных по соответствующим цветам из RGB #include <cv.h> #include <highgui.h> #include <stdlib.h> #include <stdio.h> #include <vector> #include <algorithm> #define RECT_COLORS_SIZE 10 // получение пикселя изображения (по типу картинки и координатам) #define CV_PIXEL(type,img,x,y) (((type*)((img)->imageData+(y)*(img)->widthStep))+(x)*(img)->nChannels) // Various color types // 0 1 2 3 4 5 6 7 8 9 10 enum {cBLACK=0, cWHITE, cGREY, cRED, cORANGE, cYELLOW, cGREEN, cAQUA, cBLUE, cPURPLE, NUM_COLOR_TYPES}; char* sCTypes[NUM_COLOR_TYPES] = {"Black", "White","Grey","Red","Orange","Yellow","Green","Aqua","Blue","Purple"}; uchar cCTHue[NUM_COLOR_TYPES] = {0, 0, 0, 0, 20, 30, 60, 85, 120, 138 }; uchar cCTSat[NUM_COLOR_TYPES] = {0, 0, 0, 255, 255, 255, 255, 255, 255, 255 }; uchar cCTVal[NUM_COLOR_TYPES] = {0, 255, 120, 255, 255, 255, 255, 255, 255, 255 }; typedef unsigned int uint; // число пикселей данного цвета на изображении uint colorCount[NUM_COLOR_TYPES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Determine what type of color the HSV pixel is. Returns the colorType between 0 and NUM_COLOR_TYPES. int getPixelColorType(int H, int S, int V) { int color = cBLACK; #if 1 if (V < 75) color = cBLACK; else if (V > 190 && S < 27) color = cWHITE; else if (S < 53 && V < 185) color = cGREY; else #endif { if (H < 7) color = cRED; else if (H < 25) color = cORANGE; else if (H < 34) color = cYELLOW; else if (H < 73) color = cGREEN; else if (H < 102) color = cAQUA; else if (H < 140) color = cBLUE; else if (H < 170) color = cPURPLE; else // full circle color = cRED; // back to Red } return color; } // сортировка цветов по количеству bool colors_sort(std::pair< int, uint > a, std::pair< int, uint > b) { return (a.second > b.second); } int main(int argc, char* argv[]) { // для хранения изображения IplImage* image=0, *hsv=0, *dst=0, *dst2=0, *color_indexes=0, *dst3=0; // // загрузка изображения // char img_name[] = "Image0.jpg"; // имя картинки задаётся первым параметром char* image_filename = argc >= 2 ? argv[1] : img_name; // получаем картинку image = cvLoadImage(image_filename, 1); printf("[i] image: %s\n", image_filename); if(!image){ printf("[!] Error: cant load test image: %s\n", image_filename); return -1; } // показываем картинку cvNamedWindow("image"); cvShowImage("image", image); // // преобразуем изображение в HSV // hsv = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 3 ); cvCvtColor( image, hsv, CV_BGR2HSV ); // картинки для хранения результатов dst = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 3 ); dst2 = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 3 ); color_indexes = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 1 ); //для хранения индексов цвета // для хранения RGB-х цветов CvScalar rgb_colors[NUM_COLOR_TYPES]; int i=0, j=0, x=0, y=0; // обнуляем цвета for(i=0; i<NUM_COLOR_TYPES; i++) { rgb_colors[i] = cvScalarAll(0); } for (y=0; y<hsv->height; y++) { for (x=0; x<hsv->width; x++) { // получаем HSV-компоненты пикселя uchar H = CV_PIXEL(uchar, hsv, x, y)[0]; // Hue uchar S = CV_PIXEL(uchar, hsv, x, y)[1]; // Saturation uchar V = CV_PIXEL(uchar, hsv, x, y)[2]; // Value (Brightness) // определяем к какому цвету можно отнести данные значения int ctype = getPixelColorType(H, S, V); // устанавливаем этот цвет у отладочной картинки CV_PIXEL(uchar, dst, x, y)[0] = cCTHue[ctype]; // Hue CV_PIXEL(uchar, dst, x, y)[1] = cCTSat[ctype]; // Saturation CV_PIXEL(uchar, dst, x, y)[2] = cCTVal[ctype]; // Value // собираем RGB-составляющие rgb_colors[ctype].val[0] += CV_PIXEL(uchar, image, x, y)[0]; // B rgb_colors[ctype].val[1] += CV_PIXEL(uchar, image, x, y)[1]; // G rgb_colors[ctype].val[2] += CV_PIXEL(uchar, image, x, y)[2]; // R // сохраняем к какому типу относится цвет CV_PIXEL(uchar, color_indexes, x, y)[0] = ctype; // подсчитываем :) colorCount[ctype]++; } } // усреднение RGB-составляющих for(i=0; i<NUM_COLOR_TYPES; i++) { rgb_colors[i].val[0] /= colorCount[i]; rgb_colors[i].val[1] /= colorCount[i]; rgb_colors[i].val[2] /= colorCount[i]; } // теперь загоним массив в вектор и отсортируем :) std::vector< std::pair< int, uint > > colors; colors.reserve(NUM_COLOR_TYPES); for(i=0; i<NUM_COLOR_TYPES; i++){ std::pair< int, uint > color; color.first = i; color.second = colorCount[i]; colors.push_back( color ); } // сортируем std::sort( colors.begin(), colors.end(), colors_sort ); // для отладки - выводим коды, названия цветов и их количество for(i=0; i<colors.size(); i++){ printf("[i] color %d (%s) - %d\n", colors[i].first, sCTypes[colors[i].first], colors[i].second ); } // выдаём код первых цветов printf("[i] color code: \n"); for(i=0; i<NUM_COLOR_TYPES; i++) printf("%02d ", colors[i].first); printf("\n"); printf("[i] color names: \n"); for(i=0; i<NUM_COLOR_TYPES; i++) printf("%s ", sCTypes[colors[i].first]); printf("\n"); // покажем цвета cvZero(dst2); int h = dst2->height / RECT_COLORS_SIZE; int w = dst2->width; for(i=0; i<RECT_COLORS_SIZE; i++ ){ cvRectangle(dst2, cvPoint(0, i*h), cvPoint(w, i*h+h), rgb_colors[colors[i].first], -1); } cvShowImage("colors", dst2); //cvSaveImage("dominate_colors_table.png", dst2); // покажем картинку в найденных цветах dst3 = cvCloneImage(image); for (y=0; y<dst3->height; y++) { for (x=0; x<dst3->width; x++) { int color_index = CV_PIXEL(uchar, color_indexes, x, y)[0]; CV_PIXEL(uchar, dst3, x, y)[0] = rgb_colors[color_index].val[0]; CV_PIXEL(uchar, dst3, x, y)[1] = rgb_colors[color_index].val[1]; CV_PIXEL(uchar, dst3, x, y)[2] = rgb_colors[color_index].val[2]; } } cvNamedWindow("dst3"); cvShowImage("dst3", dst3); //cvSaveImage("dominate_colors.png", dst3); // конвертируем отладочную картинку обратно в RGB cvCvtColor( dst, dst, CV_HSV2BGR ); // показываем результат cvNamedWindow("color"); cvShowImage("color", dst); // ждём нажатия клавиши cvWaitKey(0); // освобождаем ресурсы cvReleaseImage(&image); cvReleaseImage(&hsv); cvReleaseImage(&dst); cvReleaseImage(&dst2); cvReleaseImage(&color_indexes); cvReleaseImage(&dst3); // удаляем окна cvDestroyAllWindows(); return 0; }
Пример работы:
Использовать метод k-средних для определения доминирующих цветов очень просто.
Фактически, решение задачи заключается в определении кластеров цветов на изображении.
Как мы помним, идея метода k-средних состоит в минимизации суммарного квадратичного отклонения точек выборки от центров кластеров.
Сначала, начальные центры кластеров выбираются случайно, затем вычисляется принадлежность каждого элемента к тому или иному центру, а потом на каждой итерации алгоритма выполняется пересчёт центров масс кластеров — и так до тех пор, пока центры не перестают смещаться.
При решении задачи определения доминирующих цветов, каждый пиксель изображения рассматривается как точка в трёхмерном пространстве RGB, в котором вычисляется расстояние до центров масс кластеров.
Для оптимизации работы алгоритма k-means, картинку желательно предварительно уменьшить.
Исходный код:
// // определение преобладающих цветов на изображении // при помощи k-Means // #include <cv.h> #include <highgui.h> #include <stdlib.h> #include <stdio.h> #include <vector> #include <algorithm> // получение пикселя изображения (по типу картинки и координатам) #define CV_PIXEL(type,img,x,y) (((type*)((img)->imageData+(y)*(img)->widthStep))+(x)*(img)->nChannels) const CvScalar BLACK = CV_RGB(0, 0, 0); // чёрный const CvScalar WHITE = CV_RGB(255, 255, 255); // белый const CvScalar RED = CV_RGB(255, 0, 0); // красный const CvScalar ORANGE = CV_RGB(255, 100, 0); // оранжевый const CvScalar YELLOW = CV_RGB(255, 255, 0); // жёлтый const CvScalar GREEN = CV_RGB(0, 255, 0); // зелёный const CvScalar LIGHTBLUE = CV_RGB(60, 170, 255); // голубой const CvScalar BLUE = CV_RGB(0, 0, 255); // синий const CvScalar VIOLET = CV_RGB(194, 0, 255); // фиолетовый const CvScalar GINGER = CV_RGB(215, 125, 49); // рыжий const CvScalar PINK = CV_RGB(255, 192, 203); // розовый const CvScalar LIGHTGREEN = CV_RGB(153, 255, 153); // салатовый const CvScalar BROWN = CV_RGB(150, 75, 0); // коричневый typedef unsigned char uchar; typedef unsigned int uint; typedef struct ColorCluster { CvScalar color; CvScalar new_color; int count; ColorCluster():count(0) { } } ColorCluster; float rgb_euclidean(CvScalar p1, CvScalar p2) { float val = sqrtf( (p1.val[0]-p2.val[0])*(p1.val[0]-p2.val[0]) + (p1.val[1]-p2.val[1])*(p1.val[1]-p2.val[1]) + (p1.val[2]-p2.val[2])*(p1.val[2]-p2.val[2]) + (p1.val[3]-p2.val[3])*(p1.val[3]-p2.val[3])); return val; } // сортировка цветов по количеству bool colors_sort(std::pair< int, uint > a, std::pair< int, uint > b) { return (a.second > b.second); } int main(int argc, char* argv[]) { // для хранения изображения IplImage* image=0, *src=0, *dst=0, *dst2=0; // // загрузка изображения // char img_name[] = "Image0.jpg"; // имя картинки задаётся первым параметром char* image_filename = argc >= 2 ? argv[1] : img_name; // получаем картинку image = cvLoadImage(image_filename, 1); printf("[i] image: %s\n", image_filename); if(!image){ printf("[!] Error: cant load test image: %s\n", image_filename); return -1; } // показываем картинку cvNamedWindow("image"); cvShowImage("image", image); // ресайзим картинку (для скорости обработки) src = cvCreateImage(cvSize(image->width/2, image->height/2), IPL_DEPTH_8U, 3); cvResize(image, src, CV_INTER_LINEAR); cvNamedWindow("img"); cvShowImage("img", src); // картинка для хранения индексов кластеров IplImage* cluster_indexes = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); cvZero(cluster_indexes); #define CLUSTER_COUNT 10 int cluster_count = CLUSTER_COUNT; ColorCluster clusters[CLUSTER_COUNT]; int i=0, j=0, k=0, x=0, y=0; // начальные цвета кластеров #if 0 clusters[0].new_color = RED; clusters[1].new_color = ORANGE; clusters[2].new_color = YELLOW; // clusters[3].new_color = GREEN; // clusters[4].new_color = LIGHTBLUE; // clusters[5].new_color = BLUE; // clusters[6].new_color = VIOLET; #elif 0 clusters[0].new_color = BLACK; clusters[1].new_color = GREEN; clusters[2].new_color = WHITE; #else CvRNG rng = cvRNG(-1); for(k=0; k<cluster_count; k++) clusters[k].new_color = CV_RGB(cvRandInt(&rng)%255, cvRandInt(&rng)%255, cvRandInt(&rng)%255); #endif // k-means float min_rgb_euclidean = 0, old_rgb_euclidean=0; while(1) { for(k=0; k<cluster_count; k++) { clusters[k].count = 0; clusters[k].color = clusters[k].new_color; clusters[k].new_color = cvScalarAll(0); } for (y=0; y<src->height; y++) { for (x=0; x<src->width; x++) { // получаем RGB-компоненты пикселя uchar B = CV_PIXEL(uchar, src, x, y)[0]; // B uchar G = CV_PIXEL(uchar, src, x, y)[1]; // G uchar R = CV_PIXEL(uchar, src, x, y)[2]; // R min_rgb_euclidean = 255*255*255; int cluster_index = -1; for(k=0; k<cluster_count; k++) { float euclid = rgb_euclidean(cvScalar(B, G, R, 0), clusters[k].color); if( euclid < min_rgb_euclidean ) { min_rgb_euclidean = euclid; cluster_index = k; } } // устанавливаем индекс кластера CV_PIXEL(uchar, cluster_indexes, x, y)[0] = cluster_index; clusters[cluster_index].count++; clusters[cluster_index].new_color.val[0] += B; clusters[cluster_index].new_color.val[1] += G; clusters[cluster_index].new_color.val[2] += R; } } min_rgb_euclidean = 0; for(k=0; k<cluster_count; k++) { // new color clusters[k].new_color.val[0] /= clusters[k].count; clusters[k].new_color.val[1] /= clusters[k].count; clusters[k].new_color.val[2] /= clusters[k].count; float ecli = rgb_euclidean(clusters[k].new_color, clusters[k].color); if(ecli > min_rgb_euclidean) min_rgb_euclidean = ecli; } //printf("%f\n", min_rgb_euclidean); if( fabs(min_rgb_euclidean - old_rgb_euclidean)<1 ) break; old_rgb_euclidean = min_rgb_euclidean; } //----------------------------------------------------- // теперь загоним массив в вектор и отсортируем :) std::vector< std::pair< int, uint > > colors; colors.reserve(CLUSTER_COUNT); int colors_count = 0; for(i=0; i<CLUSTER_COUNT; i++){ std::pair< int, uint > color; color.first = i; color.second = clusters[i].count; colors.push_back( color ); if(clusters[i].count>0) colors_count++; } // сортируем std::sort( colors.begin(), colors.end(), colors_sort ); // покажем цвета dst2 = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 3 ); cvZero(dst2); int h = dst2->height / CLUSTER_COUNT; int w = dst2->width; for(i=0; i<CLUSTER_COUNT; i++ ){ cvRectangle(dst2, cvPoint(0, i*h), cvPoint(w, i*h+h), clusters[colors[i].first].color, -1); printf("[i] Color: %d %d %d (%d)\n", (int)clusters[colors[i].first].color.val[2], (int)clusters[colors[i].first].color.val[1], (int)clusters[colors[i].first].color.val[0], clusters[colors[i].first].count); } cvNamedWindow("colors"); cvShowImage("colors", dst2); //cvResize(dst2, image, CV_INTER_LINEAR); //cvSaveImage("dominate_colors_table.png", image); //----------------------------------------------------- // покажем картинку в найденных цветах dst = cvCloneImage(src); for (y=0; y<dst->height; y++) { for (x=0; x<dst->width; x++) { int cluster_index = CV_PIXEL(uchar, cluster_indexes, x, y)[0]; CV_PIXEL(uchar, dst, x, y)[0] = clusters[cluster_index].color.val[0]; CV_PIXEL(uchar, dst, x, y)[1] = clusters[cluster_index].color.val[1]; CV_PIXEL(uchar, dst, x, y)[2] = clusters[cluster_index].color.val[2]; } } cvNamedWindow("dst"); cvShowImage("dst", dst); //cvResize(dst, image, CV_INTER_LINEAR); //cvSaveImage("dominate_colors.png", image); //----------------------------------------------------- // ждём нажатия клавиши cvWaitKey(0); // освобождаем ресурсы cvReleaseImage(&image); cvReleaseImage(&src); cvReleaseImage(&cluster_indexes); cvReleaseImage(&dst); cvReleaseImage(&dst2); // удаляем окна cvDestroyAllWindows(); return 0; }
Пример работы:
— стоит обратить внимание, что здесь заметны слабые стороны алгоритма k-means:
возможность разных результатов при разных начальных значениях
А теперь сравним результаты работы алгоритмов
исходная картинка:
алгоритм HSV (10 цветов)
алгоритм k-means (10 цветов)
алгоритм k-means (5 цветов)
алгоритм k-means (3 цвета)
Сравнивним результирующие картинки в найденных цветах:
исходная — HSV — k-means (10 цветов) — k-means (5 цветов)
Сравним таблицы доминирующих цветов:
HSV — k-means (10 цветов) — k-means (5 цветов)
Ссылки
Определение доминирующих тонов на изображении
Определение доминирующих тонов на изображении [v 1.1]
Определение доминирующих цветов: Python и метод k-средних
0 комментариев на «“OpenCV — определение доминирующих цветов на изображении”»
Здравствуйте!
Меня очень заинтересовала Ваша статья об OpenCV — определение доминирующих цветов на изображении/
Хотел уточнить возможно ли запустить указанный Вами код на С++ builder для использования компонента Timage
на данный момент не получается запустить Ваши примеры на С++ builder а нужно именно на С++ builder, буду очень благодарен если подскажете в этом плане что-то я студент начинающий очень нужна Ваша помощь также еще вопрос возможна реализация описанных Вами методов без использования OpenCV. Заранее благодарен за ответ.
Привет!tinycv ).
Т.к. здесь никаких специфичных функций не используется, то не вижу проблем перенести этот код под любую другую платформу (пример —
Привет вот еще такая проблема при использовании Вашего кода тот что второй метод k средних появляется ошибка при первом заходе в данный цикл
min_rgb_euclidean = 0;
for(k=0; k<cluster_count; k++) {
// new color
clusters[k].new_color.val[0] /= clusters[k].count;
clusters[k].new_color.val[1] /= clusters[k].count;
clusters[k].new_color.val[2] /= clusters[k].count;
float ecli = rgb_euclidean(clusters[k].new_color, clusters[k].color);
if(ecli > min_rgb_euclidean)
min_rgb_euclidean = ecli;
}
ошибка следующая: Exception class $C0000090 with message ‘floating point invalid operation at 0x00403cf1’ не подскажете в чем может быть причина, также как можно сделать в данном методе чтобы белый фон игнорировался?
Очевидно, нужно проверять, что clusters[k].count не равен нулю.
Чтобы игнорировать белый — можно просто пропускать пиксели с RGB-параметрами около (255, 255, 255).
Здравствуйте, не подскажите пожалуйста, можно ли обрабатывать при помощи open cv более сложные изображения, типа мозаики, а именно выделение на изображении определенных объектов, и довести это до автомата? Сейчас я смог находить на изображении только точные геометрические предметы( круг, треугольник, прямоугольник и линии, но честно говоря получилось не очень)
Я думал может как-то различать по цвету от темного к светлому
Просто я студент 2 курса, и недавно занимаюсь программированием c#, а c++ для меня вообще не знаком) поэтому ваши примеры мне не понятны( заранее спасибо за ответ
Разумеется, можно. OpenCV — это библиотека в которой уже реализованы многие важные функции. Но реализовывать свои собственные или же применять существующие — это полностью ваша задача.
Здоавствуйте. Не подскажите, пожалуйста, как с помощью первого способа вывести в консоль количество пикселей определенного цвета не по всей картинке, а только в заданной области? Просто в коде цвета подсчитываются сразу colorCount[ctype]++; а если задать массив, что array[x][y]= ctype, то выдаются разные ошибки.
Например, при помощиROI .
На самом деле надо было просто создать динамический массив через указатель 🙂 Как-то глупо получилось. Но все равно спасибо за помощь!
Да, это было бы хорошо. Но нельзя всё-таки получить именно массив/матрицу изображения, чтобы легко оперировать с ним, зная, что, например, пиксель [245][108]=0, т.е. имеет чёрный цвет, индекс которого в данной программе — 0? Просто тогда было бы легче рассматривать изображение с доминирующими цветами после его обработки, не прибегая к обращению пикселя в RGB(x,x,x) с тремя параметрами, т.к. во время работы того кода по выделению доминирующих цветов новый цвет пикселя напрямую присваивается в виде индекса, который варьируется от 0 до 7. И ни как нельзя это присвоение как-то перехватить и записать в массив? Дело в том, что для моего алгоритма, который я хочу написать, нужно рассматривать маленькие области, к примеру, 20×20 пикселей для последующей обработки, зная какие доминирующие цвета там есть. И так как новое присвоение цвета уже известно, то почему бы просто это не записать в массив? Просто тогда на картинке в 4к разрешением таких областей придется создавать ну очень много, т.к. изображение будет разбито на эти маленькие сегменты, и каждый в отдельности рассматривать неудобно с помощью ROI.
В любом случае, спасибо за ответ!
Добрый день!
Возможно, не совсем в тему, но можете подсказать, пожалуйста, как создать кнопки в OpenСV и использовать их так, чтобы доминирующие цвета присваивались, например, к 20% процентам полученных цветов и так далее?
Заранее спасибо за помощь!
здраствуйте, спасибо за туториал, у меня такой вопрос, а возможно ли отобразить доминирующие цвета ввиде pie-чарта? Спасибо.
Используя примитивы рисования (см.Drawing Functions ), думается вполне возможно.
Получилось, спасибо!
Не получается релизнуть программу. Вот ошибка: The program ‘[6932] opencv.exe’ has exited with code -1 (0xffffffff).
В чем может быть дело?
Здравствуйте. я применила данный код для видео, но что то не так, я передаю fraim как изображение, для первого фрейма работает, но последующие идут с ошибкой и средние RGB значения все уменьшаются с каждым фреймом. В чем может быть ошибка? Спасибо!
Дело было в цикле, решилось, спасибо.