很多图像处理算法是以颜色为原理展开的, 因此颜色数目很大程度上决定了算法的运行效率. 如果可以大大降低图像中的颜色数目, 将可以轻松地优化特定的图像处理算法.
使用以下的像素除法公式, 可以将颜色减少 1 / N.
color = color/div*div+div/2 \text{color = color/div*div+div/2} color = color/div*div+div/2
若 div 为 8,则原来 RGB 每个通道的 256 种颜色减少为 32 种.
若 div 为 64,则原来 RGB 每个通道的 256 种颜色减少为 4 种,此时三通道所有能表示的颜色为 4 * 4 * 4 = 64 种。
256 种颜色表示的范围是 0~255, 那么分布在这个范围内的数除以 64 之后得到的是 4 个数,也就是 0,1,2,3.
255 / 64 * 64,上面的 4 个数 * 64 = 0,64,128,192
256 / 64 * 64 + 64 / 2,上面 4 个数 + 32 = 32,96,160,224
所以 256 种颜色就减少为 4 种.
首先明确一下颜色数量 ( 即直方图中的 color bins ) 的概念.
对于 256 种颜色的单通道灰度图像, 计算其直方图后, 直方图中 color bins 的数量为 256, 直方图会统计出每种颜色(可能的取值范围为 0 - 256 ) 在灰度图中出现的次数.
对于 256 种颜色的三通道彩色图像, 其颜色直方图中 color bins 的数量为 256^3 ≈ 16 万, 而原图中就包含着大约 16 万种颜色, 这对于后续的基于颜色的图像处理算法而言有很大的计算量, 因此, 减少颜色数量就显得非常必要了.
我们可以为每个通道直接指定一个合适的 colors 种类来减少图像中颜色的数量, 比如将每通道 256 种颜色指定为 12 种颜色, 这样对于彩色图像来说, 最大可表示的颜色数量就变成 12^3 = 1728 种.
原图:
使用每通道 12 个 颜色量化图像后可将颜色数目减少为 274 ( 最大可表示为 1728), 效果如下:
可以看到, 274 个直方图 color bins 的色彩仍然得到了很高的视觉质量.
虽然 274 种颜色和 16 万种颜色相比而言确实减少了很多, 但是通过观察这 274 种颜色直方图后可以发现, 有很大一部分 bins 中对应的颜色数量很少, 这意味着这部分颜色在图像中出现的次数很少, 可以去掉这部分颜色, 进一步减少颜色数量.
去掉的颜色会引起图片中出现瑕疵, 如果某个颜色由于出现次数较少需要去掉时, 可以使用出现次数多的颜色中和它最相近的颜色替换它, 这样就可以做到颜色空间的平滑.
最终颜色数目减少为 82 种, 对于后续的图像处理应用效率提升有很大的帮助. 显示效果如下:
可以看到, 和 274 种直方图 color bins 的色彩图相比, 因为只是去除了图像中出现很少的颜色, 因此, 82 种颜色的图像整体效果和 274 种颜色的图像视觉效果差别不大, 依然得到了很高的视觉质量.
为防止伸手党, 代码中贴出了核心的实现细节, 部分关键代码都给出了注释:
/**
* \brief: Quantize Color numbers in RGB Color Space
* Default: quantize the color number to 12 with each channel.
*
*
* \param: img3f - src image, normalized to [0, 1]
* n_colors - color numbers (3 channels) after quantize
* ratio - significant colors' pixels percentage, default = 0.95
*
* \return: color_q - Finally quantized image
*
*/
int Quantize(cv::Mat& img3f, cv::Mat &color_q, double ratio, const int n_colors[3])
{
CV_Assert(img3f.data != NULL);
int rows = img3f.rows, cols = img3f.cols;
int color_bins_quant[3] = {n_colors[0], n_colors[1], n_colors[2]};
// color bins weights of channels
int weights[3] = {n_colors[0] * n_colors[0], n_colors[0], 1}; // 12^2, 12, 1
cv::Mat idx1i = cv::Mat::zeros(img3f.size(), CV_32S);
if (img3f.isContinuous() && idx1i.isContinuous()){
cols *= rows;
rows = 1;
}
// First Step: reduce the color bins to fixed bins, 12
// Build color pallet, get every quantized_color's number
std::map pallet; // (quantized_color, num) pairs in pallet
int color_val = 0;
for (int y = 0; y < rows; y++)
{
const float* imgData = img3f.ptr(y);
int* idx = idx1i.ptr(y);
for (int x = 0; x < cols; x++, imgData += 3)
{
// color after quantized, eg: 256^3 -> 256 * 256 * 256
color_val = (int)(imgData[0]*color_bins_quant[0])*weights[0] +
(int)(imgData[1]*color_bins_quant[1])*weights[1] +
(int)(imgData[2]*color_bins_quant[2]);
pallet[color_val] ++;
idx[x] = color_val; // fill Mat: idx1i
}
}
std::cout << "color reduced first step: " << pallet.size() << std::endl;
// Second Step: Reduce more by removing colors rarely appear
// Find significant colors, this will cover 95% pixels in image
int maxNum = 0; // colors number left finally, 3 channles total: [10 - 256]
{
std::vector> num; // (num, color) pairs in num
num.reserve(pallet.size());
for (std::map::iterator it = pallet.begin(); it != pallet.end(); it++)
num.push_back(std::pair(it->second, it->first)); // (color, num) pairs in pallet
std::sort(num.begin(), num.end(), std::greater>()); // sort with key
// remove the colors appear rarely
maxNum = (int)num.size();
int maxDropNum = cvRound(rows * cols * (1-ratio));
for (int crnt = num[maxNum-1].first; crnt < maxDropNum && maxNum > 1; maxNum--)
crnt += num[maxNum - 2].first;
std::cout << "color reduced second step: " << maxNum << std::endl;
maxNum = std::min(maxNum, 256); // To avoid very rarely case
if (maxNum <= 10)
maxNum = std::min(10, (int)num.size());
pallet.clear();
// Beacuse "maxNum" is the total colors number left finally, and "num" has been sorted
// "pallet" contains (color, color_index) pairs
for (int i = 0; i < maxNum; i++)
pallet[num[i].second] = num[i].second;
// RGB color after quantized
std::vector color3i(num.size());
for (unsigned int i = 0; i < num.size(); i++)
{
color3i[i][0] = num[i].second / weights[0];
color3i[i][1] = num[i].second % weights[0] / weights[1];
color3i[i][2] = num[i].second % weights[1];
}
// reassign the 5% pixels colors to its nearest color which left in histogram bins
for (unsigned int i = maxNum; i < num.size(); i++)
{
int similar_idx = 0, similar_val = INT_MAX;
// remained bins
for (int j = 0; j < maxNum; j++)
{
int dist_ij = vecSqrDist(color3i[i], color3i[j]); // color distance, use vector abs distance
if (dist_ij < similar_val)
similar_val = dist_ij, similar_idx = j;
}
pallet[num[i].second] = pallet[num[similar_idx].second];
}
}
// Finally quantized image
color_q = cv::Mat::zeros(img3f.size(), CV_8UC3);
for (int y = 0; y < rows; y++)
{
const float* imgData = img3f.ptr(y);
int* idx = idx1i.ptr(y);
cv::Vec3b* q = color_q.ptr(y);
for (int x = 0; x < cols; x++, imgData += 3)
{
// quantized image, * 21.3 to make image color bright
q[x][0] = (uchar)(pallet[idx[x]] / weights[0]*21.3);
q[x][1] = (uchar)(pallet[idx[x]] % weights[0] / weights[1]*21.3);
q[x][2] = (uchar)(pallet[idx[x]] % weights[1]*21.3);
}
}
return 0;
}
[1]. 减少图像的颜色数量
[2]. Global Contrast based Salient Region detection