代码之家  ›  专栏  ›  技术社区  ›  Matt Brindley

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

  •  21
  • Matt Brindley  · 技术社区  · 15 年前

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

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

    我需要这个,因为我们的printwindow API有问题。我们发现,近20%的时间,至少部分图像将是一个黑色正方形(随后的捕获将成功)。我解决这个问题的想法是调用每个子窗口的print window或wm_print,然后将窗口的整个图像拼合起来。如果我能找到一种有效的方法来检测printwindow返回的特定子窗口的黑色图像,那么我就可以在捕获时再次调用printwindow。它很糟糕,但是printwindow是捕获在所有窗口上工作的窗口的唯一方法(不管怎样,我希望如此),并且支持捕获隐藏和/或屏幕外的窗口。

    当printwindow失败时,它不会设置错误代码或返回任何指示它失败的内容。当它有这个黑方问题时,它总是一个返回黑色的整个窗口或子窗口。因此,通过分别捕获每个子窗口,我可以确保每个捕获都会有效,前提是它至少包含一个非黑色像素。

    显然,在Vista和更高版本中,PrintWindow更好,但在本例中,我们仅限于Server2003。

    12 回复  |  直到 10 年前
        1
  •  20
  •   leonard    15 年前

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

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

        2
  •  7
  •   tvanfosson    15 年前

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

        3
  •  6
  •   Craig Stuntz    15 年前

    Lock the bitmap into memory 用逐位运算扫描。不使用 GetPixel 等等,这很慢。

        4
  •  6
  •   DanoB    11 年前

    这篇文章的第一个答案很好。我修改了代码,以更一般地确定图像是否都是一种颜色(全黑、全白、全品红等等)。假设您有一个包含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;
    }
    
        5
  •  3
  •   FlorinB    15 年前

    使用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
  •  2
  •   Hans Passant    15 年前

    用一个对角线中有3 x 255的颜色矩阵绘制位图,它将把任何非黑色像素吹成纯白色。然后将该位图绘制成一个较小的位图,其宽度是4的倍数,格式为24bpprgb。这样就消除了alpha,减小了大小,如果位图真的是黑色的,则只留下零。

    你需要做一些实验,看看你能让位图变得多小,用一个只有一个白色像素的示例来看看插值器什么时候能让它消失。我猜你可以走很远。

        7
  •  1
  •   Matti Virkkunen    15 年前

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

        8
  •  1
  •   ChristopheD    15 年前

    只是一些随机的想法:

    • 也许你可以 apply a ColorMatrix 原版 位图(完全转换为 黑色)。然后将结果与 原件。
    • 或者创建大小相同的位图 (填充纯黑色)然后 与原始位图进行比较。
        9
  •  1
  •   Stewbob    15 年前

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

    如果你知道文件类型,你就知道一些关于平均压缩比的基本知识。您可以很容易地确定文件的维度,而无需循环整个文件。

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

    这个方法需要花费一点时间来测试和建立一个知识库,来确定一个全黑图像的文件大小应该与一个非全黑图像进行比较,但是它会非常快。

    如果您有许多非全黑图像非常接近全黑的实例,显然,这种方法将不起作用。

        10
  •  1
  •   Steve Wortham    15 年前

    我有一个想法,在盒子外面。

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

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

        11
  •  0
  •   Dylan Nicholson    10 年前
        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
  •  0
  •   user3979266    10 年前

    一个可能也能做到这一点的诀窍是在某个地方放置一个指示器像素,它总是具有相同的颜色,除非图像捕获失败,在这种情况下,我假设所有东西都是完全黑色的。