性能测量和改进技术
一、目标
在图像处理中,由于您每秒处理大量操作,因此您的代码必须不仅提供正确的解决方案,而且还要以最快的方式提供。所以在本章中,您将学习
- 衡量代码的性能。
- 一些提高代码性能的技巧。
- 您将看到以下函数:
cv2.getTickCount
,cv2.getTickFrequency
等。
除了OpenCV
之外,Python
还提供了一个time
模块,有助于测量执行时间。另一个profile
模块有助于获得有关代码的详细报告,例如代码中每个函数花费的时间,调用函数的次数等。但是,如果您使用的是IPython
,所有这些功能都集成在一个用户友好的方式。我们将看到一些重要的内容,有关详细信息,请查看Additional Resouces
部分中的链接。
二、使用OpenCV测量性能
cv2.getTickCount
函数返回参考事件(如机器开启时刻)到调用此函数的时钟周期数。因此,如果在函数执行之前和之后调用它,则会获得用于执行函数的时钟周期数。
cv2.getTickFrequency
函数返回时钟周期的频率或每秒的时钟周期数。因此,要在几秒钟内找到执行时间,您可以执行以下操作:
e1 = cv2.getTickCount()
# your code execution
e2 = cv2.getTickCount()
time = (e2 - e1)/ cv2.getTickFrequency()
我们将通过以下示例演示。下面的例子使用奇数大小从5到49的内核应用中值过滤。(不要担心结果会是什么样的,这不是我们的目标):
img1 = cv2.imread('messi5.jpg')
e1 = cv2.getTickCount()
for i in xrange(5,49,2):
img1 = cv2.medianBlur(img1,i)
e2 = cv2.getTickCount()
t = (e2 - e1)/cv2.getTickFrequency()
print t
# Result I got is 0.521107655 seconds
如果使用python3
,可以使用:
import cv2
img1 = cv2.imread('images/p1.png')
e1 = cv2.getTickCount()
for i in range(5,49,2):
img1 = cv2.medianBlur(img1,i)
e2 = cv2.getTickCount()
t = (e2 - e1)/cv2.getTickFrequency()
print(t)
# 获得0.599947172
注意
您可以对
time
模块执行相同操作。而不是cv2.getTickCount
,使用time.time()
功能。然后取两次的差异。
三、OpenCV中的默认优化
许多OpenCV
功能都使用SSE2,AVX
等进行了优化。它还包含未经优化的代码。因此,如果我们的系统支持这些功能,我们应该利用它们(几乎所有现代处理器都支持它们)。编译时默认启用它。因此,OpenCV
默认运行优化代码(如果已启用),否则运行未优化代码。您可以使用cv2.useOptimized()
来检查它是否已启用/禁用,并使用cv2.setUseOptimized()
来启用/禁用它。让我们看一个简单的例子。
# check if optimization is enabled
In [5]: cv2.useOptimized()
Out[5]: True
In [6]: %timeit res = cv2.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop
# Disable it
In [7]: cv2.setUseOptimized(False)
In [8]: cv2.useOptimized()
Out[8]: False
In [9]: %timeit res = cv2.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop
看,优化中值滤波比未优化版本快约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
会占据优势。
我们将再尝试一个例子。这一次,我们将比较同一图像的cv2.countNonZero()
和np.count_nonzero()
的性能。
In [35]: %timeit z = cv2.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
看,OpenCV
功能比Numpy
功能快近25倍。
注意
通常,
OpenCV
函数比Numpy
函数更快。因此,对于相同的操作,OpenCV
功能是首选。但是,可能有例外,尤其是当Numpy
使用视图而不是副本时。
五、更多IPython魔术命令
还有其他几个神奇的命令来衡量性能,分析,线性分析,内存测量等。它们都有很好的文档记录。因此,此处仅提供指向这些文档的链接。建议有兴趣的读者试一试。
六、性能优化技术
有几种技术和编码方法可以利用Python
和Numpy
的最大性能。此处仅注明相关的内容,并提供重要来源的链接。这里要注意的主要是,首先尝试以简单的方式实现算法。一旦工作,对其进行分析,找出瓶颈并对其进行优化。
- 1、尽量避免在Python中使用循环,尤其是double/triple循环等。它们本身就很慢。
- 2、将算法/代码矢量化到最大可能范围,因为Numpy和OpenCV针对向量运算进行了优化。
- 3、利用缓存一致性。
- 4、除非需要,否则永远不要复制数组。尝试使用视图。阵列复制是一项耗费性能的操作。 即使在完成所有这些操作之后,如果您的代码仍然很慢,或者使用大型循环是不可避免的,请使用其他库(如
Cython
)来加快速度。