【问题标题】:OpenCV MatchTemplate in C# is too slow compared to Python与 Python 相比,C# 中的 OpenCV MatchTemplate 太慢了
【发布时间】:2018-10-05 07:57:30
【问题描述】:

我用 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

在 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 毫秒。

还有优化的空间,如果有人想提出任何改进建议,欢迎提出。

【问题讨论】:

  • 您想只查找一张图片还是所有张图片匹配? Python 代码仅找到一张图像。 C# 代码将找到所有匹配项
  • BTW Emgu 是 Python 使用的同一个 OpenCV 库的包装器。如果两个程序都执行相同的操作,您应该不会看到任何显着差异。如果在每种情况下都使用并行处理,您可以提高性能。在 C# 中,您可以使用例如 PLINQ 或 Parallel.ForEach

标签: c# python .net image-processing image-recognition


【解决方案1】:

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

if len(retorno)> 1:
  return retorno

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

【讨论】:

    【解决方案2】:

    我在下面的源代码中结合了denfromufa和HouseCat提出的解决方案,并做了一些整体的清理,所以你可以看看你的代码是怎样的。由于我使用 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 调用中处理的内容比您实际需要的要多得多。当谈到高性能算法时,您应该始终重新考虑从这些库中实现目标所需的基本功能是什么,如果它们是您的瓶颈,请自己做。

    我可以说,一个好的微调图像识别算法不会花费超过几毫秒的时间来做你想做的事。我体验过图像识别应用程序,它们几乎可以立即对较大的屏幕截图进行识别(例如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
                }
    
                // ...
            });
    

    愉快的调整;-)

    【讨论】:

    • 哇,这太棒了,我知道所有三个建议的理论,但无法想象在代码上的使用,你在建议和解释中说得很清楚。我会在稍后尝试,如果一切正常,我会回来接受答案并奖励您。
    • 不客气。几年来,我一直在痛苦的 UI 测试自动化项目中使用类似的东西。将所有必要的 SO 问题粘合在一起需要一段时间才能解决这个问题 ;-)
    【解决方案3】:

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

    1.) 您的GetFiles 可以替换为并行文件枚举器,这也是递归子目录。我在GitHub上厚颜无耻地写了几篇。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-20
      • 2020-11-19
      • 1970-01-01
      • 2021-03-26
      • 2013-08-18
      • 1970-01-01
      • 2014-05-15
      • 2018-10-25
      相关资源
      最近更新 更多