OpenCV MatchTemplate в C # слишком медленный по сравнению с Python - PullRequest
0 голосов
/ 25 апреля 2018

Я запрограммировал решение на Python, которое прекрасно работало, но для его установки требовалось несколько библиотек и много бюрократических установок.Я решил построить его с графическим интерфейсом в C # на Visual Studio Community 2017, но в первой успешной функции результат был намного медленнее, чем в Python.Какой IMO это на самом деле должно быть быстрее.

Код, по сути, просто делает иголку при поиске изображений в стоге сена, получая все изображения из папки и проверяя каждую иглу (всего 60 изображений) в стоге сена, в pythonЯ возвращаю строку, но в C # я только печатаю.

Мой код на Python следующий:

def getImages(tela):
    retorno = []
    folder = 'Images'
    img_rgb = cv2.imread(tela)
    for filename in os.listdir(folder):
        template = cv2.imread(os.path.join(folder,filename))
        w, h = template.shape[:-1]
        res = cv2.matchTemplate(img_rgb, template, cv2.TM_CCOEFF_NORMED)
        threshold = .96
        loc = np.where(res >= threshold)
        if loc[0]>0:
            retorno.append(filename[0]+filename[1].lower())
            if len(retorno)> 1:
                return retorno

и в C #:

Debug.WriteLine(ofd.FileName);
Image<Bgr, byte> source = new Image<Bgr, byte>(ofd.FileName);
string filepath = Directory.GetCurrentDirectory().ToString()+"\\Images";
DirectoryInfo d = new DirectoryInfo(filepath);
var files = d.GetFiles();
foreach (var fname in files){
    Image<Bgr, byte> template = new Image<Bgr, byte>(fname.FullName);
    Image<Gray, float> result = source.MatchTemplate(template, Emgu.CV.CvEnum.TemplateMatchingType.CcoeffNormed);
    double[] minValues, maxValues;
    Point[] minLocations, maxLocations;
    result.MinMax(out minValues, out maxValues, out minLocations, out maxLocations);
    if (maxValues[0] > 0.96) {
        Debug.WriteLine(fname);
    }
}

Я не измерял время между каждой из них, но могу сказать, что результат в C # занимает около 3 секунд, а в Python - около 100 мс.

Есть место для оптимизации, если кто-то захочет предложить какие-либо улучшенияДобро пожаловать.

Ответы [ 3 ]

0 голосов
/ 14 мая 2018

Это ( ответ denfromufa ) действительно объясняет вашу проблему, но давайте добавим несколько предложений / оптимизаций:

1.) Ваш GetFiles можно заменить наПараллельный перечислитель файлов, который также рекурсивен с дочерними каталогами.Я беззастенчиво написал несколько на GitHub .

2.) Вы можете parellelize цикл foreach в Parallel.ForEach(files, fname () => { Code(); }); Опять же, мой репозиторий FileSearchBenchmark на GitHub имеет множество исполняемых файловых кодов в параллельном режиме.предоставить примеров .

0 голосов
/ 15 мая 2018

Я объединил решения, предложенные denfromufa и HouseCat в приведенном ниже исходном коде, и провел некоторую общую очистку, чтобы вы могли увидеть, каким может быть ваш код. Вы также заметите небольшие улучшения читабельности, поскольку я написал переработанный код, используя C # 7.0 / .NET 4.7 .

Реальная оптимизация алгоритма

