├── .gitattributes ├── .idea ├── LicensePlateRecognition.iml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── __pycache__ └── predict.cpython-37.pyc ├── config.js ├── predict.py ├── surface.py ├── svm.dat ├── svmchinese.dat ├── test ├── 1.jpg ├── 2.jpg ├── cAA662F.jpg ├── car3.jpg ├── car4.jpg ├── car5.jpg ├── car7.jpg ├── lLD9016.jpg ├── u=1691206349,1754151067&fm=26&gp=0.jpg ├── u=1955857547,2196176231&fm=26&gp=0.jpg ├── u=3022089789,3948911321&fm=26&gp=0.jpg ├── wA87271.jpg ├── wATH859.jpg ├── wAUB816.jpg └── 下载.jpg └── train ├── chars2.7z └── charsChinese.7z /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.idea/LicensePlateRecognition.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /__pycache__/predict.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/__pycache__/predict.cpython-37.pyc -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | { 2 | "config":[ 3 | { 4 | "open":1, 5 | "blur":3, 6 | "morphologyr":4, 7 | "morphologyc":19, 8 | "col_num_limit":10, 9 | "row_num_limit":21 10 | }, 11 | { 12 | "open":0, 13 | "blur":3, 14 | "morphologyr":5, 15 | "morphologyc":12, 16 | "col_num_limit":10, 17 | "row_num_limit":18 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /predict.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | from numpy.linalg import norm 4 | import sys 5 | import os 6 | import json 7 | 8 | SZ = 20 #训练图片长宽 9 | MAX_WIDTH = 1000 #原始图片最大宽度 10 | Min_Area = 2000 #车牌区域允许最大面积 11 | PROVINCE_START = 1000 12 | #读取图片文件 13 | def imreadex(filename): 14 | return cv2.imdecode(np.fromfile(filename, dtype=np.uint8), cv2.IMREAD_COLOR) 15 | 16 | def point_limit(point): 17 | if point[0] < 0: 18 | point[0] = 0 19 | if point[1] < 0: 20 | point[1] = 0 21 | 22 | #根据设定的阈值和图片直方图,找出波峰,用于分隔字符 23 | def find_waves(threshold, histogram): 24 | up_point = -1#上升点 25 | is_peak = False 26 | if histogram[0] > threshold: 27 | up_point = 0 28 | is_peak = True 29 | wave_peaks = [] 30 | for i,x in enumerate(histogram): 31 | if is_peak and x < threshold: 32 | if i - up_point > 2: 33 | is_peak = False 34 | wave_peaks.append((up_point, i)) 35 | elif not is_peak and x >= threshold: 36 | is_peak = True 37 | up_point = i 38 | if is_peak and up_point != -1 and i - up_point > 4: 39 | wave_peaks.append((up_point, i)) 40 | return wave_peaks 41 | 42 | #根据找出的波峰,分隔图片,从而得到逐个字符图片 43 | def seperate_card(img, waves): 44 | part_cards = [] 45 | for wave in waves: 46 | part_cards.append(img[:, wave[0]:wave[1]]) 47 | return part_cards 48 | 49 | #来自opencv的sample,用于svm训练 50 | def deskew(img): 51 | m = cv2.moments(img) 52 | if abs(m['mu02']) < 1e-2: 53 | return img.copy() 54 | skew = m['mu11']/m['mu02'] 55 | M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]]) 56 | img = cv2.warpAffine(img, M, (SZ, SZ), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR) 57 | return img 58 | #来自opencv的sample,用于svm训练 59 | def preprocess_hog(digits): 60 | samples = [] 61 | for img in digits: 62 | gx = cv2.Sobel(img, cv2.CV_32F, 1, 0) 63 | gy = cv2.Sobel(img, cv2.CV_32F, 0, 1) 64 | mag, ang = cv2.cartToPolar(gx, gy) 65 | bin_n = 16 66 | bin = np.int32(bin_n*ang/(2*np.pi)) 67 | bin_cells = bin[:10,:10], bin[10:,:10], bin[:10,10:], bin[10:,10:] 68 | mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:] 69 | hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)] 70 | hist = np.hstack(hists) 71 | 72 | # transform to Hellinger kernel 73 | eps = 1e-7 74 | hist /= hist.sum() + eps 75 | hist = np.sqrt(hist) 76 | hist /= norm(hist) + eps 77 | 78 | samples.append(hist) 79 | return np.float32(samples) 80 | #不能保证包括所有省份 81 | provinces = [ 82 | "zh_cuan", "川", 83 | "zh_e", "鄂", 84 | "zh_gan", "赣", 85 | "zh_gan1", "甘", 86 | "zh_gui", "贵", 87 | "zh_gui1", "桂", 88 | "zh_hei", "黑", 89 | "zh_hu", "沪", 90 | "zh_ji", "冀", 91 | "zh_jin", "津", 92 | "zh_jing", "京", 93 | "zh_jl", "吉", 94 | "zh_liao", "辽", 95 | "zh_lu", "鲁", 96 | "zh_meng", "蒙", 97 | "zh_min", "闽", 98 | "zh_ning", "宁", 99 | "zh_qing", "靑", 100 | "zh_qiong", "琼", 101 | "zh_shan", "陕", 102 | "zh_su", "苏", 103 | "zh_sx", "晋", 104 | "zh_wan", "皖", 105 | "zh_xiang", "湘", 106 | "zh_xin", "新", 107 | "zh_yu", "豫", 108 | "zh_yu1", "渝", 109 | "zh_yue", "粤", 110 | "zh_yun", "云", 111 | "zh_zang", "藏", 112 | "zh_zhe", "浙" 113 | ] 114 | class StatModel(object): 115 | def load(self, fn): 116 | self.model = self.model.load(fn) 117 | def save(self, fn): 118 | self.model.save(fn) 119 | class SVM(StatModel): 120 | def __init__(self, C = 1, gamma = 0.5): 121 | self.model = cv2.ml.SVM_create() 122 | self.model.setGamma(gamma) 123 | self.model.setC(C) 124 | self.model.setKernel(cv2.ml.SVM_RBF) 125 | self.model.setType(cv2.ml.SVM_C_SVC) 126 | #训练svm 127 | def train(self, samples, responses): 128 | self.model.train(samples, cv2.ml.ROW_SAMPLE, responses) 129 | #字符识别 130 | def predict(self, samples): 131 | r = self.model.predict(samples) 132 | return r[1].ravel() 133 | 134 | class CardPredictor: 135 | def __init__(self): 136 | #车牌识别的部分参数保存在js中,便于根据图片分辨率做调整 137 | f = open('config.js') 138 | j = json.load(f) 139 | for c in j["config"]: 140 | if c["open"]: 141 | self.cfg = c.copy() 142 | break 143 | else: 144 | raise RuntimeError('没有设置有效配置参数') 145 | 146 | def __del__(self): 147 | self.save_traindata() 148 | def train_svm(self): 149 | #识别英文字母和数字 150 | self.model = SVM(C=1, gamma=0.5) 151 | #识别中文 152 | self.modelchinese = SVM(C=1, gamma=0.5) 153 | if os.path.exists("svm.dat"): 154 | self.model.load("svm.dat") 155 | else: 156 | chars_train = [] 157 | chars_label = [] 158 | 159 | for root, dirs, files in os.walk("train\\chars2"): 160 | if len(os.path.basename(root)) > 1: 161 | continue 162 | root_int = ord(os.path.basename(root)) 163 | for filename in files: 164 | filepath = os.path.join(root,filename) 165 | digit_img = cv2.imread(filepath) 166 | digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY) 167 | chars_train.append(digit_img) 168 | #chars_label.append(1) 169 | chars_label.append(root_int) 170 | 171 | chars_train = list(map(deskew, chars_train)) 172 | chars_train = preprocess_hog(chars_train) 173 | #chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32) 174 | chars_label = np.array(chars_label) 175 | print(chars_train.shape) 176 | self.model.train(chars_train, chars_label) 177 | if os.path.exists("svmchinese.dat"): 178 | self.modelchinese.load("svmchinese.dat") 179 | else: 180 | chars_train = [] 181 | chars_label = [] 182 | for root, dirs, files in os.walk("train\\charsChinese"): 183 | if not os.path.basename(root).startswith("zh_"): 184 | continue 185 | pinyin = os.path.basename(root) 186 | index = provinces.index(pinyin) + PROVINCE_START + 1 #1是拼音对应的汉字 187 | for filename in files: 188 | filepath = os.path.join(root,filename) 189 | digit_img = cv2.imread(filepath) 190 | digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY) 191 | chars_train.append(digit_img) 192 | #chars_label.append(1) 193 | chars_label.append(index) 194 | chars_train = list(map(deskew, chars_train)) 195 | chars_train = preprocess_hog(chars_train) 196 | #chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32) 197 | chars_label = np.array(chars_label) 198 | print(chars_train.shape) 199 | self.modelchinese.train(chars_train, chars_label) 200 | 201 | def save_traindata(self): 202 | if not os.path.exists("svm.dat"): 203 | self.model.save("svm.dat") 204 | if not os.path.exists("svmchinese.dat"): 205 | self.modelchinese.save("svmchinese.dat") 206 | 207 | def accurate_place(self, card_img_hsv, limit1, limit2, color): 208 | row_num, col_num = card_img_hsv.shape[:2] 209 | xl = col_num 210 | xr = 0 211 | yh = 0 212 | yl = row_num 213 | #col_num_limit = self.cfg["col_num_limit"] 214 | row_num_limit = self.cfg["row_num_limit"] 215 | col_num_limit = col_num * 0.8 if color != "green" else col_num * 0.5#绿色有渐变 216 | for i in range(row_num): 217 | count = 0 218 | for j in range(col_num): 219 | H = card_img_hsv.item(i, j, 0) 220 | S = card_img_hsv.item(i, j, 1) 221 | V = card_img_hsv.item(i, j, 2) 222 | if limit1 < H <= limit2 and 34 < S and 46 < V: 223 | count += 1 224 | if count > col_num_limit: 225 | if yl > i: 226 | yl = i 227 | if yh < i: 228 | yh = i 229 | for j in range(col_num): 230 | count = 0 231 | for i in range(row_num): 232 | H = card_img_hsv.item(i, j, 0) 233 | S = card_img_hsv.item(i, j, 1) 234 | V = card_img_hsv.item(i, j, 2) 235 | if limit1 < H <= limit2 and 34 < S and 46 < V: 236 | count += 1 237 | if count > row_num - row_num_limit: 238 | if xl > j: 239 | xl = j 240 | if xr < j: 241 | xr = j 242 | return xl, xr, yh, yl 243 | 244 | def predict(self, car_pic): 245 | if type(car_pic) == type(""): 246 | img = imreadex(car_pic) 247 | else: 248 | img = car_pic 249 | pic_hight, pic_width = img.shape[:2] 250 | 251 | if pic_width > MAX_WIDTH: 252 | resize_rate = MAX_WIDTH / pic_width 253 | img = cv2.resize(img, (MAX_WIDTH, int(pic_hight*resize_rate)), interpolation=cv2.INTER_AREA) 254 | 255 | blur = self.cfg["blur"] 256 | #高斯去噪 257 | if blur > 0: 258 | img = cv2.GaussianBlur(img, (blur, blur), 0)#图片分辨率调整 259 | oldimg = img 260 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 261 | #equ = cv2.equalizeHist(img) 262 | #img = np.hstack((img, equ)) 263 | #去掉图像中不会是车牌的区域 264 | kernel = np.ones((20, 20), np.uint8) 265 | img_opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) 266 | img_opening = cv2.addWeighted(img, 1, img_opening, -1, 0); 267 | 268 | #找到图像边缘 269 | ret, img_thresh = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) 270 | img_edge = cv2.Canny(img_thresh, 100, 200) 271 | #使用开运算和闭运算让图像边缘成为一个整体 272 | kernel = np.ones((self.cfg["morphologyr"], self.cfg["morphologyc"]), np.uint8) 273 | img_edge1 = cv2.morphologyEx(img_edge, cv2.MORPH_CLOSE, kernel) 274 | img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, kernel) 275 | 276 | #查找图像边缘整体形成的矩形区域,可能有很多,车牌就在其中一个矩形区域中 277 | image, contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 278 | contours = [cnt for cnt in contours if cv2.contourArea(cnt) > Min_Area] 279 | print('len(contours)', len(contours)) 280 | #一一排除不是车牌的矩形区域 281 | car_contours = [] 282 | for cnt in contours: 283 | rect = cv2.minAreaRect(cnt) 284 | area_width, area_height = rect[1] 285 | if area_width < area_height: 286 | area_width, area_height = area_height, area_width 287 | wh_ratio = area_width / area_height 288 | #print(wh_ratio) 289 | #要求矩形区域长宽比在2到5.5之间,2到5.5是车牌的长宽比,其余的矩形排除 290 | if wh_ratio > 2 and wh_ratio < 5.5: 291 | car_contours.append(rect) 292 | box = cv2.boxPoints(rect) 293 | box = np.int0(box) 294 | #oldimg = cv2.drawContours(oldimg, [box], 0, (0, 0, 255), 2) 295 | #cv2.imshow("edge4", oldimg) 296 | #print(rect) 297 | 298 | print(len(car_contours)) 299 | 300 | print("精确定位") 301 | card_imgs = [] 302 | #矩形区域可能是倾斜的矩形,需要矫正,以便使用颜色定位 303 | for rect in car_contours: 304 | if rect[2] > -1 and rect[2] < 1:#创造角度,使得左、高、右、低拿到正确的值 305 | angle = 1 306 | else: 307 | angle = rect[2] 308 | rect = (rect[0], (rect[1][0]+5, rect[1][1]+5), angle)#扩大范围,避免车牌边缘被排除 309 | 310 | box = cv2.boxPoints(rect) 311 | heigth_point = right_point = [0, 0] 312 | left_point = low_point = [pic_width, pic_hight] 313 | for point in box: 314 | if left_point[0] > point[0]: 315 | left_point = point 316 | if low_point[1] > point[1]: 317 | low_point = point 318 | if heigth_point[1] < point[1]: 319 | heigth_point = point 320 | if right_point[0] < point[0]: 321 | right_point = point 322 | 323 | if left_point[1] <= right_point[1]:#正角度 324 | new_right_point = [right_point[0], heigth_point[1]] 325 | pts2 = np.float32([left_point, heigth_point, new_right_point])#字符只是高度需要改变 326 | pts1 = np.float32([left_point, heigth_point, right_point]) 327 | M = cv2.getAffineTransform(pts1, pts2) 328 | dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight)) 329 | point_limit(new_right_point) 330 | point_limit(heigth_point) 331 | point_limit(left_point) 332 | card_img = dst[int(left_point[1]):int(heigth_point[1]), int(left_point[0]):int(new_right_point[0])] 333 | card_imgs.append(card_img) 334 | #cv2.imshow("card", card_img) 335 | #cv2.waitKey(0) 336 | elif left_point[1] > right_point[1]:#负角度 337 | 338 | new_left_point = [left_point[0], heigth_point[1]] 339 | pts2 = np.float32([new_left_point, heigth_point, right_point])#字符只是高度需要改变 340 | pts1 = np.float32([left_point, heigth_point, right_point]) 341 | M = cv2.getAffineTransform(pts1, pts2) 342 | dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight)) 343 | point_limit(right_point) 344 | point_limit(heigth_point) 345 | point_limit(new_left_point) 346 | card_img = dst[int(right_point[1]):int(heigth_point[1]), int(new_left_point[0]):int(right_point[0])] 347 | card_imgs.append(card_img) 348 | #cv2.imshow("card", card_img) 349 | #cv2.waitKey(0) 350 | #开始使用颜色定位,排除不是车牌的矩形,目前只识别蓝、绿、黄车牌 351 | colors = [] 352 | for card_index,card_img in enumerate(card_imgs): 353 | green = yello = blue = black = white = 0 354 | card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV) 355 | #有转换失败的可能,原因来自于上面矫正矩形出错 356 | if card_img_hsv is None: 357 | continue 358 | row_num, col_num= card_img_hsv.shape[:2] 359 | card_img_count = row_num * col_num 360 | 361 | for i in range(row_num): 362 | for j in range(col_num): 363 | H = card_img_hsv.item(i, j, 0) 364 | S = card_img_hsv.item(i, j, 1) 365 | V = card_img_hsv.item(i, j, 2) 366 | if 11 < H <= 34 and S > 34:#图片分辨率调整 367 | yello += 1 368 | elif 35 < H <= 99 and S > 34:#图片分辨率调整 369 | green += 1 370 | elif 99 < H <= 124 and S > 34:#图片分辨率调整 371 | blue += 1 372 | 373 | if 0 < H <180 and 0 < S < 255 and 0 < V < 46: 374 | black += 1 375 | elif 0 < H <180 and 0 < S < 43 and 221 < V < 225: 376 | white += 1 377 | color = "no" 378 | 379 | limit1 = limit2 = 0 380 | if yello*2 >= card_img_count: 381 | color = "yello" 382 | limit1 = 11 383 | limit2 = 34#有的图片有色偏偏绿 384 | elif green*2 >= card_img_count: 385 | color = "green" 386 | limit1 = 35 387 | limit2 = 99 388 | elif blue*2 >= card_img_count: 389 | color = "blue" 390 | limit1 = 100 391 | limit2 = 124#有的图片有色偏偏紫 392 | elif black + white >= card_img_count*0.7:#TODO 393 | color = "bw" 394 | print(color) 395 | colors.append(color) 396 | print(blue, green, yello, black, white, card_img_count) 397 | #cv2.imshow("color", card_img) 398 | #cv2.waitKey(0) 399 | if limit1 == 0: 400 | continue 401 | #以上为确定车牌颜色 402 | #以下为根据车牌颜色再定位,缩小边缘非车牌边界 403 | xl, xr, yh, yl = self.accurate_place(card_img_hsv, limit1, limit2, color) 404 | if yl == yh and xl == xr: 405 | continue 406 | need_accurate = False 407 | if yl >= yh: 408 | yl = 0 409 | yh = row_num 410 | need_accurate = True 411 | if xl >= xr: 412 | xl = 0 413 | xr = col_num 414 | need_accurate = True 415 | card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh-yl)//4 else card_img[yl-(yh-yl)//4:yh, xl:xr] 416 | if need_accurate:#可能x或y方向未缩小,需要再试一次 417 | card_img = card_imgs[card_index] 418 | card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV) 419 | xl, xr, yh, yl = self.accurate_place(card_img_hsv, limit1, limit2, color) 420 | if yl == yh and xl == xr: 421 | continue 422 | if yl >= yh: 423 | yl = 0 424 | yh = row_num 425 | if xl >= xr: 426 | xl = 0 427 | xr = col_num 428 | card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh-yl)//4 else card_img[yl-(yh-yl)//4:yh, xl:xr] 429 | #以上为车牌定位 430 | #以下为识别车牌中的字符 431 | predict_result = [] 432 | roi = None 433 | card_color = None 434 | for i, color in enumerate(colors): 435 | if color in ("blue", "yello", "green"): 436 | card_img = card_imgs[i] 437 | gray_img = cv2.cvtColor(card_img, cv2.COLOR_BGR2GRAY) 438 | #黄、绿车牌字符比背景暗、与蓝车牌刚好相反,所以黄、绿车牌需要反向 439 | if color == "green" or color == "yello": 440 | gray_img = cv2.bitwise_not(gray_img) 441 | ret, gray_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) 442 | #查找水平直方图波峰 443 | x_histogram = np.sum(gray_img, axis=1) 444 | x_min = np.min(x_histogram) 445 | x_average = np.sum(x_histogram)/x_histogram.shape[0] 446 | x_threshold = (x_min + x_average)/2 447 | wave_peaks = find_waves(x_threshold, x_histogram) 448 | if len(wave_peaks) == 0: 449 | print("peak less 0:") 450 | continue 451 | #认为水平方向,最大的波峰为车牌区域 452 | wave = max(wave_peaks, key=lambda x:x[1]-x[0]) 453 | gray_img = gray_img[wave[0]:wave[1]] 454 | #查找垂直直方图波峰 455 | row_num, col_num= gray_img.shape[:2] 456 | #去掉车牌上下边缘1个像素,避免白边影响阈值判断 457 | gray_img = gray_img[1:row_num-1] 458 | y_histogram = np.sum(gray_img, axis=0) 459 | y_min = np.min(y_histogram) 460 | y_average = np.sum(y_histogram)/y_histogram.shape[0] 461 | y_threshold = (y_min + y_average)/5#U和0要求阈值偏小,否则U和0会被分成两半 462 | 463 | wave_peaks = find_waves(y_threshold, y_histogram) 464 | 465 | #for wave in wave_peaks: 466 | # cv2.line(card_img, pt1=(wave[0], 5), pt2=(wave[1], 5), color=(0, 0, 255), thickness=2) 467 | #车牌字符数应大于6 468 | if len(wave_peaks) <= 6: 469 | print("peak less 1:", len(wave_peaks)) 470 | continue 471 | 472 | wave = max(wave_peaks, key=lambda x:x[1]-x[0]) 473 | max_wave_dis = wave[1] - wave[0] 474 | #判断是否是左侧车牌边缘 475 | if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis/3 and wave_peaks[0][0] == 0: 476 | wave_peaks.pop(0) 477 | 478 | #组合分离汉字 479 | cur_dis = 0 480 | for i,wave in enumerate(wave_peaks): 481 | if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6: 482 | break 483 | else: 484 | cur_dis += wave[1] - wave[0] 485 | if i > 0: 486 | wave = (wave_peaks[0][0], wave_peaks[i][1]) 487 | wave_peaks = wave_peaks[i+1:] 488 | wave_peaks.insert(0, wave) 489 | 490 | #去除车牌上的分隔点 491 | point = wave_peaks[2] 492 | if point[1] - point[0] < max_wave_dis/3: 493 | point_img = gray_img[:,point[0]:point[1]] 494 | if np.mean(point_img) < 255/5: 495 | wave_peaks.pop(2) 496 | 497 | if len(wave_peaks) <= 6: 498 | print("peak less 2:", len(wave_peaks)) 499 | continue 500 | part_cards = seperate_card(gray_img, wave_peaks) 501 | for i, part_card in enumerate(part_cards): 502 | #可能是固定车牌的铆钉 503 | if np.mean(part_card) < 255/5: 504 | print("a point") 505 | continue 506 | part_card_old = part_card 507 | w = abs(part_card.shape[1] - SZ)//2 508 | 509 | part_card = cv2.copyMakeBorder(part_card, 0, 0, w, w, cv2.BORDER_CONSTANT, value = [0,0,0]) 510 | part_card = cv2.resize(part_card, (SZ, SZ), interpolation=cv2.INTER_AREA) 511 | 512 | #part_card = deskew(part_card) 513 | part_card = preprocess_hog([part_card]) 514 | if i == 0: 515 | resp = self.modelchinese.predict(part_card) 516 | charactor = provinces[int(resp[0]) - PROVINCE_START] 517 | else: 518 | resp = self.model.predict(part_card) 519 | charactor = chr(resp[0]) 520 | #判断最后一个数是否是车牌边缘,假设车牌边缘被认为是1 521 | if charactor == "1" and i == len(part_cards)-1: 522 | if part_card_old.shape[0]/part_card_old.shape[1] >= 7:#1太细,认为是边缘 523 | continue 524 | predict_result.append(charactor) 525 | roi = card_img 526 | card_color = color 527 | break 528 | 529 | return predict_result, roi, card_color#识别到的字符、定位的车牌图像、车牌颜色 530 | 531 | if __name__ == '__main__': 532 | c = CardPredictor() 533 | c.train_svm() 534 | r, roi, color = c.predict("黑A16341.jpg") 535 | print(r) 536 | -------------------------------------------------------------------------------- /surface.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter.filedialog import * 3 | from tkinter import ttk 4 | import predict 5 | import cv2 6 | from PIL import Image, ImageTk 7 | import threading 8 | import time 9 | 10 | 11 | 12 | class Surface(ttk.Frame): 13 | pic_path = "" 14 | viewhigh = 600 15 | viewwide = 600 16 | update_time = 0 17 | thread = None 18 | thread_run = False 19 | camera = None 20 | color_transform = {"green":("绿牌","#55FF55"), "yello":("黄牌","#FFFF00"), "blue":("蓝牌","#6666FF")} 21 | 22 | def __init__(self, win): 23 | ttk.Frame.__init__(self, win) 24 | frame_left = ttk.Frame(self) 25 | frame_right1 = ttk.Frame(self) 26 | frame_right2 = ttk.Frame(self) 27 | win.title("车牌识别") 28 | win.state("zoomed") 29 | self.pack(fill=tk.BOTH, expand=tk.YES, padx="5", pady="5") 30 | frame_left.pack(side=LEFT,expand=1,fill=BOTH) 31 | frame_right1.pack(side=TOP,expand=1,fill=tk.Y) 32 | frame_right2.pack(side=RIGHT,expand=0) 33 | ttk.Label(frame_left, text='原图:').pack(anchor="nw") 34 | ttk.Label(frame_right1, text='车牌位置:').grid(column=0, row=0, sticky=tk.W) 35 | 36 | from_pic_ctl = ttk.Button(frame_right2, text="来自图片", width=20, command=self.from_pic) 37 | from_vedio_ctl = ttk.Button(frame_right2, text="来自摄像头", width=20, command=self.from_vedio) 38 | self.image_ctl = ttk.Label(frame_left) 39 | self.image_ctl.pack(anchor="nw") 40 | 41 | self.roi_ctl = ttk.Label(frame_right1) 42 | self.roi_ctl.grid(column=0, row=1, sticky=tk.W) 43 | ttk.Label(frame_right1, text='识别结果:').grid(column=0, row=2, sticky=tk.W) 44 | self.r_ctl = ttk.Label(frame_right1, text="") 45 | self.r_ctl.grid(column=0, row=3, sticky=tk.W) 46 | self.color_ctl = ttk.Label(frame_right1, text="", width="20") 47 | self.color_ctl.grid(column=0, row=4, sticky=tk.W) 48 | from_vedio_ctl.pack(anchor="se", pady="5") 49 | from_pic_ctl.pack(anchor="se", pady="5") 50 | self.predictor = predict.CardPredictor() 51 | self.predictor.train_svm() 52 | 53 | def get_imgtk(self, img_bgr): 54 | img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) 55 | im = Image.fromarray(img) 56 | imgtk = ImageTk.PhotoImage(image=im) 57 | wide = imgtk.width() 58 | high = imgtk.height() 59 | if wide > self.viewwide or high > self.viewhigh: 60 | wide_factor = self.viewwide / wide 61 | high_factor = self.viewhigh / high 62 | factor = min(wide_factor, high_factor) 63 | wide = int(wide * factor) 64 | if wide <= 0 : wide = 1 65 | high = int(high * factor) 66 | if high <= 0 : high = 1 67 | im=im.resize((wide, high), Image.ANTIALIAS) 68 | imgtk = ImageTk.PhotoImage(image=im) 69 | return imgtk 70 | 71 | def show_roi(self, r, roi, color): 72 | if r : 73 | roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) 74 | roi = Image.fromarray(roi) 75 | self.imgtk_roi = ImageTk.PhotoImage(image=roi) 76 | self.roi_ctl.configure(image=self.imgtk_roi, state='enable') 77 | self.r_ctl.configure(text=str(r)) 78 | self.update_time = time.time() 79 | try: 80 | c = self.color_transform[color] 81 | self.color_ctl.configure(text=c[0], background=c[1], state='enable') 82 | except: 83 | self.color_ctl.configure(state='disabled') 84 | elif self.update_time + 8 < time.time(): 85 | self.roi_ctl.configure(state='disabled') 86 | self.r_ctl.configure(text="") 87 | self.color_ctl.configure(state='disabled') 88 | 89 | def from_vedio(self): 90 | if self.thread_run: 91 | return 92 | if self.camera is None: 93 | self.camera = cv2.VideoCapture(0) 94 | if not self.camera.isOpened(): 95 | mBox.showwarning('警告', '摄像头打开失败!') 96 | self.camera = None 97 | return 98 | self.thread = threading.Thread(target=self.vedio_thread, args=(self,)) 99 | self.thread.setDaemon(True) 100 | self.thread.start() 101 | self.thread_run = True 102 | 103 | def from_pic(self): 104 | self.thread_run = False 105 | self.pic_path = askopenfilename(title="选择识别图片", filetypes=[("jpg图片", "*.jpg")]) 106 | if self.pic_path: 107 | img_bgr = predict.imreadex(self.pic_path) 108 | self.imgtk = self.get_imgtk(img_bgr) 109 | self.image_ctl.configure(image=self.imgtk) 110 | r, roi, color = self.predictor.predict(img_bgr) 111 | self.show_roi(r, roi, color) 112 | 113 | @staticmethod 114 | def vedio_thread(self): 115 | self.thread_run = True 116 | predict_time = time.time() 117 | while self.thread_run: 118 | _, img_bgr = self.camera.read() 119 | self.imgtk = self.get_imgtk(img_bgr) 120 | self.image_ctl.configure(image=self.imgtk) 121 | if time.time() - predict_time > 2: 122 | r, roi, color = self.predictor.predict(img_bgr) 123 | self.show_roi(r, roi, color) 124 | predict_time = time.time() 125 | print("run end") 126 | 127 | 128 | def close_window(): 129 | print("destroy") 130 | if surface.thread_run : 131 | surface.thread_run = False 132 | surface.thread.join(2.0) 133 | win.destroy() 134 | 135 | 136 | if __name__ == '__main__': 137 | win=tk.Tk() 138 | 139 | surface = Surface(win) 140 | win.protocol('WM_DELETE_WINDOW', close_window) 141 | win.mainloop() 142 | 143 | -------------------------------------------------------------------------------- /test/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/1.jpg -------------------------------------------------------------------------------- /test/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/2.jpg -------------------------------------------------------------------------------- /test/cAA662F.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/cAA662F.jpg -------------------------------------------------------------------------------- /test/car3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/car3.jpg -------------------------------------------------------------------------------- /test/car4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/car4.jpg -------------------------------------------------------------------------------- /test/car5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/car5.jpg -------------------------------------------------------------------------------- /test/car7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/car7.jpg -------------------------------------------------------------------------------- /test/lLD9016.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/lLD9016.jpg -------------------------------------------------------------------------------- /test/u=1691206349,1754151067&fm=26&gp=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/u=1691206349,1754151067&fm=26&gp=0.jpg -------------------------------------------------------------------------------- /test/u=1955857547,2196176231&fm=26&gp=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/u=1955857547,2196176231&fm=26&gp=0.jpg -------------------------------------------------------------------------------- /test/u=3022089789,3948911321&fm=26&gp=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/u=3022089789,3948911321&fm=26&gp=0.jpg -------------------------------------------------------------------------------- /test/wA87271.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/wA87271.jpg -------------------------------------------------------------------------------- /test/wATH859.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/wATH859.jpg -------------------------------------------------------------------------------- /test/wAUB816.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/wAUB816.jpg -------------------------------------------------------------------------------- /test/下载.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/test/下载.jpg -------------------------------------------------------------------------------- /train/chars2.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/train/chars2.7z -------------------------------------------------------------------------------- /train/charsChinese.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mufeng510/LicensePlateRecognition/eec3f858360b08e87fb469f2f4d27cf86d54deda/train/charsChinese.7z --------------------------------------------------------------------------------