判断位图是否完全为黑色的有效方法是什么?

22

我想知道是否有一种超级有效的方法来确认一个Image对象引用了一个完全黑色的图像,因此位图中的每个像素都是ARGB(255, 0, 0, 0)。

你会推荐什么?其中大部分位图将是1024 x 6000像素(尽管不能安全地假定它们总是那个大小)。

我需要这个是因为我们在使用PrintWindow API时遇到问题。我们发现近20%的时间,至少部分图像将是黑色的正方形(随后的捕获将成功)。我解决这个问题的想法是对每个子窗口调用PrintWindow或WM_PRINT,然后将整个窗口的图像拼接起来。如果我能找到一种有效的方法来检测PrintWindow是否针对特定子窗口返回了黑色图像,那么我就可以快速地在该捕获上再次调用PrintWindow。这很糟糕,但PrintWindow是捕获所有窗口(至少我想要的)并支持捕获隐藏和/或离屏窗口的唯一方法。

当PrintWindow失败时,它不设置错误代码或返回任何指示失败的内容。当它出现这个黑色正方形的问题时,总是会返回一个完整的窗口或子窗口是黑色的。所以通过单独捕获每个子窗口,我可以确定我的每个捕获都已经成功工作,只要它包含至少一个非黑色像素即可。

据说PrintWindow在Vista及以上版本中更好,但在这种情况下,我们受限于Server 2003。


除了逐像素检查,您还有其他的意思吗? - jlew
3
随机选择一些像素,保证它们分布均匀。如果这些像素全部为黑色,则认为整个图像是黑色的? - user1228
2
@brendan 不完全是这样。我们的眼睛无法真正区分0x010101和0。此外,我们的眼-脑连接和大脑电路需要太长时间。 - luiscubal
1
@Mat - 鉴于您现在告诉我们您正在尝试检测失败的WM_PRINT调用,请修改问题以包含此非常重要的信息。 - Simeon Pilgrim
1
@Simeon Pilgrim - 抱歉,我不想在问题中提供过多的信息,现在我已经添加了我认为相关的内容。 - Matt Brindley
显示剩余10条评论
12个回答

22

我建议使用System.Drawing.Bitmap类型的LockBits方法锁定位图内存。该方法返回BitmapData类型,从中可以获得指向锁定内存区域的指针。然后迭代内存,搜索非零字节(实际上,通过扫描Int32或甚至Int64值来加快速度,取决于您使用的平台)。代码将如下所示:

// Lock the bitmap's bits.  
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData =bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;

// Declare an array to hold the bytes of the bitmap.
int bytes  = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];

// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes);

// Scanning for non-zero bytes
bool allBlack = true;
for (int index = 0; index < rgbValues.Length; index++)
    if (rgbValues[index] != 0) 
    {
       allBlack = false;
       break;
    }
// Unlock the bits.
bmp.UnlockBits(bmpData);

考虑使用不安全代码和直接内存访问(使用指针)来提高性能。


3
如果可能的话,将大多数像素强制转换为int[],然后对值进行value |= 0xFF000000操作也会加快速度。Marshal.Copy可能会很慢。 - Pasi Savolainen
完全同意。只需使用Scan0属性接收的IntPtr并将其转换为(uint*)。然后使用此指针进行迭代。 下一步是使用非托管代码程序集,您可以使用SIMD指令(C++中的__asm块)。 - leonard
3
检查DWORD组中的0xFF000000值比逐字节检查要好得多。 - Chris O

9

这篇文章的第一个回答非常棒。我修改了代码,使其更通用,可以确定图像是否完全为一种颜色(全黑、全白、全洋红等)。假设您有一个带有4个部分颜色值ARGB的位图,则将每个颜色与左上角的颜色进行比较,如果有任何不同,则该图像不是完全为一种颜色。

private bool AllOneColor(Bitmap bmp)
{
    // Lock the bitmap's bits.  
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

    // Get the address of the first line.
    IntPtr ptr = bmpData.Scan0;

    // Declare an array to hold the bytes of the bitmap.
    int bytes = bmpData.Stride * bmp.Height;
    byte[] rgbValues = new byte[bytes];

    // Copy the RGB values into the array.

    System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

    bool AllOneColor = true;
    for (int index = 0; index < rgbValues.Length; index++)
    {
        //compare the current A or R or G or B with the A or R or G or B at position 0,0.
        if (rgbValues[index] != rgbValues[index % 4])
        {
            AllOneColor= false;
            break;
        }
    }
    // Unlock the bits.
    bmp.UnlockBits(bmpData);
    return AllOneColor;
}

