1. 程式人生 > 實用技巧 >【車牌識別】-車牌中字元分割程式碼詳解

【車牌識別】-車牌中字元分割程式碼詳解

車牌識別專案中,關於字元分割的實現:

思路:

  1. 使用 cv2 讀取圖片

  2. 使用 cv2.cvtColor( img,cv2.COLOR_RGB2GRAY) 函式將 BGR 影象轉為灰度圖

  3. 車牌原圖 較為簡單,使用閾值處理灰度圖,將畫素值大於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 '''

圖片展示:

### 對車牌圖片進行處理,分割出車牌中的每一個字元並儲存
# 在本地讀取圖片的時候,如果路徑中包含中文,會導致讀取失敗。

import cv2
import paddle
import numpy as np
import matplotlib.pyplot as plt
#以下兩行實現了在plt畫圖時,可以輸出中文字元
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False


# cv2.imread() 讀進來直接是BGR 格式資料,數值範圍在 0~255 。在本地讀取,路徑中不要含有中文
license_plate = cv2.imread('../data/car.png') # license 拍照,plate 車牌
print('license_plate type ', type(license_plate), license_plate.shape) # <class 'numpy.ndarray'> (170, 722, 3)

plt.subplot(231)
plt.imshow(license_plate)
plt.title('原圖 BGR ')

gray_plate2 = cv2.cvtColor(license_plate, cv2.COLOR_BGR2RGB) # RGB 轉灰度圖
plt.subplot(234)
plt.imshow(gray_plate2)
plt.title('RGB影象')
# cv2.cvtColor(p1,p2) 是顏色空間轉換函式,p1是需要轉換的圖片,p2是轉換成何種格式。
# cv2.COLOR_BGR2RGB BGR格式轉換成RGB格式
# cv2.COLOR_BGR2GRAY BGR格式轉換成灰度圖片(灰度圖片並不是指常規意義上的黑白圖片,只用看是不是無符號八位整型(unit8,單通道即可判斷)
gray_plate = cv2.cvtColor(license_plate, cv2.COLOR_RGB2GRAY) # RGB 轉灰度圖
print('gray_plate.shape ', gray_plate.shape) # (170, 722)

plt.subplot(232)
plt.imshow(gray_plate)
plt.title('GRAY 影象')

# Python: cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
# src:表示的是圖片源
# thresh:表示的是閾值(起始值)
# maxval:表示的是最大值,在高於閾值是賦予的新值
# type:表示的是這裡劃分的時候使用的是什麼型別的演算法**,常用值為0cv2.THRESH_BINARY
# cv2.THRESH_BINARY 大於閾值取最大值 maxval ,小於等於閾值取 0
# 兩個返回值,第一個retVal(得到的閾值值(在後面一個方法中會用到)),第二個就是閾值化後的影象。
ret, binary_plate = cv2.threshold(gray_plate, 175, 255, cv2.THRESH_BINARY) # ret:閾值,binary_plate:根據閾值處理後的影象資料
print('ret ', ret) # 175.0
print('binary_plate ', binary_plate.shape ) # (170, 722)

plt.subplot(233)
plt.imshow(binary_plate)
plt.title('閾值處理之後的影象 ')

# 按列統計畫素分佈
result = []
for col in range(binary_plate.shape[1]):
result.append(0) # 每一列畫素值初始化為 0
for row in range(binary_plate.shape[0]):
result[col] = result[col] + binary_plate[row][col] / 255
# print(result)
# 記錄車牌中字元的位置
character_dict = {} # character_dict 是一個字典,裡面一個元素中的value 部分儲存一個車牌中的字元
num = 0 # 記錄統計的車牌中的第幾個字元,同時是 字典 character_dict 中的 key
i = 0 # 表示是第幾列畫素
while i < len(result):
# 這一列上沒有畫素值
if result[i] == 0:
i += 1
else:
index = i + 1
while result[index] != 0:
index += 1
# i 列 到 第 index-1 列,儲存了一個字元,這裡做了一個假設像這樣左右結構的字,在列的方向上是沒有畫素斷點的
# character_dict 是一個字典,num 是字典的 key[i, index - 1] 是一個儲存了兩個數的列表作為字典的value
character_dict[num] = [i, index - 1]
num += 1
i = index
print('character_dict ', character_dict)

# 將每個字元填充,並存儲
characters = []
for i in range(8): # 車牌一共 8 個字元,其中第 3 個字元(下標為 2 )是一個 ·
if i == 2:
continue
# 將字元填充為 170*170 的灰度圖,padding 為計算左右需要各自填充多少列元素
padding = (170 - (character_dict[i][1] - character_dict[i][0])) / 2

# np.pad() 函式原型:ndarray = numpy.pad(array, pad_width, mode, **kwargs)
# array為要填補的陣列
# pad_width 是在各維度的各個方向上想要填補的長度,如((12),(22)),
# 表示在第一個維度上水平方向上padding=1,垂直方向上padding=2, 在第二個維度上水平方向上padding=2,垂直方向上padding=2
# 如果直接輸入一個整數,則說明各個維度和各個方向所填補的長度都一樣。
# mode為填補型別,即怎樣去填補,有“constant”“edge”等模式,如果為constant模式,就得指定填補的值,如果不指定,則預設填充0
# 剩下的都是一些可選引數,具體可檢視
# https://docs.scipy.org/doc/numpy/reference/generated/numpy.pad.html

# ndarray為填充好的返回值。
ndarray = np.pad(binary_plate[:, character_dict[i][0]:character_dict[i][1]], # array : 為要填補的陣列
# pad_width:在各維度的各個方向上想要填補的長度。在第一個維度()前面填充 0 行,後面填充 0 行;
# 在第二個維度()前面填充 padding 列 後面填充 padding
((0, 0), (int(padding), int(padding))),
# mode為填補型別,即怎樣去填補,有“constant”“edge”等模式,
# 如果為constant模式,就得指定填補的值,如果不指定,則預設填充0
'constant', constant_values=(0, 0)
)
print(' {} 個字元'.format(i+1))
print('原陣列尺寸 : ', binary_plate[:, character_dict[i][0]:character_dict[i][1]].shape)
print('填充之後的尺寸 :', ndarray.shape)
ndarray = cv2.resize(ndarray, (20, 20))
print('resize 之後的尺寸 :', ndarray.shape)

cv2.imwrite('../data/' + str(i) + '.png', ndarray)
characters.append(ndarray)
ndarray2 = cv2.resize(binary_plate[:, character_dict[i][0]:character_dict[i][1]], (20, 20))
cv2.imwrite('../data/2' + str(i) + '.png', ndarray)

plt.show()


''' 輸出結果:
license_plate type <class 'numpy.ndarray'> (170, 722, 3)
gray_plate.shape (170, 722)
ret 175.0
binary_plate (170, 722)
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]}

1 個字元
原陣列尺寸 : (170, 70)
填充之後的尺寸 : (170, 170)
resize 之後的尺寸 : (20, 20)
2 個字元
原陣列尺寸 : (170, 70)
填充之後的尺寸 : (170, 170)
resize 之後的尺寸 : (20, 20)
4 個字元
原陣列尺寸 : (170, 71)
填充之後的尺寸 : (170, 169)
resize 之後的尺寸 : (20, 20)
5 個字元
原陣列尺寸 : (170, 72)
填充之後的尺寸 : (170, 170)
resize 之後的尺寸 : (20, 20)
6 個字元
原陣列尺寸 : (170, 73)
填充之後的尺寸 : (170, 169)
resize 之後的尺寸 : (20, 20)
7 個字元
原陣列尺寸 : (170, 75)
填充之後的尺寸 : (170, 169)
resize 之後的尺寸 : (20, 20)
8 個字元
原陣列尺寸 : (170, 77)
填充之後的尺寸 : (170, 169)
resize 之後的尺寸 : (20, 20)
'''