【车牌识别】-车牌中字符分割代码详解

时间:2024-03-10 20:59:19

车牌识别项目中,关于字符分割的实现:

思路:

  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 \'\'\'

 

处理图片的过程展示: