与Python相比,C#中的OpenCV MatchTemplate太慢了

Gui*_*osa 6 .net c# python image-processing image-recognition

我已经在Python中编写了一个解决方案,该解决方案效果很好,但是需要安装几个库以及许多专业设置才能工作.我决定在Visual Studio Community 2017上使用C#中的GUI构建它,但在第一个成功的函数中,结果比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
Run Code Online (Sandbox Code Playgroud)

在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我没有测量每一个之间经过的时间,但我可以说C#中的结果大约需要3秒,而Python中的结果大约需要100ms.

有优化的空间,如果有人想建议任何改进,欢迎他们.

den*_*ufa 8

问题是,在Python代码中,当至少添加一个匹配时,您完成迭代retorno:

if len(retorno)> 1:
  return retorno
Run Code Online (Sandbox Code Playgroud)

在C#示例中,您将继续迭代,直到所有文件都循环通过.


sɐu*_*qɐp 2

我在下面的源代码中结合了denfromufaHouseCat提出的解决方案,并进行了一些整体清理,以便您可以看到您的代码如何。您还会注意到可读性的微小改进,因为我使用C# 7.0 / .NET 4.7编写了重构的代码。

真正的算法优化

尽管denfromula正确地指出了实现问题,并且HouseCat提到了使用更多的 CPU 资源,但真正的收益依赖于减少图像搜索算法期间执行的操作数量。

  • TURBO STAGE 1 -假设该MinMax()函数遍历所有图像的像素来收集所有这些统计数据,但您只对使用maxValue[0]. 一个极端的微调是编写一个特定的函数,当低于最小阈值时,该函数会停止迭代所有图像的像素。maxValue[0]显然,这就是您的功能所需要的全部。请记住:永远不要烧掉所有计算大量未使用的图像统计数据的处理器

  • TURBO STAGE 2 -看起来您正在尝试识别图像集中的任何图像是否与您的输入屏幕截图匹配 ( tela)。如果没有太多要匹配的图像,并且您经常检查屏幕是否有新的匹配项,则强烈建议预加载所有这些图像匹配对象,并在函数调用中重用它们。持续的磁盘 IO 操作和实例化位图类(对于每个屏幕截图)会导致严重的性能损失。

  • TURBO STAGE 3 -以防万一您每秒截取多个屏幕截图,然后尝试重用屏幕截图的缓冲区。当整个屏幕截图的尺寸没有改变时,不断地重新分配整个屏幕截图的缓冲区也会导致性能损失。

  • TURBO STAGE 4 -这很难获得,取决于您想为此投资多少。将您的图像识别系统视为一个大管道。位图作为数据在各个阶段(图像匹配阶段、OCR 阶段、鼠标位置绘制阶段、视频录制阶段等)之间流动的容器。这个想法是创建固定数量的容器并重用它们,避免它们的创建和销毁。容器的数量就像管道系统的“缓冲区大小”。当管道的多个阶段完成使用这些容器时,它们将返回到管道的起点,即一种容器池。


使用这些外部库确实很难实现最后一项优化,因为在大多数情况下,它们的 API 需要一些内部位图实例化,并且微调也会导致库与外部库之间的极端软件耦合。因此,您必须深入研究这些优秀的库以了解它们的实际工作原理,并构建您自己的自定义框架。我可以说这是一次很好的学习经历。

这些库对于很多用途来说都非常酷;它们提供通用 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
            }

            // ...
        });
Run Code Online (Sandbox Code Playgroud)

快乐调音;-)