⚠️这个系列是自己瞎翻的,文法很丑,主要靠意会,跳着跳着捡重要的部分翻,翻错了不负责,就这样哈。
⚠️基于3.4.3,Performance Measurement and Improvement Techniques,附原文。
目标
在处理图像的过程中,既然你每秒都要处理大量的数据计算,这就强制要求你的代码不仅仅要提供正确的解决逻辑,也要使用运算速度更快的方式。所以这一节,你会学到:
- 测量你代码的性能。
- 一些小建议来提升你代码的性能。
- 你会遇到这些方法 : cv.getTickCount, cv.getTickFrequency 等等。
脱离开OpenCV,Python也提供了一个在测量执行时间时非常有用的模组 time 。另一个模组 profile 帮忙拿到代码的详细报告,比如代码中每个方法要花掉多少时间,一个方法被调用了多少次等等。但是如果你使用IPython,所有的这些特性都被整合成了一种对用户非常友好的方式。我们会遇到一些重要的特性,想要知道更多,查看 额外资源 这个部分里的链接。
用OpenCV来测量性能
cv.getTickCount 方法返回一个特定的事件(比如开机的时刻)之后到你调用此方法的时刻之间的时间周期数。因此你在(自己的业务)方法执行前和(自己的业务)方法执行后调用这个方法,你就能得到之前执行(自己的业务)方法所花费的时间周期数。
cv.getTickFrequency 方法返回时间周期的频率,或者叫做每秒时间周期的数量。所以要得到每秒执行的次数,你可以像下面这样做。
e1 = cv.getTickCount()
# your code execution
e2 = cv.getTickCount()
time = (e2 - e1)/ cv.getTickFrequency()
我们将会用以下例子来论证,下面的示例用一个从5到49,奇数距离修正的种子,来做中值滤波。(不用管这个结果是啥样的,那不是我们的目标):
img1 = cv.imread('messi5.jpg')
e1 = cv.getTickCount()
for i in xrange(5,49,2):
img1 = cv.medianBlur(img1,i)
e2 = cv.getTickCount()
t = (e2 - e1)/cv.getTickFrequency()
print( t )
# Result I got is 0.521107655 seconds
提示
你可以用 time 模组来做同样的事,用 time.time() 方法代替 cv.getTickCount ,然后拿到两个时间的差值。
OpenCV里的默认优化
许多 OpenCV 方法用 SSE2, AVX 等等优化过。但OpenCV也包含了未优化的代码(译者注:这对使用者是无感的,调用的方法相同,根据硬件和设置效率不同。)。所以如果你的系统支持这些特性我们就应该利用这些特性(几乎所有的现代处理器都支持他们)。当编译时它默认就开启了,因此如果它被开启了的话,OpenCV会运行优化过的代码,否则运行未优化过的。你可以使用 cv.useOptimized() 方法来检查它是开启了还是被关闭了,用 cv.setUseOptimized() 方法来开启或者关闭它,让我们看一个简单示例。
# check if optimization is enabled
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
# Disable it
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
看,优化过的中值滤波比未优化过的版本快两倍。 如果你查看它的源码,你可以发现这个中值滤波的计算是用单指令多数据流(译者注: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) ?我们将用timeit在IPython 的脚本中找到答案。
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倍左右(译者注:结果中的单位-> ns-纳秒10的-9次方秒,us-微秒10的-6次方秒)。如果你还考虑到数组的创建,它可能要快到(Numpy的)接近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
看,OpenCV方法比Numpy方法快了接近25倍。
提示
通常,OpenCV函数比Numpy函数更快。 因此,对于相同的操作,OpenCV方法是首选。 但是,可能有例外,尤其是当Numpy使用视图而不是副本时。
IPython更多的神奇命令
还有其他一些神奇的命令来衡量性能,分析,线性分析,内存测量等等。它们都有很好的文档记录。因此,此处仅提供指向这些文档的链接。 建议有兴趣的读者试一试。(译者注:在本节末尾的连接中)
性能优化技巧
有几种技术和编码方法可以利用Python和Numpy最大化的性能。 此处仅注明相关的内容,并提供重要资源的链接。 这里主要应该注意的是,首先尝试以简单的方式实现算法。 一旦代码有效工作,分析它,找到瓶颈并进行优化它。
- 尽一切可能,避免在Python中使用循环,特别是双/三循环。因为循环本身就很慢。
- 尽可能把算法/代码向量化,因为Numpy和OpenCV都为向量操作做过优化。
- 利用缓存的相关性。
- 除非必须,否则绝不拷贝数组。尝试用视图代替,拷贝数组是个消耗极大的操作。
即使照所有这些技巧做了之后,你的代码仍然很慢,或者使用大型循环是不可避免的,请使用其他库比如 Cython 来加快速度。