Распознавание изображения в изображении в C # - PullRequest
13 голосов
/ 11 октября 2011

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

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

  1. захватить иголку и скриншот стога сена
  2. перебрать стог сена, ища стог сенаof needle
  3. [if 2. is true:] обведите второй и последний пиксель иглы и сравните его со стогом сена [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 ]

3 голосов
/ 11 октября 2011

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

РЕДАКТИРОВАТЬ: И когда ваша проблема все еще возникает после этого, я бы попытался сохранить изображение в файл и попытаться снова с этим файлом, используя ваш отладчик, создавая воспроизводимую ситуацию.

2 голосов
/ 11 октября 2011

Во-первых, проблема с циклом 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 голосов
/ 11 октября 2011

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

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

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

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

0 голосов
/ 11 апреля 2018

Вот справочник по классу с примером кода , который отлично подходит для моего приложения на C # для поиска иголки в стоге сена для каждого кадра с USB-камеры в 2018 году ... Я считаю, что Accord в основном кучаобертки C # для быстрого кода C ++.

Также обратите внимание на оболочку C # для Microsoft C ++ DirectShow , которую я использую для поиска иглы в каждом кадре с USB-камеры

...