使用.Net检测灰度图像

10

我正在将文档扫描为JPG图像。 扫描仪必须将所有页面扫描为彩色或黑白两种颜色中的其中一种。 由于我的许多页面是彩色的,所以我必须将所有页面都扫描成彩色。 扫描完成后,我想用 .Net 检查这些图像并尝试检测哪些图像是黑白的,以便我可以将这些图像转换为灰度并节省存储空间。

有人知道如何使用 .Net 检测灰度图像吗?

请告诉我。


1
检查图像类型并不够,因为它将被设置为24或32位(因为您正在扫描彩色图像)。您可能需要检查每个像素;如果在所有像素中 R == G == B,则它是灰度图像,否则它可能是彩色图像。 - Michael Todd
1
一个想法:尽管理论上扫描仪提供的R == G == B,但在JPEG压缩过程中可能存在一些像素只是几乎相等的情况吗?考虑到JPEG是一种有损压缩算法。也许JPEG会对附近的像素颜色进行一些处理。但我承认,我不是一个JPEG专家。但在依赖R == G == B之前,我想了解它的工作原理。 - Chris W. Rea
是的,我不想依赖于 r==g==b 完全相等,因为即使 jpg 不做任何调整(我敢打赌它会),你的扫描仪和原始文件也必须完美无缺,这在许多情况下似乎不太可能。 - Beska
好的。我没有考虑到像素的变化(当然,在扫描时会发生这种情况)。这是一个有趣的问题。 - Michael Todd
1
@Dave:我应该早点做的,但是今天早上我在我的答案中粘贴了一段代码片段,它实际上会返回图像中最高像素RGB差值。如何解释这个差值取决于你。你可以测试是否为0(真正的完全灰度)或略大于零以允许一些颜色信息。 - Paul Sasik
6个回答

14

如果你找不到相关的库,可以尝试获取一张图片的大量像素(或全部像素),然后查看它们的r、g和b值是否相差不大(你可以根据经验或设置来确定一个阈值)。如果相似,则该图像是灰度图。

我肯定会将测试阈值设得比0大一些...例如我不会测试r=g,而是(abs(r-g) < e),其中e是你的阈值。这样可以减少误判情况...因为我猜你否则将获得相当多的假彩色结果,除非原始图像和扫描技术恰好是纯灰度的。


我不会使用库来执行像检测R=G=B这样简单的操作。 - Ed S.
或者检查一个增量是否超过某个阈值,这将是更好的方法。 - Ed S.
@Ed:这不就是我说的吗? - Beska

6
一个测试颜色的简单算法:通过一个嵌套的for循环(宽和高),逐像素扫描图像,检查该像素的RGB值是否相等。如果不相等,则该图像具有颜色信息。如果在没有遇到此条件的情况下扫描完所有像素,则该图像为灰度图像。
经过更复杂算法的修订: 在此帖子的第一版中,我提出了一个简单的算法,假设如果每个像素的RGB值相等,则表示像素为灰度。因此,RGB值为0,0,0或128,128,128或230,230,230都将被测试为灰色,而123,90,78则不是。以下是检测与灰度的差异代码段。这两种方法是更复杂过程的一小部分,但应足以提供原始代码来帮助解决原始问题。
/// <summary>
/// This function accepts a bitmap and then performs a delta
/// comparison on all the pixels to find the highest delta
/// color in the image. This calculation only works for images
/// which have a field of similar color and some grayscale or
/// near-grayscale outlines. The result ought to be that the
/// calculated color is a sample of the "field". From this we
/// can infer which color in the image actualy represents a
/// contiguous field in which we're interested.
/// See the documentation of GetRgbDelta for more information.
/// </summary>
/// <param name="bmp">A bitmap for sampling</param>
/// <returns>The highest delta color</returns>
public static Color CalculateColorKey(Bitmap bmp)
{
    Color keyColor = Color.Empty;
    int highestRgbDelta = 0;

    for (int x = 0; x < bmp.Width; x++)
    {
        for (int y = 0; y < bmp.Height; y++)
        {
            if (GetRgbDelta(bmp.GetPixel(x, y)) <= highestRgbDelta) continue;

            highestRgbDelta = GetRgbDelta(bmp.GetPixel(x, y));
            keyColor = bmp.GetPixel(x, y);
        }
    }

    return keyColor;
}

