1. 背景
该问题源于实验室项目,里面涉及图像处理的相关操作。在其中利用重心法检测二值化目标的圆心时,发现运行效率很低,给图像的精度测试带来了很大的麻烦。因此,考虑将该部分改进,以提高重心法检测的速度。
2. 传统重心法检测
# --重心法检测圆心--
sum_x = 0
sum_y = 0
cnt_x = 0
cnt_y = 0
for i in range(blank_bg.shape[0]):
for j in range(blank_bg.shape[1]):
if blank_bg[i,j] < 10:
sum_x += j
cnt_x += 1
sum_y += i
cnt_y += 1
point_x = sum_x/cnt_x
point_y = sum_y/cnt_y
可以看到,这里使用for循环对图像进行逐像素遍历。对1000×1000的单通道图像,运行时间是1.264秒(电脑处理器i7-11800H 8核心16线程,内存32G)。可以说在这个硬件条件下,这个速度不快,甚至可以说是相当慢。
分析了一下原因。这是因为在Python中,for循环是特别低效的。然而在这里for循环里面套了给for循环,这一部分会被执行1000000(100万)次,大幅降低了程序的运行效率。因此,需要考虑用别的函数来替换掉这两个for循环。
3. 利用np.where改进重心法检测
# --重心法检测圆心--
xy=np.where(blank_bg < 10)
listx=list(xy)[0] #行
listy=list(xy)[1] #列
point_x = np.sum(listy)/len(listy)
point_y = np.sum(listx)/len(listx)
使用np的内置函数可以大幅提高程序的运行效率。这里使用np.where来替换掉两个for循环。
np.where返回的是一个元组,n维数组的输入会返回n维元组。对图像来说,输入为二维数组(像素坐标为二维),因此返回值为2维元组,如下所示:
(array([509, 509, 509, ..., 766, 766, 766], dtype=int64), array([337, 338, 339, ..., 380, 381, 382], dtype=int64))
这个2维元组的第一个数表示满足条件的行坐标,第二个数表示满足条件的列坐标。这样就通过np.where替代了两层for循环。之后便可以根据返回的结果对像素进行操作。实测下来,该部分运行时间是0.003秒,运行效率提高了421倍。
4. 利用numba改进重心法检测
将该部分(有大量数值计算的部分)封装为一个函数(注意第一行的@jit!!!):
@jit
def centroid(blank_bg):
sum_x = 0
sum_y = 0
cnt_x = 0
cnt_y = 0
for i in range(blank_bg.shape[0]):
for j in range(blank_bg.shape[1]):
if blank_bg[i,j] < 10:
sum_x += j
cnt_x += 1
sum_y += i
cnt_y += 1
point_x = sum_x/cnt_x
point_y = sum_y/cnt_y
return point_x,point_y
在头文件中加入:
from numba import jit
注意:@jit标注的函数内不能存在非数值运算语句。因此,类似于time.time()这样的函数需要放在函数外面,函数内只放大量数值运算的语句。
Python通常只调用了CPU来进行运算,而numba模块可以调用GPU来分担CPU的工作。加速后的时间为0.23秒。
numba往往对大循环效果更好。如果将该循环重复100次,运行时间仅为0.28秒,增幅不大。而不使用numba模块加速则需要127.03秒。
5. 总结
对于运算量中等的遍历像素操作,使用np.where来替代for循环是最佳的选择,可以达到最快的运算速度。然而对于更复杂的数值运算过程,使用numba加速效率更高。但是在图像处理中像素的数量级还远没有达到需要numba加速的程度,使用np.where即可。