性能评估和改进技术
<h1>目标</h1>
<p>在图像处理中,由于每秒要处理大量的操作,所以必须让代码不仅提供正确的解决方案,而且以最快的方式提供正确的解决方案。所以在这一章中,你会学到</p>
<ul>
<li>来度量代码的性能。</li>
<li>提高代码性能的一些技巧。</li>
<li>你会看到这些函数:cv.getTickCount, cv.getTickFrequency 等等。
除了OpenCV, Python还提供了一个模块时间,这有助于度量执行时间。另一个模块概要文件有助于获得关于代码的详细报告,比如代码中每个函数花费了多少时间,调用了多少次函数等等。但是,如果您正在使用IPython,那么所有这些功能都是以用户友好的方式集成的。我们将看到一些重要的链接,有关更多细节,请查看附加参考资料部分中的链接。</li>
</ul>
<h2>使用OpenCV测量性能</h2>
<p>cv.getTickCount函数返回一个引用事件(比如打开机器的时刻)之后到调用该函数的时刻的时钟周期数。如果在函数执行之前和之后调用它,就会得到用于执行函数的时钟周期数。</p>
<p>cv.getTickFrequency函数返回时钟周期的频率,或每秒时钟周期的数量。因此,要计算以秒为单位的执行时间,可以执行以下操作:</p>
<pre><code class="language-python">e1 = cv.getTickCount()
# your code execution
e2 = cv.getTickCount()
time = (e2 - e1)/ cv.getTickFrequency()</code></pre>
<p>我们将通过以下示例进行演示。下面的示例应用中值滤波,其内核大小为奇数,范围从5到49。(不要担心结果会怎样,这不是我们的目标):</p>
<pre><code class="language-python">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</code></pre>
<h4>注意</h4>
<pre><code>你也可以用python内置的时间模块做到, 替代cv.getTickCount, 使用time.time()函数, 然后取两次时间的差</code></pre>
<h2>OpenCV中的默认优化</h2>
<p>许多OpenCV函数都是使用SSE2、AVX等进行优化的。它还包含未加密的代码。因此,如果我们的系统支持这些特性,我们就应该利用它们(几乎所有现代处理器都支持它们)。编译时默认启用它。因此,如果OpenCV启用了优化代码,它将运行优化后的代码,否则它将运行未经优化的代码。可以使用cv.useoptimization()检查是否启用/禁用了它,并使用cv.setuseoptimization()启用/禁用它。让我们看一个简单的例子。</p>
<pre><code class="language-python"># 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</code></pre>
<p>优化的中值滤波比未优化的版本快2倍。如果您检查它的源代码,您可以看到中值滤波是SIMD优化的。因此,可以使用它在代码顶部启用优化(请记住,默认情况下启用了优化)。</p>
<h2>在IPython中度量性能</h2>
<p>有时您可能需要比较两个类似操作的性能。IPython提供了一个神奇的命令timeit来执行此操作。它运行代码几次以获得更精确的结果。同样,它们也适用于测量单行代码。</p>
<pre><code>例如,你知道下面哪个加法运算更好吗,x = 5;y = x**2, x = 5;y = x*x, x = np.uint8([5]);y = x*x还是y = np.square(x) ?我们将在IPython shell中使用timeit找到它。</code></pre>
<pre><code class="language-python">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
</code></pre>
<pre><code>可以看到,x = 5;y = x*x是最快的,与Numpy相比大约快20倍。如果您也考虑数组的创建,它可能会更快达到100倍。很酷,对吧?*(Numpy开发人员正在处理这个问题)*</code></pre>
<p>Python标量操作比Numpy标量操作快。因此,对于包含一两个元素的操作,Python标量优于Numpy数组。当数组的大小稍微大一点时,Numpy就会发挥作用。</p>
<p>我们再试一个例子。这一次,我们将比较相同图像的cv.countNonZero()和np.count_nonzero()的性能。</p>
<pre><code class="language-python">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</code></pre>
<p>看, OpenCV函数比Numpy函数快25倍。</p>
<h4>注意</h4>
<pre><code>通常,OpenCV函数比Numpy函数更快。因此,对于相同的操作,OpenCV函数是首选的。但是,也有例外,特别是当Numpy使用视图而不是副本时。</code></pre>
<h2>更多IPython的神奇命令</h2>
<p>还有其他一些神奇的命令可以测量性能、概要、行概要、内存度量等。它们都有很好的记录。这里只提供到这些文档的链接。有兴趣的读者可以尝试一下。</p>
<h2>性能优化技术</h2>
<p>有几种技术和编码方法可以利用Python和Numpy的最大性能。这里只提到了相关的内容,并给出了重要来源的链接。这里要注意的主要事情是,首先尝试以一种简单的方式实现算法。一旦它开始工作,分析它,找到瓶颈并优化它们。
尽量避免在Python中使用循环,特别是双/三重循环等。他们天生迟钝。
将算法/代码向量化到最大程度,因为Numpy和OpenCV是针对向量操作优化的。
利用缓存一致性。
除非需要,否则不要复制数组。尝试使用视图。数组复制是一项代价高昂的操作。
即使在完成所有这些操作之后,如果您的代码仍然很慢,或者不可避免地要使用大型循环,那么可以使用额外的库(如Cython)来提高速度。</p>
<h1>其它资源</h1>
<p><a href="http://wiki.python.org/moin/PythonSpeed/PerformanceTips" title="Python优化技术">Python优化技术</a>
<a href="http://scipy-lectures.github.io/advanced/advanced_numpy/index.html#advanced-numpy" title="Scipy讲义-高级Numpy">Scipy讲义-高级Numpy</a>
<a href="http://pynash.org/2013/03/06/timing-and-profiling/" title="IPython中的计时和分析">IPython中的计时和分析</a></p>