整个功能完成后,对扫描速度进行了测试,在I7 6700的CPU机器上,一个图片扫描的时间大概在5-7秒之间:
"E:python workspaceqrcode-complementvenvScriptspython.exe" "E:/python workspace/qrcode-complement/main.py"
对二维码部分进行切片处理:0.25531697273254395
对图片进行预处理:0.013962984085083008
对图片基于极点进行拉伸转换:1.6815056800842285
将图片基于自适应阈值进行二值化:0.007976531982421875
left_top_flag:all
left_bottom_flag:none
right_top_flag:all
right_bottom_flag:all
将图片恢复竖直状态:0.2682826519012451
寻找图片的分割线:1.8709981441497803
生成目标图片:0.26429295539855957
总共耗费时间:4.4122045040130615
总共扫描出:0
从时间耗费来看,拉伸转换和分割线耗费了大部分的时间。经过分析程序,应该是有两段for循环赋值的逻辑导致的速度过慢。
按照我们预计的407 * 407 的图片大小,每次执行for循环都需要执行16.5万次,是一种比较低效的写法。
比较高效的写法应该是采用numpy提供的一些矩阵运算的函数来进行计算,自己琢磨了一下,改进了一下算法
矩阵运算
自定义灰度算法
# 加权平均值法灰度化
def gray_scale(self, image):
gray_image = image.copy()
# 循环写法
# for i in range(image.shape[0]):
# for j in range(image.shape[1]):
# if (image[i, j][0] < image[i, j][1] or image[i, j][0] < image[i, j][2]) and image[i, j][0] < 150:
# # 其他颜色值高于蓝色,且蓝色低于150,说明是其他颜色覆盖
# gray_image[i][j] = 255
# else:
# # gray_image[i, j] = min(0.8 * image[i, j][0] + 0.1 * image[i, j][1] + 0.1 * image[i, j][2],255)
# grayResult = max(min(-1.5 * image[i, j][0] + 1.5 * image[i, j][1] + 1.5 * image[i, j][2], 255), 0)
# gray_image[i][j] = grayResult
# return gray_image
# 矩阵算法
ratio = 1.5
b = np.ones((image.shape[0], image.shape[1]), np.int16)
g = np.ones((image.shape[0], image.shape[1]), np.int16)
r = np.ones((image.shape[0], image.shape[1]), np.int16)
source_b, source_g, source_r = cv2.split(gray_image)
b[:, :] = source_b[:, :]
g[:, :] = source_g[:, :]
r[:, :] = source_r[:, :]
g_minus_b = g - b
r_minus_b = r - b
g_minus_b = np.where(g_minus_b > 0, 1000, 0)
r_minus_b = np.where(r_minus_b > 0, 1000, 0)
g_minus_b_150 = np.where((g_minus_b - b) >= 850, 255, 0)
r_minus_b_150 = np.where((r_minus_b - b) >= 850, 255, 0)
g_r_minus_b_150 = g_minus_b_150 + r_minus_b_150
not_b_lt_150 = np.where(g_r_minus_b_150 >= 255, -9999, 0)
b_plus_g_r = -1 * ratio * b + ratio * g + ratio * r
b_plus_g_r = np.where(b_plus_g_r < 0, 0, b_plus_g_r)
b_plus_g_r = np.where(b_plus_g_r > 255, 255, b_plus_g_r)
b_result = not_b_lt_150 + b_plus_g_r
b_result = np.where(b_result < -9000, 255, b_result)
b_result = np.where(b_result < 0, 0, b_result)
b_result = np.where(b_result > 255, 255, b_result)
gray_image[:, :, 0] = b_result[:, :]
gray_image[:, :, 1] = b_result[:, :]
gray_image[:, :, 2] = b_result[:, :]
return gray_image
寻找分割线
def Point_rows(mat, border):
# 先找上下限
cv2.imwrite("images/erode9.1.jpg", mat)
# loop写法
# lower_limit_j = []
# for i in range(0, mat.shape[1]):
# for j2 in range(0, min(border, mat.shape[0]) - 1):
# if mat[0:j2 + 1, i].mean() == 255 and mat[j2 + 1, i] == 0 and j2 > 2: # 如果这一列,超过5个全是白色,下一个是黑色 则认为也到了边界
# lower_limit_j.append(j2)
# break
# elif mat[j2, i] == 0 and mat[j2 + 1, i] == 255 and j2 > 2: # 如果这个是黑色而且下一个是白色,并且黑色数量大于五个,则认为到达了下限边界
# lower_limit_j.append(j2)
# break
# if mat[:, i].mean() == 0: # 如果这一列全部为黑色,则占满13
# # lower_limit_j.append(12.4)
# continue
# elif mat[:, i].mean() == 255: # 如果这一列全部为白色色,则占11
# # lower_limit_j.append(11.3)
# continue
# if len(lower_limit_j) == 0:
# return 6
# return max(int(np.median(np.array(lower_limit_j))), int((np.array(lower_limit_j).mean())))
# 矩阵算法
gap = 3
result_array = []
white_tmp_matrix = np.zeros((1, mat.shape[1]), np.int16)
black_last_matrix = np.zeros((1, mat.shape[1]), np.int16)
# 从白色点变为黑色点,白色连续线段超过2,下一个点为黑点
for i in range(0, min(border, mat.shape[0])):
# 循环所有列
if i == 0:
white_tmp_matrix = np.where(mat[i, :] == 255, 1, 0)
else:
white_current_matrix = np.where(mat[i, :] == 255, 1, -1)
white_cal_matrix = white_tmp_matrix * white_current_matrix
white_count_matrix = np.where(white_cal_matrix == -1, 1, 0)
white_change_count = white_count_matrix.sum()
# 变更tmp,将已经变化的点修改为0
white_tmp_matrix = white_tmp_matrix * np.where(white_cal_matrix == -1, 0, 1)
# j > gap宽度则计入计算
if i > gap and white_change_count > 0:
for k in range(0, white_change_count):
result_array.append(i - 1)
# 循环所有列
if i == 0:
black_last_matrix = np.where(mat[i, :] == 0, 1, 0)
else:
black_current_matrix = np.where(mat[i, :] == 0, 5, 1)
# 计算结果为:
# 上次为白,本次为黑:5
# 上次为黑,本次为白,2
# 上次为白,本次为白,1
# 上次为黑,本次为黑,6
# 历史存在过黑, 1000 +,不参与计算
black_cal_matrix = black_last_matrix + black_current_matrix
# 统计上次为黑本次为白
black_count_matrix = np.where(black_cal_matrix == 2, 1, 0)
black_change_count = black_count_matrix.sum()
# 变更tmp
black_last_matrix = black_cal_matrix
# 上次为白,本次为白,更新为0
black_last_matrix = np.where(black_last_matrix == 1, 0, black_last_matrix)
# 上次为黑,本次为白,更新为1000
black_last_matrix = np.where(black_last_matrix == 2, 1000, black_last_matrix)
# 上次为白,本次为黑,更新为1
black_last_matrix = np.where(black_last_matrix == 5, 1, black_last_matrix)
# 上次为黑,本次为黑,更新为1
black_last_matrix = np.where(black_last_matrix == 6, 1, black_last_matrix)
# j > gap宽度则计入计算
if i > gap and black_change_count > 0:
for k in range(0, black_change_count):
result_array.append(i - 1)
# 无需考虑_白点和黑点的变更都计入,不在参与下一行的处理
# if i != 0:
# white_change_matrix = np.where(white_cal_matrix == -1, 1000, 0)
# black_change_matrix = np.where(black_current_matrix == 2, 0, 1)
#
# black_last_matrix = black_last_matrix + white_change_matrix
# white_tmp_matrix = white_tmp_matrix * black_change_matrix
result = max(int(np.median(np.array(result_array))), int((np.array(result_array).mean())))
return result
优化后的扫描速度:1.4秒
对二维码部分进行切片处理:0.2293868064880371
对图片进行预处理:0.011968851089477539
对图片基于极点进行拉伸转换:0.0857694149017334
将图片基于自适应阈值进行二值化:0.007978439331054688
left_top_flag:all
left_bottom_flag:none
right_top_flag:all
right_bottom_flag:all
将图片恢复竖直状态:0.2982046604156494
寻找图片的分割线:0.3809800148010254
生成目标图片:0.37898731231689453
总共耗费时间:1.4411475658416748
从时间耗费来看,只要存在循环处理的部分,程序的速度还是偏慢,而调用opencv的api确实速度非常快。
这种优化的主要思想是:
借助数值分段,将原本需要进行循环读取,进行if判断的逻辑,通过存储的数值的分段进行分割。最终通过numpy.where方法,将分段区间内的数字转换成目标值。
降低运算量
还有一种速度优化的思路,就是降低目标图片的分辨率。这种方案可能对图片的识别效果产生一定的不良影响,但是可以极大的提升扫描速度。
例如,当我们定义每一个单元格由5px组成是,那么整个图片的目标面积为 37 * 5 * 37 * 5 = 3.4万,比起目前的16.5万整体运算量直接降低到20%
这个识别率和扫描速度最优的点需要调试后才能得出,作为后续持续优化的一个方向。