目标
在图像处理中,由于每秒要处理大量的操作,因此代码不仅要提供正确的解决方案,而且必须以最快的速度提供。所以在本章中,你将学习:
除了 OpenCV,Python 还提供了一个time模块,它有助于测量执行时间。另一个模块profile有助于获取代码的详细报告,例如每个函数的执行时间、函数调用的次数等。但是,如果你使用的是 IPython,所有这些功能都以用户友好的方式集成在一起。我们将看到一些重要的功能,更多细节请查看附加资源部分的链接。
使用 OpenCV 测量性能
cv.getTickCount 函数返回从参考事件(例如机器启动的时刻)到调用此函数的时刻的时钟周期数。因此,如果在函数执行之前和之后调用它,就可以得到执行函数所使用的时钟周期数。
cv.getTickFrequency 函数返回时钟周期的频率,或每秒的时钟周期数。因此,要查找以秒为单位的执行时间,可以执行以下操作:
double getTickFrequency()
返回每秒的滴答数。
int64 getTickCount()
返回滴答数。
我们将用下面的例子来演示。下面的例子使用大小从 5 到 49 的奇数内核应用中值滤波。(不用担心结果是什么样的——这不是我们的目标)
assert img1 is not None, "文件无法读取,请使用 os.path.exists() 检查"
for i in range(5,49,2):
print( t )
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() 来启用/禁用它。让我们看一个简单的例子。
Out[5]: True
10 loops, best of 3: 34.9 ms per loop
Out[8]: False
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() 对同一图像的性能。
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 的性能。这里只记录相关的技巧,并给出重要资源的链接。需要注意的是,首先尝试以简单的方式实现算法。一旦算法工作正常,对其进行分析,找到瓶颈,然后进行优化。
- 尽可能避免在 Python 中使用循环,尤其是双重/三重循环等。它们本身就非常慢。
- 尽可能地将算法/代码向量化,因为 NumPy 和 OpenCV 针对向量运算进行了优化。
- 利用缓存一致性。
- 除非必要,否则不要复制数组。尽量使用视图。数组复制是一项代价高昂的操作。
如果完成所有这些操作后代码仍然很慢,或者不可避免地要使用大型循环,请使用 Cython 等附加库来提高速度。
附加资源
- Python 优化技巧
- Scipy 教程 - 高级 NumPy
- IPython 中的时间测量和性能分析