纯python代码实现<规则数字字符>识别
前言
本篇博客主要记录在不使用机器学习和深度学习算法的情况下,自己实现规则数字字符的识别,具体场景如下:
- 先看图 :
- 需求: 将对应图片上出现的速度识别出来,以用于数据的处理和结果的合并(比较多的应用场景基础)
- 效果: 准确率达到100%,在inter i7-cpu上,速度为:4-8 ms(完成对应图片的处理,输出速度)
具体步骤
1、准确定位速度区域:
- 裁剪: 将如上图区域切割出来;
- 模板匹配: 将速度区域的左上角右下角定位出来;
- 切片: 将定位出来的区域切割出来;
- 模板和最终截取图,如下: 因为速度位长度不一,所以需要两个模板;
2、字符分割:
- 图片二值化处理: 因为图片像素值并不均匀,也为后面判断提供便利,此处阈值设置为像素值小于20就置255否则就是0;
- 投影法分割字符: 如下:
3、字符识别:
-
可视化字符: 可视化字符,分析字符特性;
-
特征分析: 将速度区域的左上角右下角定位出来;
数字0: 字符上半部分有空心,下半部分有空心,且没有像素值全部是255的行;
数字1: 没有像素值全部是255的行,字符的宽度最窄,在一列上像素值为255的最多和4一样;
数字2: 在下半部分有像素值全部是255的行,且在一列上,为255的像素值不多;
数字3: 上半部分没有空心,下半部分没有空心,且没有像素值全部是255的行;
数字4: 在下半部分有像素值全部是255的行,且在一列上,为255的像素值最多和1差不多;
数字5: 没有像素值全部是255的行,在下半部分像素值为<左无右有>;
数字6: 没有像素值全部是255的行,在下半部分有空心,上半部分没有;
数字7: 上半部分有像素值全部是255的行;
数字8: 上半部分有空心,但是小一个单位,下半部分有空心,大一个单位;
数字9: 没有像素值全部是255的行,在下半部分没有空心,上半部分有,与6相反; -
逻辑梳理:
1)、 依次读取一个文件夹下所有图片,并进行速度区域定位,切割该区域;
2)、 将速度区域进行字符分割,依次对分割的图片进行识别,将识别内容保存并返回;
3)、 判断识别出来的字符个数是否满足要求,根据要求输出速度;
代码如下:
// 主函数
def main(image_path,star_tem_path,end_tem_path):
num = 0
name = "test_data"
filename = os.listdir(image_path)
start_tem_image = cv2.imread(star_tem_path)
end_tem_image = cv2.imread(end_tem_path)
sh, sw = start_tem_image.shape[0], start_tem_image.shape[1]
eh, ew = end_tem_image.shape[0], end_tem_image.shape[1]
for image_name in filename:
alone_image_path = image_path + image_name
image_data = cv2.imread(alone_image_path)
image_speed = image_data[0:35, 0:1400]
res = cv2.matchTemplate(image_speed, start_tem_image, method=cv2.TM_CCOEFF_NORMED)
end_res = cv2.matchTemplate(image_speed, end_tem_image, method=cv2.TM_CCOEFF_NORMED)
# 取匹配程度大于0.8的坐标
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
end_min_val, end_max_val, end_min_loc, end_max_loc = cv2.minMaxLoc(end_res)
if max_val >= 0.8 and end_max_val >= 0.8:
satrt_top_left = max_loc
end_top_left = end_max_loc
left = (satrt_top_left[0] + sw,satrt_top_left[1])
right = (end_top_left[0],end_top_left[1] + eh - 3)
cv2.rectangle(image_speed, left, right, 255, 2)
div_image = image_data[satrt_top_left[1]:(end_top_left[1] + eh - 3),satrt_top_left[0]+ sw :end_top_left[0]]#调整剪切的高度和宽度
name_all = name + "-%d"%num
# print(div_image.shape)
speed_result = chardivision(div_image,name_all)
if speed_result != -1:
print(speed_result)
if len(speed_result) == 3:
print("速度是:{}.{}{}Km/h".format(speed_result[0],speed_result[1],speed_result[2]))
elif len(speed_result) == 4:
print("速度是:{}{}.{}{}Km/h".format(speed_result[0],speed_result[1],speed_result[2],speed_result[3]))
else:
print("检测失败!!!")
num += 1
image_speed = cv2.cvtColor(image_speed, cv2.COLOR_BGR2RGB)
result = np.asarray(image_speed)
cv2.namedWindow("result", cv2.WINDOW_AUTOSIZE)
cv2.imshow("result", result)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
sleep(1)
整体效果如下:
3. 功能函数:
1)、 字符切割函数 chardivision(div_image,name_all):
代码如下:
def chardivision(image,name):
image = image.astype(np.uint8)
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
h,w= image.shape
tran_tem_image = np.zeros((h,w))
tran_tem_image[np.where(image[:,:]<=20)] = 255
left_pix = 0
num = 1
speed_dete = []
for w_pix in range(1,w-1):
if np.all(tran_tem_image[:,w_pix-1] == 0) and np.any(tran_tem_image[:,w_pix] != 0):
left_pix = w_pix
elif np.any(tran_tem_image[:,w_pix] != 0) and np.all(tran_tem_image[:,w_pix+1] == 0):
image_char = tran_tem_image[:,left_pix:w_pix+1]
char_w = w_pix+1-left_pix
if char_w > 3:
speed_num = c0_6and9(image_char)
if speed_num != -1:
speed_dete.append(speed_num)
else:
path = "./a/other/" + name + "%d.jpg" % w_pix
cv2.imwrite(path, image_char)
cv2.imwrite("./a/other/" + name + "%d_%s.jpg" % (w_pix,"cuowu"), image)
continue
left_pix = 0
num += 1
return speed_dete
2)、 字符识别函数 c0_6and9(image_char):
代码如下:
。
def c0_6and9(image):
num1 = is1(image)
if num1 == 1:
return 1
num2and4 = is2and4(image)
if num2and4 == 2 or num2and4 == 4:
return num2and4
num7 = is7(image)
if num7 == 7:
return 7
up_3, down_3,up_5, down_5,up,up_8,down = 0,0,0,0,0,0,0
for i in range(image.shape[0]):
if i <= int(image.shape[0] / 2):
if len(image[np.where(image[i, 0] >= 200)]) == 1 and len(image[np.where(image[i, -1:] >= 200)]) == 1 and \
len(image[np.where(image[i, :] >= 200)]) <= 6:
up += 1
if len(image[np.where(image[i, 1] >= 200)]) == 1 and len(image[np.where(image[i, -2:-1] >= 200)]) == 1 and \
len(image[np.where(image[i, :] >= 200)]) <= 6:
up_8 += 1
if len(image[np.where(image[i, :2] >= 200)]) == 0 and len(image[np.where(image[i, -2:-1] >= 200)]) == 1:
up_3 += 1
if len(image[np.where(image[i, 0] >= 200)]) == 1 and len(image[np.where(image[i, -1:] >= 200)]) == 0:
up_5 += 1
elif i > int(image.shape[0] / 2):
if len(image[np.where(image[i, 0] >= 200)])==0 and len(image[np.where(image[i, -1:] >= 200)])==1:
down_5 += 1
if len(image[np.where(image[i, 0] >= 200)]) == 1 and len(image[np.where(image[i, -1:] >= 200)]) == 1 and \
len(image[np.where(image[i, :] >= 200)]) <= 6:
down += 1
elif len(image[np.where(image[i, 0] >= 200)]) == 0 and len(image[np.where(image[i, -1:] >= 200)]) == 1:
down_3 += 1
if up >= 2 and down >= 2:
return 0
elif down == 0 and up >= 2:
return 9
elif up == 0 and up_8 == 0 and down >= 2 and num2and4 == -1:
return 6
elif up_8 >= 2 and down >= 2 and up == 0:
return 8
elif up_3 >= 2 and down_3 >= 2 and num2and4 == -1:
return 3
elif up_5 >= 2 and down_5 >= 2:
return 5
else:
return -1
3)、 字符识别函数,判断是否是1,2,4,7字符函数:
代码如下:
def is1(image):
is_1 = 0
initial = np.array([i for i in range(image.shape[1])])#生成列索引
for row_1 in range(image.shape[0]):
col_len = []
if len(np.where(image[row_1, :] >= 200)[0]) != 0:#开始判断是否有一行像素值全部大于阈值(200)
frist_row_result = np.where(image[row_1, :] >= 200)[0]
second_row_result = np.where(image[row_1 + 1, :] >= 200)[0]
for val in initial:
if val in frist_row_result:
col_len.append(val)
elif val not in frist_row_result and val in second_row_result:
col_len.append(val)
if len(col_len) == image.shape[1] and image.shape[1] > 6:#规定字符宽度达到阈值#有相等,但大于6个像素值宽度,则判断不是1
is_1 += 1
return -1
for col_1 in range(image.shape[1]):
if is_1 == 0 and len(np.where(image[:, col_1] >= 200)[0]) >= 12 and image.shape[1] < 7:#字符宽度小于7,才确定为1
return 1
#判断是否是7
def is7(image):
initial = np.array([i for i in range(image.shape[1])])#生成列索引
for row in range(0, int(image.shape[0] / 2) + 1):#判断上半部分遍历是否有像素值大于阈值,且一行所有个数等于宽度
col_len = []
if len(np.where(image[row, :] >= 200)[0]) != 0:
frist_row_result = np.where(image[row, :] >= 200)[0]
second_row_result = np.where(image[row + 1, :] >= 200)[0]
for val in initial:
if val in frist_row_result:
col_len.append(val)
elif val not in frist_row_result and val in second_row_result:
col_len.append(val)
if len(col_len) == image.shape[1]:#上半部分如果有,则直接判断为7,为否没有,5的上半部分没有达到整个宽度
return 7
return -1
#判断是否是2或4
def is2and4(image):
down_2, down_4 = 0,0
initial = np.array([i for i in range(image.shape[1])])#生成列的index
for row in range(int(image.shape[0] / 2), image.shape[0]):#遍历下半部分
col_len = []
frist_row_result = np.where(image[row, :] >= 200)[0]#该行所有列是否有像素值大于200
if len(frist_row_result) != 0:
second_row_result = np.where(image[row + 1, :] >= 200)[0]#该行的下一行所有列是否有像素值大于200
for val in initial:
if val in frist_row_result:
col_len.append(val)#将第一行大于200的值添加到新列表
elif val not in frist_row_result and val in second_row_result:
col_len.append(val)#将第二列有且第一列没有的,大于200的像素值添加到新列表中
if len(col_len) == image.shape[1]:#如果大于200的像素值的索引个数长度等于图片的宽度
is5 = 0
for col in range(image.shape[1]):
if row - int(image.shape[0] / 2) < 3:#在下半部分开始的三行内出现整行,判断为5
# print("这个字符是5")
is5 += 1
return -1
# if np.sum(image[int(image.shape[0] / 2):row, col] > 200) >= 4 :
if np.sum(image[:, col] > 200) >= 14:#整列大于14个200以上的值则为4
down_4 += 1
return 4
if is5 == 0 and down_4 == 0:#既不是5,也不是4的便是2
down_2 += 1
return 2
return -1
4)、 最后:
if __name__ == "__main__":
star_tem_path = './template/start_tem.jpg'#定位速度区域开始位置的模板
end_tem_path = './template/end_tem.jpg'#定位速度区域结束位置的模板
image_path = "./test_data/"#带检测的图片
main(image_path,star_tem_path,end_tem_path)