在另一个具有缩进和 return 坐标的位图中搜索位图

Search for a bitmap in another bitmap with indentation and return coordinates

我找到了一个完美的功能,但不知道如何添加搜索缩进:


        public static bool FindBitmap(Bitmap bmpNeedle, Bitmap bmpHaystack, out Point location)
        {
            if (bmpNeedle == null || bmpHaystack == null)
            {
                location = new Point();
                return false;
            }
            for (int outerX = 0; outerX < bmpHaystack.Width - bmpNeedle.Width; outerX++)
            {
                for (int outerY = 0; outerY < bmpHaystack.Height - bmpNeedle.Height; outerY++)
                {
                    for (int innerX = 0; innerX < bmpNeedle.Width; innerX++)
                    {
                        for (int innerY = 0; innerY < bmpNeedle.Height; innerY++)
                        {
                            Color cNeedle = bmpNeedle.GetPixel(innerX, innerY);
                            Color cHaystack = bmpHaystack.GetPixel(innerX + outerX, innerY + outerY);

                            if (cNeedle.R != cHaystack.R || cNeedle.G != cHaystack.G || cNeedle.B != cHaystack.B)
                            {
                                goto notFound;
                            }
                        }
                    }
                    location = new Point(outerX, outerY);
                    return true;
                notFound:
                continue;
                }
            }
            location = Point.Empty;
            return false;
        }

我通过添加变量 xFrom 和 yFrom 修改了函数 public static bool FindBitmap(Bitmap bmpNeedle, Bitmap bmpHaystack, out Point location, int xFrom, int yFrom)

并设置计数器的初始值:outerXouterY

但是没用。不明白怎么修改功能

让我们从一个完整的解决方案开始,它使用您按原样提供的函数:

Bitmap fullImage = (Bitmap)Bitmap.FromFile(@"G:\large.png");
Bitmap smallImage = (Bitmap)Bitmap.FromFile(@"G:\small.png");

if (FindBitmap(smallImage, fullImage, out Point location))
{
    Console.WriteLine($"Found small image embedded at {location}");
}
else
{
    Console.WriteLine("Couldn't find small image embedded in larger image.");
}

我 运行 使用一些示例数据并能够找到嵌入的图像。在我的示例中,我的嵌入图像位于 {X=88,Y=74},并且在我的 CPU 上需要大约 330k 个滴答才能找到这个位置。现在,问题是如何修改您的原始函数以提供搜索范围 space。正如您所建议的,您可以为您的函数设置一个初始的 x 和 y 位置,作为猜测或缩进提供。

public static bool FindBitmap(Bitmap bmpNeedle, Bitmap bmpHaystack, out Point location, int xFrom = 0, int yFrom = 0)
{
    if (bmpNeedle == null || bmpHaystack == null)
    {
        location = new Point();
        return false;
    }
    for (int outerX = xFrom; outerX < bmpHaystack.Width - bmpNeedle.Width; outerX++)
    {
        for (int outerY = yFrom; outerY < bmpHaystack.Height - bmpNeedle.Height; outerY++)
        {
            for (int innerX = 0; innerX < bmpNeedle.Width; innerX++)
            {
                for (int innerY = 0; innerY < bmpNeedle.Height; innerY++)
                {
                    Color cNeedle = bmpNeedle.GetPixel(innerX, innerY);
                    Color cHaystack = bmpHaystack.GetPixel(innerX + outerX, innerY + outerY);

                    if (cNeedle.R != cHaystack.R || cNeedle.G != cHaystack.G || cNeedle.B != cHaystack.B)
                    {
                        goto notFound;
                    }
                }
            }
            location = new Point(outerX, outerY);
            return true;
        notFound:
            continue;
        }
    }
    location = Point.Empty;
    return false;
}

现在我们可以调用 with 进行更好的猜测:if (FindBitmap(smallImage, fullImage, out Point location, 80, 70)) 与 returns 相同的值。由于搜索 space 减少了,现在我的机器上只需要 45k 个滴答。您可以类似地添加 xToyTo 值以进一步限制搜索 space.

但是,我们可以做得更好!现在您正在使用 GetPixel,这是出了名的低效。您可以锁定位图图像并直接访问像素数据:

