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



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

代码本质上只是在干草堆图像搜索中做针,从文件夹中获取所有图像并在python中测试所有针(总计60张图像((总计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中大约100ms。

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

问题是,在Python代码中,您完成了至少将一匹匹配添加到retorno时的迭代:

if len(retorno)> 1:
  return retorno

在c#样本中,您继续迭代,直到所有文件都通过。

我将Denfromufa和HouseCat提出的解决方案组合在下面的源代码中,并进行了一些整体清理,因此您可以看到您的代码如何。您还将注意到未经可读性的改进,因为我使用 C#7.0 / .net 4.7

真正的算法优化

虽然 denfromula 正确指出了实施问题,并且使用更多CPU资源提到的 housecat housecat ,真正的收益依赖于减少图像搜索算法中执行的操作数量。

  • 涡轮阶段1- 假设MinMax()函数通过所有图像的像素来收集所有这些统计信息,但是您只想使用maxValue[0]。一个极端的微调是编写一个特定功能,该功能在maxValue[0]低于最小阈值时停止遍历所有图像的像素。显然,这就是您的功能所需的。请记住:永远不要燃烧所有处理器计算许多未使用的图像统计

  • 涡轮阶段2 - 看起来您正在尝试识别一组图像的任何图像是否与输入屏幕截图(tela(相匹配。如果没有太多的图像可以匹配,并且如果您不断检查屏幕上的新匹配项,则强烈建议预先加载所有这些图像匹配对象,并在功能调用中重复使用它们。常数磁盘IO操作和实例化位图类(对于每个屏幕截图(会导致强烈的性能命中。

  • 涡轮增压阶段3- 以防万一您每秒进行几个屏幕截图,然后尝试重复使用屏幕截图的缓冲区。不断当整个屏幕截图的尺寸根本不变时,将整个屏幕截图的缓冲区重新计算也会导致性能损失

  • 涡轮增压阶段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
            }
            // ...
        });

快乐调整; - (

this(Denfromufa的答案(确实解释了您的问题,但还可以添加一些建议/优化:

1。(您的GetFiles可以用并行文件枚举代替,该文件也可以用儿童目录进行递归。我在Github上无耻地写了几篇。

2。(您可以再次将foreach循环列入 Parallel.ForEach(files, fname () => { Code(); });,github上的my fileSearchBenchmark存储库并行了大量的文件代码执行以提供示例。

最新更新