使用LockBits进行图像处理,是获取像素的getpixel的替代方案吗?

6

我正在尝试使用LockBits来增强我的图像检测类,但是这会导致代码出现问题,从而无法运行。如何同时使用LockBits和GetPixel以加速图像检测,或者有没有其他同样快速的替代方案?请帮助我。

代码:

static IntPtr Iptr = IntPtr.Zero;
    static BitmapData bitmapData = null;
    static public byte[] Pixels { get; set; }
    static public int Depth { get; private set; }
    static public int Width { get; private set; }
    static public int Height { get; private set; }

    static public void LockBits(Bitmap source)

    {
            // Get width and height of bitmap
            Width = source.Width;
            Height = source.Height;

            // get total locked pixels count
            int PixelCount = Width * Height;

            // Create rectangle to lock
            Rectangle rect = new Rectangle(0, 0, Width, Height);

            // get source bitmap pixel format size
            Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);


            // Lock bitmap and return bitmap data
            bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
                                         source.PixelFormat);

            // create byte array to copy pixel values
            int step = Depth / 8;
            Pixels = new byte[PixelCount * step];
            Iptr = bitmapData.Scan0;

            // Copy data from pointer to array
            Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);

    }


     static public bool SimilarColors(int R1, int G1, int B1, int R2, int G2, int B2, int Tolerance)
    {
        bool returnValue = true;
        if (Math.Abs(R1 - R2) > Tolerance || Math.Abs(G1 - G2) > Tolerance || Math.Abs(B1 - B2) > Tolerance)
        {
            returnValue = false;
        }
        return returnValue;
    }


     public bool findImage(Bitmap small, Bitmap large, out Point location)
     {
         unsafe
         {
             LockBits(small);
             LockBits(large);
             //Loop through large images width
             for (int largeX = 0; largeX < large.Width; largeX++)
             {
                 //And height
                 for (int largeY = 0; largeY < large.Height; largeY++)
                 {
                     //Loop through the small width
                     for (int smallX = 0; smallX < small.Width; smallX++)
                     {
                         //And height
                         for (int smallY = 0; smallY < small.Height; smallY++)
                         {
                             //Get current pixels for both image
                             Color currentSmall = small.GetPixel(smallX, smallY);
                             Color currentLarge = large.GetPixel(largeX + smallX, largeY + smallY);
                             //If they dont match (i.e. the image is not there)

                             if (!colorsMatch(currentSmall, currentLarge))
                                 //Goto the next pixel in the large image

                                 goto nextLoop;
                         }
                     }
                     //If all the pixels match up, then return true and change Point location to the top left co-ordinates where it was found
                     location = new Point(largeX, largeY);
                     return true;
                 //Go to next pixel on large image
                 nextLoop:
                     continue;
                 }
             }
             //Return false if image is not found, and set an empty point
             location = Point.Empty;
             return false;
         }
     }

你的LockBits方法是无用的...它将像素复制到字节数组中,但你从未使用过该数组。 - Thomas Levesque
4
使用LockBits的目的是为了停止使用GetPixel。 - Hans Passant
2个回答

7

在图像处理中,你不应该依赖于getPixel()方法;虽然偶尔可以调用该方法来获取一个点的值(例如鼠标悬停),但一般最好在图像内存或某个2D数组中进行图像处理,并在必要时将其转换为位图。

首先,你可以尝试编写一个使用LockBits/UnlockBits方法提取方便操作的数组的函数。一旦完成了对该数组的操作,你可以使用另一个LockBits/UnlockBits函数将其写回到位图中。

以下是我过去使用过的一些示例代码。第一个函数返回位图的一个1D值数组。由于你知道位图的宽度,因此可以将这个1D数组转换为2D数组以进一步处理。一旦处理完成,你可以调用第二个函数将(修改后的)1D数组再次转换为位图。

public static byte[] Array1DFromBitmap(Bitmap bmp){
    if (bmp == null) throw new NullReferenceException("Bitmap is null");

    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData data = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
    IntPtr ptr = data.Scan0;

    //declare an array to hold the bytes of the bitmap
    int numBytes = data.Stride * bmp.Height;
    byte[] bytes = new byte[numBytes];

    //copy the RGB values into the array
    System.Runtime.InteropServices.Marshal.Copy(ptr, bytes, 0, numBytes);

    bmp.UnlockBits(data);

    return bytes;           
}

public static Bitmap BitmapFromArray1D(byte[] bytes, int width, int height)
{
    Bitmap grayBmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
    Rectangle grayRect = new Rectangle(0, 0, grayBmp.Width, grayBmp.Height);
    BitmapData grayData = grayBmp.LockBits(grayRect, ImageLockMode.ReadWrite, grayBmp.PixelFormat);
    IntPtr grayPtr = grayData.Scan0;

    int grayBytes = grayData.Stride * grayBmp.Height;
    ColorPalette pal = grayBmp.Palette;

    for (int g = 0; g < 256; g++){
        pal.Entries[g] = Color.FromArgb(g, g, g);
    }

    grayBmp.Palette = pal;

    System.Runtime.InteropServices.Marshal.Copy(bytes, 0, grayPtr, grayBytes);

    grayBmp.UnlockBits(grayData);
    return grayBmp;
}

这些方法对于 Bitmap 像素格式作出了一些假设,可能不适用于您的情况,但是我希望这个基本思路是清晰的:使用 LockBits/UnlockBits 从 Bitmap 中提取一个字节数组,以便您可以最轻松地编写和调试算法,然后再次使用 LockBits/UnlockBits 将数组写入 Bitmap。
为了提高移植性,建议您的方法返回所需的数据类型,而不是在方法内部操作全局变量。
如果您一直在使用 getPixel(),那么按上述描述转换为/从数组可能会大大加快代码速度,只需要进行少量的编程工作。

1
你至少为我省了几个头疼,非常感谢你。 - Ken
1
你应该检查目标字节的步幅,并按行复制。毕竟,在一个新对象上,它可能与你得到的8位数据不同。如果Array1DFromBitmap没有将数据压缩到完全符合宽度,那么它肯定应该输出该步幅,否则数据无法正确处理。 - Nyerguds

2

好的,从哪里开始呢?最好先了解一下lockBits是什么。首先确保不要覆盖你的字节数组。

LockBits(small);              
LockBits(large);

由于第二个调用,第一个调用只是锁定您的图像,这不好,因为您没有再次解锁它。 因此,添加另一个字节数组来表示图像。 您可以像这样做:

LockBits(small, true);              
LockBits(large, false);

并更改您的Lockbits方法

static public void LockBits(Bitmap source, bool flag)                        
{   
...
Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);

if(flag)
   PixelsSmall=Pixels;
else
   PixelsLarge=Pixels;
}

其中PixelsLarge和PixelsSmall是全局变量,而Pixels不是。这两个变量包含了您的图像。现在您需要进行比较。

现在您需要比较每个“字节集”,因此您需要知道像素格式。它是32b/pix 24还是只有8(ARGB、RGB、灰度)

让我们以ARGB图像为例。在这种情况下,一个集合将由4个字节组成(=32/8)。我不确定顺序,但我认为一个集合的顺序是ABGR或BGRA。

希望这可以帮助您。如果您不知道如何比较正确的像素,请再次询问。啊,别忘了使用UnlockBits命令。


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