【问题标题】:What's an efficient way to tell if a bitmap is entirely black?判断位图是否全黑的有效方法是什么?
【发布时间】:2011-02-03 03:04:48
【问题描述】:

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

你会推荐什么?这些位图中的大多数将是 1024 x 6000 像素(尽管假设它们总是这样的大小并不安全)。

我需要这个,因为我们遇到了 PrintWindow API 的问题。我们发现在将近 20% 的情况下,至少图像的某些部分将是黑色方块(后续捕获将成功)。我解决这个问题的想法是对每个子窗口调用 PrintWindow 或 WM_PRINT,然后将窗口的整个图像拼凑在一起。如果我能找到一种有效的方法来检测 PrintWindow 为特定子窗口返回了黑色图像,那么我可以在捕获时再次快速调用 PrintWindow。这很糟糕,但 PrintWindow 是唯一一种捕获适用于所有窗口(无论如何都是我想要的)的窗口的方法,并且支持捕获隐藏和/或屏幕外的窗口。

当 PrintWindow 失败时,它不会设置错误代码或返回任何表明它失败的信息。当它出现这个黑色方块问题时,它总是返回黑色的整个窗口或子窗口。因此,通过分别捕获每个子窗口,我可以确保我的每个捕获都有效,只要它包含至少一个非黑色像素。

显然,PrintWindow 在 Vista 及更高版本中更好,但在这种情况下,我们仅限于 Server 2003。

【问题讨论】:

  • 你的意思不是检查每个像素?
  • 选择一个随机但分布良好的像素数,如果它们都是黑色的,就假设整个像素都是黑色的?
  • @brendan 不完全是。我们的眼睛无法真正区分 0x010101 和 0。此外,我们的眼-脑连接和大脑回路花费的时间太长。
  • @Mat - 鉴于您现在告诉我们您尝试检测失败的 WM_PRINT 调用,请更改问题以包含此非常重要的信息。
  • @Simeon Pilgrim - 抱歉,我不想用信息过多地回答这个问题,我已经添加了我认为相关的内容。

标签: c# .net image bitmap


【解决方案1】:

我建议您使用 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);

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

【讨论】:

  • 如果可能的话,转换为 int[] 然后值 |= 0xFF000000 对于大多数像素也会加快速度。 Marshal.Copy 可能很慢..
  • 完全同意。只需使用 Scan0 属性接收的 IntPtr 并将其转换为 (uint*)。然后使用这个指针进行迭代。下一步是使用非托管代码汇编,您可以在其中使用 SIMD 指令(C++ 中的 __asm 块)
  • 是的,在 DWORD 组中检查值 0xFF000000 比一次检查单个字节要好得多。
【解决方案2】:

这篇文章的第一个答案是太棒了。我修改了代码以更通用地确定图像是否全是一种颜色(全黑、全白、全洋红色等)。假设您有一个具有 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;
}

【讨论】:

    【解决方案3】:

    如果您对图像非黑色的条件有更多了解,那就更容易了。例如,当图像不是黑色时,图像的边缘或中心看起来像什么。本质上,您创建的内容是启发式猜测非黑色图像并采样那些可以让您最快阅读的区域。如果您的启发式方法指示全黑图像,那么您可以决定它是全黑的,或者对所有像素进行全面检查。不过,这在很大程度上取决于您的图像。如果您必须能够区分全黑图像和随机位置包含单个非黑色像素的图像,则必须全部检查。

    【讨论】:

      【解决方案4】:

      Lock the bitmap into memory 并使用按位操作对其进行扫描。不要使用GetPixel之类的;这很慢。

      【讨论】:

        【解决方案5】:

        使用 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;
        }
        

        对于您项目中的 ImageStatistics 类参考 AForge.Imaging.dll。

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

        【讨论】:

          【解决方案6】:

          使用对角线为 3 x 255 的 ColorMatrix 绘制位图,这会将任何非黑色像素吹成纯白色。然后将该位图绘制为宽度为 4 的倍数且格式为 Format24bppRgb 的较小位图。如果位图是真正的黑色,这会消除 alpha,减小大小并只留下零。

          您必须进行试验,看看您可以使位图变得多小,使用只有一个白色像素的示例,看看插值器何时使其消失。我猜你可以走得很远。

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              只是一些随机的想法:

              • 也许你可以apply a ColorMatrix 原版 位图(完全把它变成 黑色的)。然后将结果与 原件。
              • 或者创建一个相同大小的位图 (充满纯黑色)然后 与原始位图进行比较。

              【讨论】:

                【解决方案9】:

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

                如果您了解文件类型,您就会了解有关平均压缩率的一些基本信息。而且您可以很容易地确定文件的尺寸,而无需循环浏览整个文件。

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

                这种方法需要一些时间来测试和建立一个知识库,即应该将全黑图像的文件大小与非全黑图像进行比较,但它会非常快.

                如果您有很多非全黑图像非常接近全黑的情况,那么显然这种方法是行不通的。

                【讨论】:

                • 如果这行得通,那就太酷了。但我只是测试了它。但事实并非如此。事实上,任何两个相同尺寸的位图都将具有相同的文件大小,无论它们是由什么组成的。
                • @Gabe:我正要这么说。不管多么不可能,OP 只提到位图。 1024 x 6000 位图是巨大的文件,所以对我来说似乎并不理想。如果它们像你说的那样被压缩,那么你可以利用这种优化。
                【解决方案10】:

                我有一个不合时宜的想法。

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

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

                【讨论】:

                  【解决方案11】:
                      private bool AllOneColor(Bitmap bmp)
                      {
                          BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
                          byte[] rgbValues = new byte[bmpData.Stride * bmpData.Height];
                          System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, rgbValues, 0, rgbValues.Length);
                          bmp.UnlockBits(bmpData);
                          return !rgbValues.Where((v, i) => i % bmpData.Stride < bmp.Width && v != rgbValues[0]).Any();
                      }
                  

                  【讨论】:

                    【解决方案12】:

                    也可以这样做的一个技巧是在某处放置一个指示像素,它始终具有相同的颜色,除非图像捕获失败,在这种情况下,我假设一切都是全黑的

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2017-04-27
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-06-03
                      • 2011-10-01
                      • 2012-05-30
                      • 1970-01-01
                      • 2013-10-11
                      相关资源
                      最近更新 更多