RGB 空间颜色量化 - 减少颜色数目

很多图像处理算法是以颜色为原理展开的, 因此颜色数目很大程度上决定了算法的运行效率. 如果可以大大降低图像中的颜色数目, 将可以轻松地优化特定的图像处理算法.


1. 简单方法 - 像素除法


使用以下的像素除法公式, 可以将颜色减少 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 种.


2. 将每通道 255 种 colors 减少为固定的 colors


首先明确一下颜色数量 ( 即直方图中的 color bins ) 的概念.

对于 256 种颜色的单通道灰度图像, 计算其直方图后, 直方图中 color bins 的数量为 256, 直方图会统计出每种颜色(可能的取值范围为 0 - 256 ) 在灰度图中出现的次数.

对于 256 种颜色的三通道彩色图像, 其颜色直方图中 color bins 的数量为 256^3 ≈ 16 万, 而原图中就包含着大约 16 万种颜色, 这对于后续的基于颜色的图像处理算法而言有很大的计算量, 因此, 减少颜色数量就显得非常必要了.

每个通道指定固定的 colors

我们可以为每个通道直接指定一个合适的 colors 种类来减少图像中颜色的数量, 比如将每通道 256 种颜色指定为 12 种颜色, 这样对于彩色图像来说, 最大可表示的颜色数量就变成 12^3 = 1728 种.

原图:

RGB 空间颜色量化 - 减少颜色数目_第1张图片

使用每通道 12 个 颜色量化图像后可将颜色数目减少为 274 ( 最大可表示为 1728), 效果如下:

RGB 空间颜色量化 - 减少颜色数目_第2张图片

可以看到, 274 个直方图 color bins 的色彩仍然得到了很高的视觉质量.

去掉在图像中出现次数很少的颜色

虽然 274 种颜色和 16 万种颜色相比而言确实减少了很多, 但是通过观察这 274 种颜色直方图后可以发现, 有很大一部分 bins 中对应的颜色数量很少, 这意味着这部分颜色在图像中出现的次数很少, 可以去掉这部分颜色, 进一步减少颜色数量.

去掉的颜色会引起图片中出现瑕疵, 如果某个颜色由于出现次数较少需要去掉时, 可以使用出现次数多的颜色中和它最相近的颜色替换它, 这样就可以做到颜色空间的平滑.

最终颜色数目减少为 82 种, 对于后续的图像处理应用效率提升有很大的帮助. 显示效果如下:

RGB 空间颜色量化 - 减少颜色数目_第3张图片

可以看到, 和 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

你可能感兴趣的:(图像处理)