├── PlateRecogntion ├── lib │ ├── config.js │ ├── function.py │ ├── math.py │ ├── recognition.py │ ├── sql.py │ ├── svm.dat │ └── svmchinese.dat ├── main.py └── pic │ ├── hy.png │ └── locate.png └── README.md /PlateRecogntion/lib/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 | } -------------------------------------------------------------------------------- /PlateRecogntion/lib/function.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import cv2 6 | import numpy as np 7 | import lib.math as img_math 8 | import lib.recognition as img_recognition 9 | 10 | SZ = 20 # 训练图片长宽 11 | MAX_WIDTH = 1000 # 原始图片最大宽度 12 | Min_Area = 2000 # 车牌区域允许最大面积 13 | PROVINCE_START = 1000 14 | 15 | 16 | def cv_show(name, img): 17 | cv2.imshow(name, img) 18 | cv2.waitKey(0) 19 | cv2.destroyAllWindows() 20 | 21 | 22 | class StatModel(object): 23 | def load(self, fn): 24 | self.model = self.model.load(fn) 25 | 26 | def save(self, fn): 27 | self.model.save(fn) 28 | 29 | 30 | class SVM(StatModel): 31 | def __init__(self, C=1, gamma=0.5): 32 | self.model = cv2.ml.SVM_create() 33 | self.model.setGamma(gamma) 34 | self.model.setC(C) 35 | self.model.setKernel(cv2.ml.SVM_RBF) 36 | self.model.setType(cv2.ml.SVM_C_SVC) 37 | 38 | # 训练svm 39 | def train(self, samples, responses): 40 | self.model.train(samples, cv2.ml.ROW_SAMPLE, responses) 41 | 42 | # 字符识别 43 | def predict(self, samples): 44 | r = self.model.predict(samples) 45 | return r[1].ravel() 46 | 47 | 48 | class CardPredictor: 49 | def __init__(self): 50 | pass 51 | 52 | def train_svm(self): 53 | '''生成dat文件,完成机器学习算子,此步操作在main文件执行时,就被自动调用了''' 54 | # 识别英文字母和数字 55 | self.model = SVM(C=1, gamma=0.5) 56 | # 识别中文 57 | self.modelchinese = SVM(C=1, gamma=0.5) 58 | if os.path.exists("lib/svm.dat"): 59 | self.model.load("lib/svm.dat") 60 | if os.path.exists("lib/svmchinese.dat"): 61 | self.modelchinese.load("lib/svmchinese.dat") 62 | 63 | def img_first_pre(self, car_pic_file): 64 | """ 65 | 返回Otsu’s二值化边缘化的新图像和原图,需要两个变量接收 66 | :param car_pic_file: 图像文件 67 | :return:已经处理好的图像文件 原图像文件 68 | """ 69 | if type(car_pic_file) == type(""): 70 | img = img_math.img_read(car_pic_file) 71 | else: 72 | img = car_pic_file 73 | 74 | pic_hight, pic_width = img.shape[:2] 75 | if pic_width > MAX_WIDTH: 76 | resize_rate = MAX_WIDTH / pic_width 77 | img = cv2.resize(img, (MAX_WIDTH, int(pic_hight * resize_rate)), interpolation=cv2.INTER_AREA) 78 | # cv_show("img", img) 79 | # 缩小图片 80 | 81 | blur = 5 82 | img = cv2.GaussianBlur(img, (blur, blur), 0) 83 | # cv_show("blur=5", img) 84 | oldimg = img 85 | return img, oldimg 86 | 87 | def color_and_contour(self, filename, oldimg, img_contours): 88 | """ 89 | color_and_contour()方法是通过颜色和形状定位车牌位置的方法 90 | 返回识别到的字符、定位的车牌图像、车牌颜色 91 | :param filename: 图像文件 92 | :param oldimg: 原图像文件 93 | :return: 返回识别到的字符、定位的车牌图像、车牌颜色 94 | """ 95 | pic_hight, pic_width = img_contours.shape[:2] 96 | lower_blue = np.array([100, 110, 110]) 97 | # 在 OpenCV 的 HSV 格式中,H(色彩/色度)的取值范围是 [0,179],S(饱和度)的取值范围 [0,255],V(亮度)的取值范围 [0,255]。 98 | upper_blue = np.array([130, 255, 255]) 99 | lower_yellow = np.array([15, 55, 55]) 100 | upper_yellow = np.array([50, 255, 255]) 101 | lower_green = np.array([50, 50, 50]) 102 | upper_green = np.array([100, 255, 255]) 103 | hsv = cv2.cvtColor(filename, cv2.COLOR_BGR2HSV) 104 | # cv_show('hsv', hsv) 105 | mask_blue = cv2.inRange(hsv, lower_blue, upper_blue) 106 | # 第一个参数:hsv指的是原图 107 | # 第二个参数:lower_red指的是图像中低于这个lower_red的值,图像值变为0 108 | # 第三个参数:upper_red指的是图像中高于这个upper_red的值,图像值变为0 109 | # 而在lower_red~upper_red之间的值变成255 110 | # cv_show('mask_blue', mask_blue) 111 | mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow) 112 | # cv_show('mask_yellow', mask_yellow) 113 | mask_green = cv2.inRange(hsv, lower_green, upper_green) 114 | # cv_show('mask_green', mask_green) 115 | output = cv2.bitwise_and(hsv, hsv, mask=mask_blue + mask_yellow + mask_green) 116 | # https://www.cnblogs.com/Undo-self-blog/p/8434906.html 117 | # 将hsv与mask的像素值想与 118 | # cv_show('output', output) 119 | # 根据阈值找到对应颜色 120 | 121 | output = cv2.cvtColor(output, cv2.COLOR_BGR2GRAY) 122 | # cv_show('output', output) 123 | Matrix = np.ones((20, 20), np.uint8) 124 | img_edge1 = cv2.morphologyEx(output, cv2.MORPH_CLOSE, Matrix) 125 | img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, Matrix) 126 | # cv_show('img_edge2',img_edge2) 127 | 128 | card_contours = img_math.img_findContours(img_edge2) 129 | card_imgs = img_math.img_Transform(card_contours, oldimg, pic_width, pic_hight) 130 | # print(card_imgs.shape) 131 | colors, card_imgs = img_math.img_color(card_imgs) 132 | # print(card_imgs) 133 | 134 | predict_result = [] 135 | predict_str = "" 136 | roi = None 137 | card_color = None 138 | 139 | for i, color in enumerate(colors): 140 | # print('i的值:', i) i = 0 141 | 142 | if color in ("blue", "yello", "green"): 143 | card_img = card_imgs[i] 144 | # cv_show('card_img', card_img) 145 | 146 | try: 147 | gray_img = cv2.cvtColor(card_img, cv2.COLOR_BGR2GRAY) 148 | # cv_show('gray_img0',gray_img) 149 | except: 150 | print("gray转换失败") 151 | 152 | # 黄、绿车牌字符比背景暗、与蓝车牌刚好相反,所以黄、绿车牌需要反向 153 | if color == "green" or color == "yello": 154 | gray_img = cv2.bitwise_not(gray_img) 155 | ret, gray_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) 156 | print('二值化阈值为:', ret) 157 | # cv_show('gray_img1',gray_img) 158 | x_histogram = np.sum(gray_img, axis=1) 159 | print('x_histogram的值为:--', x_histogram) 160 | 161 | x_min = np.min(x_histogram) 162 | x_average = np.sum(x_histogram) / x_histogram.shape[0] 163 | x_threshold = (x_min + x_average) / 2 164 | wave_peaks = img_math.find_waves(x_threshold, x_histogram) 165 | if len(wave_peaks) == 0: 166 | # print("peak less 0:") 167 | continue 168 | # 认为水平方向,最大的波峰为车牌区域 169 | wave = max(wave_peaks, key=lambda x: x[1] - x[0]) 170 | gray_img = gray_img[wave[0]:wave[1]] 171 | # cv_show('gray_img2', gray_img) 172 | # 查找垂直直方图波峰 173 | row_num, col_num = gray_img.shape[:2] 174 | # 去掉车牌上下边缘1个像素,避免白边影响阈值判断 175 | gray_img = gray_img[1:row_num - 1] 176 | # cv_show('gray_img3', gray_img) 177 | y_histogram = np.sum(gray_img, axis=0) 178 | y_min = np.min(y_histogram) 179 | y_average = np.sum(y_histogram) / y_histogram.shape[0] 180 | y_threshold = (y_min + y_average) / 5 # U和0要求阈值偏小,否则U和0会被分成两半 181 | wave_peaks = img_math.find_waves(y_threshold, y_histogram) 182 | if len(wave_peaks) < 6: 183 | # print("peak less 1:", len(wave_peaks)) 184 | continue 185 | 186 | wave = max(wave_peaks, key=lambda x: x[1] - x[0]) 187 | max_wave_dis = wave[1] - wave[0] 188 | # 判断是否是左侧车牌边缘 189 | if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis / 3 and wave_peaks[0][0] == 0: 190 | wave_peaks.pop(0) 191 | 192 | # 组合分离汉字 193 | cur_dis = 0 194 | for i, wave in enumerate(wave_peaks): 195 | if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6: 196 | break 197 | else: 198 | cur_dis += wave[1] - wave[0] 199 | if i > 0: 200 | wave = (wave_peaks[0][0], wave_peaks[i][1]) 201 | wave_peaks = wave_peaks[i + 1:] 202 | wave_peaks.insert(0, wave) 203 | 204 | point = wave_peaks[2] 205 | point_img = gray_img[:, point[0]:point[1]] 206 | # cv_show('point_img', point_img) 207 | if np.mean(point_img) < 255 / 5: 208 | wave_peaks.pop(2) 209 | 210 | if len(wave_peaks) <= 6: 211 | # print("peak less 2:", len(wave_peaks)) 212 | continue 213 | 214 | part_cards = img_math.seperate_card(gray_img, wave_peaks) 215 | 216 | for i, part_card in enumerate(part_cards): 217 | # 可能是固定车牌的铆钉 218 | 219 | if np.mean(part_card) < 255 / 5: 220 | # print("a point") 221 | continue 222 | part_card_old = part_card 223 | 224 | w = abs(part_card.shape[1] - SZ) // 2 225 | 226 | part_card = cv2.copyMakeBorder(part_card, 0, 0, w, w, cv2.BORDER_CONSTANT, value=[0, 0, 0]) 227 | # cv_show('part_card', part_card) 228 | part_card = cv2.resize(part_card, (SZ, SZ), interpolation=cv2.INTER_AREA) 229 | # cv_show('part_card2', part_card) 230 | part_card = img_recognition.preprocess_hog([part_card]) 231 | # cv_show('part_card3', part_card) 232 | if i == 0: 233 | resp = self.modelchinese.predict(part_card) 234 | charactor = img_recognition.provinces[int(resp[0]) - PROVINCE_START] 235 | else: 236 | resp = self.model.predict(part_card) 237 | charactor = chr(resp[0]) 238 | # 判断最后一个数是否是车牌边缘,假设车牌边缘被认为是1 239 | if charactor == "1" and i == len(part_cards) - 1: 240 | if part_card_old.shape[0] / part_card_old.shape[1] >= 7: # 1太细,认为是边缘 241 | continue 242 | predict_result.append(charactor) 243 | predict_str = "".join(predict_result) 244 | 245 | roi = card_img 246 | card_color = color 247 | break 248 | return predict_str, roi, card_color # 识别到的字符、定位的车牌图像、车牌颜色 249 | -------------------------------------------------------------------------------- /PlateRecogntion/lib/math.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import cv2 5 | import numpy as np 6 | 7 | MAX_WIDTH = 1000 8 | Min_Area = 2000 9 | SZ = 20 10 | PROVINCE_START = 1000 11 | 12 | def cv_show(name, img): 13 | cv2.imshow(name, img) 14 | cv2.waitKey(0) 15 | cv2.destroyAllWindows() 16 | 17 | """ 18 | 该文件包含读文件函数 19 | 取零值函数 20 | 矩阵校正函数 21 | 颜色判断函数 22 | """ 23 | 24 | 25 | def img_read(filename): 26 | """以uint8方式读取filename 放入imdecode中,即返回以uint8方式读取的彩色照片""" 27 | return cv2.imdecode(np.fromfile(filename, dtype=np.uint8), cv2.IMREAD_COLOR) 28 | # 以uint8方式读取filename 放入imdecode中,cv2.IMREAD_COLOR读取彩色照片 29 | 30 | 31 | def find_waves(threshold, histogram): 32 | up_point = -1 # 上升点 33 | is_peak = False 34 | if histogram[0] > threshold: 35 | up_point = 0 36 | is_peak = True 37 | wave_peaks = [] 38 | for i, x in enumerate(histogram): 39 | if is_peak and x < threshold: 40 | if i - up_point > 2: 41 | is_peak = False 42 | wave_peaks.append((up_point, i)) 43 | elif not is_peak and x >= threshold: 44 | is_peak = True 45 | up_point = i 46 | if is_peak and up_point != -1 and i - up_point > 4: 47 | wave_peaks.append((up_point, i)) 48 | return wave_peaks 49 | 50 | 51 | def point_limit(point): 52 | if point[0] < 0: 53 | point[0] = 0 54 | if point[1] < 0: 55 | point[1] = 0 56 | 57 | 58 | def accurate_place(card_img_hsv, limit1, limit2, color): 59 | row_num, col_num = card_img_hsv.shape[:2] 60 | xl = col_num 61 | xr = 0 62 | yh = 0 63 | yl = row_num 64 | row_num_limit = 21 65 | col_num_limit = col_num * 0.8 if color != "green" else col_num * 0.5 # 绿色有渐变 66 | for i in range(row_num): 67 | count = 0 68 | for j in range(col_num): 69 | H = card_img_hsv.item(i, j, 0) 70 | S = card_img_hsv.item(i, j, 1) 71 | V = card_img_hsv.item(i, j, 2) 72 | if limit1 < H <= limit2 and 34 < S and 46 < V: 73 | count += 1 74 | if count > col_num_limit: 75 | if yl > i: 76 | yl = i 77 | if yh < i: 78 | yh = i 79 | for j in range(col_num): 80 | count = 0 81 | for i in range(row_num): 82 | H = card_img_hsv.item(i, j, 0) 83 | S = card_img_hsv.item(i, j, 1) 84 | V = card_img_hsv.item(i, j, 2) 85 | if limit1 < H <= limit2 and 34 < S and 46 < V: 86 | count += 1 87 | if count > row_num - row_num_limit: 88 | if xl > j: 89 | xl = j 90 | if xr < j: 91 | xr = j 92 | return xl, xr, yh, yl 93 | 94 | 95 | def img_findContours(img_contours): 96 | contours, hierarchy = cv2.findContours(img_contours, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 97 | # print(contours) 98 | # RETR_TREE检索所有的轮廓,并重构嵌套轮廓的整个层次,一般选择RETR_TREE轮廓检索方式 99 | # CHAIN_APPROX_SIMPLE压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分 100 | contours = [cnt for cnt in contours if cv2.contourArea(cnt) > Min_Area] 101 | # print("findContours len = ", len(contours)) 102 | # 排除面积最小的点 103 | car_contours = [] 104 | for cnt in contours: 105 | ant = cv2.minAreaRect(cnt) 106 | # 函数cv2.minAreaRect()返回一个Box2D结构,ant:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度)。 107 | # 分别对应于返回值:((rect[0][0], rect[0][1]), (rect[1][0], rect[1][1]), rect[2]) 108 | width, height = ant[1] 109 | if width < height: 110 | width, height = height, width 111 | ration = width / height 112 | 113 | if 2 < ration < 5.5: 114 | # 车牌长宽比有固定的比例,筛选符合条件的轮廓 115 | car_contours.append(ant) 116 | box = cv2.boxPoints(ant) 117 | print('车牌位置:', car_contours) 118 | 119 | return car_contours 120 | 121 | #进行矩形矫正 122 | def img_Transform(car_contours, oldimg, pic_width, pic_hight): 123 | """https://zhuanlan.zhihu.com/p/53953631""" 124 | car_imgs = [] 125 | for car_rect in car_contours: 126 | # 遍历所有筛选得到的车牌轮廓 127 | if -1 < car_rect[2] < 1: 128 | # 判断车牌位置: [((497.9271240234375, 399.27001953125), (144.60279846191406, 463.2875671386719), -89.2500991821289)]的第三个字段的值:角度 129 | angle = 1 130 | # 对于角度为-1 1之间时,默认为1 131 | else: 132 | angle = car_rect[2] 133 | car_rect = (car_rect[0], (car_rect[1][0] + 5, car_rect[1][1] + 5), angle) 134 | box = cv2.boxPoints(car_rect) 135 | # [[731.07184 477.12946](右下) 136 | # [262.8244 471.00055](右上) 137 | # [264.7824 321.41058](左上) 138 | # [733.02985 327.5395 ]](左下) 139 | # print(box) 140 | # boxPoints返回矩形的四个顶点的信息 141 | 142 | heigth_point = right_point = [0, 0] 143 | left_point = low_point = [pic_width, pic_hight] 144 | # 注意这里是w,h,不是h,w 145 | # 对于实例冀TGK857来说,[pic_width, pic_hight]=[1000, 750] 146 | # print(left_point) 147 | for point in box: 148 | # point存储的是矩形的四个顶点的信息 149 | if left_point[0] > point[0]: 150 | left_point = point 151 | if low_point[1] > point[1]: 152 | low_point = point 153 | if heigth_point[1] < point[1]: 154 | heigth_point = point 155 | if right_point[0] < point[0]: 156 | right_point = point 157 | # [731.07184 477.12946] [731.07184 477.12946] [731.07184 477.12946] [731.07184 477.12946](右下 右下 右下 右下) 158 | # [262.8244 471.00055] [262.8244 471.00055] [731.07184 477.12946] [731.07184 477.12946](右上 右上 右下 右下) 159 | # [262.8244 471.00055] [264.7824 321.41058] [731.07184 477.12946] [731.07184 477.12946](右上 左上 右下 右下) 160 | # [262.8244 471.00055] [264.7824 321.41058] [731.07184 477.12946] [733.02985 327.5395 ](右上 左上 右下 左下) 161 | # ( 左下角 ) ( 左上角 ) ( 右下角 ) ( 右上角 ) 162 | # ( left_point, low_point, heigth_point, right_point ) 163 | # [262.8244 471.00055] [264.7824 321.41058] [731.07184 477.12946] [733.02985 327.5395 ] 164 | # print(left_point, low_point, heigth_point, right_point) 165 | if left_point[1] <= right_point[1]: # 正角度 166 | new_right_point = [right_point[0], heigth_point[1]] 167 | pts2 = np.float32([left_point, heigth_point, new_right_point]) # 字符只是高度需要改变 168 | pts1 = np.float32([left_point, heigth_point, right_point]) 169 | M = cv2.getAffineTransform(pts1, pts2) 170 | dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight)) 171 | point_limit(new_right_point) 172 | point_limit(heigth_point) 173 | point_limit(left_point) 174 | car_img = dst[int(left_point[1]):int(heigth_point[1]), int(left_point[0]):int(new_right_point[0])] 175 | car_imgs.append(car_img) 176 | 177 | elif left_point[1] > right_point[1]: # 负角度 178 | new_left_point = [left_point[0], heigth_point[1]] 179 | pts2 = np.float32([new_left_point, heigth_point, right_point]) # 字符只是高度需要改变 180 | pts1 = np.float32([left_point, heigth_point, right_point]) 181 | print('pts1为---:', pts1) 182 | print('pts2为---:', pts2) 183 | M = cv2.getAffineTransform(pts1, pts2) 184 | dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight)) 185 | point_limit(right_point) 186 | point_limit(heigth_point) 187 | point_limit(new_left_point) 188 | car_img = dst[int(right_point[1]):int(heigth_point[1]), int(new_left_point[0]):int(right_point[0])] 189 | car_imgs.append(car_img) 190 | # print('----------', len(car_imgs)) 191 | return car_imgs 192 | 193 | 194 | def img_color(card_imgs): 195 | colors = [] 196 | for card_index, card_img in enumerate(card_imgs): 197 | 198 | green = yello = blue = black = white = 0 199 | try: 200 | card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV) 201 | except: 202 | print("矫正矩形出错, 转换失败") 203 | # 有转换失败的可能,原因来自于上面矫正矩形出错 204 | 205 | if card_img_hsv is None: 206 | continue 207 | row_num, col_num = card_img_hsv.shape[:2] 208 | card_img_count = row_num * col_num 209 | 210 | for i in range(row_num): 211 | for j in range(col_num): 212 | H = card_img_hsv.item(i, j, 0) 213 | S = card_img_hsv.item(i, j, 1) 214 | V = card_img_hsv.item(i, j, 2) 215 | if 11 < H <= 34 and S > 34: 216 | yello += 1 217 | elif 35 < H <= 99 and S > 34: 218 | green += 1 219 | elif 99 < H <= 124 and S > 34: 220 | blue += 1 221 | 222 | if 0 < H < 180 and 0 < S < 255 and 0 < V < 46: 223 | black += 1 224 | elif 0 < H < 180 and 0 < S < 43 and 221 < V < 225: 225 | white += 1 226 | color = "no" 227 | 228 | limit1 = limit2 = 0 229 | if yello * 2 >= card_img_count: 230 | color = "yello" 231 | limit1 = 11 232 | limit2 = 34 # 有的图片有色偏偏绿 233 | elif green * 2 >= card_img_count: 234 | color = "green" 235 | limit1 = 35 236 | limit2 = 99 237 | elif blue * 2 >= card_img_count: 238 | color = "blue" 239 | limit1 = 100 240 | limit2 = 124 # 有的图片有色偏偏紫 241 | elif black + white >= card_img_count * 0.7: 242 | color = "bw" 243 | colors.append(color) 244 | card_imgs[card_index] = card_img 245 | 246 | if limit1 == 0: 247 | continue 248 | xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color) 249 | if yl == yh and xl == xr: 250 | continue 251 | need_accurate = False 252 | if yl >= yh: 253 | yl = 0 254 | yh = row_num 255 | need_accurate = True 256 | if xl >= xr: 257 | xl = 0 258 | xr = col_num 259 | need_accurate = True 260 | 261 | if color == "green": 262 | card_imgs[card_index] = card_img 263 | else: 264 | card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh - yl) // 4 else card_img[ 265 | yl - ( 266 | yh - yl) // 4:yh, 267 | xl:xr] 268 | 269 | if need_accurate: 270 | card_img = card_imgs[card_index] 271 | card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV) 272 | xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color) 273 | if yl == yh and xl == xr: 274 | continue 275 | if yl >= yh: 276 | yl = 0 277 | yh = row_num 278 | if xl >= xr: 279 | xl = 0 280 | xr = col_num 281 | if color == "green": 282 | card_imgs[card_index] = card_img 283 | else: 284 | card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh - yl) // 4 else card_img[ 285 | yl - ( 286 | yh - yl) // 4:yh, 287 | xl:xr] 288 | 289 | return colors, card_imgs 290 | 291 | 292 | # 根据设定的阈值和图片直方图,找出波峰,用于分隔字符 293 | def find_waves(threshold, histogram): 294 | up_point = -1 # 上升点 295 | is_peak = False 296 | if histogram[0] > threshold: 297 | up_point = 0 298 | is_peak = True 299 | wave_peaks = [] 300 | for i, x in enumerate(histogram): 301 | if is_peak and x < threshold: 302 | if i - up_point > 2: 303 | is_peak = False 304 | wave_peaks.append((up_point, i)) 305 | elif not is_peak and x >= threshold: 306 | is_peak = True 307 | up_point = i 308 | if is_peak and up_point != -1 and i - up_point > 4: 309 | wave_peaks.append((up_point, i)) 310 | return wave_peaks 311 | 312 | #分离车牌字符 313 | def seperate_card(img, waves): 314 | part_cards = [] 315 | for wave in waves: 316 | part_cards.append(img[:, wave[0]:wave[1]]) 317 | 318 | return part_cards 319 | 320 | 321 | def img_mser_color(card_imgs): 322 | colors = [] 323 | for card_index, card_img in enumerate(card_imgs): 324 | green = yello = blue = black = white = 0 325 | card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV) 326 | if card_img_hsv is None: 327 | continue 328 | row_num, col_num = card_img_hsv.shape[:2] 329 | card_img_count = row_num * col_num 330 | for i in range(row_num): 331 | for j in range(col_num): 332 | H = card_img_hsv.item(i, j, 0) 333 | S = card_img_hsv.item(i, j, 1) 334 | V = card_img_hsv.item(i, j, 2) 335 | if 11 < H <= 34 and S > 34: 336 | yello += 1 337 | elif 35 < H <= 99 and S > 34: 338 | green += 1 339 | elif 99 < H <= 124 and S > 34: 340 | blue += 1 341 | if 0 < H < 180 and 0 < S < 255 and 0 < V < 46: 342 | black += 1 343 | elif 0 < H < 180 and 0 < S < 43 and 221 < V < 225: 344 | white += 1 345 | color = "no" 346 | if yello * 2 >= card_img_count: 347 | color = "yello" 348 | 349 | elif green * 2 >= card_img_count: 350 | color = "green" 351 | 352 | elif blue * 2 >= card_img_count: 353 | color = "blue" 354 | 355 | elif black + white >= card_img_count * 0.7: 356 | color = "bw" 357 | colors.append(color) 358 | card_imgs[card_index] = card_img 359 | return colors, card_imgs 360 | -------------------------------------------------------------------------------- /PlateRecogntion/lib/recognition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import cv2 5 | import numpy as np 6 | from numpy.linalg import norm 7 | 8 | SZ = 20 # 训练图片长宽 9 | MAX_WIDTH = 1000 # 原始图片最大宽度 10 | Min_Area = 2000 # 车牌区域允许最大面积 11 | PROVINCE_START = 1000 12 | 13 | # 来自opencv的sample,用于svm训练 14 | def preprocess_hog(digits): 15 | samples = [] 16 | for img in digits: 17 | gx = cv2.Sobel(img, cv2.CV_32F, 1, 0) 18 | gy = cv2.Sobel(img, cv2.CV_32F, 0, 1) 19 | mag, ang = cv2.cartToPolar(gx, gy) 20 | bin_n = 16 21 | bin = np.int32(bin_n * ang / (2 * np.pi)) 22 | bin_cells = bin[:10, :10], bin[10:, :10], bin[:10, 10:], bin[10:, 10:] 23 | mag_cells = mag[:10, :10], mag[10:, :10], mag[:10, 10:], mag[10:, 10:] 24 | hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)] 25 | hist = np.hstack(hists) 26 | 27 | # transform to Hellinger kernel 28 | eps = 1e-7 29 | hist /= hist.sum() + eps 30 | hist = np.sqrt(hist) 31 | hist /= norm(hist) + eps 32 | 33 | samples.append(hist) 34 | return np.float32(samples) 35 | 36 | 37 | provinces = [ 38 | "zh_cuan", "川", 39 | "zh_e", "鄂", 40 | "zh_gan", "赣", 41 | "zh_gan1", "甘", 42 | "zh_gui", "贵", 43 | "zh_gui1", "桂", 44 | "zh_hei", "黑", 45 | "zh_hu", "沪", 46 | "zh_ji", "冀", 47 | "zh_jin", "津", 48 | "zh_jing", "京", 49 | "zh_jl", "吉", 50 | "zh_liao", "辽", 51 | "zh_lu", "鲁", 52 | "zh_meng", "蒙", 53 | "zh_min", "闽", 54 | "zh_ning", "宁", 55 | "zh_qing", "青", 56 | "zh_qiong", "琼", 57 | "zh_shan", "陕", 58 | "zh_su", "苏", 59 | "zh_sx", "晋", 60 | "zh_wan", "皖", 61 | "zh_xiang", "湘", 62 | "zh_xin", "新", 63 | "zh_yu", "豫", 64 | "zh_yu1", "渝", 65 | "zh_yue", "粤", 66 | "zh_yun", "云", 67 | "zh_zang", "藏", 68 | "zh_zhe", "浙" 69 | ] 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /PlateRecogntion/lib/sql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import tkinter 5 | import pymysql 6 | 7 | def sql(TIME, COLOR1, TEXT1, SOURCE): 8 | """连接数据库,将数据插入数据表""" 9 | try: 10 | # 打开数据库连接 11 | db = pymysql.connect(host="localhost", 12 | port=3306, 13 | user="root", 14 | passwd="123", 15 | database="chepai", 16 | charset="utf8") 17 | except: 18 | print("数据库连接失败") 19 | return 20 | # 使用cursor()方法获取操作游标 21 | cursor = db.cursor() 22 | 23 | # SQL 插入语句 24 | sql = "INSERT INTO ALLVEHICLE(TIME, \ 25 | COLOR1, TEXT1, SOURCE) \ 26 | VALUES ('%s', '%s', '%s', '%s')" % \ 27 | (TIME, COLOR1, TEXT1, SOURCE) 28 | 29 | try: 30 | # 执行sql语句 31 | cursor.execute(sql) 32 | # 提交到数据库执行 33 | db.commit() 34 | print("数据库写入成功") 35 | except: 36 | # 如果发生错误则回滚 37 | db.rollback() 38 | print("数据库写入失败") 39 | 40 | sql0 = "SELECT * FROM BLACKLIST WHERE TEXT1 like ('%s')" \ 41 | % (TEXT1) 42 | try: 43 | # 执行sql语句 44 | len = cursor.execute(sql0) 45 | # 提交到数据库执行 46 | db.commit() 47 | if len == 0: 48 | tkinter.messagebox.showwarning(title='提示', message='快速放行!') 49 | else: 50 | tkinter.messagebox.showwarning(title='提示', message='拦截此车辆!') 51 | except: 52 | # 如果发生错误则回滚 53 | db.rollback() 54 | print("数据库查询失败") 55 | 56 | # 关闭数据库连接 57 | db.close() 58 | 59 | def sql2(TIME, COLOR1, TEXT1, SOURCE): 60 | """连接数据库,将数据插入数据表""" 61 | try: 62 | # 打开数据库连接 63 | db = pymysql.connect(host="localhost", 64 | port=3306, 65 | user="root", 66 | passwd="123", 67 | database="chepai", 68 | charset="utf8") 69 | except: 70 | print("数据库连接失败") 71 | return 72 | # 使用cursor()方法获取操作游标 73 | cursor = db.cursor() 74 | 75 | # SQL 插入语句 76 | sql = "INSERT INTO BLACKLIST(TIME, \ 77 | COLOR1, TEXT1, SOURCE) \ 78 | VALUES ('%s', '%s', '%s', '%s')" % \ 79 | (TIME, COLOR1, TEXT1, SOURCE) 80 | 81 | try: 82 | # 执行sql语句 83 | cursor.execute(sql) 84 | # 提交到数据库执行 85 | db.commit() 86 | print("数据库写入成功") 87 | except: 88 | # 如果发生错误则回滚 89 | db.rollback() 90 | print("数据库写入失败") 91 | 92 | # 关闭数据库连接 93 | db.close() 94 | 95 | 96 | def create_sql(): 97 | # 创建表CARINFO,车牌信息的表 98 | try: 99 | # 打开数据库连接 100 | db = pymysql.connect(host="localhost", port=3306, user="root", passwd="123", database="chepai", charset="utf8") 101 | except: 102 | print("数据库连接失败") 103 | return 104 | # 使用 cursor() 方法创建一个游标对象 cursor 105 | cursor = db.cursor() 106 | 107 | # 使用预处理语句创建表 108 | sql = """CREATE TABLE ALLVEHICLE ( 109 | TIME VARCHAR(100), 110 | COLOR1 VARCHAR(100), 111 | TEXT1 VARCHAR(100), 112 | SOURCE VARCHAR(500))""" 113 | 114 | try: 115 | # 执行sql语句 116 | cursor.execute(sql) 117 | # 提交到数据库执行 118 | db.commit() 119 | print("数据库创建成功") 120 | except: 121 | # 如果发生错误则回滚 122 | db.rollback() 123 | print("数据库已存在") 124 | 125 | # 使用预处理语句创建表 126 | sql2 = """CREATE TABLE BLACKLIST ( 127 | TIME VARCHAR(100), 128 | COLOR1 VARCHAR(100), 129 | TEXT1 VARCHAR(100), 130 | SOURCE VARCHAR(500))""" 131 | 132 | try: 133 | # 执行sql语句 134 | cursor.execute(sql2) 135 | # 提交到数据库执行 136 | db.commit() 137 | print("数据库创建成功") 138 | except: 139 | # 如果发生错误则回滚 140 | db.rollback() 141 | print("数据库已存在") 142 | 143 | 144 | # 关闭数据库连接 145 | db.close() 146 | -------------------------------------------------------------------------------- /PlateRecogntion/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import threading 5 | import time 6 | import tkinter as tk 7 | import cv2 8 | import lib.function as predict 9 | import lib.math as img_math 10 | import lib.sql as img_sql 11 | from lib.api import api_pic 12 | from threading import Thread 13 | from tkinter import ttk 14 | from tkinter.filedialog import * 15 | from PIL import Image, ImageTk, ImageGrab 16 | import tkinter.messagebox 17 | 18 | from hyperlpr import * 19 | 20 | def cv_show(name, img): 21 | cv2.imshow(name, img) 22 | cv2.waitKey(0) 23 | cv2.destroyAllWindows() 24 | 25 | class HyperLPR_PlateRecogntion(object): 26 | pass 27 | 28 | 29 | class ThreadWithReturnValue(Thread): 30 | def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None): 31 | Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon) 32 | self._return1 = None 33 | self._return2 = None 34 | self._return3 = None 35 | 36 | def run(self): 37 | if self._target is not None: 38 | try: 39 | self._return1, self._return2, self._return3 = self._target(*self._args, **self._kwargs) 40 | except: 41 | pass 42 | 43 | def join(self): 44 | Thread.join(self) 45 | return self._return1, self._return2, self._return3 46 | 47 | 48 | class Surface(ttk.Frame): 49 | pic_path = "" 50 | viewhigh = 600 51 | viewwide = 600 52 | update_time = 0 53 | thread = None 54 | thread_run = False 55 | camera = None 56 | pic_source = "" 57 | color_transform = {"green": ("绿牌", "#55FF55"), "yello": ("黄牌", "#FFFF00"), "blue": ("蓝牌", "#6666FF")} 58 | # 用于后续的将返回的"blue"的key对应成value打印出来 59 | 60 | def __init__(self, win): 61 | """初始化函数,用于初始化方法、属性、excel表、数据库等""" 62 | ttk.Frame.__init__(self, win) 63 | frame_left = ttk.Frame(self) # 创建四个容器frame_left、frame_right1、frame_right2、top 64 | frame_right1 = ttk.Frame(self) 65 | frame_right2 = ttk.Frame(self) 66 | win.title("车牌识别监测报警系统") 67 | win.minsize(850, 700) 68 | self.center_window() 69 | self.pic_path3 = "" 70 | self.cameraflag = 0 71 | 72 | self.pack(fill=tk.BOTH, expand=tk.YES, padx="10", pady="10") # 放置对象 73 | frame_left.pack(side=LEFT, expand=1) 74 | frame_right1.pack(side=TOP, expand=1, fill=tk.Y) 75 | frame_right2.pack(side=RIGHT, expand=0) 76 | 77 | self.image_ctl = ttk.Label(frame_left) # 创建一个标签image_ctl贴在容器frame_left上 78 | self.image_ctl.pack(anchor="nw") # 锚定位(anchor ),加 padx, pady , 可将组件安排在指定位置 79 | 80 | # 右上角的容器部署 81 | ttk.Label(frame_right1, text='定位车牌位置:').grid(column=0, row=0, sticky=tk.W) 82 | self.roi_ct2 = ttk.Label(frame_right1) 83 | self.roi_ct2.grid(column=0, row=1, sticky=tk.W) 84 | 85 | ttk.Label(frame_right1, text='定位识别结果:').grid(column=0, row=2, sticky=tk.W) 86 | self.r_ct2 = ttk.Label(frame_right1, text="", font=('Times', '20')) 87 | self.r_ct2.grid(column=0, row=3, sticky=tk.W) 88 | self.color_ct2 = ttk.Label(frame_right1, text="", width="20") 89 | self.color_ct2.grid(column=0, row=4, sticky=tk.W) 90 | 91 | ttk.Label(frame_right1, text='-------------------------------').grid(column=0, row=5, sticky=tk.W) 92 | 93 | from_pic_ctl = ttk.Button(frame_right2, text="选择图片", width=20, command=self.from_pic) 94 | from_pic_ctl2 = ttk.Button(frame_right2, text="路径批量识别", width=20, command=self.from_pic2) 95 | from_vedio_ctl = ttk.Button(frame_right2, text="打开/关闭摄像头", width=20, command=self.from_vedio) 96 | from_video_ctl = ttk.Button(frame_right2, text="拍照并识别", width=20, command=self.video_pic) 97 | clean_ctrl = ttk.Button(frame_right2, text="清除识别数据", width=20, command=self.clean) 98 | camera_ctrl = ttk.Button(frame_right2, text="开关摄像头实时识别", width=20, command=self.camera_flag) 99 | 100 | # 放置按钮 101 | camera_ctrl.pack(anchor="se", pady="5") # 开关摄像头实时识别 102 | from_vedio_ctl.pack(anchor="se", pady="5") # 打开/关闭摄像头 103 | from_video_ctl.pack(anchor="se", pady="5") # 拍照并识别 104 | from_pic_ctl2.pack(anchor="se", pady="5") # 路径批量识别 105 | from_pic_ctl.pack(anchor="se", pady="5") # 来自图片 106 | clean_ctrl.pack(anchor="se", pady="5") # 清除识别数据 107 | # 右上角的容器部署结束 108 | 109 | self.clean() 110 | self.apistr = None 111 | img_sql.create_sql() # 调用create_sql()方法,创建数据库表 112 | 113 | self.predictor = predict.CardPredictor() # 调用lib.function下的类CardPredictor()创建对象predictor 114 | self.predictor.train_svm() # 调用lib.function下的train_svm()方法 115 | 116 | def reset(self): 117 | """调用clean()方法,清除信息,重置属性""" 118 | win.geometry("850x700") 119 | self.clean() 120 | self.thread_run7 = False 121 | self.count = 0 122 | self.center_window() 123 | 124 | def center_window(self): 125 | """窗口中心化""" 126 | screenwidth = win.winfo_screenwidth() 127 | screenheight = win.winfo_screenheight() 128 | win.update() 129 | width = win.winfo_width() 130 | height = win.winfo_height() 131 | size = '+%d+%d' % ((screenwidth - width)/2, (screenheight - height)/2) 132 | win.geometry(size) 133 | 134 | def get_imgtk(self, img_bgr): 135 | """返回一个与tkinter兼容的照片图像imgtk""" 136 | img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) 137 | im = Image.fromarray(img) 138 | # Image.fromarray()从导出数组接口的对象(使用缓冲区协议)创建图像内存。 139 | w, h = im.size 140 | pil_image_resized = self.resize2(w, h, im) # 调用resize2()方法进行图像大小格式化 141 | imgtk = ImageTk.PhotoImage(image=pil_image_resized) 142 | # 返回一个与tkinter兼容的照片图像imgtk。这可以在任何Tkinter需要图像对象的地方使用 143 | return imgtk 144 | 145 | def resize(self, w, h, pil_image): 146 | """将经过处理的图像的大小格式化""" 147 | w_box = 200 148 | h_box = 50 149 | f1 = 1.0*w_box/w 150 | f2 = 1.0*h_box/h 151 | factor = min([f1, f2]) 152 | width = int(w*factor) 153 | height = int(h*factor) 154 | return pil_image.resize((width, height), Image.ANTIALIAS) 155 | 156 | def resize2(self, w, h, pil_image): 157 | """将经过处理的图像的大小格式化""" 158 | width = win.winfo_width() 159 | height = win.winfo_height() 160 | w_box = width - 250 161 | h_box = height - 100 162 | f1 = 1.0*w_box/w 163 | f2 = 1.0*h_box/h 164 | factor = min([f1, f2]) 165 | width = int(w*factor) 166 | height = int(h*factor) 167 | return pil_image.resize((width, height), Image.ANTIALIAS) 168 | 169 | def show_roi2(self, r, roi, color): 170 | """将定位到的车牌图像展示在右边中间--颜色定位车牌位置--颜色定位识别结果处,其他注释参照show_roi1""" 171 | if r: 172 | try: 173 | roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) 174 | roi = Image.fromarray(roi) 175 | w, h = roi.size 176 | pil_image_resized = self.resize(w, h, roi) 177 | self.tkImage2 = ImageTk.PhotoImage(image=pil_image_resized) 178 | self.roi_ct2.configure(image=self.tkImage2, state='enable') 179 | except: 180 | pass 181 | self.r_ct2.configure(text=str(r)) 182 | self.update_time = time.time() 183 | try: 184 | c = self.color_transform[color] 185 | self.color_ct2.configure(text=c[0], state='enable') 186 | except: 187 | self.color_ct2.configure(state='disabled') 188 | elif self.update_time + 8 < time.time(): 189 | 190 | self.roi_ct2.configure(state='disabled') 191 | self.r_ct2.configure(text="") 192 | self.color_ct2.configure(state='disabled') 193 | 194 | def camera_flag(self): 195 | """打开摄像头,对拍摄到的每一帧进行实时识别""" 196 | if not self.thread_run: 197 | tkinter.messagebox.showinfo('提示', '请点击[打开摄像头]按钮!') 198 | return 199 | if not self.cameraflag: 200 | self.cameraflag = 1 201 | self.thread2 = threading.Thread(target=self.video_pic2) 202 | # 启动线程thread2,通过run()方法,调用video_pic2 203 | self.thread2.setDaemon(True) 204 | self.thread2.start() 205 | self.thread_run2 = True 206 | else: 207 | self.cameraflag = 0 208 | self.thread_run2 = False 209 | print("关闭摄像头实时识别 self.cameraflag", self.cameraflag) 210 | 211 | def from_vedio(self): 212 | """判断摄像头的状态,并打开摄像头,新建线程thread并调用方法vedio_thread()""" 213 | # camera参数初始为None 214 | if self.thread_run: 215 | if self.camera.isOpened(): 216 | self.camera.release() 217 | print("关闭摄像头") 218 | self.camera = None 219 | self.thread_run = False 220 | return 221 | if self.camera is None: 222 | self.camera = cv2.VideoCapture(1) 223 | if not self.camera.isOpened(): 224 | self.camera = None 225 | print("没有外置摄像头") 226 | self.camera = cv2.VideoCapture(0) 227 | if not self.camera.isOpened(): 228 | print("没有内置摄像头") 229 | tkinter.messagebox.showinfo('警告', '摄像头打开失败!') 230 | self.camera = None 231 | return 232 | else: 233 | print("打开内置摄像头") 234 | else: 235 | print("打开外置摄像头") 236 | self.pic_source = "摄像头" 237 | self.cameraflag = 0 238 | self.thread = threading.Thread(target=self.vedio_thread) 239 | self.thread.setDaemon(True) 240 | self.thread.start() 241 | self.thread_run = True 242 | 243 | def pic(self, pic_path): 244 | """对图像进行处理,处理完成后在主窗口左边显示要识别的图像, 245 | 在右边展示--形状定位车牌位置--形状定位识别结果--颜色定位车牌位置--颜色定位识别结果--的信息""" 246 | self.apistr = None # self.apistr字段表示的是,调用api进行车牌识别返回的车牌完整信息的字符串 247 | img_bgr = img_math.img_read(pic_path) # 以uint8方式读取pic_path返回给img_bgr,即返回以uint8方式读取的彩色照片 248 | first_img, oldimg = self.predictor.img_first_pre(img_bgr) 249 | # 返回Otsu’s二值化边缘化的新图像和原图,需要两个变量接收 250 | # 其中first_img接收Otsu’s二值化边缘化的新图像 251 | if not self.cameraflag: 252 | # 这个if的作用相当于vedio_thread(),即传入的图像不是由相机拍摄的图像,将图像显示在主窗口左边 253 | # self.cameraflag置为0时,即传入的图像不是由相机拍摄的图像,将图像显示在主窗口左边 254 | self.imgtk = self.get_imgtk(img_bgr) 255 | self.image_ctl.configure(image=self.imgtk) # 将图像显示在主窗口左边 256 | th2 = ThreadWithReturnValue(target=self.predictor.color_and_contour, args=(oldimg, oldimg, first_img)) 257 | # 创建线程th2,通过run()方法回调color_and_contour()方法,并将args作为参数传入color_and_contour()方法中 258 | th2.start() 259 | # 开启线程th2,将color_and_contour()方法返回的结果存储到变量r_color, roi_color, color_color中 260 | r_color, roi_color, color_color = th2.join() 261 | 262 | try: 263 | Plate = HyperLPR_PlateRecogntion(img_bgr) 264 | # HyperLPR_PlateRecogntion是未完成的通过HyperLPR库进行车牌识别的更高版本 265 | # print(Plate[0][0]) 266 | r_c = Plate[0][0] 267 | r_color = Plate[0][0] 268 | except: 269 | pass 270 | self.show_roi2(r_color, roi_color, color_color) 271 | localtime = time.asctime(time.localtime(time.time())) 272 | if not self.cameraflag: 273 | if not (r_color or color_color): 274 | self.api_ctl2(pic_path) 275 | return 276 | value = [localtime, color_color, r_color, self.pic_source] 277 | # img_excel.excel_add(value) 278 | img_sql.sql(value[0], value[1], value[2], value[3]) 279 | print(localtime, "|", color_color, r_color, "| ", self.pic_source) 280 | 281 | def pic2(self, pic_path): 282 | """对图像进行处理,处理完成后在主窗口左边显示要识别的图像, 283 | 在右边展示--形状定位车牌位置--形状定位识别结果--颜色定位车牌位置--颜色定位识别结果--的信息""" 284 | self.apistr = None # self.apistr字段表示的是,调用api进行车牌识别返回的车牌完整信息的字符串 285 | img_bgr = img_math.img_read(pic_path) # 以uint8方式读取pic_path返回给img_bgr,即返回以uint8方式读取的彩色照片 286 | first_img, oldimg = self.predictor.img_first_pre(img_bgr) 287 | # 返回Otsu’s二值化边缘化的新图像和原图,需要两个变量接收 288 | # 其中first_img接收Otsu’s二值化边缘化的新图像 289 | if not self.cameraflag: 290 | # 这个if的作用相当于vedio_thread(),即传入的图像不是由相机拍摄的图像,将图像显示在主窗口左边 291 | # self.cameraflag置为0时,即传入的图像不是由相机拍摄的图像,将图像显示在主窗口左边 292 | self.imgtk = self.get_imgtk(img_bgr) 293 | self.image_ctl.configure(image=self.imgtk) # 将图像显示在主窗口左边 294 | th2 = ThreadWithReturnValue(target=self.predictor.color_and_contour, args=(oldimg, oldimg, first_img)) 295 | # 创建线程th2,通过run()方法回调color_and_contour()方法,并将args作为参数传入color_and_contour()方法中 296 | # color_and_contour()方法是通过"颜色定位车牌位置,颜色定位识别结果"的方法 297 | # th1.start() 298 | th2.start() 299 | r_color, roi_color, color_color = th2.join() 300 | 301 | try: 302 | Plate = HyperLPR_PlateRecogntion(img_bgr) 303 | # HyperLPR_PlateRecogntion是未完成的通过HyperLPR库进行车牌识别的更高版本 304 | # print(Plate[0][0]) 305 | r_c = Plate[0][0] 306 | r_color = Plate[0][0] 307 | except: 308 | pass 309 | self.show_roi2(r_color, roi_color, color_color) 310 | localtime = time.asctime(time.localtime(time.time())) 311 | if not self.cameraflag: 312 | if not (r_color or color_color): 313 | self.api_ctl2(pic_path) 314 | return 315 | value = [localtime, color_color, r_color, self.pic_source] 316 | # img_excel.excel_add(value) 317 | img_sql.sql2(value[0], value[1], value[2], value[3]) 318 | print(localtime, "|", color_color, r_color, "|", self.pic_source) 319 | 320 | def from_pic(self): 321 | """手动选择要识别的本地图像,之后调用pic()方法""" 322 | self.thread_run = False 323 | self.thread_run2 = False 324 | self.cameraflag = 0 325 | self.pic_path = askopenfilename(title="选择识别图片", filetypes=[("jpeg图片", "*.jpeg"), ("jpg图片", "*.jpg"), ("png图片", "*.png")]) 326 | self.clean() 327 | self.pic_source = "本地文件:" + self.pic_path 328 | self.pic(self.pic_path) # 将要识别的图像的路径传递给pic()方法 329 | 330 | def from_pic2(self): 331 | """进行路径批量识别,新建线程,调用pic_search方法""" 332 | self.pic_path3 = askdirectory(title="选择识别路径") 333 | self.get_img_list(self.pic_path3) # 将路径传给get_img_list()方法,调用get_img_list()方法 334 | self.thread7 = threading.Thread(target=self.pic_search, args=(self,)) 335 | # 通过run()方法回调pic_search() 336 | self.thread7.setDaemon(True) 337 | self.thread7.start() # 启动线程thread7 338 | self.thread_run7 = True 339 | 340 | def get_img_list(self, images_path): 341 | """目的是为了生成一个存储图像路径的列表array_of_img,其中array_of_img是全局变量""" 342 | self.count = 0 343 | self.array_of_img = [] 344 | for filename in os.listdir(images_path): 345 | # 遍历路径下的图像文件 346 | # print(filename) 347 | try: 348 | self.pilImage3 = Image.open(images_path + "/" + filename) 349 | self.array_of_img.append(images_path + "/" + filename) 350 | # 向列表中新增图像的完整路径 351 | self.count = self.count + 1 352 | except: 353 | pass 354 | print(self.array_of_img) 355 | 356 | def pic_search(self, self2): 357 | """遍历传入路径的所有图像,每一个图像都调用pic()方法进行处理""" 358 | self.thread_run7 = True 359 | print("开始批量识别") 360 | wait_time = time.time() 361 | while self.thread_run7: 362 | # 知道计数器count变为0,置参数thread_run7为False,关闭线程thread7 363 | while self.count: 364 | self.pic_path7 = self.array_of_img[self.count-1] 365 | 366 | if time.time()-wait_time > 2: 367 | # print(self.pic_path7) 368 | print("正在批量识别", self.count) 369 | self.clean() 370 | self.pic_source = "本地文件:" + self.pic_path7 371 | try: 372 | self.pic2(self.pic_path7) 373 | # 调用pic()方法对图像进行处理,处理完成后在主窗口左边显示要识别的图像, 374 | # 在右边展示–形状定位车牌位置–形状定位识别结果–颜色定位车牌位置–颜色定位识别结果–的信息 375 | except: 376 | pass 377 | self.count = self.count - 1 378 | wait_time = time.time() 379 | if self.count == 0: 380 | self.thread_run7 = False 381 | print("批量识别结束") 382 | return 383 | 384 | def vedio_thread(self): 385 | """将摄像头实时拍摄的视频显示在主窗口""" 386 | self.thread_run = True 387 | while self.thread_run: 388 | # 将摄像头实时拍摄的照片显示在主界面窗口 389 | _, self.img_bgr = self.camera.read() 390 | self.imgtk = self.get_imgtk(self.img_bgr) 391 | # 调用get_imgtk方法,传入img_bgr参数 392 | self.image_ctl.configure(image=self.imgtk) 393 | # 将摄像头拍摄的视频贴在标签image_ctl上 394 | print("run end") # 当关闭程序时,thread_run置为False,执行下方print()语句 395 | 396 | def video_pic2(self): 397 | """把实时拍摄到的图像保存到本地,并调用pic()方法对传入的图像进行识别""" 398 | self.thread_run2 = True 399 | predict_time = time.time() 400 | while self.thread_run2: 401 | if self.cameraflag: 402 | if time.time() - predict_time > 2: 403 | print("实时识别中self.cameraflag", self.cameraflag) 404 | cv2.imwrite("tmp/timetest.jpg", self.img_bgr) 405 | self.pic_path = "tmp/timetest.jpg" 406 | self.pic(self.pic_path) # 调用pic()方法对传入的图像进行识别 407 | predict_time = time.time() 408 | print("run end") 409 | 410 | def video_pic(self): 411 | """拍摄照片,将照片保存到tmp/test.jpg,并调用pic()方法对图片进行处理""" 412 | if not self.thread_run: 413 | tkinter.messagebox.showinfo('提示', '请点击[打开摄像头]按钮!') 414 | return 415 | self.thread_run = False 416 | self.thread_run2 = False 417 | _, img_bgr = self.camera.read() 418 | cv2.imwrite("tmp/test.jpg", img_bgr) 419 | self.pic_path = "tmp/test.jpg" 420 | self.clean() 421 | self.pic(self.pic_path) 422 | print("video_pic") 423 | 424 | 425 | def api_ctl2(self, pic_path66): 426 | """自己定义的算法完全没识别出车牌信息,则调用api_ctl2进行车牌识别""" 427 | if self.thread_run: 428 | return 429 | self.thread_run = False 430 | self.thread_run2 = False 431 | colorstr, textstr = api_pic(pic_path66) # 调用api进行车牌识别,返回颜色字段和车牌号字段,分别赋值给colorstr, textstr 432 | self.apistr = colorstr + textstr # self.apistr字段的值包括车牌颜色和车牌号信息,即完整的车牌信息 433 | self.show_roi2(textstr, None, colorstr) 434 | localtime = time.asctime(time.localtime(time.time())) 435 | value = [localtime, textstr, colorstr, self.pic_source] 436 | print(localtime, textstr, colorstr, self.pic_source) 437 | img_sql.sql(value[0], value[1], value[2], value[3]) 438 | 439 | def clean(self): 440 | """执行完一次操作后,清除信息,将所有参数、图片复原,实现重置窗口的效果""" 441 | if self.thread_run: 442 | self.cameraflag=0 443 | return 444 | self.thread_run = False 445 | self.thread_run2 = False 446 | # self.p1.set("") 447 | img_bgr3 = img_math.img_read("pic/hy.png") 448 | self.imgtk2 = self.get_imgtk(img_bgr3) 449 | self.image_ctl.configure(image=self.imgtk2) 450 | 451 | self.r_ct2.configure(text="") 452 | self.color_ct2.configure(text="", state='enable') 453 | 454 | self.pilImage3 = Image.open("pic/locate.png") 455 | w, h = self.pilImage3.size 456 | pil_image_resized = self.resize(w, h, self.pilImage3) 457 | self.tkImage3 = ImageTk.PhotoImage(image=pil_image_resized) 458 | self.roi_ct2.configure(image=self.tkImage3, state='enable') 459 | 460 | def close_window(): 461 | print("destroy") 462 | if surface.thread_run: 463 | surface.thread_run = False 464 | surface.thread.join(2.0) 465 | win.destroy() 466 | 467 | 468 | if __name__ == '__main__': 469 | win = tk.Tk() 470 | 471 | surface = Surface(win) 472 | # close,退出输出destroy 473 | win.protocol('WM_DELETE_WINDOW', close_window) 474 | # 进入消息循环 475 | win.mainloop() 476 | -------------------------------------------------------------------------------- /PlateRecogntion/pic/hy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrHHYang/Python-OpenCV-License-Plate-Recognition/272737793d810f1805f815da09bb70f31d8046d7/PlateRecogntion/pic/hy.png -------------------------------------------------------------------------------- /PlateRecogntion/pic/locate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrHHYang/Python-OpenCV-License-Plate-Recognition/272737793d810f1805f815da09bb70f31d8046d7/PlateRecogntion/pic/locate.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-OpenCV-License-Plate-Recognition 2 | python+opencv实现的车牌识别系统 3 | 效果图展示见博客https://blog.csdn.net/qq_38132105/article/details/107050676 4 | --------------------------------------------------------------------------------