在C#中识别图像内的图像

时间:2011-10-11 17:07:28

标签: c# image lockbits

我想在图像中找到一个图像( needle )( haystack )。

为了简单起见,我拍了两张桌面截图。一个完整尺寸( haystack )和一个小尺寸( needle )。然后我循环通过干草堆图像并尝试找到针图像。

  1. 捕捉针和干草堆截图
  2. 循环通过haystack,寻找haystack [i] ==第一针像素
  3. [if 2. is true:]循环穿过针的第2到最后一个像素并将其与haystack [i]
  4. 进行比较

    预期结果:在正确的位置找到针图像。

    我已经让它适用于某些坐标/宽度/高度(A)。

    但有时比特似乎“关闭”,因此找不到匹配(B)。

    我可能做错了什么?欢迎任何建议。感谢。


    var needle_height = 25;
    var needle_width = 25;
    var haystack_height = 400;
    var haystack_width = 500;
    

    :一种。示例输入 - 匹配

    var needle = screenshot(5, 3, needle_width, needle_height); 
    var haystack = screenshot(0, 0, haystack_width, haystack_height);
    var result = findmatch(haystack, needle);
    

    B中。示例输入 - 不匹配

    var needle = screenshot(5, 5, needle_width, needle_height); 
    var haystack = screenshot(0, 0, haystack_width, haystack_height);
    var result = findmatch(haystack, needle);
    

    1。捕获针和干草堆图像

    private int[] screenshot(int x, int y, int width, int height)
    {
    Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
    Graphics.FromImage(bmp).CopyFromScreen(x, y, 0, 0, bmp.Size);
    
    var bmd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 
      ImageLockMode.ReadOnly, bmp.PixelFormat);
    var ptr = bmd.Scan0;
    
    var bytes = bmd.Stride * bmp.Height / 4;
    var result = new int[bytes];
    
    Marshal.Copy(ptr, result, 0, bytes);
    bmp.UnlockBits(bmd);
    
    return result;
    }
    

    2。尝试找到匹配

    public Point findmatch(int[] haystack, int[] needle)
    {
    var firstpixel = needle[0];
    
    for (int i = 0; i < haystack.Length; i++)
    {
        if (haystack[i] == firstpixel)
        {
        var y = i / haystack_height;
        var x = i % haystack_width;
    
        var matched = checkmatch(haystack, needle, x, y);
        if (matched)
            return (new Point(x,y));
        }
    }    
    return new Point();
    }
    

    第3。验证完全匹配

    public bool checkmatch(int[] haystack, int[] needle, int startx, int starty)
    {
        for (int y = starty; y < starty + needle_height; y++)
        {
            for (int x = startx; x < startx + needle_width; x++)
            {
                int haystack_index = y * haystack_width + x;
                int needle_index = (y - starty) * needle_width + x - startx;
                if (haystack[haystack_index] != needle[needle_index])
                    return false;
            }
        }
        return true;
    }
    

4 个答案:

答案 0 :(得分:3)

我不会使用它们之间的时间间隔制作桌面的两个屏幕截图,而是截取一次屏幕截图并从相同的位图源中剪切“needle”和“haystack”。否则,您可能会在拍摄屏幕截图的两个时刻之间更改桌面内容。

编辑:当你的问题仍然发生之后,我会尝试将图像保存到文件中,然后使用调试器再次使用该文件,为您提供可重现的情况。

答案 1 :(得分:2)

我不认为haystack_indexneedle_index的公式是正确的。复制位图数据时,您似乎考虑了Scan0偏移量,但在计算字节位置时需要使用位图的Stride

此外,Format32bppArgb格式每像素使用4个字节。看起来你假设每个像素有1个字节。

以下是我用来帮助处理这些公式的网站:http://www.bobpowell.net/lockingbits.htm

  

Format32BppArgb:给定X和Y坐标,表示第一个元素的地址   像素是Scan0 +(y * stride)+(x * 4)。这指向蓝色字节。该   以下三个字节包含绿色,红色和alpha字节。

答案 2 :(得分:2)

首先,findmatch循环存在问题。您不应该只将haystack图像用作数组,因为您需要分别从右侧和底部减去针的宽度和高度:

public Point? findmatch(int[] haystack, int[] needle)
{
    var firstpixel = needle[0];

    for (int y = 0; y < haystack_height - needle_height; y++)
        for (int x = 0; x < haystack_width - needle_width; x++)
        {
            if (haystack[y * haystack_width + x] == firstpixel)
            {
                var matched = checkmatch(haystack, needle, x, y);
                if (matched)
                    return (new Point(x, y));
            }
        }

    return null;
}

这应该可能解决问题。另请注意,可能存在多个匹配。例如,如果“needle”是窗口的完全白色矩形部分,则整个屏幕中很可能会有很多匹配。如果有可能,请修改您的findmatch方法,以便在找到第一个结果后继续搜索结果:

public IEnumerable<Point> FindMatches(int[] haystack, int[] needle)
{
    var firstpixel = needle[0];
    for (int y = 0; y < haystack_height - needle_height; y++)
        for (int x = 0; x < haystack_width - needle_width; x++)
        {
            if (haystack[y * haystack_width + x] == firstpixel)
            {
                if (checkmatch(haystack, needle, x, y))
                    yield return (new Point(x, y));
            }
        }
}

接下来,您需要养成手动处理实现IDisposable的所有对象的习惯,这些对象是您自己创建的。 BitmapGraphics是这样的对象,这意味着需要修改screenshot方法以将这些对象包装在using语句中:

private int[] screenshot(int x, int y, int width, int height)
{
    // dispose 'bmp' after use
    using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb))
    {
        // dispose 'g' after use
        using (var g = Graphics.FromImage(bmp))
        {
            g.CopyFromScreen(x, y, 0, 0, bmp.Size);

            var bmd = bmp.LockBits(
                new Rectangle(0, 0, bmp.Width, bmp.Height),
                ImageLockMode.ReadOnly,
                bmp.PixelFormat);

            var ptr = bmd.Scan0;

            // as David pointed out, "bytes" might be
            // a bit misleading name for a length of
            // a 32-bit int array (so I've changed it to "len")

            var len = bmd.Stride * bmp.Height / 4;
            var result = new int[len];
            Marshal.Copy(ptr, result, 0, len);

            bmp.UnlockBits(bmd);

            return result;
        }
    }
}

其余的代码看起来还不错,并指出它对某些输入效率不高。例如,您可能会将大的纯色作为桌面背景,这可能会导致许多checkmatch次调用。

如果您感兴趣的是性能,您可能需要检查加速搜索的不同方法(类似于修改后的Rabin-Karp,但我确信有一些现有算法可确保无效候选人立即被跳过。)

答案 3 :(得分:0)

这是class reference with example code,它适用于我的C#应用​​程序,可以在2018年从USB摄像头中查找每个帧的大海捞针......我相信Accord主要是用于快速C ++的一堆C#包装器码。

另请查看我用于在USB相机的每一帧内搜索针的C# wrapper for Microsoft C++ DirectShow