当我们处理经典计算机视觉问题时,首先会检查是否有针对特定事物的OpenCV实现,这并不是错误的方法。它可以是边缘检测、色彩空间转换、阈值调整、特征提取......但有时,我们可能需要为特定任务编写自己的函数和方法。而当我们处理实时应用时,纯Python非常麻烦。具体来说,当存在嵌套循环时,性能会大幅下降。你总可以用C++实现高性能,但Python中有一些提升FPS的解决方案。有一个叫Numba的库,非常适合提升Python的FPS。你不必局限于计算机视觉,你可以用Numba配合任何Python代码。
什么是Numba及其运作方式
那么 Numba 是如何提升性能的呢?实际上,它是一个 Python 代码的编译器。调用函数时,它会将 Python 代码编译成机器码。Python 是一种解释型语言,所以每行执行时都是逐行执行的。如果存在循环,每次迭代都会重复相同的作,尽管代码是相同的。使用 Numba 时,你只需编译一次 Python 代码,并一直使用直到程序结束。甚至还有保存 Numba 代码缓存的选项,这样你在再次运行代码时就不必编译它。见下面的示意图,Numba 使用 LLVM 编译器库。
性能比较
在性能(FPS)方面,使用C++总是更好的选择。如果你真的需要一个快速运行的计算机视觉流水线,应该直接选C++。在经典的计算机视觉应用中,性能方面无法超越Python的C++。但这并不意味着你不能在 Python 代码上提升帧率,而 Numba 就派上用场了。所以如果表现不好,我建议你:
- 使用C++
- 使用OpenCV函数
- 如果你需要用 Python 写自定义算法,可以用 Numba →。
- 千万别用纯Python,:)
现在,我来教你如何使用Numba。将有两个简单的实现和一个对比。
Numba 与纯 Python / 代码
我选择了边缘检测来做对比。这个想法很简单,如果你检查像素值之间的差异,就能找到边缘。想象一下白色墙上的一个黑色物体。黑色物体的边缘(像素值0)和白墙(像素值255)之间的差异会非常大,可以被归类为边缘。设定一个阈值,找出梯度(差异),如果超过阈值,就将其归类为边缘,并展示结果。
我们先从Numba的实现说起。Numba 提供两种不同的编译装饰工具@njit 和 @jit.njit。
基本上是把所有内容编译成本地机器码,如果有些行无法转换,就会报错。另一端更灵活,不会出错,会退回到Python解释器(注意,你可能不知道)。参数相同:
nopython=True/False:无Python模式。 等价于@njit@jit(nopython=True)parallel=True:利用多线程实现的自动并行化fastmath=True快速数学优化cache=True: Cahes将机器码编译到磁盘,这样下次运行时所需时间更短。
你可以为每一帧读取视频调用numba_dege_detect()函数,并显示该函数的输出,它是二进制图像。
import cv2import numpy as npfrom numba import njit@njit(fastmath=True)def numba_edge_kernel(gray): """Fast Numba version with thresholding""" # height and width h, w = gray.shape edges = np.zeros_like(gray) # Edge detection with gradient for i in range(1, h-1): for j in range(1, w-1): # computer gradient in x and y direction gx = gray[i+1, j] - gray[i-1, j] gy = gray[i, j+1] - gray[i, j-1] # compute edge strength edges[i, j] = np.sqrt(gx2 + gy2) # Apply threshold: if edge strength > threshold, it's an edge (255), else not (0) threshold = 15 for i in range(h): for j in range(w): if edges[i, j] > threshold: edges[i, j] = 255 else: edges[i, j] = 0 return edgesdef numba_edge_detect(frame): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY).astype(np.float32) # Numba needs the kernel to be compiled, first run might be slower edges = numba_edge_kernel(gray) return edges.astype(np.uint8)
注意:如果你设置 ,必须将标准替换 为 parallel=Truerange()prange()
现在是实现Python的时候了,这里不需要花哨的。
import cv2import numpy as npdef python_edge_detect(img): """Python version with loops""" # Convert to grayscale h, w = img.shape[:2] gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).astype(np.float32) # Initialize edges array edges = np.zeros_like(gray) # Edge detection with gradient for i in range(1, h-1): for j in range(1, w-1): # Compute gradient in x and y direction gx = gray[i+1, j] - gray[i-1, j] gy = gray[i, j+1] - gray[i, j-1] # Compute edge strength edges[i, j] = np.sqrt(gx2 + gy2) # Apply threshold: if edge strength > threshold, it's an edge (255), else not (0) threshold = 15 for i in range(h): for j in range(w): # Set pixel to 255 if it's an edge if edges[i, j] > threshold: edges[i, j] = 255 else: edges[i, j] = 0 return edges.astype(np.uint8)
觉得有用,麻烦给个赞和在看