【问题标题】:How to reduce the number of colors in an image with OpenCV?如何使用 OpenCV 减少图像中的颜色数量?
【发布时间】:2011-08-19 21:15:42
【问题描述】:

我有一组图像文件,我想将它们的颜色数量减少到 64 种。如何使用 OpenCV 做到这一点?

我需要这个,所以我可以处理 64 大小的图像直方图。 我正在实施 CBIR 技术

我想要的是 4 位调色板的颜色量化。

【问题讨论】:

  • 如果您想要 64 种颜色,您需要 6 位调色板。请参阅下面的答案,以获得更好的解释和代码,以使用每个颜色通道中的 2 位来构建 6 位调色板图像。
  • @Felipe 添加了一个新答案,您可能会觉得它很有趣。

标签: image opencv image-processing colors cbir


【解决方案1】:

简单的按位和适当的位掩码就可以解决问题。

python,64色,

img = img & int("11000000", 2)

RGB 图像的颜色数量应该是一个完美的立方体(在 3 个通道上相同)。

对于此方法,通道的可能值的数量应为 2 的幂。(此检查被代码忽略,并采用下一个较低的 2 幂)

import numpy as np
import cv2 as cv


def is_cube(n):
    cbrt = np.cbrt(n)
    return cbrt ** 3 == n, int(cbrt)


def reduce_color_space(img, n_colors=64):
    n_valid, cbrt = is_cube(n_colors)

    if not n_valid:
        print("n_colors should be a perfect cube")
        return

    n_bits = int(np.log2(cbrt))

    if n_bits > 8:
        print("Can't generate more colors")
        return

    bitmask = int(f"{'1' * n_bits}{'0' * (8 - n_bits)}", 2)

    return img & bitmask


img = cv.imread("image.png")

cv.imshow("orig", img)
cv.imshow("reduced", reduce_color_space(img))

cv.waitKey(0)

