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

fan*_*nti 13 c# image lockbits

我想在图像(干草堆)中找到一个图像(针).

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

  1. 捕获针和干草堆截图
  2. 循环通过干草堆,寻找干草堆[i] ==第一针像素
  3. [如果2.是真的:]循环通过针的第2到最后一个像素并将其与haystack [i]进行比较

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

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

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

我能做错什么?欢迎任何建议.谢谢.


var needle_height = 25;
var needle_width = 25;
var haystack_height = 400;
var haystack_width = 500;
Run Code Online (Sandbox Code Playgroud)

A.示例输入 - 匹配

var needle = screenshot(5, 3, needle_width, needle_height); 
var haystack = screenshot(0, 0, haystack_width, haystack_height);
var result = findmatch(haystack, needle);
Run Code Online (Sandbox Code Playgroud)

B.示例输入 - 不匹配

var needle = screenshot(5, 5, needle_width, needle_height); 
var haystack = screenshot(0, 0, haystack_width, haystack_height);
var result = findmatch(haystack, needle);
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

Gro*_*roo 2

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

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;
}
Run Code Online (Sandbox Code Playgroud)

这应该可以解决问题。另外,请记住,可能存在多个匹配项。例如,如果“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));
            }
        }
}
Run Code Online (Sandbox Code Playgroud)

IDisposable接下来,您需要保持手动处理您自己创建的所有实现 的对象的习惯。Bitmap和Graphics是这样的对象,这意味着您的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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

代码的其余部分似乎没问题,但需要注意的是,对于某些输入来说,它不会非常有效。例如,您的桌面背景可能采用大的纯色,这可能会导致许多checkmatch呼叫。

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