车牌识别项目中,关于字符分割的实现:
思路:
1. 读取图片,使用 cv2 。
2. 将 BGR 图像转为灰度图,使用 cv2.cvtColor( img,cv2.COLOR_RGB2GRAY) 函数。
3. 车牌原图尺寸 (170, 722) ,使用阈值处理灰度图,将像素值大于175的像素点的像素设置为 255 ,不大于175的像素点的像素设置为 0 。
4.观察车牌中字符,可以看到每个字符块中的 每列像素值的和 都不为 0 ,这里做了假设,将左右结构的省份简写的字也看作是由连续相邻的列组成的,如 “ 桂 ” 。
5. 对于经过阈值处理的车牌中的字符进行按列求像素值的和,
- 如果一列像素值的和为 0,则表明该列不含有字符为空白区域。
- 反之,则该列属于字符中的一列。判断直到又出现一列像素点的值的和为0,则这这两列中间的列构成一个字符,保存到字典 character_dict 中,
- 字典的 key 值为第几个字符 ( 下标从0开始 ),字典的value值为起始列的下标和终止列的下标 。
- character_dict 是字典,每一个元素中的value 是一个列表记录了夹住一个字符的起始列下标和终止列下标 。
6. 之后再对字符进行填充,填充为170*170大小的灰度图(第三个字符为一个点,不需要处理,跳过即可。有可能列数不足170,这影响不大)。
7. 对填充之后的字符进行resize,处理成20*20的灰度图,然后对字符分别进行存储。
代码实现:
1 ### 对车牌图片进行处理,分割出车牌中的每一个字符并保存 2 # 在本地读取图片的时候,如果路径中包含中文,会导致读取失败。 3 4 import cv2 5 import paddle 6 import numpy as np 7 import matplotlib.pyplot as plt 8 #以下两行实现了在plt画图时,可以输出中文字符 9 plt.rcParams['font.sans-serif']=['SimHei'] 10 plt.rcParams['axes.unicode_minus'] = False 11 12 13 # cv2.imread() 读进来直接是BGR 格式数据,数值范围在 0~255 。在本地读取,路径中不要含有中文 14 license_plate = cv2.imread('../data/car.png') # license 拍照,plate 车牌 15 print('license_plate 的 type ', type(license_plate), license_plate.shape) # <class 'numpy.ndarray'> (170, 722, 3) 16 17 plt.subplot(231) 18 plt.imshow(license_plate) 19 plt.title('原图 BGR ') 20 21 gray_plate2 = cv2.cvtColor(license_plate, cv2.COLOR_BGR2RGB) # RGB 转灰度图 22 plt.subplot(234) 23 plt.imshow(gray_plate2) 24 plt.title('RGB图像') 25 # cv2.cvtColor(p1,p2) 是颜色空间转换函数,p1是需要转换的图片,p2是转换成何种格式。 26 # cv2.COLOR_BGR2RGB 将BGR格式转换成RGB格式 27 # cv2.COLOR_BGR2GRAY 将BGR格式转换成灰度图片(灰度图片并不是指常规意义上的黑白图片,只用看是不是无符号八位整型(unit8),单通道即可判断) 28 gray_plate = cv2.cvtColor(license_plate, cv2.COLOR_RGB2GRAY) # RGB 转灰度图 29 print('gray_plate.shape ', gray_plate.shape) # (170, 722) 30 31 plt.subplot(232) 32 plt.imshow(gray_plate) 33 plt.title('GRAY 图像') 34 35 # Python: cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst 36 # src:表示的是图片源 37 # thresh:表示的是阈值(起始值) 38 # maxval:表示的是最大值,在高于阈值是赋予的新值 39 # type:表示的是这里划分的时候使用的是什么类型的算法**,常用值为0(cv2.THRESH_BINARY) 40 # cv2.THRESH_BINARY 大于阈值取最大值 maxval ,小于等于阈值取 0 41 # 两个返回值,第一个retVal(得到的阈值值(在后面一个方法中会用到)),第二个就是阈值化后的图像。 42 ret, binary_plate = cv2.threshold(gray_plate, 175, 255, cv2.THRESH_BINARY) # ret:阈值,binary_plate:根据阈值处理后的图像数据 43 print('ret ', ret) # 175.0 44 print('binary_plate ', binary_plate.shape ) # (170, 722) 45 46 plt.subplot(233) 47 plt.imshow(binary_plate) 48 plt.title('阈值处理之后的图像 ') 49 50 # 按列统计像素分布 51 result = [] 52 for col in range(binary_plate.shape[1]): 53 result.append(0) # 每一列像素值初始化为 0 54 for row in range(binary_plate.shape[0]): 55 result[col] = result[col] + binary_plate[row][col] / 255 56 # print(result) 57 # 记录车牌中字符的位置 58 character_dict = {} # character_dict 是一个字典,里面一个元素中的value 部分存储一个车牌中的字符 59 num = 0 # 记录统计的车牌中的第几个字符,同时是 字典 character_dict 中的 key 值 60 i = 0 # 表示是第几列像素 61 while i < len(result): 62 # 这一列上没有像素值 63 if result[i] == 0: 64 i += 1 65 else: 66 index = i + 1 67 while result[index] != 0: 68 index += 1 69 # 第 i 列 到 第 index-1 列,存储了一个字符,这里做了一个假设像 “ 桂 ” 这样左右结构的字,在列的方向上是没有像素断点的 70 # character_dict 是一个字典,num 是字典的 key,[i, index - 1] 是一个存储了两个数的列表作为字典的value 71 character_dict[num] = [i, index - 1] 72 num += 1 73 i = index 74 print('character_dict ', character_dict) 75 76 # 将每个字符填充,并存储 77 characters = [] 78 for i in range(8): # 车牌一共 8 个字符,其中第 3 个字符(下标为 2 )是一个 · 79 if i == 2: 80 continue 81 # 将字符填充为 170*170 的灰度图,padding 为计算左右需要各自填充多少列元素 82 padding = (170 - (character_dict[i][1] - character_dict[i][0])) / 2 83 84 # np.pad() 函数原型:ndarray = numpy.pad(array, pad_width, mode, **kwargs) 85 # array为要填补的数组 86 # pad_width 是在各维度的各个方向上想要填补的长度,如((1,2),(2,2)), 87 # 表示在第一个维度上水平方向上padding=1,垂直方向上padding=2, 在第二个维度上水平方向上padding=2,垂直方向上padding=2。 88 # 如果直接输入一个整数,则说明各个维度和各个方向所填补的长度都一样。 89 # mode为填补类型,即怎样去填补,有“constant”,“edge”等模式,如果为constant模式,就得指定填补的值,如果不指定,则默认填充0。 90 # 剩下的都是一些可选参数,具体可查看 91 # https://docs.scipy.org/doc/numpy/reference/generated/numpy.pad.html 92 93 # ndarray为填充好的返回值。 94 ndarray = np.pad(binary_plate[:, character_dict[i][0]:character_dict[i][1]], # array : 为要填补的数组 95 # pad_width:在各维度的各个方向上想要填补的长度。在第一个维度(行)前面填充 0 行,后面填充 0 行; 96 # 在第二个维度(列)前面填充 padding 列 后面填充 padding 列 97 ((0, 0), (int(padding), int(padding))), 98 # mode为填补类型,即怎样去填补,有“constant”,“edge”等模式, 99 # 如果为constant模式,就得指定填补的值,如果不指定,则默认填充0。 100 'constant', constant_values=(0, 0) 101 ) 102 print('第 {} 个字符'.format(i+1)) 103 print('原数组尺寸 : ', binary_plate[:, character_dict[i][0]:character_dict[i][1]].shape) 104 print('填充之后的尺寸 :', ndarray.shape) 105 ndarray = cv2.resize(ndarray, (20, 20)) 106 print('resize 之后的尺寸 :', ndarray.shape) 107 108 cv2.imwrite('../data/' + str(i) + '.png', ndarray) 109 characters.append(ndarray) 110 ndarray2 = cv2.resize(binary_plate[:, character_dict[i][0]:character_dict[i][1]], (20, 20)) 111 cv2.imwrite('../data/2' + str(i) + '.png', ndarray) 112 113 plt.show() 114 115 116 ''' 输出结果: 117 license_plate 的 type <class 'numpy.ndarray'> (170, 722, 3) 118 gray_plate.shape (170, 722) 119 ret 175.0 120 binary_plate (170, 722) 121 character_dict {0: [17, 87], 1: [109, 179], 2: [203, 216], 3: [240, 311], 4: [334, 406], 5: [430, 503], 6: [528, 603], 7: [629, 706]} 122 123 第 1 个字符 124 原数组尺寸 : (170, 70) 125 填充之后的尺寸 : (170, 170) 126 resize 之后的尺寸 : (20, 20) 127 第 2 个字符 128 原数组尺寸 : (170, 70) 129 填充之后的尺寸 : (170, 170) 130 resize 之后的尺寸 : (20, 20) 131 第 4 个字符 132 原数组尺寸 : (170, 71) 133 填充之后的尺寸 : (170, 169) 134 resize 之后的尺寸 : (20, 20) 135 第 5 个字符 136 原数组尺寸 : (170, 72) 137 填充之后的尺寸 : (170, 170) 138 resize 之后的尺寸 : (20, 20) 139 第 6 个字符 140 原数组尺寸 : (170, 73) 141 填充之后的尺寸 : (170, 169) 142 resize 之后的尺寸 : (20, 20) 143 第 7 个字符 144 原数组尺寸 : (170, 75) 145 填充之后的尺寸 : (170, 169) 146 resize 之后的尺寸 : (20, 20) 147 第 8 个字符 148 原数组尺寸 : (170, 77) 149 填充之后的尺寸 : (170, 169) 150 resize 之后的尺寸 : (20, 20) 151 '''
处理图片的过程展示: