C# 获取所有像素均为白色的图像行时出现问题

C# 获取所有像素均为白色的图像行时出现问题,c#,.net,image,image-processing,system.drawing,C#,.net,Image,Image Processing,System.drawing,我试图找出图像是否从底部剪裁,如果是,那么我将从最后一个白色像素行将其分割为两个图像。下面是我创建的简单方法,用于检查剪裁并获取空的白色像素行。此外,正如您所见,这不是一个很好的解决方案。这可能会导致较大映像的性能问题。因此,如果有人能向我建议更好的方法,那将是一个很大的帮助: private static bool IsImageBottomClipping(Bitmap image) { for (int i = 0; i < image.Width; i++) {

我试图找出图像是否从底部剪裁,如果是,那么我将从最后一个白色像素行将其分割为两个图像。下面是我创建的简单方法,用于检查剪裁并获取空的白色像素行。此外,正如您所见,这不是一个很好的解决方案。这可能会导致较大映像的性能问题。因此,如果有人能向我建议更好的方法,那将是一个很大的帮助:

private static bool IsImageBottomClipping(Bitmap image)
{
    for (int i = 0; i < image.Width; i++)
    {
        var pixel = image.GetPixel(i, image.Height - 1);
        if (pixel.ToArgb() != Color.White.ToArgb())
        {
            return true;
        }
    }

    return false;
}

private static int GetLastWhiteLine(Bitmap image)
{
    for (int i = image.Height - 1; i >= 0; i--)
    {
        int whitePixels = 0;
        for (int j = 0; j < image.Width; j++)
        {
            var pixel = image.GetPixel(j, i);
            if (pixel.ToArgb() == Color.White.ToArgb())
            {
                whitePixels = j + 1;
            }
        }
        if (whitePixels == image.Width)
            return i;
    }

    return -1;
}
私有静态bool IsImageBottomClipping(位图图像)
{
对于(int i=0;i=0;i--)
{
int-whitePixels=0;
对于(int j=0;j
IsImageBottomClipping
工作正常。但另一种方法是不发送正确的白色像素行。它只发送了少一行。示例图像:


在这种情况下,180附近的行应该是
GetLastWhiteLine
方法的返回值。但是它返回了192个。

好吧,所以。。。我们有两个问题要解决。首先是优化,然后是bug。我将从优化开始

最快的方法是直接在内存中工作,但老实说,这有点笨拙。我通常使用的第二个最佳选择是将原始图像数据字节从图像对象中复制出来。这将使您得到四个重要的数据:

  • 宽度,您可以从图像中获得
  • 高度,你可以从图像中得到
  • 字节数组,包含图像字节
  • 步幅,它提供图像上每行使用的字节数
(从技术上讲,还有第五种格式,即像素格式,但我们在这里将强制将其设置为32bpp,这样我们就不必在整个过程中考虑到这一点。)

请注意,从技术上讲,步幅不仅仅是每像素使用的字节数乘以图像宽度。它被四舍五入到下一个4字节的倍数。在处理32位ARGB内容时,这并不是一个真正的问题,因为32位是4字节,但一般来说,最好使用跨距,而不仅仅是乘以的宽度,并在假设每行后面可能有填充字节的情况下编写所有代码。如果你用这种系统处理24位RGB内容,你会感谢我的

然而,当浏览图像内容时,你显然应该只检查包含像素数据的精确范围,而不是整个步幅

获取这些内容的方法非常简单:在图像上使用
LockBits
,告诉它以每像素32位的ARGB数据的形式显示图像(如果需要,它将实际转换),获取行跨距,然后使用
Marshal.Copy
将整个图像内容复制到字节数组中

Int32 width = image.Width;
Int32 height = image.Height;
BitmapData sourceData = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Int32 stride = sourceData.Stride;
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
image.UnlockBits(sourceData);
如前所述,这被强制为32位ARGB格式。如果您想使用此系统以图像中的原始格式获取数据,只需将
PixelFormat.Format32bppArgb
更改为
image.PixelFormat

现在,您必须意识到,
锁位
是一个相当繁重的操作,它以请求的像素格式将数据复制到新内存中,在新内存中可以读取或编辑数据(如果没有像我在这里指定为只读)。这比您的方法更优化的原因很简单,
GetPixel
在每次请求单个像素值时执行
LockBits
操作。因此,您正在将
锁位
调用的数量从数千个减少到一个


不管怎样,现在,关于你的功能

在我看来,第一种方法完全没有必要;您应该在任何图像上运行第二个。它的输出为您提供了图像的最后一条白线,因此,如果该值等于
height-1
,您就完成了,如果不是,您就立即获得了进一步处理所需的值。毕竟,第一个函数与第二个函数的作用完全相同;它检查一行上的所有像素是否为白色。唯一的区别是它只处理最后一行

那么,第二种方法。这就是问题所在。您将白色像素的数量设置为“当前像素索引加一”,而不是增加它以检查所有像素是否匹配,这意味着该方法将覆盖所有像素,但只检查行上的最后一个像素是否为白色。因为您的图像在最后一行的末尾确实有一个白色像素,所以它在一行之后中止

此外,无论何时发现不匹配的像素,都应该立即中止该行的扫描,就像第一种方法一样;在那之后再继续打那条线是没有意义的

因此,让我们修复第二个函数,并将其重写为使用“字节数组”、“跨距”、“宽度”和“高度”集合,而不是使用图像。我还添加了“白色”作为参数,以使其更易于重用,因此它从
GetLastWhiteLine
更改为
GetLastClearLine

一个通用的可用性注意事项:如果您在高度和宽度上迭代,请实际调用循环变量
y
x
;它使代码中的内容更加清晰

我在代码注释中解释了使用的系统

private static Int32 GetLastClearLine(Byte[] sourceData, Int32 stride, Int32 width, Int32 height, Color checkColor)
{
    // Get color as UInt32 in advance.
    UInt32 checkColVal = (UInt32)checkColor.ToArgb();
    // Use MemoryStream with BinaryReader since it can read UInt32 from a byte array directly.
    using (MemoryStream ms = new MemoryStream(sourceData))
    using (BinaryReader sr = new BinaryReader(ms))
    {
        for (Int32 y = height - 1; y >= 0; --y)
        {
            // Set position in the memory stream to the start of the current row.
            ms.Position = stride * y;
            Int32 matchingPixels = 0;
            // Read UInt32 pixels for the whole row length.
            for (Int32 x = 0; x < width; ++x)
            {
                // Read a UInt32 for one whole 32bpp ARGB pixel.
                UInt32 colorVal = sr.ReadUInt32();
                // Compare with check value.
                if (colorVal == checkColVal)
                    matchingPixels++;
                else
                    break;
            }
            // Test if full line matched the given color.
            if (matchingPixels == width)
                return y;
        }
    }
    return -1;
}

对于您的示例图像,这会得到值178,这在我签入Gimp时是正确的。

好的,所以。。。我们有两个问题要解决。首先是优化,然后是bug。我
{
    ms.Position = stride * y;
    Int32 x;
    for (x = 0; x < width; ++x)
        if (sr.ReadUInt32() != checkColVal)
            break;
    if (x == width)
        return y;
}