Хотя denfromula правильно указал на эту проблему реализации, а HouseCat упомянул использование большего количества ресурсов ЦП, истинное усиление зависит от уменьшения количества операций, выполняемых во время алгоритма поиска изображений.

  • TURBO STAGE 1 - Предположим, что функция MinMax() просматривает все пиксели вашего изображения, чтобы собрать всю эту статистику, но вас интересует только использование maxValue[0]. Чрезвычайно тонкой настройкой было бы написать определенную функцию, которая прекращает повторение всех пикселей вашего изображения , когда maxValue[0] опускается ниже вашего минимального порога. Видимо, это все, что вам нужно в вашей функции. Помните: никогда не сжигает все ваши процессоры, вычисляя большое количество неиспользуемой статистики изображений .

  • TURBO STAGE 2 - Похоже, вы пытаетесь распознать, соответствует ли какое-либо изображение из вашего набора изображений вашему входному скриншоту (tela). Если не требуется слишком много изображений для сопоставления и если вы постоянно проверяете свой экран на наличие новых совпадений, настоятельно рекомендуется предварительно загрузить все эти объекты сопоставления изображений и повторно использовать их в вызовах функций. Постоянные операции дискового ввода-вывода и создание экземпляров классов растровых изображений (для каждого отдельного снимка экрана) приводят к сильному снижению производительности.

  • TURBO STAGE 3 - На всякий случай, если вы делаете несколько снимков экрана в секунду, затем попробуйте повторно использовать буфер снимка экрана. Постоянное перераспределение всего буфера скриншота, когда его размеры просто не изменились, также приводит к снижению производительности .

  • ТУРБО ЭТАП 4 - Это трудно получить, и зависит от того, сколько вы хотите вложить в это. Думайте о своей системе распознавания изображений как о большом конвейере. Растровые изображения как контейнеры данных, проходящих между вашими этапами (этап сопоставления изображений, этап распознавания, этап рисования положения мыши, этап видеозаписи и т. Д.). Идея состоит в том, чтобы создать фиксированное количество контейнеров и использовать их повторно, избегая их создания и уничтожения. Количество контейнеров похоже на «размер буфера» для вашей конвейерной системы. Когда несколько этапов вашего конвейера закончили использовать эти контейнеры, они возвращаются в начало вашего конвейера, в своего рода пул контейнеров.


Эту последнюю оптимизацию действительно трудно достичь с помощью этих внешних библиотек, поскольку в большинстве случаев их API требует некоторой внутренней реализации растрового изображения, а точная настройка также приведет к чрезмерной программной связи между вашей библиотекой и внешней. Так что вам придется копаться в этих замечательных библиотеках, чтобы понять, как они на самом деле работают, и создать свой собственный Framework. Я могу сказать, что это хороший опыт обучения.

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

Могу сказать, что хороший отлаженный алгоритм распознавания изображений занимает не более нескольких миллисекунд, чтобы сделать то, что вы хотите. У меня были приложения для распознавания изображений, которые делают это практически мгновенно для больших скриншотов (например, Функциональность баклажана ).

Теперь вернемся к вашему коду ...

Ваш реорганизованный код должен выглядеть следующим образом. Я не включил все те тонко настроенные алгоритмы, о которых упоминал, - вам лучше задать для них отдельные вопросы в SO.

        Image<Bgr, byte> source = new Image<Bgr, byte>(ofd.FileName);

        // Preferably use Path.Combine here:
        string dir = Path.Combine(Directory.GetCurrentDirectory(), "Images");

        // Check whether directory exists:
        if (!Directory.Exists(dir))
            throw new Exception($"Directory was not found: '{dir}'");

        // It looks like you just need filenames here...
        // Simple parallel foreach suggested by HouseCat (in 2.):
        Parallel.ForEach(Directory.GetFiles(dir), (fname) =>
        {
            Image<Gray, float> result = source.MatchTemplate(
                new Image<Bgr, byte>(fname.FullName),
                Emgu.CV.CvEnum.TemplateMatchingType.CcoeffNormed);

            // By using C# 7.0, we can do inline out declarations here:
            result.MinMax(
                out double[] minValues,
                out double[] maxValues,
                out Point[] minLocations,
                out Point[] maxLocations);

            if (maxValues[0] > 0.96)
            {
                // ...
                var result = ...
                return result; // <<< As suggested by: denfromufa
            }

            // ...
        });

Happy Tuning; -)

0 голосов
/ 10 мая 2018

Проблема в том, что в коде Python вы заканчиваете итерацию, когда к retorno добавляется хотя бы одно совпадение:

if len(retorno)> 1:
  return retorno

В примере C # вы продолжаете итерацию, пока все файлы не будут зациклены.

...