主要参考博客:https://blog.csdn.net/GK_2014/article/details/84779166
主体算法没有修改,这里添加了通过H(色调)和S(饱和度)来对车牌颜色进行判断,然后使用tkinter搭建了简单的GUI,可以实现打开摄像头拍摄照片然后再对照片进行识别。
界面如下:
carPlateIdentity.py代码如下,添加了一些注释:
1 import cv2
2 import os
3 import sys
4 import numpy as np
5 import tensorflow as tf
6
7 config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True))
8 sess = tf.Session(config=config)
9
10 char_table = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
11 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '川', '鄂', '赣', '甘', '贵',
12 '桂', '黑', '沪', '冀', '津', '京', '吉', '辽', '鲁', '蒙', '闽', '宁', '青', '琼', '陕', '苏', '晋',
13 '皖', '湘', '新', '豫', '渝', '粤', '云', '藏', '浙']
14
15 def hist_image(img):
16 assert img.ndim==2
17 hist = [0 for i in range(256)]
18 img_h,img_w = img.shape[0],img.shape[1]
19
20 for row in range(img_h):
21 for col in range(img_w):
22 hist[img[row,col]] += 1
23 p = [hist[n]/(img_w*img_h) for n in range(256)]
24 p1 = np.cumsum(p)
25 for row in range(img_h):
26 for col in range(img_w):
27 v = img[row,col]
28 img[row,col] = p1[v]*255
29 return img
30
31 def find_board_area(img):
32 assert img.ndim==2
33 img_h,img_w = img.shape[0],img.shape[1]
34 top,bottom,left,right = 0,img_h,0,img_w
35 flag = False
36 h_proj = [0 for i in range(img_h)]
37 v_proj = [0 for i in range(img_w)]
38
39 for row in range(round(img_h*0.5),round(img_h*0.8),3):
40 for col in range(img_w):
41 if img[row,col]==255:
42 h_proj[row] += 1
43 if flag==False and h_proj[row]>12:
44 flag = True
45 top = row
46 if flag==True and row>top+8 and h_proj[row]<12:
47 bottom = row
48 flag = False
49
50 for col in range(round(img_w*0.3),img_w,1):
51 for row in range(top,bottom,1):
52 if img[row,col]==255:
53 v_proj[col] += 1
54 if flag==False and (v_proj[col]>10 or v_proj[col]-v_proj[col-1]>5):
55 left = col
56 break
57 return left,top,120,bottom-top-10
58
59 def verify_scale(rotate_rect):
60 error = 0.4
61 aspect = 4#4.7272
62 min_area = 10*(10*aspect)#min_area=10*(10*4)=400
63 max_area = 150*(150*aspect)#max_area=150*(150*4)=90000
64 min_aspect = aspect*(1-error)#min_aspect=4*(1-0.4)=2.4
65 max_aspect = aspect*(1+error)#max_aspect=4*(1+0.4)=6.4
66 theta = 30
67
68 # 宽或高为0,不满足矩形直接返回False
69 if rotate_rect[1][0]==0 or rotate_rect[1][1]==0:
70 return False
71 '''
72 rotate_rect[0]为外接矩形的中心坐标(x,y);[1][0]为宽,[1][1]为高,[2]为旋转角度.
73 旋转角度θ是水平轴(x轴)逆时针旋转,直到碰到矩形的第一条边停住,此时该边与水平轴的夹角。并且这个边的边长是width,另一条边边长是height
74 在opencv中,坐标系原点在左上角,相对于x轴,逆时针旋转角度为负,顺时针旋转角度为正。所以,θ∈(-90度,0]
75 '''
76
77 r = rotate_rect[1][0]/rotate_rect[1][1]#r=宽除以高
78 r = max(r,1/r)
79 area = rotate_rect[1][0]*rotate_rect[1][1]#area为实际面积
80 if area>min_area and area<max_area and r>min_aspect and r<max_aspect:#如果实际面积大于最小面积且小于最大面积,并且2.4<r<6.4
81 # 矩形的倾斜角度不超过theta
82 if ((rotate_rect[1][0] < rotate_rect[1][1] and rotate_rect[2] >= -90 and rotate_rect[2] < -(90 - theta)) or#旋转角度在[-90,-60)
83 (rotate_rect[1][1] < rotate_rect[1][0] and rotate_rect[2] > -theta and rotate_rect[2] <= 0)):#旋转角度在(-30,0]
84 return True
85 return False
86
87 def img_Transform(car_rect,image):#传入填充掩膜后的最小矩形,原图
88 img_h,img_w = image.shape[:2]
89 rect_w,rect_h = car_rect[1][0],car_rect[1][1]
90 angle = car_rect[2]
91
92 return_flag = False
93 if car_rect[2]==0:#旋转角度为0
94 return_flag = True
95 if car_rect[2]==-90 and rect_w<rect_h:#旋转角度=-90并且矩形的宽<高
96 rect_w, rect_h = rect_h, rect_w
97 return_flag = True
98 if return_flag:
99 car_img = image[int(car_rect[0][1]-rect_h/2):int(car_rect[0][1]+rect_h/2),
100 int(car_rect[0][0]-rect_w/2):int(car_rect[0][0]+rect_w/2)]
101 return car_img
102
103 car_rect = (car_rect[0],(rect_w,rect_h),angle)
104 box = cv2.boxPoints(car_rect)#获取矩形的四个顶点坐标
105
106 heigth_point = right_point = [0,0]
107 left_point = low_point = [car_rect[0][0], car_rect[0][1]]#矩形中心点坐标(x,y)
108 for point in box:
109 if left_point[0] > point[0]:
110 left_point = point
111 if low_point[1] > point[1]:
112 low_point = point
113 if heigth_point[1] < point[1]:
114 heigth_point = point
115 if right_point[0] < point[0]:
116 right_point = point
117
118 if left_point[1] <= right_point[1]: # 正角度
119 new_right_point = [right_point[0], heigth_point[1]]
120 pts1 = np.float32([left_point, heigth_point, right_point])
121 pts2 = np.float32([left_point, heigth_point, new_right_point]) # 字符只是高度需要改变
122 M = cv2.getAffineTransform(pts1, pts2)
123 print('Mat1',M)
124 print('pts1_1',pts1)
125 print('pts1_2',pts2)
126 '''
127 仿射变换,其实是将图形在2D平面内做变换,变换前后图片中原来平行的线仍会保持平行,可以想象是将长方形变换为平行四边形
128 M=cv2.getAffineTransform(pos1,pos2),其中两个位置就是变换前后的对应位置关系。输出的就是仿射矩阵M,shape为[2,3]
129 cv.getAffineTransform将创建一个2x3矩阵,该矩阵将传递给cv.warpAffine。
130 '''
131 dst = cv2.warpAffine(image, M, (round(img_w*2), round(img_h*2)))
132 '''
133 cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst
134 dsize为输出图像的大小;
135 flags表示插值方式,默认为 flags=cv2.INTER_LINEAR,表示线性插值,此外还有:cv2.INTER_NEAREST(最近邻插值)、cv2.INTER_AREA(区域插值)、cv2.INTER_CUBIC(三次样条插值)、cv2.INTER_LANCZOS4(Lanczos插值)
136 borderMode - 边界像素模式
137 borderValue - 边界填充值; 默认情况下,它为0
138 round() 方法返回浮点数x的四舍五入值。round(x,n) 返回浮点数x的四舍五入的小数点后的n位数值
139 '''
140 car_img = dst[int(left_point[1]):int(heigth_point[1]), int(left_point[0]):int(new_right_point[0])]
141
142 elif left_point[1] > right_point[1]: # 负角度
143 new_left_point = [left_point[0], heigth_point[1]]
144 pts1 = np.float32([left_point, heigth_point, right_point])
145 pts2 = np.float32([new_left_point, heigth_point, right_point]) # 字符只是高度需要改变
146 print('pts2_1',pts1)
147 print('pts2_2',pts2)
148 M = cv2.getAffineTransform(pts1, pts2)
149 print('Mat2',M)
150 dst = cv2.warpAffine(image, M, (round(img_w*2), round(img_h*2)))
151 car_img = dst[int(right_point[1]):int(heigth_point[1]), int(new_left_point[0]):int(right_point[0])]
152
153 return car_img
154
155 def pre_process(orig_img):
156
157 gray_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2GRAY) #将原图转换为灰度图
158 cv2.imwrite('./carIdentityData/opencv_output/gray_img.jpg', gray_img)
159 #cv2.imshow('gray_img', gray_img)
160
161 blur_img = cv2.blur(gray_img, (3, 3)) #均值滤波
162 cv2.imwrite('./carIdentityData/opencv_output/blur.jpg', blur_img)
163 #cv2.imshow('blur', blur_img)
164
165 sobel_img = cv2.Sobel(blur_img, cv2.CV_16S, 1, 0, ksize=3) #沿x轴求导,找边缘
166 sobel_img = cv2.convertScaleAbs(sobel_img) #转换图片格式
167 cv2.imwrite('./carIdentityData/opencv_output/sobel.jpg', sobel_img)
168 #cv2.imshow('sobel', sobel_img)
169
170 hsv_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2HSV) #将原图的颜色域转到HSV域,色调、饱和度、亮度
171 cv2.imwrite('./carIdentityData/opencv_output/hsv_pic.jpg', hsv_img)
172 #cv2.imshow('hsv_pic',hsv_img)
173
174 h, s, v = hsv_img[:, :, 0], hsv_img[:, :, 1], hsv_img[:, :, 2] #h,s,v分别取矩阵的第一列、第二列、第三列的所有元素
175 # 黄色色调区间[26,34],蓝色色调区间:[100,124],饱和度和亮度均需要高于70
176 blue_img = (((h > 11) & (h < 34)) | ((h > 35) & (h < 99)) | ((h > 100) & (h < 124))) & (s > 70) & (v > 70)
177 blue_img = blue_img.astype('float32') #将blue_img格式转换为浮点型32位
178 cv2.imwrite('./carIdentityData/opencv_output/blue&yellow.jpg', blue_img)
179 #cv2.imshow('blue&yellow',blue_img)
180
181 mix_img = np.multiply(sobel_img, blue_img) #两个数组或矩阵相乘,对应位置直接相乘
182 cv2.imwrite('./carIdentityData/opencv_output/mix.jpg', mix_img)
183 #cv2.imshow('mix', mix_img)
184
185 mix_img = mix_img.astype(np.uint8)
186
187 ret, binary_img = cv2.threshold(mix_img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
188 #ret, binary_img = cv2.threshold(mix_img, 50, 255, cv2.THRESH_BINARY)
189 '''
190 使用最大类间方差法将图像二值化,cv2.THRESH_OTSU自适应找出最合适的阈值
191 cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
192 src:表示的是图片源
193 thresh:表示的是阈值(起始值)
194 maxval:表示的是最大值
195 type:表示的是这里划分的时候使用的是什么类型的算法,常用值为0(cv2.THRESH_BINARY),超过阈值的设置为最大值255,其他设置为0
196 返回值:
197 ret :cv2.THRESH_OTSU 求解出的阈值
198 binary_img :二值图像
199 '''
200 print('ret',ret)
201 cv2.imwrite('./carIdentityData/opencv_output/binary.jpg', binary_img)
202 #cv2.imshow('binary',binary_img)
203
204 kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(21,5)) #获得结构元素
205 close_img = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel) #闭操作,先膨胀再腐蚀,使图像轮廓更光滑(need more)
206 cv2.imwrite('./carIdentityData/opencv_output/close.jpg', close_img)
207 #cv2.imshow('close', close_img)
208
209 return close_img
210
211 # 给候选车牌区域做漫水填充算法,一方面补全上一步求轮廓可能存在轮廓歪曲的问题,
212 # 另一方面也可以将非车牌区排除掉
213 def verify_color(rotate_rect,src_image):
214 img_h,img_w = src_image.shape[:2]#shape[0],shape[1]
215 mask = np.zeros(shape=[img_h+2,img_w+2],dtype=np.uint8)
216 #cv2.imshow('flood_mask',mask)
217 connectivity = 4#0100
218 #种子点上下左右4邻域与种子颜色值在[loDiff,upDiff]的被涂成new_value,也可设置8邻域,
219 #如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
220 loDiff,upDiff = 30,30#负差最大值,正差最大值.loDiff表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。
221 new_value = 255
222 flags = connectivity#0100
223 print('flags1',flags)
224 flags |= cv2.FLOODFILL_FIXED_RANGE #按位或,FLOODFILL_FIXED_RANGE=2**16=65536.考虑当前像素与种子象素之间的差,不设置的话则和邻域像素比较,运算结果为01 0000 0000 0000 0100,十进制为65540
225 print('flags2',flags)
226 print('cv2.FLOODFILL_FIXED_RANGE',cv2.FLOODFILL_FIXED_RANGE)
227 '''
228 flags = flags | cv2.FLOODFILL_FIXED_RANGE
229 cv.FLOODFILL_FIXED_RANGE: 指定颜色填充,二进制为 01 0000 0000 0000 0000.填充时的判断标准是:src(seed.x’, seed.y’) - loDiff <= src(x, y) <= src(seed.x’, seed.y’) +upDiff,此范围内被填充指定的颜色
230 cv.FLOODFILL_MASK_ONLY: 指定位置填充,二进制为 10 0000 0000 0000 0000
231 '''
232 flags |= new_value << 8
233 #<<左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0. 255左移8位是1111111100000000,运算结果为01 1111 1111 0000 0100,十进制为130820
234 print('flags3',flags)
235 flags |= cv2.FLOODFILL_MASK_ONLY
236 #FLOODFILL_MASK_ONLY=2**17=131072.设置这个标识符则不会去填充改变原始图像,而是去填充掩模图像(mask),运算结果为11 1111 1111 0000 0100,十进制为261892
237 print('flags4',flags)
238 print('FLOODFILL_MASK_ONLY',cv2.FLOODFILL_MASK_ONLY)
239 '''
240 相当于flags = 4 | cv2.FLOODFILL_FIXED_RANGE | 255 << 8 | cv2.FLOODFILL_MASK_ONLY
241 通俗来讲,就是用4邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为255
242 标识符的0-7位为connectivity,8-15位为new_value左移8位的值,16-23位为cv2.FLOODFILL_FIXED_RANGE\cv2.FLOODFILL_MASK_ONLY或者0
243 1.低8位用于控制算法的连通性,可取4(填充算法只考虑当前享受水平方向和垂直方向)/8(还考虑对角线方向)
244 2.高8位可为0/FLOODFILL_FIXED_RANGE(考虑当前像素与种子像素之间的差)/FLOODFILL_MASK_ONLY(不填充改变原始图像,去填充掩模图像)
245 3.中间8位制定填充掩码图像的值
246 最终得到的flags为11 1111 1111 0000 0100,十进制为261892
247 '''
248 rand_seed_num = 5000 #生成多个随机种子
249 valid_seed_num = 200 #从rand_seed_num中随机挑选valid_seed_num个有效种子
250 adjust_param = 0.1
251 box_points = cv2.boxPoints(rotate_rect)
252 '''
253 cv2.boxPoints根据minAreaRect的返回值rotate_rect计算矩形的四个点
254 旋转的边界矩形,这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为cv2.minAreaRect()。返回的是一个Box2D结构,
255 其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是要绘制这个矩形需要矩形的4个角点,可以通过函数cv2.boxPoints()获得。
256 返回形式[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]
257 '''
258 box_points_x = [n[0] for n in box_points]#每一个坐标点的x值
259 print('box_points_x1',box_points_x)
260 box_points_x.sort(reverse=False)#list.sort( key=None, reverse=False),reverse -- 排序规则,reverse = True 降序, reverse = False 升序(默认)
261 print('box_points_x2',box_points_x)
262 adjust_x = int((box_points_x[2]-box_points_x[1])*adjust_param)#=(第三个x-第二个x*0.1),对角点
263 print('adjust_x',adjust_x)
264 col_range = [box_points_x[1]+adjust_x,box_points_x[2]-adjust_x]
265 print('col_range',col_range)
266 box_points_y = [n[1] for n in box_points]#每一个坐标点的y值
267 print('box_points_y1',box_points_y)
268 box_points_y.sort(reverse=False)
269 print('box_points_y2',box_points_y)
270 adjust_y = int((box_points_y[2]-box_points_y[1])*adjust_param)
271 print('adjust_y',adjust_y)
272 row_range = [box_points_y[1]+adjust_y, box_points_y[2]-adjust_y]
273 print('row_range',row_range)
274 # 如果以上方法种子点在水平或垂直方向可移动的范围很小,则采用旋转矩阵对角线来设置随机种子点
275 if (col_range[1]-col_range[0])/(box_points_x[3]-box_points_x[0])<0.4\
276 or (row_range[1]-row_range[0])/(box_points_y[3]-box_points_y[0])<0.4:#小于0.4时重新定义
277 points_row = []
278 points_col = []
279 for i in range(2):
280 pt1,pt2 = box_points[i],box_points[i+2]#第一个和第三个坐标点,第二个和第四个坐标点
281 x_adjust,y_adjust = int(adjust_param*(abs(pt1[0]-pt2[0]))),int(adjust_param*(abs(pt1[1]-pt2[1])))
282 if (pt1[0] <= pt2[0]):
283 pt1[0], pt2[0] = pt1[0] + x_adjust, pt2[0] - x_adjust
284 else:
285 pt1[0], pt2[0] = pt1[0] - x_adjust, pt2[0] + x_adjust
286 if (pt1[1] <= pt2[1]):
287 pt1[1], pt2[1] = pt1[1] + adjust_y, pt2[1] - adjust_y
288 else:
289 pt1[1], pt2[1] = pt1[1] - y_adjust, pt2[1] + y_adjust
290 temp_list_x = [int(x) for x in np.linspace(pt1[0],pt2[0],int(rand_seed_num /2))]
291 temp_list_y = [int(y) for y in np.linspace(pt1[1],pt2[1],int(rand_seed_num /2))]
292 points_col.extend(temp_list_x)
293 points_row.extend(temp_list_y)
294 print('in for')
295 else:
296 points_row = np.random.randint(row_range[0],row_range[1],size=rand_seed_num)
297 '''
298 numpy.random.randint(low, high=None, size=None, dtype='l')返回一个随机整型数,范围从低(包括)到高(不包括),即[low, high)。如果没有写参数high的值,则返回[0,low)的值。
299 定义rand_seed_num = 5000。size为输出随机数的尺寸,这里输出5000个随机数
300 '''
301 points_col = np.linspace(col_range[0],col_range[1],num=rand_seed_num).astype(np.int)
302 '''
303 np.linspace主要用来创建等差数列。np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None),在start和stop之间返回均匀间隔的数据
304 endpoint:True则包含stop;False则不包含stop; retstep如果为True则结果会给出数据间隔
305 在[col_range[0],col_range[1]]之间输出包含5000个数据的等差数列,并将其修改格式为整型
306 '''
307 print('in else')
308
309 points_row = np.array(points_row)
310 points_col = np.array(points_col)
311 hsv_img = cv2.cvtColor(src_image, cv2.COLOR_BGR2HSV)
312 h,s,v = hsv_img[:,:,0],hsv_img[:,:,1],hsv_img[:,:,2]
313 # 将随机生成的多个种子依次做漫水填充,理想情况是整个车牌被填充
314 flood_img = src_image.copy()
315 seed_cnt = 0
316 for i in range(rand_seed_num):
317 rand_index = np.random.choice(rand_seed_num,1,replace=False)#从[0,5000)之间随机抽取一个数,且不能重复
318 row,col = points_row[rand_index],points_col[rand_index]
319 # 限制随机种子必须是车牌背景色,黄色色调区间[26,34],蓝色色调区间:[100,124]
320 if (((h[row,col]>11)&(h[row,col]<34))|((h[row,col]>35)&(h[row,col]<100))|((h[row,col]>100)&(h[row,col]<124)))&(s[row,col]>70)&(v[row,col]>70):
321 cv2.floodFill(src_image, mask, (col,row), (255, 255, 255), (loDiff,) * 3, (upDiff,) * 3, flags)
322 '''
323 floodFill(image, mask, seedPoint, newVal, loDiff=None, upDiff=None, flags=None)
324 floodFill( 1.操作的图像, 2.掩模, 3.起始像素值,4.填充的颜色, 5.填充颜色的低值, 6.填充颜色的高值 ,7.填充的方法) (255, 255, 255)是白色
325 mask = np.zeros(shape=[img_h+2,img_w+2],dtype=np.uint8)
326 loDiff,upDiff = 30,30;(loDiff,) * 3=(loDiff,loDiff,loDiff)
327
328 '''
329 cv2.circle(flood_img,center=(col,row),radius=2,color=(0,0,255),thickness=2)
330 '''
331 cv2.circle(img, center, radius, color[, thickness[, lineType[, shift]]]),根据给定的圆心和半径等画圆
332 center:圆心位置;radius:圆的半径;color:圆的颜色;thickness:圆形轮廓的粗细(如果为正),负厚度表示要绘制实心圆;lineType:圆边界的类型;shift:中心坐标和半径值中的小数位数。
333 '''
334 seed_cnt += 1
335 if seed_cnt >= valid_seed_num:
336 break
337 #======================调试用======================#
338 show_seed = np.random.uniform(1,100,1).astype(np.uint16)
339 '''
340 numpy.random.uniform(low,high,size),从一个均匀分布[low,high)中随机采样,注意定义域是左闭右开,即包含low,不包含high.
341 low: 采样下界,float类型,默认值为0;
342 high: 采样上界,float类型,默认值为1;
343 size: 输出样本数目,为int或元组(tuple)类型
344 返回值:ndarray类型,其形状和参数size中描述一致
345 '''
346 #cv2.imshow('floodfill'+str(show_seed),flood_img)
347 #cv2.imshow('flood_mask'+str(show_seed),mask)
348
349 cv2.imwrite('./carIdentityData/opencv_output/floodfill.jpg', flood_img)
350 cv2.imwrite('./carIdentityData/opencv_output/flood_mask.jpg', mask)
351 #======================调试用======================#
352 # 获取掩模上被填充点的像素点,并求点集的最小外接矩形
353 mask_points = []
354 for row in range(1,img_h+1):
355 for col in range(1,img_w+1):
356 if mask[row,col] != 0:
357 mask_points.append((col-1,row-1))#把不是黑色的像素点添加进mask_points,把mask被填充成白色的点集添加进去
358 mask_rotateRect = cv2.minAreaRect(np.array(mask_points))#获取点集的最小矩形
359 if verify_scale(mask_rotateRect):
360 return True,mask_rotateRect
361 else:
362 return False,mask_rotateRect
363
364 # 车牌定位
365 def locate_carPlate(orig_img,pred_image):
366 car_plate_w, car_plate_h = 136, 36 #dengjie.tkadd
367 carPlate_list = []
368 temp1_orig_img = orig_img.copy() #调试用
369 temp2_orig_img = orig_img.copy() #调试用
370 #cloneImg,contours,heriachy = cv2.findContours(pred_image,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
371 contours, heriachy = cv2.findContours(pred_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
372 #print(contours) #dengjie
373 # RETR_EXTERNAL找最外层轮廓,CHAIN_APPROX_SIMPLE仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留。heriachy这里没有用到
374 for i,contour in enumerate(contours): #enumerate同时获得列表或者字符串的索引和值,i是索引,contour是值
375 cv2.drawContours(temp1_orig_img, contours, i, (0, 255, 0), 2)#用绿色线宽为2的线条画出原图的所有轮廓
376 # 获取轮廓最小外接矩形,返回值rotate_rect。rotate_rect是点集数组或向量(里面存放的是点的坐标),并且这个点集中的元素不定个数(中心(x,y), (宽,高), 旋转角度)
377 rotate_rect = cv2.minAreaRect(contour)
378 # 根据矩形面积大小和长宽比判断是否是车牌
379 if verify_scale(rotate_rect):#return True
380 ret,rotate_rect2 = verify_color(rotate_rect,temp2_orig_img)#返回True和mask上被填充点的像素点集的最小矩形
381 if ret == False:
382 continue
383 # 车牌位置矫正
384 car_plate = img_Transform(rotate_rect2, temp2_orig_img)#做仿射变换
385 car_plate = cv2.resize(car_plate,(car_plate_w,car_plate_h)) #调整尺寸为后面CNN车牌识别做准备
386 #========================调试看效果========================#
387 box = cv2.boxPoints(rotate_rect2)#获取矩形顶点坐标
388 for k in range(4):
389 n1,n2 = k%4,(k+1)%4
390 cv2.line(temp1_orig_img,(box[n1][0],box[n1][1]),(box[n2][0],box[n2][1]),(0,0,255),2)
391 '''
392 cv2.line(img, pt1, pt2, color[, thickness[, lineType[, shift]]]) → img
393 img:原图
394 pt1:直线起点坐标,(box[n1][0],box[n1][1]);(box[0][0],box[0][1])
395 pt2,直线终点坐标,(box[n2][0],box[n2][1]);(box[1][0],box[1][1])
396 color,当前绘画的颜色;如在BGR模式下,传递(255,0,0)表示蓝色画笔。
397 hickness,画笔的粗细,线宽。若是-1表示画封闭图像,如填充的圆。默认值是1
398 lineType,线条的类型
399 '''
400 #cv2.imshow('opencv_' + str(i), car_plate)
401 cv2.imwrite('./carIdentityData/opencv_output/opencv_%d.jpg'%(i), car_plate)
402 #========================调试看效果========================#
403 carPlate_list.append(car_plate)
404 #print('carPlate_list',carPlate_list)
405
406 cv2.imwrite('./carIdentityData/opencv_output/contour.jpg', temp1_orig_img)
407 #cv2.imshow('contour', temp1_orig_img)
408 return carPlate_list
409
410 # 左右切割
411 def horizontal_cut_chars(plate):#传入车牌二值图像中的字符部分
412 char_addr_list = []
413 area_left,area_right,char_left,char_right= 0,0,0,0
414 img_h,img_w = plate.shape[:2]
415
416 # 获取车牌每列边缘像素点个数
417 def getColSum(img,col):
418 sum = 0
419 for i in range(img.shape[0]):
420 sum += round(img[i,col]/255)#二值图像,img[i,col]=0或255,获取每一列像素值为255的像素个数
421 return sum;
422
423 sum = 0
424 for col in range(img_w):
425 sum += getColSum(plate,col)#所有列白色像素点的个数总和
426
427 col_limit = 0
428 #col_limit = round(0.3*sum/img_w) # 每列边缘像素点必须超过均值的30%才能判断属于字符区域
429 #print('col_limit',sum,img_w,col_limit)#1344.0,136,6.0
430 # 每个字符宽度也进行限制
431 charWid_limit = [round(img_w/12),round(img_w/5)]#[11,27]
432 is_char_flag = False
433
434 for i in range(img_w):
435 colValue = getColSum(plate,i)#每一列像素值为255的像素个数,ex:i=7时,colValue=3;i=8时,colValue=9;i=9时,colValue=19;i=18时,colValue=16;i=19时,colValue=0
436 #print('colValue'+str(i),colValue)
437 if colValue > col_limit:
438 if is_char_flag == False:
439 area_right = round((i+char_right)/2)#ex:i=8,area_right=4
440 area_width = area_right-area_left#area_width=4
441 char_width = char_right-char_left#char_width=0
442 if (area_width>charWid_limit[0]) and (area_width<charWid_limit[1]):
443 char_addr_list.append((area_left,area_right,char_width))
444 char_left = i#i=8
445 area_left = round((char_left+char_right) / 2)#area_left=4
446 is_char_flag = True
447 else:
448 if is_char_flag == True:
449 char_right = i-1#19-1=18
450 is_char_flag = False
451 #print('is_char_flag'+str(i),area_left,area_right,char_right,char_left)
452 #print('char_addr_list1',char_addr_list)
453 # 手动结束最后未完成的字符分割
454 if area_right < char_left:
455 area_right,char_right = img_w,img_w#以img_w为右边界
456 #area_right = round((img_w+char_right)/2)
457 area_width = area_right - area_left
458 char_width = char_right - char_left
459 if (area_width > charWid_limit[0]) and (area_width < charWid_limit[1]):
460 char_addr_list.append((area_left, area_right, char_width))#每一个字符区域的左右边界及字符的宽度
461 print('char_addr_list2',char_addr_list)#ex.char_addr_list=[(4, 20, 10), (20, 45, 14), (45, 62, 7), (62, 83, 13), (83, 96, 6), (96, 114, 14), (114, 132, 14)]
462 return char_addr_list
463
464 def get_chars(car_plate):#传入车牌二值化图像
465 char_w, char_h = 20, 20#dengjie.tkadd
466 img_h,img_w = car_plate.shape[:2]
467 h_proj_list = [] # 水平投影长度列表
468 h_temp_len,v_temp_len = 0,0
469 h_startIndex,h_end_index = 0,0 # 水平投影记索引
470 h_proj_limit = [0.2,0.8] # 车牌在水平方向的轮廓长度少于20%或多余80%过滤掉
471 char_imgs = []
472
473 def getColSum(img,col):
474 sum = 0
475 for i in range(img.shape[0]):
476 sum += round(img[i,col]/255)#二值图像,img[i,col]=0或255,获取每一列像素值为255的像素个数
477 return sum;
478
479 sum = 0
480 for col in range(img_w):
481 sum += getColSum(car_plate,col)#所有列白色像素点的个数总和
482
483 # 将二值化的车牌水平投影到Y轴,计算投影后的连续长度,连续投影长度可能不止一段
484 h_count = [0 for i in range(img_h)]
485 for row in range(img_h):
486 temp_cnt = 0
487 for col in range(img_w):
488 if car_plate[row,col] == 255:
489 temp_cnt += 1
490 h_count[row] = temp_cnt#统计每一行像素值为255的像素个数
491 if temp_cnt/img_w<h_proj_limit[0] or temp_cnt/img_w>h_proj_limit[1]:#每一行像素值为255的像素个数/车牌宽度<0.2 或者 每一行像素值为255的像素个数/车牌宽度>0.8
492 if h_temp_len != 0:
493 h_end_index = row-1
494 h_proj_list.append((h_startIndex,h_end_index))
495 print('h_proj_list1',h_proj_list)
496 h_temp_len = 0
497 continue
498 if temp_cnt > 0:
499 if h_temp_len == 0:
500 h_startIndex = row#从0.2<(像素值为255的像素个数/img_w)<0.8 的行开始
501 h_temp_len = 1
502 else:
503 h_temp_len += 1
504 else:
505 if h_temp_len > 0:
506 h_end_index = row-1
507 h_proj_list.append((h_startIndex,h_end_index))
508 print('h_proj_list2',h_proj_list)
509 h_temp_len = 0
510 print('h_temp_len',h_temp_len)
511 # 手动结束最后得水平投影长度累加
512 if h_temp_len != 0:
513 h_end_index = img_h-1
514 h_proj_list.append((h_startIndex, h_end_index))#h_temp_len不等于0时再添加一对值
515 print('h_proj_list',h_proj_list)#ex:[(1, 1), (7, 29), (34, 35)]或者[(2, 2), (7, 28), (34, 34)]
516 # 选出最长的投影,该投影长度占整个截取车牌高度的比值必须大于0.5
517 h_maxIndex,h_maxHeight = 0,0
518 for i,(start,end) in enumerate(h_proj_list):
519 if h_maxHeight < (end-start):
520 h_maxHeight = (end-start)
521 h_maxIndex = i
522 if h_maxHeight/img_h < 0.5:
523 return char_imgs
524 chars_top,chars_bottom = h_proj_list[h_maxIndex][0],h_proj_list[h_maxIndex][1]#chars_top=h_proj_list[1][0],chars_bottom=h_proj_list[1][1]
525
526 if sum > img_h *img_w * 0.5:
527 ret,car_plate = cv2.threshold(car_plate,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
528 #cv2.imshow('THRESH_BINARY_INV',car_plate)
529
530 plates = car_plate[chars_top:chars_bottom+1,:]#获取车牌二值图像中的字符高度部分,plates比car_plate要窄,然后在进行字符分割
531 cv2.imwrite('./carIdentityData/opencv_output/car_plate.jpg',car_plate)
532 cv2.imwrite('./carIdentityData/opencv_output/plates.jpg', plates)
533 char_addr_list = horizontal_cut_chars(plates)#ex.char_addr_list=[(4, 20, 10), (20, 45, 14), (45, 62, 7), (62, 83, 13), (83, 96, 6), (96, 114, 14), (114, 132, 14)]
534
535 for i,addr in enumerate(char_addr_list):
536 char_img = car_plate[chars_top:chars_bottom+1,addr[0]:addr[1]]#输出单个字符
537 char_img = cv2.resize(char_img,(char_w,char_h))#resize字符
538 char_imgs.append(char_img)
539 #cv2.imshow('22',char_img) #dengjie2
540 cv2.imwrite('./carIdentityData/opencv_output/char_%d.jpg'%(i),char_img)
541 return char_imgs
542
543 def extract_char(car_plate):#传入正确的车牌
544 gray_plate = cv2.cvtColor(car_plate,cv2.COLOR_BGR2GRAY)#转换成灰度图
545 ret,binary_plate = cv2.threshold(gray_plate,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)#使用最大类间方差法将图像二值化,自适应找出最合适的阈值
546 #cv2.imshow('extract_char_binary_plate',binary_plate)
547 cv2.imwrite('./carIdentityData/opencv_output/extract_char_binary_plate.jpg',binary_plate)
548 char_img_list = get_chars(binary_plate)
549 #cv2.imshow('1',binary_plate) #dengjie
550 return char_img_list
551
552 def cnn_select_carPlate(plate_list,model_path):
553 if len(plate_list) == 0:
554 return False,plate_list
555 g1 = tf.Graph()
556 sess1 = tf.Session(graph=g1)
557 '''
558 Tensorflow中的图(tf.Graph)和会话(tf.Session)
559 tf.Graph()表示实例化一个用于tensorflow计算和表示用的数据流图,不负责运行计算
560 1、使用g = tf.Graph()函数创建新的计算图
561 2、在with g.as_default():语句下定义属于计算图g的张量和操作
562 3、在with tf.Session()中通过参数graph=xxx指定当前会话所运行的计算图
563 4、如果没有显示指定张量和操作所属的计算图,则这些张量和操作属于默认计算图
564 5、一个图可以在多个sess中运行,一个sess也能运行多个图
565 '''
566 with sess1.as_default():
567 with sess1.graph.as_default():#使用此图作为默认图的上下文管理器
568 model_dir = os.path.dirname(model_path)# 获取文件的完整目录,得到当前文件的绝对路径
569 saver = tf.train.import_meta_graph(model_path)#用来加载训练模型meta文件中的图,以及图上定义的结点参数包括权重偏置项等需要训练的参数,也包括训练过程生成的中间参数
570 saver.restore(sess1, tf.train.latest_checkpoint(model_dir))#自动找到最近保存的变量文件并载入
571 graph = tf.get_default_graph()#获取当前默认的计算图
572 net1_x_place = graph.get_tensor_by_name('x_place:0')#按tensor名称获取tensor信息,Tensor("x_place:0", shape=(?, 36, 136, 3), dtype=float32)
573 #Once you know the name you can fetch the Tensor using <name>:0 (0 refers to endpoint which is somewhat redundant)
574 #一旦知道名称,就可以使用<name>:0来获取Tensor(0表示冗余的端点)
575 print('net1_x_place',net1_x_place)
576 net1_keep_place = graph.get_tensor_by_name('keep_place:0')#Tensor("keep_place:0", dtype=float32)
577 print('net1_keep_place',net1_keep_place)
578 net1_out = graph.get_tensor_by_name('out_put:0')#Tensor("out_put:0", shape=(?, 2), dtype=float32),获取cnn_construct()的输出
579 print('net1_out',net1_out)
580
581 input_x = np.array(plate_list)
582 net_outs = tf.nn.softmax(net1_out)
583 preds = tf.argmax(net_outs,1) #预测结果,按行取最大值对应的索引
584 probs = tf.reduce_max(net_outs,reduction_indices=[1]) #结果概率值,按行取概率的最大值
585 pred_list,prob_list = sess1.run([preds,probs],feed_dict={net1_x_place:input_x,net1_keep_place:1.0})
586 print('pred_list',pred_list)
587 print('prob_list',prob_list)
588 # 选出概率最大的车牌
589 result_index,result_prob = -1,0.
590 for i,pred in enumerate(pred_list):
591 if pred==1 and prob_list[i]>result_prob:
592 result_index,result_prob = i,prob_list[i]#0,概率
593 print('in pred')
594 print(result_index,result_prob)
595 if result_index == -1:
596 return False,plate_list[0]#返回第一张车牌
597 else:
598 green = yellow = blue = 0
599 img_hsv = cv2.cvtColor(plate_list[result_index], cv2.COLOR_BGR2HSV)
600 row_num, col_num= img_hsv.shape[:2]
601 # ~ 总共的像素个数
602 card_img_count = row_num * col_num
603
604 for i in range(row_num):
605 for j in range(col_num):
606 H = img_hsv.item(i, j, 0)
607 S = img_hsv.item(i, j, 1)
608 V = img_hsv.item(i, j, 2)
609 # ~ 根据HSV空间的值确定颜色
610 if 11 < H <= 34 and S > 34:
611 yellow += 1
612 elif 35 < H <= 99 and S > 34:
613 green += 1
614 elif 99 < H <= 124 and S > 34:
615 blue += 1
616 color = "no"
617
618 # ~ 若某种颜色的像素个数占一半以上,则判别为该颜色
619 if yellow*2 >= card_img_count:
620 color = "yellow"
621
622 elif green*2 >= card_img_count:
623 color = "green"
624
625 elif blue*2 >= card_img_count:
626 color = "blue"
627
628 return True,plate_list[result_index],color#返回正确的索引对应的车牌
629
630 def cnn_recongnize_char(img_list,model_path):
631 g2 = tf.Graph()
632 sess2 = tf.Session(graph=g2)
633 text_list = []
634
635 if len(img_list) == 0:
636 return text_list
637 with sess2.as_default():
638 with sess2.graph.as_default():
639 model_dir = os.path.dirname(model_path)
640 saver = tf.train.import_meta_graph(model_path)
641 saver.restore(sess2, tf.train.latest_checkpoint(model_dir))
642 graph = tf.get_default_graph()
643 net2_x_place = graph.get_tensor_by_name('x_place:0')
644 net2_keep_place = graph.get_tensor_by_name('keep_place:0')
645 net2_out = graph.get_tensor_by_name('out_put:0')
646
647 data = np.array(img_list)
648 # 数字、字母、汉字,从67维向量找到概率最大的作为预测结果
649 net_out = tf.nn.softmax(net2_out)
650 preds = tf.argmax(net_out,1)
651 my_preds= sess2.run(preds, feed_dict={net2_x_place: data, net2_keep_place: 1.0})
652 print('my_preds',my_preds)#ex.my_preds=[49 11 13 8 19 5 3]
653
654 for i in my_preds:
655 text_list.append(char_table[i])
656 return text_list
657
658 if __name__ == '__main__':
659 cur_dir = sys.path[0]
660 car_plate_w,car_plate_h = 136,36
661 char_w,char_h = 20,20
662 plate_model_path = os.path.join(cur_dir, './carIdentityData/model/plate_recongnize/model.ckpt-1020.meta')
663 char_model_path = os.path.join(cur_dir,'./carIdentityData/model/char_recongnize/model.ckpt-1030.meta')
664 img = cv2.imread('./plate_pic/24.jpg')
665
666 # 预处理
667 pred_img = pre_process(img)
668
669 # 车牌定位
670 car_plate_list = locate_carPlate(img,pred_img)
671
672 # CNN车牌过滤
673 ret,car_plate,color = cnn_select_carPlate(car_plate_list,plate_model_path)#True,正确的车牌
674 if ret == False:
675 print("未检测到车牌")
676 sys.exit(-1)#sys.exit(-1)告诉程序退出。它基本上只是停止继续执行python代码。-1只是传入的状态码。通常0表示成功执行,其他任何数字(通常为1)表示发生故障。
677 #cv2.imshow('cnn_plate',car_plate)
678 cv2.imwrite('./carIdentityData/opencv_output/cnn_plate.jpg', car_plate)
679
680 # 字符提取
681 char_img_list = extract_char(car_plate)
682
683 # CNN字符识别
684 text = cnn_recongnize_char(char_img_list,char_model_path)
685 print('result:',text)
686 print(color)
687
688 cv2.waitKey(0)
GUI代码如下:
1 import tkinter as tk
2 from tkinter.filedialog import *
3 from tkinter import ttk
4 import carPlateIdentity
5 import cv2
6 from PIL import Image, ImageTk
7 #import threading
8 import time
9 import os
10 # video import create_capture
11 import sys
12 import getopt
13
14
15 class Surface(ttk.Frame):
16 pic_path = ""
17 viewhigh = 600
18 viewwide = 600
19 update_time = 0
20 thread = None
21 thread_run = False
22 camera = None
23 color_transform = {"green":("绿牌","#55FF55"), "yello":("黄牌","#FFFF00"), "blue":("蓝牌","#6666FF")}
24
25 def __init__(self, win):
26 ttk.Frame.__init__(self, win)
27 frame_left = ttk.Frame(self)
28 frame_right1 = ttk.Frame(self)
29 frame_right2 = ttk.Frame(self)
30 style=ttk.Style()
31 #style.configure("BW.Tlable",foreground="blue",background="blue")
32 style.configure("TButton",font=("Times",12),foreground="black",background="green")
33 win.title("车牌识别")
34 #win.state("zoomed")
35 self.pack(fill=tk.BOTH, expand=tk.YES, padx="5", pady="5")
36 #fill=tk.BOTH:水平和竖直方向填充;expand=tk.YES:扩展整个空白区;padx:x方向的外边距;pady:y方向的外边距
37 frame_left.pack(side=LEFT,expand=1,fill=BOTH)
38 #side=LEFT:按扭停靠在窗口的左侧
39 frame_right1.pack(side=TOP,expand=1,fill=tk.Y)
40 frame_right2.pack(side=RIGHT,expand=1)
41 ttk.Label(frame_left, text='Original pic:',font=("Times",12)).pack(anchor="nw") #nw表示位置在上左,n是north,w是west
42 ttk.Label(frame_right1, text='Plate Location:',font=("Times",12)).grid(column=0, row=0, sticky=tk.W)
43 #位置在上面
44 from_vedio_ctl = ttk.Button(frame_right2, text="Open camera", width=20, style="TButton",command=self.from_vedio)
45 from_pic_ctl = ttk.Button(frame_right2, text="Open picture",width=20, style="TButton",command=self.from_pic)
46 from_img_pre = ttk.Button(frame_right2, text="Show pre_img",width=20, style="TButton",command=self.show_img_pre)
47
48 self.image_ctl = ttk.Label(frame_left)
49 self.image_ctl.pack(anchor="nw")
50
51 self.roi_ctl = ttk.Label(frame_right1)#车牌
52 self.roi_ctl.grid(column=0, row=1, sticky=tk.W)
53 ttk.Label(frame_right1, text='Recognition result:',font=("Times",12)).grid(column=0, row=2, sticky=tk.W)
54 self.r_ctl = ttk.Label(frame_right1, text="",font=("Times",12))#字符
55 self.r_ctl.grid(column=0, row=3, sticky=tk.W)
56 self.color_ctl = ttk.Label(frame_right1, text="", font=("Times",12),width="20")
57 self.color_ctl.grid(column=0, row=4, sticky=tk.W)
58 from_pic_ctl.pack(anchor="se", pady="5")
59 from_vedio_ctl.pack(anchor="se", pady="5")
60 from_img_pre.pack(anchor="se", pady="5")
61
62
63 def get_imgtk(self, img_bgr):
64 img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
65 im = Image.fromarray(img)#array转换成image图片
66 imgtk = ImageTk.PhotoImage(image=im)#显示图片
67 wide = imgtk.width()#图片的宽
68 high = imgtk.height()#图片的高
69 #print('wide',wide)
70 #print('high',high)
71 if wide > self.viewwide or high > self.viewhigh:#前面有定义,viewwide和viewhigh都为600
72 wide_factor = self.viewwide / wide#viewwide除以图片的宽
73 high_factor = self.viewhigh / high#viewhigh除以图片的高
74 factor = min(wide_factor, high_factor)#取两者的最小值
75 wide = int(wide * factor)
76 if wide <= 0 : wide = 1#如果wide<=0,则令wide=1
77 high = int(high * factor)
78 if high <= 0 : high = 1
79 im=im.resize((wide, high), Image.ANTIALIAS)#Image.ANTIALIAS:PIL高质量、抗锯齿
80 imgtk = ImageTk.PhotoImage(image=im)
81 return imgtk
82 #此方法得到resize后且高质量的图片
83
84 def show_roi(self, r, roi,color):#传入字符r与车牌图像roi
85 if r :
86 roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)
87 roi = Image.fromarray(roi)
88 self.imgtk_roi = ImageTk.PhotoImage(image=roi)
89 self.roi_ctl.configure(image=self.imgtk_roi, state='enable')
90 self.r_ctl.configure(text=str(r))
91 self.update_time = time.time()
92
93 try:
94 c = self.color_transform[color]
95 self.color_ctl.configure(text=c[0], background=c[1], state='enable')
96 except:
97 self.color_ctl.configure(state='disabled')
98
99 if self.update_time + 8 < time.time():
100 self.roi_ctl.configure(state='disabled')
101 self.r_ctl.configure(text="")
102 self.color_ctl.configure(state='disabled')
103
104 def show_img_pre(self):
105 pre_img1=cv2.imread('./carIdentityData/opencv_output/blur.jpg')
106 pre_img2=cv2.imread('./carIdentityData/opencv_output/sobel.jpg')
107 pre_img3=cv2.imread('./carIdentityData/opencv_output/hsv_pic.jpg')
108 pre_img4=cv2.imread('./carIdentityData/opencv_output/contour.jpg')
109 pre_img5=cv2.imread('./carIdentityData/opencv_output/floodfill.jpg')
110 pre_img6=cv2.imread('./carIdentityData/opencv_output/plates.jpg')
111 pre_img7=cv2.imread('./carIdentityData/opencv_output/cnn_plate.jpg')
112
113 cv2.imshow('blur',pre_img1)
114 cv2.imshow('sobel',pre_img2)
115 cv2.imshow('hsv_pic',pre_img3)
116 cv2.imshow('contour',pre_img4)
117 cv2.imshow('floodfill',pre_img5)
118 cv2.imshow('plates',pre_img6)
119 cv2.imshow('cnn_plate',pre_img7)
120
121 while True:
122 ch = cv2.waitKey(1)
123 if ch == 27:
124 break
125 cv2.destroyAllWindows()
126
127
128 def from_vedio(self):
129 video=[0,"http://admin:admin@192.168.0.13:8081","http://admin:admin@iPhone.local:8081","http://admin:admin@10.119.223.51:8081"]
130 '''
131 默认情况下用户名和密码都是admin,客户端与IP摄像机服务器需处于同一局域网下,wifi
132 参数为0表示打开内置摄像头,参数是视频文件路径则打开视频
133 video="http://admin:admin@192.168.0.13:8081"
136 video = video[0]
137 capture =cv2.VideoCapture(video)
138
139 # 建个窗口并命名
140 cv2.namedWindow("camera",1)
141 num = 0
142
143 # 用于循环显示图片,达到显示视频的效果
144 while True:
145 ret, frame = capture.read()
146
147 # 在frame上显示test字符
148 image1=cv2.putText(frame,'test', (50,100),
149 cv2.FONT_HERSHEY_COMPLEX_SMALL, 2, (255, 0 ,0),
150 thickness = 2, lineType = 2)
151
152 cv2.imshow('camera',frame)
153
154 # 不加waitkey() 则会图片显示后窗口直接关掉
155 key = cv2.waitKey(1)
156
157 if key == 27:
158 #esc键退出
159 print("esc break...")
160 break
161
162 if key == ord(' '):
163 # 保存一张图像
164 num = num+1
165 filename = "frames_%s.jpg" % num
166 print('已保存图片:%s.jpg' % num)
167 cv2.imwrite(filename,frame)
168 cv2.destroyAllWindows()
169
186 def from_pic(self):
187 self.thread_run = False
188 self.pic_path = askopenfilename(title="选择图片", filetypes=[("jpg", "*.jpg")])
189 cur_dir = sys.path[0]
190 plate_model_path = os.path.join(cur_dir, './carIdentityData/model/plate_recongnize/model.ckpt-1020.meta')
191 char_model_path = os.path.join(cur_dir,'./carIdentityData/model/char_recongnize/model.ckpt-1030.meta')
192
193 if self.pic_path:
194 img_bgr = cv2.imread(self.pic_path)
195 self.imgtk = self.get_imgtk(img_bgr)
196 self.image_ctl.configure(image=self.imgtk)
197 #预处理
198 pred_img = carPlateIdentity.pre_process(img_bgr)
199 # 车牌定位
200 car_plate_list = carPlateIdentity.locate_carPlate(img_bgr,pred_img)
201 # CNN车牌过滤
202 ret,car_plate,color = carPlateIdentity.cnn_select_carPlate(car_plate_list,plate_model_path)
203 cv2.imwrite('./carIdentityData/opencv_output/cnn_plate.jpg', car_plate)
204 # 字符提取
205 char_img_list = carPlateIdentity.extract_char(car_plate)
206 # CNN字符识别
207 text = carPlateIdentity.cnn_recongnize_char(char_img_list, char_model_path)
208 print('result:', text)
209 #r, roi = self.predictor.predict(img_bgr)#识别到的字符、定位的车牌图像
210 self.show_roi(text, car_plate,color)
211
226 def close_window():
227 print("destroy")
228 if surface.thread_run :
229 surface.thread_run = False
230 surface.thread.join(2.0)
231 win.destroy()
232
233
234 if __name__ == '__main__':
235 win = tk.Tk()
236 win.geometry('950x500')
237
238 surface = Surface(win)
239 win.protocol('WM_DELETE_WINDOW', close_window)
240 win.mainloop()#进入消息循环
来源:oschina
链接:https://my.oschina.net/u/4330227/blog/4274762