7
如果您了解图像变成非黑色的条件,那将更容易。例如,当图像变为非黑色时,边缘或中心会是什么样子。基本上,您创建的启发式算法可以猜测非黑色图像并采样那些能够让您最快地读取它的区域。如果您的启发式算法指示所有黑色图像,则可以决定它是全黑色图像,或者对所有像素进行完整检查。这在很大程度上取决于您的图像。如果您需要区分全黑色图像和包含随机位置单个非黑色像素的图像,则必须检查所有像素。

6
锁定位图到内存中,并使用按位操作进行扫描。不要使用GetPixel等方法,那样很慢。 点击此处 了解更多信息。

3
使用AForgeNET库(http://www.aforgenet.com)也可以是一种解决方案:
public bool IsNotBlackImage()
{
    Assembly assembly = this.GetType().Assembly;
    var imgTest = new Bitmap(assembly.GetManifestResourceStream("TestImage.png"));
    var imgStatistics = new ImageStatistics(imgTest);             
    return imgStatistics.PixelsCountWithoutBlack != 0;
}

在您的项目中引用AForge.Imaging.dll以获取ImageStatistics类参考。

http://code.google.com/p/aforge/source/browse/trunk/Sources/Imaging/ImageStatistics.cs


2
使用对角线上有3 x 255的ColorMatrix绘制位图,这将把任何非黑色像素变成纯白色。然后将该位图绘制到一个宽度是4的倍数且格式为Format24bppRgb的较小位图中。这样可以消除alpha通道,减小尺寸,并且如果位图真正为黑色,则只剩下零值。
您需要进行实验以确定可以缩小位图多少,使用一个只有一个白色像素的示例来查看插值器何时使其消失。我猜您可以走得很远。

1
为了完全确认图像的黑度,您需要检查每个像素,并且在不安全的块中访问像素数据很可能是最快的方式。当然,可以针对非黑色情况进行优化并尝试更早地找到它们,但在最坏的情况下,您仍然必须检查每个像素。

1

一些随机的想法:

  • 也许你可以对原始位图应用ColorMatrix(将其完全变成黑色)。然后将结果与原始图像进行比较。
  • 或者创建一个相同大小的位图(填充纯黑色),然后与原始位图进行比较。

1

我有一个超出常规的想法。

如何使用CRC校验和?您可以先检查图像的尺寸,然后计算校验和并将其与相同尺寸的全黑图像的已知(预先计算的)校验和进行比较。

编辑:我怀疑这不会比@leonard的方法更快。唯一可能的原因是,如果原始文件不是位图,而是压缩的图像格式。这样,CRC校验和算法在运行之前就不必解压缩图像。


1

一个相对可靠的方法是检查图像的文件大小。也就是说,如果那些不全是黑色的图像具有相对正常的颜色分布。

如果您知道文件类型,那么您就知道一些基本的压缩比率信息。并且您可以很容易地确定文件的尺寸而不必浏览整个文件。

任何尺寸的全黑图像都将比具有相当正常颜色分布的相同尺寸的图像使用压缩文件格式时具有非常小的文件大小。

这种方法需要一点时间来测试和建立关于全黑图像的文件大小与非全黑图像的文件大小的知识库,但它会非常快速。

如果您有许多实例,其中非全黑图像与全黑图像非常接近,那么显然这种方法将无法起作用。


如果这个能够工作的话,那就太酷了。但是我刚刚测试了一下,它并没有起作用。实际上,无论由什么组成,任何两个具有相同尺寸的位图文件都将具有相同的文件大小。 - Steve Wortham
@Gabe:我正要说这个。然而,虽然不太可能,但OP只提到了位图。1024 x 6000位图是巨大的文件,所以对我来说似乎不是理想的选择。如果像你说的那样压缩它们,那么你就可以利用这种优化。 - Steve Wortham

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