294 lines
9.9 KiB
Python
294 lines
9.9 KiB
Python
'''
|
||
基于形状和色调的检测车牌号并提取车牌号图片
|
||
'''
|
||
import cv2
|
||
import numpy as np
|
||
from numpy.linalg import norm
|
||
import sys
|
||
import os
|
||
import json
|
||
|
||
SZ = 20 # 训练图片长宽
|
||
MAX_WIDTH = 1000 # 原始图片最大宽度
|
||
Min_Area = 2000 # 车牌区域允许最大面积
|
||
PROVINCE_START = 1000
|
||
|
||
|
||
def point_limit(point):
|
||
if point[0] < 0:
|
||
point[0] = 0
|
||
if point[1] < 0:
|
||
point[1] = 0
|
||
|
||
def accurate_place(card_img_hsv, limit1, limit2, color,cfg):
|
||
row_num, col_num = card_img_hsv.shape[:2]
|
||
xl = col_num
|
||
xr = 0
|
||
yh = 0
|
||
yl = row_num
|
||
#col_num_limit = cfg["col_num_limit"]
|
||
row_num_limit = cfg["row_num_limit"]
|
||
col_num_limit = col_num * 0.8 if color != "green" else col_num * 0.5 # 绿色有渐变
|
||
for i in range(row_num):
|
||
count = 0
|
||
for j in range(col_num):
|
||
H = card_img_hsv.item(i, j, 0)
|
||
S = card_img_hsv.item(i, j, 1)
|
||
V = card_img_hsv.item(i, j, 2)
|
||
if limit1 < H <= limit2 and 34 < S and 46 < V:
|
||
count += 1
|
||
if count > col_num_limit:
|
||
if yl > i:
|
||
yl = i
|
||
if yh < i:
|
||
yh = i
|
||
for j in range(col_num):
|
||
count = 0
|
||
for i in range(row_num):
|
||
H = card_img_hsv.item(i, j, 0)
|
||
S = card_img_hsv.item(i, j, 1)
|
||
V = card_img_hsv.item(i, j, 2)
|
||
if limit1 < H <= limit2 and 34 < S and 46 < V:
|
||
count += 1
|
||
if count > row_num - row_num_limit:
|
||
if xl > j:
|
||
xl = j
|
||
if xr < j:
|
||
xr = j
|
||
return xl, xr, yh, yl
|
||
|
||
|
||
|
||
def CaridDetect(car_pic):
|
||
|
||
# 加载图片
|
||
img = cv2.imread(car_pic)
|
||
pic_hight, pic_width = img.shape[:2]
|
||
|
||
if pic_width > MAX_WIDTH:
|
||
resize_rate = MAX_WIDTH / pic_width
|
||
img = cv2.resize(img, (MAX_WIDTH, int(pic_hight*resize_rate)), interpolation=cv2.INTER_AREA)
|
||
# 车牌识别的部分参数保存在js中,便于根据图片分辨率做调整
|
||
f = open('config.js')
|
||
j = json.load(f)
|
||
for c in j["config"]:
|
||
if c["open"]:
|
||
cfg = c.copy()
|
||
break
|
||
else:
|
||
raise RuntimeError('[ ERROR ] 没有设置有效配置参数.')
|
||
|
||
blur = cfg["blur"]
|
||
# 高斯去噪
|
||
if blur > 0:
|
||
img = cv2.GaussianBlur(img, (blur, blur), 0) #图片分辨率调整
|
||
oldimg = img
|
||
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||
#equ = cv2.equalizeHist(img)
|
||
#img = np.hstack((img, equ))
|
||
# 去掉图像中不会是车牌的区域
|
||
kernel = np.ones((20, 20), np.uint8)
|
||
# morphologyEx 形态学变化函数
|
||
img_opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
|
||
img_opening = cv2.addWeighted(img, 1, img_opening, -1, 0);
|
||
|
||
# 找到图像边缘 Canny边缘检测
|
||
ret, img_thresh = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
||
img_edge = cv2.Canny(img_thresh, 100, 200)
|
||
# 使用开运算和闭运算让图像边缘成为一个整体
|
||
kernel = np.ones((cfg["morphologyr"], cfg["morphologyc"]), np.uint8)
|
||
img_edge1 = cv2.morphologyEx(img_edge, cv2.MORPH_CLOSE, kernel)
|
||
img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, kernel)
|
||
|
||
# 查找图像边缘整体形成的矩形区域,可能有很多,车牌就在其中一个矩形区域中
|
||
# cv2.findContours()函数来查找检测物体的轮廓
|
||
try:
|
||
contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||
except ValueError:
|
||
image, contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||
contours = [cnt for cnt in contours if cv2.contourArea(cnt) > Min_Area]
|
||
# print('[ INFO ] len(contours): {}'.format(len(contours)))
|
||
|
||
# 一一排除不是车牌的矩形区域,找到最小外接矩形的长宽比复合车牌条件的边缘检测到的物体
|
||
car_contours = []
|
||
for cnt in contours:
|
||
rect = cv2.minAreaRect(cnt)
|
||
# 生成最小外接矩形,点集 cnt 存放的就是该四边形的4个顶点坐标(点集里面有4个点)
|
||
# 函数 cv2.minAreaRect() 返回一个Box2D结构rect:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度),
|
||
# 但是要绘制这个矩形,我们需要矩形的4个顶点坐标box, 通过函数 cv2.boxPoints() 获得,
|
||
# 返回形式[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]。
|
||
|
||
# 得到的最小外接矩形的4个顶点顺序、中心坐标、宽度、高度、旋转角度(是度数形式,不是弧度数)
|
||
# https://blog.csdn.net/lanyuelvyun/article/details/76614872
|
||
|
||
area_width, area_height = rect[1]
|
||
if area_width < area_height:
|
||
area_width, area_height = area_height, area_width
|
||
wh_ratio = area_width / area_height
|
||
#print(wh_ratio)
|
||
# 要求矩形区域长宽比在2到5.5之间,2到5.5是车牌的长宽比,其余的矩形排除 一般的比例是3.5
|
||
if wh_ratio > 2 and wh_ratio < 5.5:
|
||
car_contours.append(rect)
|
||
box = cv2.boxPoints(rect)
|
||
box = np.int0(box)
|
||
#oldimg = cv2.drawContours(oldimg, [box], 0, (0, 0, 255), 2)
|
||
#cv2.imshow("edge4", oldimg)
|
||
#print(rect)
|
||
|
||
# print("[ INFo ] len(car_contours): {}".format(len(car_contours)))
|
||
# print("[ INFO ] 精确定位.")
|
||
|
||
card_imgs = []
|
||
|
||
# 矩形区域可能是倾斜的矩形,需要矫正,以便使用颜色定位
|
||
# 这个就是为什么我们不选择YOLO,SSD或其他的目标检测算法来检测车牌号的原因!!!(给自己偷懒找个台阶 :) )
|
||
for rect in car_contours:
|
||
if rect[2] > -1 and rect[2] < 1:#创造角度,使得左、高、右、低拿到正确的值
|
||
angle = 1
|
||
else:
|
||
angle = rect[2]
|
||
rect = (rect[0], (rect[1][0]+5, rect[1][1]+5), angle)#扩大rect范围,避免车牌边缘被排除
|
||
|
||
box = cv2.boxPoints(rect)
|
||
# 避免边界超出图像边界
|
||
heigth_point = right_point = [0, 0]
|
||
left_point = low_point = [pic_width, pic_hight]
|
||
for point in box:
|
||
if left_point[0] > point[0]:
|
||
left_point = point
|
||
if low_point[1] > point[1]:
|
||
low_point = point
|
||
if heigth_point[1] < point[1]:
|
||
heigth_point = point
|
||
if right_point[0] < point[0]:
|
||
right_point = point
|
||
|
||
if left_point[1] <= right_point[1]: # 正角度
|
||
new_right_point = [right_point[0], heigth_point[1]]
|
||
pts2 = np.float32([left_point, heigth_point, new_right_point])#字符只是高度需要改变
|
||
pts1 = np.float32([left_point, heigth_point, right_point])
|
||
M = cv2.getAffineTransform(pts1, pts2) # 仿射变换
|
||
dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight))
|
||
point_limit(new_right_point)
|
||
point_limit(heigth_point)
|
||
point_limit(left_point)
|
||
card_img = dst[int(left_point[1]):int(heigth_point[1]), int(left_point[0]):int(new_right_point[0])]
|
||
card_imgs.append(card_img)
|
||
#cv2.imshow("card", card_img)
|
||
#cv2.waitKey(0)
|
||
elif left_point[1] > right_point[1]: # 负角度
|
||
|
||
new_left_point = [left_point[0], heigth_point[1]]
|
||
pts2 = np.float32([new_left_point, heigth_point, right_point])#字符只是高度需要改变
|
||
pts1 = np.float32([left_point, heigth_point, right_point])
|
||
M = cv2.getAffineTransform(pts1, pts2)
|
||
dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight))
|
||
point_limit(right_point)
|
||
point_limit(heigth_point)
|
||
point_limit(new_left_point)
|
||
card_img = dst[int(right_point[1]):int(heigth_point[1]), int(new_left_point[0]):int(right_point[0])]
|
||
card_imgs.append(card_img)
|
||
#cv2.imshow("card", card_img)
|
||
#cv2.waitKey(0)
|
||
|
||
# 开始使用颜色定位,排除不是车牌的矩形,目前只识别蓝、绿、黄车牌
|
||
|
||
colors = []
|
||
for card_index,card_img in enumerate(card_imgs):
|
||
green = yello = blue = black = white = 0
|
||
card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV)
|
||
#有转换失败的可能,原因来自于上面矫正矩形出错
|
||
if card_img_hsv is None:
|
||
continue
|
||
row_num, col_num= card_img_hsv.shape[:2]
|
||
card_img_count = row_num * col_num
|
||
|
||
for i in range(row_num):
|
||
for j in range(col_num):
|
||
H = card_img_hsv.item(i, j, 0)
|
||
S = card_img_hsv.item(i, j, 1)
|
||
V = card_img_hsv.item(i, j, 2)
|
||
if 11 < H <= 34 and S > 34:#图片分辨率调整
|
||
yello += 1
|
||
elif 35 < H <= 99 and S > 34:#图片分辨率调整
|
||
green += 1
|
||
elif 99 < H <= 124 and S > 34:#图片分辨率调整
|
||
blue += 1
|
||
|
||
if 0 < H <180 and 0 < S < 255 and 0 < V < 46:
|
||
black += 1
|
||
elif 0 < H <180 and 0 < S < 43 and 221 < V < 225:
|
||
white += 1
|
||
color = "no"
|
||
|
||
limit1 = limit2 = 0
|
||
if yello*2 >= card_img_count:
|
||
color = "yello"
|
||
limit1 = 11
|
||
limit2 = 34#有的图片有色偏偏绿
|
||
elif green*2 >= card_img_count:
|
||
color = "green"
|
||
limit1 = 35
|
||
limit2 = 99
|
||
elif blue*2 >= card_img_count:
|
||
color = "blue"
|
||
limit1 = 100
|
||
limit2 = 124#有的图片有色偏偏紫
|
||
elif black + white >= card_img_count*0.7: #TODO
|
||
color = "bw"
|
||
# print("[ INFO ] color: {}".format(color))
|
||
colors.append(color)
|
||
# print(blue, green, yello, black, white, card_img_count)
|
||
#cv2.imshow("color", card_img)
|
||
#cv2.waitKey(0)
|
||
if limit1 == 0:
|
||
continue
|
||
#以上为确定车牌颜色
|
||
|
||
#以下为根据车牌颜色再定位,缩小边缘非车牌边界
|
||
xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color,cfg)
|
||
if yl == yh and xl == xr:
|
||
continue
|
||
need_accurate = False
|
||
if yl >= yh:
|
||
yl = 0
|
||
yh = row_num
|
||
need_accurate = True
|
||
if xl >= xr:
|
||
xl = 0
|
||
xr = col_num
|
||
need_accurate = True
|
||
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]
|
||
if need_accurate:#可能x或y方向未缩小,需要再试一次
|
||
card_img = card_imgs[card_index]
|
||
card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV)
|
||
xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color,cfg)
|
||
if yl == yh and xl == xr:
|
||
continue
|
||
if yl >= yh:
|
||
yl = 0
|
||
yh = row_num
|
||
if xl >= xr:
|
||
xl = 0
|
||
xr = col_num
|
||
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]
|
||
|
||
|
||
roi = card_img
|
||
card_color = color
|
||
labels = (int(right_point[1]), int(heigth_point[1]), int(left_point[0]), int(right_point[0]))
|
||
|
||
|
||
return roi,labels, card_color#定位的车牌图像、车牌颜色
|
||
|
||
if __name__ == '__main__':
|
||
for pic_file in os.listdir("./test_img"):
|
||
|
||
roi, label,color = CaridDetect(os.path.join("./test_img",pic_file))
|
||
cv2.imwrite(os.path.join("./result",pic_file),roi)
|
||
print("*"*50)
|
||
print("[ ROI ] {}".format(roi))
|
||
print("[ Color ] {}".format(color))
|
||
print("[ Label ] {}".format(label))
|
||
|
||
|