OpenCV  4.10.0
开源计算机视觉
加载...
搜索...
无匹配项
性能测量和改进技术

目标

在图像处理中,由于每秒需要处理大量操作,因此务必要确保您的代码不仅提供了正确的解决方案,还以最快的速度提供了该解决方案。因此,在本章中,您将学习

除了 OpenCV 之外,Python 还提供了一个模块time,它有助于测量执行时间。另一个模块profile有助于获取代码的详细报告,例如代码中每个函数花费了多少时间,该函数被调用的次数等。但是,如果您正在使用 IPython,所有这些功能都以一种用户友好的方式集成在一起。我们将重点介绍其中的一些重要功能,有关更多详细信息,请查看其他资源部分中的链接。

使用 OpenCV 测量性能

cv.getTickCount 函数返回从某个参考事件(例如机器打开时的时刻)到调用此函数的时刻的时钟周期数。因此,如果您在函数执行前和执行后调用它,您将获得用来执行函数的时钟周期数。

cv.getTickFrequency 函数返回时钟周期数的频率,或每秒的时钟周期数。因此,若要找出以秒为单位的执行时间,您可以执行以下操作:

#您的代码执行
time = (e2 - e1)/ cv.getTickFrequency()
double getTickFrequency()
返回每秒打勾次数。
int64 getTickCount()
返回打勾次数。

我们通过以下示例进行演示。以下示例应用奇数大小介于 5 到 49 之间的核进行中值滤波。(不要担心结果是什么样子 - 那不是我们的目标)

img1 = cv.imread('messi5.jpg')
assert img1 is not None, "文件无法读取,请检查 os.path.exists()"
for i in range(5,49,2)
img1 = cv.medianBlur(img1,i)
t = (e2 - e1)/cv.getTickFrequency()
print( t )
# 获得的结果是 0.521107655 秒
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件中加载图像。
void medianBlur(InputArray src, OutputArray dst, int ksize)
利用中值滤波器模糊图像。
请注意
可以使用 time 模块执行相同操作。使用 time.time() 函数代替 cv.getTickCount。然后取这两个时间差值。

OpenCV 默认优化

许多 OpenCV 函数使用 SSE2、AVX 等优化。它们也包含一些未优化的代码。因此,如果我们的系统支持这些特性,我们应充分利用它们(几乎所有现代处理器都支持)。在编译过程中默认启用这些特性。因此,如果已启用,OpenCV 将运行已优化的代码;否则,将运行未优化的代码。你可以使用 cv.useOptimized() 检查其是否已启用/禁用,使用 cv.setUseOptimized() 启用/禁用。我们来看一个简单的示例。

# 检查优化是否已启用
In [5]: cv.useOptimized()
Out[5]: True
In [6]: %timeit res = cv.medianBlur(img,49)
10 次循环,最佳 3 次:每个循环 34.9 毫秒
# 禁用它
In [7]: cv.setUseOptimized(False)
In [8]: cv.useOptimized()
Out[8]: False
In [9]: %timeit res = cv.medianBlur(img,49)
10 次循环,最佳 3 次:每个循环 64.1 毫秒
void setUseOptimized(bool onoff)
启用或禁用优化代码。
bool useOptimized()
返回优化代码使用状态。

如你所见,优化后的中值滤波比未优化版本快 2x。如果你查看它的源代码,会发现中值滤波经过 SIMD 优化。因此,你可以将其用于在代码顶部启用优化(记住,它在默认情况下是启用的)。

在 IPython 中测量性能

有时你可能需要比较两个类似操作的性能。IPython 提供了一个 magic 命令 timeit 来执行此操作。它会多次运行代码以获得更准确的结果。同样,它适合于测量代码的单行。

例如,你知道以下哪种加法操作更好,x = 5; y = x**2,x = 5; y = x*x,x = np.uint8([5]); y = x*x,还是 y = np.square(x)?我们将在 IPython shell 中使用 timeit 找出答案。

In [10]: x = 5
In [11]: %timeit y=x**2
10000000 loops, best of 3: 73 ns per loop
In [12]: %timeit y=x*x
10000000 loops, best of 3: 58.3 ns per loop
In [15]: z = np.uint8([5])
In [17]: %timeit y=z*z
1000000 loops, best of 3: 1.25 us per loop
In [19]: %timeit y=np.square(z)
1000000 loops, best of 3: 1.16 us per loop

你可以看到,x = 5 ; y = x*x 最快,与 Numpy 相比快约 20 倍。如果你还考虑数组创建,甚至可以快 100 倍。很酷吧? *(Numpy 开发人员正在努力解决此问题)*

请注意
Python 标量运算比 Numpy 标量运算更快。因此,对于包含一个或两个元素的操作,Python 标量优于 Numpy 数组。当数组的大小大一点时,Numpy 具有优势。

我们将尝试另一个示例。这一次,我们将比较 cv.countNonZero()np.count_nonzero() 的性能,针对同一张图片。

In [35]: %timeit z = cv.countNonZero(img)
100000 loops, best of 3: 15.8 us per loop
In [36]: %timeit z = np.count_nonzero(img)
1000 loops, best of 3: 370 us per loop
int countNonZero(InputArray src)
计算非零数组元素。

请看,OpenCV 函数比 Numpy 函数快近 25 倍。

请注意
通常,OpenCV 函数比 Numpy 函数更快。因此,对于相同的操作,建议使用 OpenCV 函数。但是,可能会出现例外情况,特别是当 Numpy 处理视图而不是副本时。

更多 IPython magic 命令

还有其他几个 magic 命令用于测量性能、分析、行分析、内存测量,等等。它们都有详细的文档。因此,此处仅提供那些文档的链接。建议有兴趣的读者尝试一下。

性能优化技术

有几种技术和编码方法可以利用 Python 和 Numpy 的最大性能。此处仅说明了相关方法,并给出了重要资料的链接。此处需要说明的主要事项是,首先尝试以简单的方式实现算法。一旦它开始工作,对其进行概要分析,找出瓶颈,并对其进行优化。

  1. 尽可能避免在 Python 中使用循环,尤其是双/三重循环等。它们固有地很慢。
  2. 最大程度地将算法/代码矢量化,因为 Numpy 和 OpenCV 针对矢量操作进行了优化。
  3. 利用高速缓存一致性。
  4. 除非必要,否则切勿创建数组的副本。尝试改用视图。数组复制是一个代价高昂的操作。

如果在执行所有这些操作后,您的代码仍然很慢,或者不可避免地使用大型循环,请使用 Cython 等其他库使其更快。

其他资源

  1. Python 优化技术
  2. Scipy 讲义 - 高级 Numpy
  3. IPython 中的时序和概要分析

练习