OpenCV 4.11.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_BGR)
从文件加载图像。
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 loops, best of 3: 34.9 ms per loop
# 禁用它
In [7]: cv.setUseOptimized(False)
In [8]: cv.useOptimized()
Out[8]: False
In [9]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop
void setUseOptimized(bool onoff)
启用或禁用优化代码。
bool useOptimized()
返回优化代码使用状态。

正如你所看到的,优化的中值滤波比未优化的版本快 2 倍。如果你检查它的源代码,你会发现中值滤波是 SIMD 优化的。因此,你可以在代码的顶部使用它来启用优化(记住它默认是启用的)。

在 IPython 中测量性能

有时你可能需要比较两个类似操作的性能。IPython 提供了一个神奇命令 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 次,3 次循环中最佳时间:每次循环 370 微秒
int countNonZero(InputArray src)
统计非零数组元素。

可以看到,OpenCV 函数的速度几乎是 NumPy 函数的 25 倍。

注意
通常情况下,OpenCV 函数比 NumPy 函数更快。因此,对于相同的操作,OpenCV 函数更可取。但是,也可能存在例外情况,尤其是在 NumPy 使用视图而不是副本时。

更多 IPython 魔术命令

还有其他几个魔术命令可以用于测量性能、分析性能、逐行分析性能、内存测量等。它们都有很好的文档记录。这里只提供这些文档的链接。建议感兴趣的读者尝试一下。

性能优化技巧

有几种技术和编码方法可以充分发挥 Python 和 NumPy 的性能。这里只记录相关的技巧,并给出重要资源的链接。需要注意的是,首先尝试以简单的方式实现算法。一旦算法工作正常,对其进行分析,找到瓶颈,然后进行优化。

  1. 尽可能避免在 Python 中使用循环,尤其是双重/三重循环等。它们本身就非常慢。
  2. 尽可能地将算法/代码向量化,因为 NumPy 和 OpenCV 针对向量运算进行了优化。
  3. 利用缓存一致性。
  4. 除非必要,否则不要复制数组。尽量使用视图。数组复制是一项代价高昂的操作。

如果完成所有这些操作后代码仍然很慢,或者不可避免地要使用大型循环,请使用 Cython 等附加库来提高速度。

附加资源

  1. Python 优化技巧
  2. Scipy 教程 - 高级 NumPy
  3. IPython 中的时间测量和性能分析