車牌識別項目中,關於字符分割的實現:
思路:
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 '''
處理圖片的過程展示:

