这篇博客将介绍如何使用Python和OpenCV创建超快的“for”像素循环(逐像素循环),即Cython快速优化for循环;
使用Python和OpenCV逐像素循环图像是一个非常缓慢的操作,即使图像在内部由NumPy数组表示。
为什么会这样?为什么NumPy中的单个像素访问速度如此之慢?
NumPy操作是用C实现的。这使得能够避免Python循环的昂贵开销。在使用NumPy时,性能会提高多个数量级(与标准Python列表相比)。一般来说,如果可以使用NumPy数组将问题框定为向量操作,将能够较大提升速度。
这里的问题是访问单个像素不是向量操作。因此即使NumPy可以说是几乎任何编程语言都可以使用的最好的数值处理库,但当与Python的for循环+单个元素访问相结合时,失去了很多性能增益。
在计算机视觉之旅中,需要实现一些算法,无论是需要从头开始实现局部二进制模式、创建自定义卷积算法,还是仅仅不能依赖矢量化操作,都需要了解如何使用OpenCV和Python优化循环。
1. 效果图
python:2.7 s ± 38.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cython:4.14 ms ± 28.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
使用Python耗时2700ms,Cython实现了每次调用4.14ms。这意味着通过使用Cython,可以将逐像素循环的速度提高2个数量级以上!
受萨蒂亚·马利克(SatyaMallick)最初的博客文章的启发,我决定使用Python来完成同样的事情。
不幸的是,Python只有一小部分函数调用可用作绑定(与C++相比)。因此需要使用Cython“滚动自己的”更快的“for”循环方法。
结果非常引人注目——仅使用CPU的一个内核,通过使用Cython能够将阈值函数从每次函数调用244ms(纯Python)提高到小于40.8μs(Cython)。
还可以进行一些优化。通过启用OpenMP支持,实际上可以将for-loop计算分布在多个CPU/内核上-这样做能进一步提高速度。
下一篇博客将介绍如何使用OpenMP通过OpenCV和Python增强我们的for-pixel循环。
2. 原理
pip install numpy
pip install cython
pip install matplotlib
pip install jupyter
Cython是什么以及如何使用它来加速Python内部的操作
可以将Cython视为Python与C语言的结合,它提供了类似C语言的性能。
Cython与Python的不同之处在于,代码是使用CPython解释器翻译成C的。这允许脚本主要用Python编写,并带有一些装饰符和类型声明。
使用Cython的最佳时机是在图像中逐像素循环。OpenCV和scikit image已经优化了——对模板匹配之类的函数的调用,就像在OCR银行支票和信用卡时所做的那样,已经在底层C中进行了优化。仅函数调用中有少量开销。
用Python编写任何自定义图像处理函数来逐像素(可能是使用内核)分析或修改图像,那么函数运行得非常慢。然而如果利用Cython,它使用主要的C/C++编译器进行编译,可以获得显著的性能提升。
如何使用OpenCV和Python将逐像素循环提升两个数量级以上。将实现一个简单的阈值函数。对于图像中的每个像素,将检查输入像素是否大于或等于某个阈值T。
如果像素通过阈值测试,将输出值设置为255。否则,输出像素将设置为0。
使用此函数,将能够对输入图像进行二值化,这与OpenCV和scikit image的内置阈值方法的工作原理非常相似。
将使用一个简单的阈值函数作为示例,因为它将(1)不关注实际的图像处理代码,(2)学习如何在手动循环图像中的每个像素时获得速度提升。
源码
def threshold_slow(T, image):
# grab the image dimensions
h = image.shape[0]
w = image.shape[1]
# loop over the image, pixel by pixel
for y in range(0, h):
for x in range(0, w):
# threshold the pixel
image[y, x] = 255 if image[y, x] >= T else 0
# return the thresholded image
return image
%timeit threshold_slow(5, image)
参考
- https://pyimagesearch.com/2017/08/28/fast-optimized-for-pixel-loops-with-opencv-and-python/
- https://learnopencv.com/parallel-pixel-access-in-opencv-using-foreach/