I'd like to extract the color palette of an image, similar to this (from here):
I need it to extract specific colors, like yellow, green, and brown and display the percentage of the area covered by that color. Also, I can add more colors to extract.
How can I reduce the number of colors in the original image, and how can I get the color palette?
There are three different things going on here.
Reduce the number of colors
There are many techniques to reduce the number of colors. Here you can see how to use color quantization and kmeans.
Another approach could use the median cut algorithm (not shown here).
OpenCV provides the Non-Photorealistic Rendering module. Here you can see some examples of how to use it.
Get the different colors of an image
This is pretty easy. Just iterate over the whole image. If you see a new color, store its value, with counter equal to 1. If you see a color already seen, increment its counter. A std::map
could be useful here.
Get the color name
I won't show it here. But online there are some useful resources. You need a list of all named colors. Keep in mind that not every color has a name. In fact, all possible colors for RGB values would be 256*256*256
. So find the closest color in your list, and assign its name to your current color.
For example, with this input image,
using kmeans approach, I get the reduced color image:
And its palette is:
Color: [14, 134, 225] - Area: 5.28457%
Color: [16, 172, 251] - Area: 27.3851%
Color: [22, 68, 101] - Area: 3.41029%
Color: [28, 154, 161] - Area: 3.89029%
Color: [40, 191, 252] - Area: 22.3429%
Color: [87, 204, 251] - Area: 8.704%
Color: [161, 222, 251] - Area: 3.47429%
Color: [253, 255, 255] - Area: 25.5086%
You can now search for the closest color name in your list, and you'll get what you need. How to make up the GUI to show these information is up to you: the data is all there.
Code:
#include <opencv2\opencv.hpp>
#include <opencv2\photo.hpp>
#include <iostream>
#include <map>
using namespace cv;
using namespace std;
// https://stackoverflow.com/a/34734939/5008845
void reduceColor_Quantization(const Mat3b& src, Mat3b& dst)
{
uchar N = 64;
dst = src / N;
dst *= N;
}
// https://stackoverflow.com/a/34734939/5008845
void reduceColor_kmeans(const Mat3b& src, Mat3b& dst)
{
int K = 8;
int n = src.rows * src.cols;
Mat data = src.reshape(1, n);
data.convertTo(data, CV_32F);
vector<int> labels;
Mat1f colors;
kmeans(data, K, labels, cv::TermCriteria(), 1, cv::KMEANS_PP_CENTERS, colors);
for (int i = 0; i < n; ++i)
{
data.at<float>(i, 0) = colors(labels[i], 0);
data.at<float>(i, 1) = colors(labels[i], 1);
data.at<float>(i, 2) = colors(labels[i], 2);
}
Mat reduced = data.reshape(3, src.rows);
reduced.convertTo(dst, CV_8U);
}
void reduceColor_Stylization(const Mat3b& src, Mat3b& dst)
{
stylization(src, dst);
}
void reduceColor_EdgePreserving(const Mat3b& src, Mat3b& dst)
{
edgePreservingFilter(src, dst);
}
struct lessVec3b
{
bool operator()(const Vec3b& lhs, const Vec3b& rhs) const {
return (lhs[0] != rhs[0]) ? (lhs[0] < rhs[0]) : ((lhs[1] != rhs[1]) ? (lhs[1] < rhs[1]) : (lhs[2] < rhs[2]));
}
};
map<Vec3b, int, lessVec3b> getPalette(const Mat3b& src)
{
map<Vec3b, int, lessVec3b> palette;
for (int r = 0; r < src.rows; ++r)
{
for (int c = 0; c < src.cols; ++c)
{
Vec3b color = src(r, c);
if (palette.count(color) == 0)
{
palette[color] = 1;
}
else
{
palette[color] = palette[color] + 1;
}
}
}
return palette;
}
int main()
{
Mat3b img = imread("path_to_image");
// Reduce color
Mat3b reduced;
//reduceColor_Quantization(img, reduced);
reduceColor_kmeans(img, reduced);
//reduceColor_Stylization(img, reduced);
//reduceColor_EdgePreserving(img, reduced);
// Get palette
map<Vec3b, int, lessVec3b> palette = getPalette(reduced);
// Print palette
int area = img.rows * img.cols;
for (auto color : palette)
{
cout << "Color: " << color.first << " \t - Area: " << 100.f * float(color.second) / float(area) << "%" << endl;
}
return 0;
}