代码之家  ›  专栏  ›  技术社区  ›  Guilherme Garcia da Rosa

与Python相比,C中的OpenCV MatchTemplate速度太慢

  •  7
  • Guilherme Garcia da Rosa  · 技术社区  · 6 年前

    我已经用Python编写了一个解决方案,该解决方案工作得很好,但需要安装几个库和大量的burocratic设置才能工作。我决定在Visual Studio Community 2017上使用C#中的GUI构建它,但在第一个成功的函数中,结果要比Python慢得多。在我看来,它实际上应该更快。

    代码本质上只是大海捞针式的图像搜索,通过从文件夹中获取所有图像并在大海捞针中测试每个指针(总共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 回复  |  直到 6 年前
        1
  •  10
  •   den.run.ai    6 年前

    问题是,在Python代码中,当至少有一个匹配项添加到 retorno :

    if len(retorno)> 1:
      return retorno
    

    在C#sample中,继续迭代,直到所有文件都循环通过。

        2
  •  2
  •   sɐunıɔןɐqɐp Zmey    6 年前

    我结合了 denfromufa HouseCat 在下面的源代码中,并进行了一些全面的清理,以便您可以看到您的代码是如何的。您还将注意到一些较小的可读性改进,因为我使用 C#7.0 / .NET 4.7

    实数算法优化

    虽然 denfromula 正确指出实施问题,以及 家猫 提到使用更多CPU资源,真正的收益取决于减少图像搜索算法期间执行的操作数。

    • 涡轮增压器1级- 假设 MinMax() 函数遍历图像的所有像素以收集所有这些统计信息,但您只对使用 maxValue[0] 。一个极端的微调是 编写一个特定函数,停止遍历图像的所有像素 什么时候 最大值[0] 低于您的最低阈值。显然,这就是你的功能所需要的。请记住: 永远不要烧掉所有处理器,计算大量未使用的图像统计信息

    • 涡轮增压器2级- 看起来您正在尝试识别图像集中的任何图像是否与输入屏幕截图匹配( tela )。如果没有太多要匹配的图像,并且如果您不断检查屏幕是否有新的匹配,强烈建议预加载所有这些图像匹配对象,并在函数调用中重用它们。 持续的磁盘IO操作和实例化位图类(针对每个屏幕截图)会对性能产生巨大影响。

    • 涡轮增压器3级- 以防万一,你每秒要拍摄几个屏幕截图,然后尝试重用屏幕截图的缓冲区。不断地 当整个屏幕快照的尺寸没有改变时,重新分配其缓冲区也会导致性能损失

    • 涡轮增压器4级- 这很难得到,这取决于你想在这上面投资多少。 把你的图像识别系统想象成一个大管道。位图作为数据在各个阶段之间流动的容器 (图像匹配阶段、OCR阶段、鼠标位置绘制阶段、视频录制阶段等)。其想法是创建固定数量的容器并重复使用,避免其创建和销毁。容器的数量类似于管道系统的“缓冲区大小”。 当管道的几个阶段使用完这些容器后,它们将返回到管道的开头,即一种容器池。


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

    这些图书馆在很多方面都很酷;它们提供了一个通用API,用于改进功能的可重用性。这也意味着它们处理的内容比您在单个API调用中实际需要的内容多得多。当涉及到高性能算法时,您应该始终重新思考您需要从这些库中获得什么样的基本功能来实现您的目标,如果它们是您的瓶颈,请自行完成。

    我可以说,一个好的经过微调的图像识别算法只需几毫秒就能完成您想要的任务。我曾体验过图像识别应用程序,它们几乎可以在瞬间完成较大屏幕截图的识别(例如。 Eggplant Functional )。

    现在回到您的代码。。。

    重构后的代码应该如下所示。我没有包括我提到的所有微调算法——你最好在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
                }
    
                // ...
            });
    

    愉快的调整;-)

        3
  •  2
  •   HouseCat    6 年前

    这个( denfromufa's answer )确实可以解释您的问题,但也需要补充一些建议/优化:

    1)您的 GetFiles 可以替换为并行文件枚举器,该枚举器也是递归的子目录。我无耻地写了一些 GitHub

    2)可以将foreach循环并行化为 Parallel.ForEach(files, fname () => { Code(); }); 同样,GitHub上的FileSearchBenchmark存储库有大量并行执行的文件代码,可以提供 examples