/// <summary>
/// Utility method that encapsulates the RGB Delta calculation:
/// delta = abs(R-G) + abs(G-B) + abs(B-R) 
/// So, between the color RGB(50,100,50) and RGB(128,128,128)
/// The first would be the higher delta with a value of 100 as compared
/// to the secong color which, being grayscale, would have a delta of 0
/// </summary>
/// <param name="color">The color for which to calculate the delta</param>
/// <returns>An integer in the range 0 to 510 indicating the difference
/// in the RGB values that comprise the color</returns>
private static int GetRgbDelta(Color color)
{
    return
        Math.Abs(color.R - color.G) +
        Math.Abs(color.G - color.B) +
        Math.Abs(color.B - color.R);
}

一个所有像素的RGB值都为128,128,128的图像难道不被认为是一张(单色)灰色矩形图片吗? - chrischu
@crischu:嗯,我认为那只是一个展示所有值都相等的例子。 - Beska
1
我认为你做不到。彩色扫描并使用有损压缩一定会在颜色方面产生一些伪影。即使是黑白文档也不会完全是灰度的。 - Yannick Motton
3
为了容忍扫描时的预期变化,我建议稍微改进一下。做法可以是这样的:colorDiff = (Red - Blue) ^ 2 + (Red - Green) ^ 2。如果colorDiff < COLOR_DIFF_MAX,则认为是灰度图像——我建议在已知为灰度的扫描图像上运行计算,以找到合理的COLOR_DIFF_MAX值。 - Conspicuous Compiler
@Kigurai:这是完全错误的还是不是唯一的方法?它不能两者兼备。我首先追求的是香草般的简单。"测试颜色的简单算法" 今天早上,我跟进了一个更复杂的例子,可以允许稍微偏离灰色。 - Paul Sasik
显示剩余3条评论

1
一种更快的版本。测试阈值为8。对我来说效果很好。
使用:
bool grayScale;
Bitmap bmp = new Bitmap(strPath + "\\temp.png");
grayScale = TestGrayScale(bmp, 8);
if (grayScale)
   MessageBox.Show("Grayscale image");


/// <summary>Test a image is in grayscale</summary>
/// <param name="bmp">The bmp to test</param>
/// <param name="threshold">The threshold for maximun color difference</param>
/// <returns>True if is grayscale. False if is color image</returns>
public bool TestGrayScale(Bitmap bmp, int threshold)
{
    Color pixelColor = Color.Empty;
    int rgbDelta;

    for (int x = 0; x < bmp.Width; x++)
    {
        for (int y = 0; y < bmp.Height; y++)
        {
            pixelColor = bmp.GetPixel(x, y);
            rgbDelta = Math.Abs(pixelColor.R - pixelColor.G) + Math.Abs(pixelColor.G - pixelColor.B) + Math.Abs(pixelColor.B - pixelColor.R);
            if (rgbDelta > threshold) return false;
        }
    }
    return true;
}

你有更快的吗?


你是在尝试回答问题还是提出问题?请明确! - Samer

0

由于JPEG支持元数据,因此您应该首先检查扫描软件是否在保存的图像上放置了一些特殊数据,并且您是否可以依赖该信息。


这对我来说毫无意义。如果扫描软件将元数据写入文件,则会写入图像为彩色图像(即使图像仅包含灰度内容),如果以彩色方式扫描。 - Beska
这只是一个想法,我指出需要验证这些假设数据,Beska。无论如何,感谢您的评论。 - Rubens Farias

0

我在Python板块发布的答案可能会有所帮助。你在网上找到的灰度图像通常不具有相同的R、G、B值,这需要一些方差计算和某种采样过程,以便您不必检查一百万个像素。Paul给出的解决方案基于最大差异,因此来自扫描仪的单个红色像素伪影可能会将灰度图像转换为非灰度图像。我发布的解决方案在13000张图像上达到了99.1%的精度和92.5%的召回率。


0

我认为这种方法应该需要最少的代码,它已经在JPEG上进行了测试。下面的bImage是一个字节数组。

 MemoryStream ms = new MemoryStream(bImage);
 System.Drawing.Image returnImage = System.Drawing.Image.FromStream(ms);
 if (returnImage.Palette.Flags == 2)
 {
      System.Diagnostics.Debug.WriteLine("Image is greyscale");
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接