使用Python / PIL检测图像是彩色,灰度还是黑白

时间:2013-11-19 10:25:13

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

我以jpeg格式从PDF文件中提取页面图像,我需要确定每个图像是否更灰度,颜色是黑白(带公差因子)。

我找到了一些使用PIL(herehere)进行颜色检测的方法,但我无法弄清楚如何回答这个简单的(视觉)问题:它是否更黑和白色,彩色或灰度图像?

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

5 个答案:

答案 0 :(得分:8)

我尝试了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

答案 1 :(得分:6)

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

# 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并且> 0表示彩色图像。

答案 2 :(得分:2)

我找到了一种用PIL ImageStat模块猜测的方法。 Thanx到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图像被视为彩色,但实际上是由于扫描过程而带有一些颜色假象的灰度图像。这就是为什么我有另一个层次来注意其他人的真实色彩图像。

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

答案 3 :(得分:1)

我个人更喜欢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()

完成

答案 4 :(得分:0)

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