2.3. 性能测量与改进技术

2.3.1. 目标

在图像处理中,由于每秒要处理大量的操作,因此你的代码不仅仅满足于提供正确的解决方案,还需要该方案是最快的。所以在这一章里,你会学到

  • 测量代码的性能。

  • 一些提高代码性能的技巧。

  • You will see these functions : cv2.getTickCount , cv2.getTickFrequency etc.

除了OpenCV之外,Python也提供了一个模块 time ,该模块有助于测量代码的执行时间。另一个模块 profile 可获得有关代码的详细报告,诸如代码中每个函数所耗费的时间、该函数调用的次数等。如果你使用的是IPython,则所有这些功能都以用户友好的方式集成。我们将看到一些重要的功能,请查看 Addtional Resource 部分以了解有关更多详细信息。

2.3.2. 通过OpenCV来测量性能

cv2.getTickCount 函数返回自从参考事件(如机器开机时刻)开始,到调用该函数的这个时刻这两件事件之间的时钟周期数。因此,如果在函数执行之前和之后调用它,就会得到用于该执行函数的时钟周期数。

cv2.getTickFrequency 函数返回时钟周期的频率或每秒的时钟周期数。因此,要以秒为单位查找执行时间,可以执行以下操作:

>>> import cv2
>>>
>>> e1 = cv2.getTickCount()
>>> # your code execution
>>> e2 = cv2.getTickCount()
>>> time = (e2 - e1)/ cv2.getTickFrequency()

We will demonstrate with following example. Following example apply median filtering with a kernel of odd size ranging from 5 to 49. (Don’t worry about what will the result look like, that is not our goal):

>>> img1 = cv2.imread('/cvdata/messi5.jpg')
>>>
>>> e1 = cv2.getTickCount()
>>> for i in range(5,49,2):
>>>     img1 = cv2.medianBlur(img1,i)
>>> e2 = cv2.getTickCount()
>>> t = (e2 - e1)/cv2.getTickFrequency()
>>> t
>>>
>>> # Result I got is 0.521107655 seconds
0.895681945

Note

你也可以通过 time 模块完成一样事情。即不是用 cv2.getTickCount``函数而使用 ``time.time() 函数。然后取两次之差。

2.3.3. OpenCV中的默认优化

Many of the OpenCV functions are optimized using SSE2, AVX etc. It contains unoptimized code also. So if our system support these features, we should exploit them (almost all modern day processors support them). It is enabled by default while compiling. So OpenCV runs the optimized code if it is enabled, else it runs the unoptimized code. You can use cv2.useOptimized() to check if it is enabled/disabled and cv2.setUseOptimized() to enable/disable it. Let’s see a simple example.

>>> # check if optimization is enabled
>>> cv2.useOptimized()
False
>>> %timeit res = cv2.medianBlur(img1,49)
10 loops, best of 5: 36.5 ms per loop

10 loops, best of 3: 34.9 ms per loop

2.3.4. Disable it

>>> cv2.setUseOptimized(False)
>>> cv2.useOptimized()
False
>>> %timeit res = cv2.medianBlur(img1,49)
10 loops, best of 5: 36.3 ms per loop

10 loops, best of 3: 64.1 ms per loop

请看,优化中值滤波比未优化版本快约2倍。如果你检查它的源代码,你可以看到中值滤波是SIMD优化的。因此,你可以在代码开头使用它启用优化(请记住,它在默认情况下是启用的)。

通过IPython来测量性能

有时你可能需要比较两个类似操作的性能。IPython给你一个神奇的命令 %timeit 来进行比较。它多次运行代码以获得更准确的结果。同样,它们也适用于测量单行代码。

例如,您知道下面哪个加法操作好一些, x = 5; y = x**2x = 5; y = x*xx = np.uint8([5]); y = x*xy = np.square(x) ? 在IPython shell中,我们可用%timeit找到这个答案。

>>> x = 5
>>>
>>> %timeit y=x**2
The slowest run took 5.72 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 5: 199 ns per loop

10000000 loops, best of 3: 73 ns per loop

>>> %timeit y=x*x
10000000 loops, best of 5: 35.8 ns per loop

10000000 loops, best of 3: 58.3 ns per loop

>>> import numpy as np
>>> z = np.uint8([5])
>>> %timeit y=z*z
The slowest run took 54.82 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 5: 447 ns per loop

1000000 loops, best of 3: 1.25 us per loop

>>> %timeit y=np.square(z)
The slowest run took 23.64 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 5: 488 ns per loop

1000000 loops, best of 3: 1.16 us per loop

你可以看到, x = 5 ; y = x*x 是最快的,它比Numpy快20倍左右。如果你将数组的创建也考虑在内,它可能会快100倍。很酷,对吧? (Numpy开发人员正在研究这个问题)

Note

Python标量操作比Numpy标量操作快。所以对于包含一个或两个元素的操作,Python 标量比Numpy数组要好。当数组的大小稍大一点时,Numpy就占了优势。

我们再举一个例子。这次,对同样的图像,我们将比较 cv2.countNonZero()**np.count_nonzero()**的表现。

>>> # cv2.countNonZero(img1)
>>> # %timeit z = cv2.countNonZero(img1)

100000 loops, best of 3: 15.8 us per loop

>>> %timeit z = np.count_nonzero(img1)
>>>
1000 loops, best of 5: 1.07 ms per loop

1000 loops, best of 3: 370 us per loop

看,OpenCV函数比Numpy函数快近25倍。

Note

通常,OpenCV函数比Numpy函数快。所以,对于相同的操作,OpenCV函数是首选的。但是,也有例外,尤其是当Numpy使用视图(浅拷贝)而不是复制(深拷贝)时。

更多的IPython魔法命令

还有一些其他的魔法命令来测量性能、分析、行分析、内存测量等等,它们都有很好的文档。所以这里只提供了这些文档的链接。推荐感兴趣的读者试用。

性能优化技术

有几种技术和编码方法可以最大限度地利用Python和Numpy的性能。这里只提到了相关的内容,并给出了重要来源的链接。这里需要注意的是,首先尝试以简单的方式实现算法。一旦开始工作,分析它,找出瓶颈并优化它们。

  1. 尽可能避免在Python中使用循环,特别是双/三循环等。它们天生就很慢。

  2. Numpy和OpenCV的优化是为了向量操作,因此应尽可能地将算法/代码矢量化。

  3. 利用缓存一致性。

  4. 除非需要,否则不要复制数组。尝试改用视图(浅拷贝)。数组复制是一项成本高昂的操作。

即使在完成所有这些操作之后,如果代码仍然很慢,或者使用大循环是不可避免的,那么使用像Cython这样的额外库来加快速度。

2.3.6. 练习