Подтвердить что ты не робот

Распознавание изображения внутри изображения в С#

Я хотел бы найти изображение (иглу) в изображении (стог сена).

Чтобы все было просто, я делаю два скриншота своего рабочего стола. Один полный размер (стог сена) и крошечный (игла). Затем я просматриваю изображение сена и пытаюсь найти изображение иглы.

  • снимок экрана захвата и стопки сена
  • цикл через стог сена, глядя на стог сена [i] == первый пиксель иглы
  • [if 2. is true:] прокрутите второй и последний пиксели иглы и сравните их с сторой сена [i]

Ожидаемый результат: изображение иглы находится в правильном месте.

Я уже работал над некоторыми координатами/ширинами/высотами (A).

Но иногда биты кажутся "выключенными", и поэтому совпадение не найдено (B).

Что я могу делать неправильно? Любые предложения приветствуются. Спасибо.


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

а. example input - match

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

В. пример ввода - НЕТ соответствия

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;
}
4b9b3361

Ответ 1

Во-первых, существует проблема с циклом 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;
}

Это, вероятно, должно решить проблему. Кроме того, имейте в виду, что может быть несколько совпадений. Например, если "игла" представляет собой полностью белую прямоугольную часть окна, скорее всего, будет много совпадений на всем экране. Если это возможно, измените свой метод 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 приходит в голову, но я уверен, что есть некоторые существующие алгоритмы, которые гарантируют немедленное пропущение недопустимых кандидатов).

Ответ 2

Вместо того, чтобы делать два скриншота рабочего стола с интервалом времени между ними, я бы сделал снимок экрана один раз и вырезал "иглу" и "стог сена" из того же самого растрового источника. В противном случае вы рискуете изменить содержимое рабочего стола между двумя моментами, когда будут сделаны снимки экрана.

EDIT: И когда ваша проблема по-прежнему возникает после этого, я попытаюсь сохранить изображение в файл и повторить попытку с этим файлом с помощью вашего отладчика, что даст вам воспроизводимую ситуацию.

Ответ 3

Я не думаю, что ваши уравнения для haystack_index или needle_index верны. Похоже, вы учитываете смещение Scan0 при копировании данных растрового изображения, но при вычислении позиции байта вам нужно использовать растровое изображение Stride.

Кроме того, формат Format32bppArgb использует 4 байта на пиксель. Похоже, вы принимаете 1 байт на пиксель.

Здесь сайт, который я использовал, чтобы помочь с этими уравнениями: http://www.bobpowell.net/lockingbits.htm

Format32BppArgb: с учетом координат X и Y адрес первого элемента в пиксель - Scan0 + (y * stepide) + (x * 4). Это указывает на синий байт. следующие три байта содержат зеленый, красный и альфа-байты.