public static int[] GetPixels(Bitmap image)
{
    var imageLock = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    int[] pixels = new int[image.Width * image.Height];
    Marshal.Copy(imageLock.Scan0, pixels, 0, pixels.Length);
    image.UnlockBits(imageLock);
    return pixels;
}

现在您可以在搜索开始时抓取像素数据并直接访问它:

public static bool FindBitmap(Bitmap bmpNeedle, Bitmap bmpHaystack, out Point location, int xFrom = 0, int yFrom = 0)
{
    if (bmpNeedle == null || bmpHaystack == null)
    {
        location = new Point();
        return false;
    }

    var needlePixels = GetPixels(bmpNeedle);
    var haystackPixels = GetPixels(bmpHaystack);

    for (int outerX = xFrom; outerX < bmpHaystack.Width - bmpNeedle.Width; outerX++)
    {
        for (int outerY = yFrom; outerY < bmpHaystack.Height - bmpNeedle.Height; outerY++)
        {
            for (int innerX = 0; innerX < bmpNeedle.Width; innerX++)
            {
                for (int innerY = 0; innerY < bmpNeedle.Height; innerY++)
                {
                    var cNeedle = needlePixels[innerX + innerY * bmpNeedle.Width];
                    var cHaystack = haystackPixels[innerX + outerX + (innerY + outerY) * bmpHaystack.Width];

                    if (cNeedle != cHaystack)
                    {
                        goto notFound;
                    }
                }
            }
            location = new Point(outerX, outerY);
            return true;
        notFound:
            continue;
        }
    }
    location = Point.Empty;
    return false;
}

这将我的硬件上的搜索时间减少到 25k 次,并且可能会为更大的搜索带来好处。最后,您可以重新格式化这段代码,使其(我希望)更干净一些。这是我建议的最终方法,它在我的机器上只需要大约 15k 个滴答:

public static bool FindBitmap(Bitmap bmpNeedle, Bitmap bmpHaystack, out Point location, int xFrom = 0, int yFrom = 0)
{
    location = Point.Empty;

    if (bmpNeedle == null || bmpHaystack == null)
    {
        return false;
    }

    var needlePixels = GetPixels(bmpNeedle);
    var haystackPixels = GetPixels(bmpHaystack);

    for (int outerX = xFrom; outerX < bmpHaystack.Width - bmpNeedle.Width; outerX++)
    {
        for (int outerY = yFrom; outerY < bmpHaystack.Height - bmpNeedle.Height; outerY++)
        {
            if (IsMatch(needlePixels, haystackPixels, outerX, outerY, bmpNeedle.Width, bmpHaystack.Width))
            {
                location = new Point(outerX, outerY);
                return true;
            }
        }
    }

    return false;
}

public static bool IsMatch(int[] needle, int[] haystack, int x, int y, int needleWidth, int haystackWidth)
{
    for (int i = 0; i < needle.Length; i++)
    {
        var innerX = i % needleWidth;
        var innerY = i / needleWidth;
        var cNeedle = needle[i];
        var cHaystack = haystack[innerX + x + (innerY + y) * haystackWidth];
        if (cNeedle != cHaystack) return false;
    }
    return true;
}

我已经修改了您的代码,以便您可以使用搜索缩进:

public static bool FindBitmap(Bitmap bmpNeedle, Bitmap bmpHaystack, out Point location, int startX, int startY)
    {
        if (bmpNeedle == null || bmpHaystack == null)
        {
            location = new Point();
            return false;
        }
        for (int outerX = startX; outerX < bmpHaystack.Width - bmpNeedle.Width; outerX++)
        {
            for (int outerY = startY; outerY < bmpHaystack.Height - bmpNeedle.Height; outerY++)
            {
                for (int innerX = 0; innerX < bmpNeedle.Width; innerX++)
                {
                    for (int innerY = 0; innerY < bmpNeedle.Height; innerY++)
                    {
                        Color cNeedle = bmpNeedle.GetPixel(innerX, innerY);
                        Color cHaystack = bmpHaystack.GetPixel(innerX + outerX, innerY + outerY);

                        if (cNeedle.R != cHaystack.R || cNeedle.G != cHaystack.G || cNeedle.B != cHaystack.B)
                        {
                            goto notFound;
                        }
                    }
                }
                location = new Point(outerX, outerY);
                return true;
            notFound:;
            }
        }
        location = Point.Empty;
        return false;
    }