【讨论】:

    【解决方案2】:

    OpenCV 2 Computer Vision Application Programming Cookbook 很好地涵盖了这个主题:

    第 2 章展示了一些归约操作,其中一个在 C++ 中演示,后来在 Python 中演示:

    #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;
    }
    

    您可以在下面找到输入图像(左)和此操作的输出(右):

    Python 中的等效代码如下: (感谢@eliezer-bernart

    import cv2
    import numpy as np
    
    input = cv2.imread('castle.jpg')
    
    # colorReduce()
    div = 64
    quantized = input // div * div + div // 2
    
    cv2.imwrite('output.jpg', quantized)
    

    【讨论】:

      【解决方案3】:

      这是使用带有cv2.kmeans 的 K-Means 聚类的颜色量化 Python 实现。这个想法是减少图像中不同颜色的数量,同时尽可能地保留图像的颜色外观。结果如下:

      输入-&gt;输出

      代码

      import cv2
      import numpy as np
      
      def kmeans_color_quantization(image, clusters=8, rounds=1):
          h, w = image.shape[:2]
          samples = np.zeros([h*w,3], dtype=np.float32)
          count = 0
      
          for x in range(h):
              for y in range(w):
                  samples[count] = image[x][y]
                  count += 1
      
          compactness, labels, centers = cv2.kmeans(samples,
                  clusters, 
                  None,
                  (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001), 
                  rounds, 
                  cv2.KMEANS_RANDOM_CENTERS)
      
          centers = np.uint8(centers)
          res = centers[labels.flatten()]
          return res.reshape((image.shape))
      
      image = cv2.imread('1.jpg')
      result = kmeans_color_quantization(image, clusters=8)
      cv2.imshow('result', result)
      cv2.waitKey()     
      

      【讨论】:

        【解决方案4】:

        这里建议的答案非常好。我想我也会添加我的想法。我这里沿用了很多cmets的公式,据说RGB图像中每个通道的2位可以表示64种颜色。

        下面代码中的函数将图像和量化所需的位数作为输入。它使用位操作来“删除” 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;
        }
        

        这是上述函数调用中使用的图像:

        每个 RGB 通道量化为 2 位的图像(总共 64 种颜色):

        每个通道 3 位:

        4 位 ...

        【讨论】:

          【解决方案5】:

          有很多方法可以做到这一点。 jeff7推荐的方法还可以,但是有一些缺点:

          • 方法 1 具有参数 N 和 M,您必须选择它们,并且还必须将其转换为另一个颜色空间。
          • 回答的方法 2 可能非常慢,因为您应该计算一个 1670 万个 bin 直方图并按频率对其进行排序(以获得 64 个较高的频率值)

          我喜欢使用基于 Most Significant Bits 的算法在 RGB 颜色中使用并将其转换为 64 色图像。如果你使用的是 C/OpenCV,你可以使用类似下面的函数。

          如果您正在处理灰度图像,我建议使用 OpenCV 2.3 的 LUT() 函数,因为它更快。有一个关于如何使用LUT减少颜色数量的教程。请参阅:Tutorial: How to scan images, lookup tables... 但是,如果您使用 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
                  }     
              }
          }
          

          【讨论】:

          • 你不能在非灰度图像上继续使用 LUT 吗?如果您在提供here 的链接中运行 OpenCV 教程中的代码,您可以通过将 64 作为其代码的输入参数,轻松地将颜色空间量化为 64 种颜色。这基本上与提取示例中的 2 个最高有效位相同。还是我错过了什么?
          • @Moacir 此函数不应返回值,因为它的签名声明它返回void
          【解决方案6】:

          您可能会考虑使用 K-means,但在这种情况下,它很可能会非常缓慢。更好的方法可能是您自己“手动”执行此操作。假设您有 CV_8UC3 类型的图像,即每个像素由 0 到 255 (Vec3b) 的 3 个 RGB 值表示的图像。您可以将这 256 个值“映射”到仅 4 个特定值,这将产生 4 x 4 x 4 = 64 可能的颜色。

          我有一个数据集,我需要确保深色 = 黑色,浅色 = 白色,并减少两者之间所有颜色的数量。这就是我所做的(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 颜色:

          对我来说,这似乎比其他答案中提到的任何其他内容都简洁、清晰且速度更快。

          您也可以考虑将这些值减少到某个数字的倍数之一,例如:

          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 种颜色。

          【讨论】:

            【解决方案7】:

            你为什么不只做矩阵乘法/除法?值将自动四舍五入。

            伪代码:

            将您的频道转换为无符号字符 (CV_8UC3),
            被除以 总颜色/所需颜色。垫 = 垫 / (256/64)。小数点 将被截断。
            乘以相同的数字。垫子 = 垫子 * 4

            完成。现在每个通道只包含 64 种颜色。

            【讨论】:

              【解决方案8】:

              OpenCV 库中已有 K-means 聚类算法。简而言之,它确定哪些是围绕用户定义的 k 值(= 聚类数)对数据进行聚类的最佳质心。因此,在您的情况下,您可以找到围绕给定 k=64 值聚集像素值的质心。如果你用谷歌搜索,细节就在那里。 Here 是对 k-means 的简短介绍。

              SO 上使用 k-means 提出了类似于您可能正在尝试的问题,希望对您有所帮助。

              另一种方法是在 OpenCV 中使用 pyramid mean shift filter 函数。它会产生一些“扁平化”的图像,即颜色数量较少,因此它可能会对您有所帮助。

              【讨论】:

                【解决方案9】:

                假设您想对所有图像使用相同的 64 种颜色(即未针对每个图像优化调色板),我至少可以想到几个选择:

                1) 转换为 Lab 或 YCrCb 颜色空间并使用 N 位亮度和 M 位每个颜色通道进行量化,N 应大于 M。

                2) 计算所有训练图像的颜色值的 3D 直方图,然后选择具有最大 bin 值的 64 种颜色。通过为每个像素分配训练集中最接近 bin 的颜色来量化图像。

                方法 1 是最通用且最容易实现的,而方法 2 可以更好地适应您的特定数据集。

                更新: 例如,32 种颜色是 5 位,因此将 3 位分配给亮度通道,将 1 位分配给每个颜色通道。要进行此量化,请将亮度通道除以 2^8/2^3 = 32 并将每个颜色通道除以 2^8/2^1 = 128。现在只有 8 个不同的亮度值和 2 个不同的颜色通道每个。将这些值重新组合成一个整数,进行位移或数学运算(量化颜色值 = 亮度*4+颜色1*2+颜色2);

                【讨论】:

                • "使用 N 位进行亮度量化,M 位用于每个颜色通道,N 应大于 M。" - 我该如何做这部分?
                猜你喜欢
                • 2016-04-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-03-02
                • 1970-01-01
                • 2017-05-01
                相关资源
                最近更新 更多