【问题标题】:Detect if image is color, grayscale or black and white with Python/PIL使用 Python/PIL 检测图像是彩色、灰度还是黑白
【发布时间】:2013-12-02 20:27:07
【问题描述】:

我从 jpeg 格式的 PDF 文件中提取页面图像,我需要确定每个图像是否更加灰度、彩色或黑白(具有容差系数)。

我已经找到了一些使用 PIL(herehere)进行颜色检测的方法,但我不知道如何回答这个简单的(视觉)问题:它是不是更像黑白,颜色还是灰度图?

我更喜欢在这部分使用 Python 和 PIL,但如果有人有线索(或解决方案),我也可以使用 OpenCV。

【问题讨论】:

    标签: python opencv python-2.7 python-imaging-library


    【解决方案1】:

    您可以使用cv::Mat::channels() 运算符,它可以告诉您它是“灰度”(即 2 通道)还是“彩色”(即 3 通道)图像。对于黑白,您需要根据灰度设置更深入的测试,因为定义会有所不同。

    【讨论】:

    • 感谢 scap3y 的回答。您说的是图像结构,但是视觉上看起来是灰度或黑白图像的 RGB JPEG 图像(3 通道)呢(因此可以转换为 8 位 JPEG 或 TIF-Group4) ?
    • 我打算提出一个解决方案,但我想您已经找到了一种可以为您服务的基于 PIL 的方法。干杯。
    【解决方案2】:

    我找到了一种使用 PIL ImageStat 模块来猜测的方法。感谢this post 对图像进行单色测定。

    from PIL import Image, ImageStat
    
    MONOCHROMATIC_MAX_VARIANCE = 0.005
    COLOR = 1000
    MAYBE_COLOR = 100
    
    def detect_color_image(file):
        v = ImageStat.Stat(Image.open(file)).var
        is_monochromatic = reduce(lambda x, y: x and y < MONOCHROMATIC_MAX_VARIANCE, v, True)
        print file, '-->\t',
        if is_monochromatic:
            print "Monochromatic image",
        else:
            if len(v)==3:
                maxmin = abs(max(v) - min(v))
                if maxmin > COLOR:
                    print "Color\t\t\t",
                elif maxmin > MAYBE_COLOR:
                    print "Maybe color\t",
                else:
                    print "grayscale\t\t",
                print "(",maxmin,")"
            elif len(v)==1:
                print "Black and white"
            else:
                print "Don't know..."
    

    COLOR 和 MAYBE_COLOR 常量是用于查找彩色和灰度图像之间差异的快速开关,但它并不安全。例如,我有几张 JPEG 图像,它们被视为彩色,但实际上是灰度图像,由于扫描过程存在一些彩色伪影。这就是为什么我要注意另一个级别的真正舒尔彩色图像。

    如果有人有更好的方法,请告诉我。

    【讨论】:

      【解决方案3】:

      我尝试了 Gepeto 的解决方案,但它有很多误报,因为颜色大方差可能只是偶然相似。正确的方法是计算每个像素的方差。首先缩小图像,这样您就不必处理数百万像素。

      默认情况下,此函数还使用平均颜色偏差调整,我发现这可以改善预测。这样做的一个副作用是它还会检测单色但非灰度图像(通常是棕褐色的东西,该模型似乎在检测与灰度的较大偏差时有点崩溃)。您可以通过对色带均值进行阈值化,将这些与真正的灰度分开。

      我在包含 13,000 张照片的测试集上运行此程序,并以 99.1% 的准确率和 92.5% 的召回率进行分类。通过使用非线性偏差调整可能会进一步提高准确性(例如,颜色值必须在 0 到 255 之间)。也许查看中值平方误差而不是 MSE 会更好地允许例如带有小颜色标记的灰度图像。

      from PIL import Image, ImageStat
      def detect_color_image(file, thumb_size=40, MSE_cutoff=22, adjust_color_bias=True):
          pil_img = Image.open(file)
          bands = pil_img.getbands()
          if bands == ('R','G','B') or bands== ('R','G','B','A'):
              thumb = pil_img.resize((thumb_size,thumb_size))
              SSE, bias = 0, [0,0,0]
              if adjust_color_bias:
                  bias = ImageStat.Stat(thumb).mean[:3]
                  bias = [b - sum(bias)/3 for b in bias ]
              for pixel in thumb.getdata():
                  mu = sum(pixel)/3
                  SSE += sum((pixel[i] - mu - bias[i])*(pixel[i] - mu - bias[i]) for i in [0,1,2])
              MSE = float(SSE)/(thumb_size*thumb_size)
              if MSE <= MSE_cutoff:
                  print "grayscale\t",
              else:
                  print "Color\t\t\t",
              print "( MSE=",MSE,")"
          elif len(bands)==1:
              print "Black and white", bands
          else:
              print "Don't know...", bands
      

      【讨论】:

      • 嗨,谢谢。我正在尝试弄清楚这是如何工作的。 “MSE”、“SSE”是什么意思?你能链接到一些文档吗?
      • SSE/MSE 是平方误差/均方误差之和。回顾一下,如果您转换为 HSV 或一些类似的颜色空间,在概念上可能会更容易。然后,SSE 将仅在色相/饱和度通道中计算为与零或偏差(如果有的话)的差异。
      【解决方案4】:

      我们使用这个简单的函数来确定图像的颜色因子。

      # Iterate over all Pixels in the image (width * height times) and do this for every pixel:
      {
          int rg = Math.abs(r - g);
          int rb = Math.abs(r - b);
          int gb = Math.abs(g - b);
          diff += rg + rb + gb;
      }
      
      return diff / (height * width) / (255f * 3f);
      

      因为灰度值有 r-g = 0 和 r-b = 0 和 g-b = 0 对于灰度图像,diff 将接近 0,对于彩色图像,diff 将接近于 0。

      【讨论】:

        【解决方案5】:

        我个人更喜欢TomB的回答。这不是一个新的答案,我只是想发布Java版本:

        private Mat calculateChannelDifference(Mat mat) {   
        
            // Create channel list:
            List<Mat> channels = new ArrayList<>();
        
            for (int i = 0; i < 3; i++) {
                channels.add(new Mat());
            }
        
            // Split the channels of the input matrix:
            Core.split(mat, channels);
        
            Mat temp = new Mat();
        
            Mat result = Mat.zeros(mat.size(), CvType.CV_8UC1);
        
            for (int i = 0; i < channels.size(); i++) {
        
                // Calculate difference between 2 successive channels:
                Core.absdiff(channels.get(i), channels.get((i + 1) % channels.size()), temp);
        
                // Add the difference to the result:
                Core.add(temp, result, result);
            }
        
            return result;
        }
        

        结果是作为矩阵的差异,这样您可以应用一些阈值甚至检测形状。如果您希望结果为单个数字,则只需计算平均值。这可以使用Core.mean()

        【讨论】:

        • 这里的问题是,没有对每个像素进行绝对计算。负面和正面的差异会相互消除,从而导致错误的结果。
        猜你喜欢
        • 2014-07-18
        • 1970-01-01
        • 2014-07-02
        • 1970-01-01
        • 2012-01-09
        • 2021-06-23
        • 2021-12-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多