图像处理时,由于每秒都有大量数据操作,所以代码不仅需要得出正确的结果,同时需要追求执行速度,所以本节,将会学到:
- 评估代码的性能
- 一些提高代码性能的小建议
- 将会使用到一些函数,如
cv2.getTickCount, cv2.getTickFrequency
除了OpenCV,Python本身也自带time模块也可以提供执行时间的评估。另一个profile
模块可以获取代码的详细报告,比如每个函数的时间开销,函数调用次数等。但是如果你使用的时IPython,所有的这些都以一种用户友好的方式被整合了。本节我们将选取一些重要的内容描述,更多的细节,可以阅读最后的附加资源。
原文地址:Performance Measurement and Improvement Techniques
使用OpenCV评估性能
cv2.getTickCount
函数返回从某一参考事件发生后(如开机)的时钟数到当前函数调用时刻的时钟数。所以如果在某个函数的调用前后分别调用这个函数,你可以得到这个函数的执行时钟数。
cv2.getTickFrequency
函数返回时钟计数频率,即每秒的时钟数,所以如果你需要计算函数执行的时间(秒),你0可以用下面的代码
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
你可以使用python的
time
模块做同样额事,但是需要使用函数time.time()
获得两个不同的时间。
OpenCV的默认优化
许多OpenCV函数已经采用了SSE2(Streaming SIMD Extensions 2,Intel官方称为SIMD 流技术扩展2或数据流单指令多数据扩展指令集2 )、AVX(Sandy Bridge和Larrabee架构下的新指令集 )等优化方式(总是就是CPU指令集优化方法)。但它同时有不经过优化的代码。如果我们的系统支持这些指令集优化特性,我们应该利用它们(几乎所有的现代处理器都支持它们)。当编译的时候指令集优化是默认可用的,所以OpenCV能以优化方式运行,否则将采用不优化方式运行。可以调用函数cv2.useOptimized()
函数检测是否当前opencv以优化方式运行,同时调用cv2.setUesOptimized()
方法可以进行优化方案的开关,简单的例子如下
# 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
可以看到,优化版本的执行速度是未优化版本的两倍,如果你查看它的源代码,你会看到中值滤波器是经过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
可见,y=x*x
是最快的,比np的方法快了将近20倍。如果考虑到数组的创建,它可能达到100倍。
Python的标量操作会比Numpy的标量操作更快,所以对于一两个元素的操作,Python的标量性能更好,但是当数据为大的数组的时候,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的魔法指令
Ipython还提供了一些其他的魔法指令,用于测试性能,如profiling,line profiling,内存评估等。并且提供了完整的文档,本文最后贴出文档的连接,有兴趣的读者可以去查阅。
性能优化技术
有不少技术和代码方法可以利用Python和Numpy的最大优化。这里仅提供最贴切的方案,链接再下文中给出。主要需要注意的是,首先先用最简单的方案实现一个算法,测试算法工作正常,再去优化它。
- 在Python中尽量避免使用循环,尤其是多重循环,它们很慢。
- 尽量使用向量化的代码(和算法),因为Numpy和OpenCV为这些向量操作实现了很好的优化。
- 利用好缓存。
- 若无必要,不要为数组创建副本,而是使用它的视图(view,相当于引用实体),数组的拷贝是一种开销非常大的操作。
如果按上述说的做了之后,你的代码还是运行很慢,或者大量的循环不可避免,可以使用类型Cython等额外的库加速代码。
附加资源
- ipython magic functions
- Python Optimization Techniques
- Scipy Lecture Notes - Advanced Numpy