13

我想在图像(干草堆)中找到图像()。

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

  1. 抓针和干草堆截图
  2. 遍历 haystack,寻找 haystack[i] == needle 的第一个像素
  3. [如果 2. 为真:] 循环遍历 needle 的第二个到最后一个像素并将其与 haystack[i] 进行比较

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

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

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

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


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

A. 示例输入 - 匹配

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

4 回答 4

3

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

编辑:当你的问题在那之后仍然出现时,我会尝试将图像保存到一个文件中,然后使用你的调试器再次尝试使用该文件,给你一个可重现的情况。

于 2011-10-11T17:18:50.190 回答
2

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

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

这是我用来帮助解决这些方程式的网站:https ://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx

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

于 2011-10-11T18:04:53.660 回答
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您自己创建的所有实现对象的习惯。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;
        }
    }
}

其余的代码似乎还可以,但说明它对于某些输入不是很有效。例如,您可能有一个大的纯色作为桌面的背景,这可能会导致很多checkmatch调用。

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

于 2011-10-11T19:06:48.467 回答
0

这是带有示例代码的类参考,非常适合我的 C# 应用程序,用于在 2018 年从 USB 相机的每一帧中寻找大海捞针...我相信 Accord 主要是一堆用于快速 C++ 代码的 C# 包装器。

还可以查看Microsoft C++ DirectShow 的 C# 包装器,我用它来从 USB 相机的每一帧中搜索针头

于 2018-04-11T16:43:08.620 回答