Python车牌识别是一个通过Python编程语言实现的车牌号码自动识别技术。它利用图像处理、模式识别及机器学习或深度学习算法,从车辆图像中自动检测和识别车牌号码。本文为大家介绍两种不同的车牌识别方法,适用于不同需求的场景。
1 极简版-Hyperlpr库
HyperLPR是一个基于深度学习技术的高性能中文车牌识别框架,由北京智云视图科技有限公司开发。它旨在提供一种高效且精确的方法来识别各种条件下的中文车牌号,包括但不限于不同的光线环境、角度变化及遮挡情况。以下是关于HyperLPR的详细介绍:
一、技术特点
- 深度学习技术:HyperLPR使用卷积神经网络(CNN)等深度学习技术来学习和识别车牌的特征和模式,从而能够在图像中精确定位并提取出车牌号码。
- 高度优化:其算法设计高度优化,使得即使在计算资源有限的环境下也能达到令人满意的识别速度与精度。
- 跨平台支持:支持多种操作系统和硬件环境,包括Windows、Linux、Android、iOS等平台,具有良好的适应性和兼容性。
二、功能支持
- 多种车牌类型识别:HyperLPR支持多种中文车牌类型的识别,包括普通车牌(蓝牌、黄牌)、新能源车牌、使馆车牌、教练车牌、武警车牌等,满足不同场景下的需求。
- 实时处理:优化的模型能够在实时环境下快速处理图像,满足交通监控、智能停车场等实时应用的需求。
- 鲁棒性强:具有良好的鲁棒性,能够在不同光线、角度和遮挡情况下保持较高的识别率。
三、使用方法
使用 HyperLPR
库来识别图片中的车牌号码,并在图片上绘制车牌框和车牌号码文本。
from hyperlpr import HyperLPR_plate_recognition
import cv2
from PIL import ImageFont, ImageDraw, Image
import numpy as np
def main(file_path):
image = cv2.imread(file_path) # 读入图片
image = image.copy()
result = HyperLPR_plate_recognition(image) # 识别车牌
plate = result[0][0] # 车牌号码
score = result[0][1] # 置信度
pt1 = (result[0][2][0], result[0][2][1]) # 车牌框左上角坐标
pt2 = (result[0][2][2], result[0][2][3]) # 车牌框右上角坐标
# 绘制车牌框
cv2.rectangle(image, pt1=pt1, pt2=pt2, color=(0, 255, 0), thickness=3)
# 绘制文字信息需要转换回PIL Image,因为cv2不支持直接绘制文本
font_path = 'C:\Windows\Fonts\simsun.ttc' # 指定字体文件的路径
font_size = 30
font = ImageFont.truetype(font_path, font_size)
image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) # 将BGR转换为RGB
draw = ImageDraw.Draw(image_pil)
text_position = (pt1[0] + 10, pt1[1] - 10 - font_size) # 调整文本位置,确保不被遮挡
draw.text(text_position, plate, font=font, fill=(255, 0, 0)) # 红色字体
# 将PIL Image转换回numpy array,注意此时是RGB,需要转换回BGR以符合OpenCV的显示要求
image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
# 显示图片
cv2.imshow('Result', image)
cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows() # 关闭所有窗口
print(plate)
return plate
main('1.png')
这段代码通过读取图片、识别车牌、绘制车牌框和车牌号码文本、显示结果并输出车牌号码,实现了一个完整的车牌识别流程。
- 读取图片:通过
cv2.imread(file_path)
读取指定路径的图片文件,并将其存储在变量image
中。这里file_path
是传递给main
函数的参数,代表图片文件的路径。 - 车牌识别:使用
HyperLPR_plate_recognition(image)
函数对图片进行车牌识别。这个函数是HyperLPR
库提供的,用于识别图片中的车牌信息。识别结果是一个列表,其中每个元素都是一个包含车牌号码、置信度和车牌框坐标的元组。这里只处理了第一个识别结果(result[0]
),因为车牌识别通常只关注第一个最有可能的结果。 - 绘制车牌框:使用
cv2.rectangle()
函数在图片上绘制车牌框。车牌框的左上角和右上角坐标从识别结果中提取(pt1
和pt2
)。 - 准备绘制文本:因为 OpenCV (
cv2
) 不支持直接绘制文本(尤其是中文文本),所以需要将图片从 OpenCV 的numpy
数组格式转换为 PIL (PIL.Image
) 格式。指定字体文件路径和大小,创建字体对象(font
)。 - 绘制车牌号码文本:将图片从 BGR 格式转换为 RGB 格式,因为 PIL 默认处理 RGB 格式的图像。使用 PIL 的
ImageDraw.Draw
方法在图片上绘制车牌号码文本。文本位置根据车牌框的左上角坐标调整,以避免遮挡。 - 转换回 OpenCV 格式:将绘制完文本的图片从 PIL 格式转换回 OpenCV 的
numpy
数组格式,并再次从 RGB 转换为 BGR 格式,以便在 OpenCV 窗口中正确显示。 - 显示结果:使用
cv2.imshow('Result', image)
显示处理后的图片。等待用户按键(cv2.waitKey(0)
),之后关闭所有 OpenCV 窗口(cv2.destroyAllWindows()
)。 - 输出车牌号码:打印并返回识别到的车牌号码(
plate
)。
识别效果:
2 机器学习SVM
支持向量机(Support Vector Machine, SVM)是一种广泛用于模式识别领域的机器学习算法,常用于分类和回归分析。在涉及车牌识别的计算机视觉任务中,SVM可以用于实现从图像中检测和识别车牌的过程。
车牌识别通常包括四个主要步骤:
-
预处理:首先对图像进行预处理,包括灰度化、二值化、噪声去除、图像增强等步骤,以便于后续的算法处理。
-
车牌定位:通过轮廓检测、边缘检测等技术方法,对图像中可能存在的车牌区域进行定位。
-
字符分割:将定位到的车牌区域分割成单个字符,以便进行独立识别。
-
字符识别:对分割出来的字符进行识别,这个过程可以使用SVM作为识别算法之一。
2.1 模型训练
数据集准备:
这个模型训练过程涉及到使用支持向量机(SVM)对车牌字符进行识别和分类,具体分为英文字母、数字以及中文省份的识别。整个过程主要分为以下几个步骤:
1. 初始化SVM模型
- 定义了一个基类
StatModel
,它包含了模型的加载(load
)和保存(save
)方法的框架,但在这段代码中并未实际实现加载功能(load
方法内部调用了未定义的self.model.load(fn)
)。 SVM
类继承自StatModel
,并在初始化时通过 OpenCV 的cv2.ml.SVM_create()
创建了一个 SVM 模型实例。接着,设置了 SVM 的一些关键参数,如正则化参数C
、RBF 核的gamma
、核类型(这里使用 RBF 核)以及 SVM 类型(这里使用 C-SVC,即 C-Support Vector Classification)。
2. 数据准备
- 遍历指定路径下的文件夹,这些文件夹按照字符(英文字母、数字或中文省份拼音)进行命名。
- 读取每个文件夹下的图片文件,将其转换为灰度图,并应用一些预处理步骤(如去倾斜)。
- 提取图片的 HOG(Histogram of Oriented Gradients,方向梯度直方图)特征,这是一种常用于图像识别的特征描述符,对于形状的描述特别有效。
3. 训练 SVM
- 对于英文字母和数字,以及中文省份,分别训练了两个 SVM 模型(
Model
和Modelchinese
)。 - 训练过程中,将预处理后的 HOG 特征作为输入,对应的文件夹名(转换为整数或特定索引)作为标签。
- 使用 OpenCV 的
train
方法对 SVM 模型进行训练,该方法接受样本数据、样本类型和响应变量(即标签)。
4. 模型保存
- 训练完成后,使用
save
方法将训练好的 SVM 模型保存到文件中(svm.dat
和svmchinese.dat
)。 - 如果文件已存在,则先删除旧文件再保存新模型,这里假设是为了更新模型。然而,这种方式在实际应用中可能并不总是最优的,因为直接覆盖文件可能导致数据丢失的风险(尽管在这里只是模型文件)。
class SVM(StatModel):
def __init__(self, C=1, gamma=0.5):
self.model = cv2.ml.SVM_create()
self.model.setGamma(gamma)
self.model.setC(C)
self.model.setKernel(cv2.ml.SVM_RBF)
self.model.setType(cv2.ml.SVM_C_SVC)
# 不能保证包括所有省份
# 训练svm
def train(self, samples, responses):
self.model.train(samples, cv2.ml.ROW_SAMPLE, responses)
# 字符识别
def predict(self, samples):
r = self.model.predict(samples)
return r[1].ravel()
# 定义参数
SZ = args.Size # 训练图片长宽
MAX_WIDTH = args.MAX_WIDTH # 原始图片最大宽度
Min_Area = args.Min_Area # 车牌区域允许最大面积
PROVINCE_START = args.PROVINCE_START
provinces = args.provinces
# 来自opencv的sample,用于svm训练
def preprocess_hog(digits):
samples = []
for img in digits:
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
mag, ang = cv2.cartToPolar(gx, gy)
bin_n = 16
bin = np.int32(bin_n * ang / (2 * np.pi))
bin_cells = bin[:10, :10], bin[10:, :10], bin[:10, 10:], bin[10:, 10:]
mag_cells = mag[:10, :10], mag[10:, :10], mag[:10, 10:], mag[10:, 10:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists)
# transform to Hellinger kernel
eps = 1e-7
hist /= hist.sum() + eps
hist = np.sqrt(hist)
hist /= norm(hist) + eps
samples.append(hist)
return np.float32(samples)
def train_svm(path):
# 识别英文字母和数字
Model = SVM(C=1, gamma=0.5)
# 识别中文
Modelchinese = SVM(C=1, gamma=0.5)
# 英文字母和数字部分训练
chars_train = []
chars_label = []
# # 遍历指定路径下的'chars'文件夹,读取英文字母和数字的图片
for root, dirs, files in os.walk(os.path.join(path,'chars')):
if len(os.path.basename(root)) > 1:
continue
root_int = ord(os.path.basename(root)) # 获取文件夹名(字母或数字)的ASCII码
for filename in files:
print('input:{}'.format(filename))
filepath = os.path.join(root, filename)
digit_img = cv2.imread(filepath) # 读取图片
digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
chars_train.append(digit_img)
chars_label.append(root_int)
# 对训练数据进行预处理
chars_train = list(map(deskew, chars_train))
chars_train = preprocess_hog(chars_train) # 提取HOG特征
chars_label = np.array(chars_label)
Model.train(chars_train, chars_label)
if not os.path.exists("svm.dat"):
# 保存模型
Model.save("svm.dat")
else:
# 更新模型
os.remove("svm.dat")
Model.save("svm.dat")
# 中文部分训练
chars_train = []
chars_label = []
for root, dirs, files in os.walk(os.path.join(path,'charsChinese')):
if not os.path.basename(root).startswith("zh_"):
continue
pinyin = os.path.basename(root)
index = provinces.index(pinyin) + PROVINCE_START + 1 # 1是拼音对应的汉字
for filename in files:
print('input:{}'.format(filename))
filepath = os.path.join(root, filename)
digit_img = cv2.imread(filepath)
digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)
chars_train.append(digit_img)
chars_label.append(index)
chars_train = list(map(deskew, chars_train))
chars_train = preprocess_hog(chars_train)
chars_label = np.array(chars_label)
Modelchinese.train(chars_train, chars_label)
if not os.path.exists("svmchinese.dat"):
# 保存模型
Modelchinese.save("svmchinese.dat")
else:
# 更新模型
os.remove("svmchinese.dat")
Modelchinese.save("svmchinese.dat")
2.2 车牌识别
1. 图像读取
通过__imreadex
方法使用OpenCV的imdecode
函数从文件中读取图像。这里使用了np.fromfile
将文件内容读入为NumPy数组,然后解码为图像。这种方法相比直接使用cv2.imread
可以处理更灵活的图像源,比如非文件系统的数据流。
def __imreadex(self, filename):
return cv2.imdecode(np.fromfile(filename, dtype=np.uint8), cv2.IMREAD_COLOR)
2. 图像预处理
虽然代码中没有直接展示图像预处理的具体步骤,但车牌识别前通常需要对图像进行预处理以提高车牌检测的准确性。常见的预处理步骤包括:
- 灰度化:将彩色图像转换为灰度图像,因为车牌的颜色信息对于检测来说通常不是必需的,而灰度图像可以减少计算量。
- 边缘检测:使用如Sobel、Canny等边缘检测算法来突出图像中的边界,帮助后续处理。
- 滤波:使用高斯滤波、中值滤波等方法去除噪声,提高图像质量。
- 形态学操作:如腐蚀、膨胀、开运算、闭运算等,用于去除小对象、填补孔洞、分离接触的对象等。
def __point_limit(self, point):
if point[0] < 0:
point[0] = 0
if point[1] < 0:
point[1] = 0
# 利用投影法,根据设定的阈值和图片直方图,找出波峰,用于分隔字符
def __find_waves(self, threshold, histogram):
up_point = -1 # 上升点
is_peak = False
if histogram[0] > threshold:
up_point = 0
is_peak = True
wave_peaks = []
for i, x in enumerate(histogram):
if is_peak and x < threshold:
if i - up_point > 2:
is_peak = False
wave_peaks.append((up_point, i))
elif not is_peak and x >= threshold:
is_peak = True
up_point = i
if is_peak and up_point != -1 and i - up_point > 4:
wave_peaks.append((up_point, i))
return wave_peaks
# 根据找出的波峰,分隔图片,从而得到逐个字符图片
def __seperate_card(self, img, waves):
part_cards = []
for wave in waves:
part_cards.append(img[:, wave[0]:wave[1]])
return part_cards
3. 车牌候选区域的定位
- 波峰检测:通过
__find_waves
方法,在图像的灰度直方图或边缘检测结果的投影直方图上寻找波峰,这些波峰可能对应车牌的上下边界或左右边界。这种方法基于车牌区域通常比周围区域更亮或更暗的假设。 - 区域裁剪:根据波峰检测的结果,通过
__seperate_card
方法裁剪出车牌的候选区域。这一步通常涉及对图像进行切片,保留波峰之间的区域作为车牌候选。 - 精确定位:在裁剪出的候选区域内,使用
__accurate_place
方法进一步精确定位车牌的具体位置。这个方法通常基于颜色空间(如HSV)的阈值分割,通过统计特定颜色范围内的像素数量来估计车牌的精确边界。对于绿色车牌(如新能源汽车车牌),可能需要调整颜色阈值以适应颜色的渐变。
# 缩小车牌边界
def __accurate_place(self, card_img_hsv, limit1, limit2, color):
row_num, col_num = card_img_hsv.shape[:2]
xl = col_num
xr = 0
yh = 0
yl = row_num
# col_num_limit = self.cfg["col_num_limit"]
row_num_limit = self.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 __preTreatment(self, car_pic):
if type(car_pic) == type(""):
img = self.__imreadex(car_pic)
else:
img = car_pic
pic_hight, pic_width = img.shape[:2]
if pic_width > self.MAX_WIDTH:
resize_rate = self.MAX_WIDTH / pic_width
img = cv2.resize(img, (self.MAX_WIDTH, int(pic_hight * resize_rate)),
interpolation=cv2.INTER_AREA) # 图片分辨率调整
#cv2.imshow('Image', img)
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32) # 定义一个核
img = cv2.filter2D(img, -1, kernel=kernel) # 锐化
blur = self.cfg["blur"]
# 高斯去噪
if blur > 0:
img = cv2.GaussianBlur(img, (blur, blur), 0)
oldimg = img
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#cv2.imshow('GaussianBlur', img)
#cv2.waitKey(0)
kernel = np.ones((20, 20), np.uint8)
img_opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) # 开运算
img_opening = cv2.addWeighted(img, 1, img_opening, -1, 0); # 与上一次开运算结果融合
# cv2.imshow('img_opening', img_opening)
# cv2.waitKey(0)
# 找到图像边缘
ret, img_thresh = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 二值化
img_edge = cv2.Canny(img_thresh, 100, 200)
# cv2.imshow('img_edge', img_edge)
# cv2.waitKey(0)
# 使用开运算和闭运算让图像边缘成为一个整体
kernel = np.ones((self.cfg["morphologyr"], self.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.imshow('img_edge2', img_edge2)
# cv2.waitKey(0)
# 查找图像边缘整体形成的矩形区域,可能有很多,车牌就在其中一个矩形区域中
try:
image, contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
except ValueError:
# ValueError: not enough values to unpack (expected 3, got 2)
# cv2.findContours方法在高版本OpenCV中只返回两个参数
contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = [cnt for cnt in contours if cv2.contourArea(cnt) > self.Min_Area]
# 逐个排除不是车牌的矩形区域
car_contours = []
for cnt in contours:
# 框选 生成最小外接矩形 返回值(中心(x,y), (宽,高), 旋转角度)
rect = cv2.minAreaRect(cnt)
# print('宽高:',rect[1])
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是车牌的长宽比,其余的矩形排除
if wh_ratio > 2 and wh_ratio < 5.5:
car_contours.append(rect)
4. 灰度化与形态学操作
- 灰度化:将图像转换为灰度图像,因为车牌的颜色信息在车牌检测阶段通常不是必需的。
- 开运算:使用形态学开运算(先腐蚀后膨胀)来去除小的白噪声,并平滑较大对象的边界。
- 二值化:通过Otsu阈值方法将灰度图像转换为二值图像,以便更容易地检测车牌区域。
5. 边缘检测与形态学后处理
- Canny边缘检测:在二值图像上应用Canny边缘检测算法,以突出图像中的边缘信息。
- 闭运算与开运算:对边缘检测结果进行闭运算(先膨胀后腐蚀)和开运算,以进一步平滑边缘并去除小的噪声。
6. 轮廓检测与车牌候选区域提取
- 轮廓检测:使用
cv2.findContours
函数检测图像中的轮廓。 - 轮廓筛选:根据轮廓的面积(
Min_Area
)和宽高比(车牌通常具有特定的宽高比范围)来筛选车牌候选区域。
7. 车牌区域校正与提取
- 仿射变换:对于每个车牌候选区域,使用仿射变换将其校正为水平或垂直方向,以便更容易地提取车牌图像。
- 车牌图像提取:根据校正后的车牌区域坐标,从原始图像中提取车牌图像。
8. 车牌颜色识别
- 颜色空间转换:将提取的车牌图像从BGR颜色空间转换到HSV颜色空间,以便进行颜色识别。
- 颜色统计:统计车牌图像中各个颜色分量的像素数量,以确定车牌的主要颜色。
- 颜色分类:根据颜色统计结果,将车牌分类为黄色、绿色、蓝色或黑白(对于新能源汽车车牌)。
# 矩形区域可能是倾斜的矩形,需要矫正,以便使用颜色定位
card_imgs = []
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) # 扩大范围,避免车牌边缘被排除
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))
self.__point_limit(new_right_point)
self.__point_limit(heigth_point)
self.__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)
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))
self.__point_limit(right_point)
self.__point_limit(heigth_point)
self.__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_imgs[0])
# cv2.waitKey(0)
# #____开始使用颜色定位,排除不是车牌的矩形,目前只识别蓝、绿、黄车牌
colors = []
for card_index, card_img in enumerate(card_imgs):
green = yellow = blue = black = white = 0
try:
# 有转换失败的可能,原因来自于上面矫正矩形出错
card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV)
except:
card_img_hsv = None
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: # 图片分辨率调整
yellow += 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"
# print('黄:{:<6}绿:{:<6}蓝:{:<6}'.format(yellow,green,blue))
9. 字符分割识别
- 字符分割:基于垂直直方图的波峰,将车牌图像分割成单独的字符图像。此步骤可能包括合并相邻的波峰以处理汉字(省份简称),因为汉字通常比英文字符宽。
- 预处理:对每个字符图像进行预处理,包括边缘填充和缩放,以确保所有字符图像具有相同的尺寸,并符合机器学习模型的输入要求。
- 识别:使用训练好的机器学习模型(
model
和modelchinese
)对每个字符图像进行识别。model
用于识别英文字符和数字,而modelchinese
用于识别汉字(省份简称)。 - 结果过滤:对识别结果进行过滤,例如忽略由于车牌边缘或噪声而错误识别的字符(如将边缘识别为"1")。
10. 输出
- 识别结果:返回车牌字符的识别结果列表。
- 车牌图像:返回包含车牌区域的原始图像(可能经过一些处理,如锐化)。
- 车牌颜色:返回识别出的车牌颜色(蓝色、黄色或绿色)。
# 分割字符并识别车牌文字
def __identification(self, card_imgs, colors,model,modelchinese):
# 识别车牌中的字符
result = {}
predict_result = []
roi = None
card_color = None
for i, color in enumerate(colors):
if color in ("blue", "yellow", "green"):
card_img = card_imgs[i]
# old_img = card_img
# 做一次锐化处理
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32) # 锐化
card_img = cv2.filter2D(card_img, -1, kernel=kernel)
# cv2.imshow("custom_blur", card_img)
# RGB转GARY
gray_img = cv2.cvtColor(card_img, cv2.COLOR_BGR2GRAY)
# cv2.imshow('gray_img', gray_img)
# 黄、绿车牌字符比背景暗、与蓝车牌刚好相反,所以黄、绿车牌需要反向
if color == "green" or color == "yellow":
gray_img = cv2.bitwise_not(gray_img)
# 二值化
ret, gray_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# cv2.imshow('gray_img', gray_img)
# 查找水平直方图波峰
x_histogram = np.sum(gray_img, axis=1)
# 最小值
x_min = np.min(x_histogram)
# 均值
x_average = np.sum(x_histogram) / x_histogram.shape[0]
x_threshold = (x_min + x_average) / 2
wave_peaks = self.__find_waves(x_threshold, x_histogram)
if len(wave_peaks) == 0:
continue
# 认为水平方向,最大的波峰为车牌区域
wave = max(wave_peaks, key=lambda x: x[1] - x[0])
gray_img = gray_img[wave[0]:wave[1]]
# cv2.imshow('gray_img', gray_img)
# 查找垂直直方图波峰
row_num, col_num = gray_img.shape[:2]
# 去掉车牌上下边缘1个像素,避免白边影响阈值判断
gray_img = gray_img[1:row_num - 1]
# cv2.imshow('gray_img', gray_img)
y_histogram = np.sum(gray_img, axis=0)
y_min = np.min(y_histogram)
y_average = np.sum(y_histogram) / y_histogram.shape[0]
y_threshold = (y_min + y_average) / 5 # U和0要求阈值偏小,否则U和0会被分成两半
wave_peaks = self.__find_waves(y_threshold, y_histogram)
# print(wave_peaks)
# for wave in wave_peaks:
# cv2.line(card_img, pt1=(wave[0], 5), pt2=(wave[1], 5), color=(0, 0, 255), thickness=2)
# 车牌字符数应大于6
if len(wave_peaks) <= 6:
# print(wave_peaks)
continue
wave = max(wave_peaks, key=lambda x: x[1] - x[0])
max_wave_dis = wave[1] - wave[0]
# 判断是否是左侧车牌边缘
if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis / 3 and wave_peaks[0][0] == 0:
wave_peaks.pop(0)
# 组合分离汉字
cur_dis = 0
for i, wave in enumerate(wave_peaks):
if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6:
break
else:
cur_dis += wave[1] - wave[0]
if i > 0:
wave = (wave_peaks[0][0], wave_peaks[i][1])
wave_peaks = wave_peaks[i + 1:]
wave_peaks.insert(0, wave)
# 去除车牌上的分隔点
point = wave_peaks[2]
if point[1] - point[0] < max_wave_dis / 3:
point_img = gray_img[:, point[0]:point[1]]
if np.mean(point_img) < 255 / 5:
wave_peaks.pop(2)
if len(wave_peaks) <= 6:
# print("peak less 2:", wave_peaks)
continue
# print(wave_peaks)
# 分割牌照字符
part_cards = self.__seperate_card(gray_img, wave_peaks)
# 分割输出
#for i, part_card in enumerate(part_cards):
# cv2.imshow(str(i), part_card)
# 识别
for i, part_card in enumerate(part_cards):
# 可能是固定车牌的铆钉
if np.mean(part_card) < 255 / 5:
continue
part_card_old = part_card
w = abs(part_card.shape[1] - self.SZ) // 2
# 边缘填充
part_card = cv2.copyMakeBorder(part_card, 0, 0, w, w, cv2.BORDER_CONSTANT, value=[0, 0, 0])
# cv2.imshow('part_card', part_card)
# 图片缩放(缩小)
part_card = cv2.resize(part_card, (self.SZ, self.SZ), interpolation=cv2.INTER_AREA)
# cv2.imshow('part_card', part_card)
part_card = SVM_Train.preprocess_hog([part_card])
if i == 0: # 识别汉字
resp = self.modelchinese.predict(part_card) # 匹配样本
charactor = self.provinces[int(resp[0]) - self.PROVINCE_START]
# print(charactor)
else: # 识别字母
resp = self.model.predict(part_card) # 匹配样本
charactor = chr(resp[0])
# print(charactor)
# 判断最后一个数是否是车牌边缘,假设车牌边缘被认为是1
if charactor == "1" and i == len(part_cards) - 1:
if color == 'blue' and len(part_cards) > 7:
if part_card_old.shape[0] / part_card_old.shape[1] >= 7: # 1太细,认为是边缘
continue
elif color == 'blue' and len(part_cards) > 7:
if part_card_old.shape[0] / part_card_old.shape[1] >= 7: # 1太细,认为是边缘
continue
elif color == 'green' and len(part_cards) > 8:
if part_card_old.shape[0] / part_card_old.shape[1] >= 7: # 1太细,认为是边缘
continue
predict_result.append(charactor)
roi = card_img # old_img
card_color = color
break
return predict_result, roi, card_color # 识别到的字符、定位的车牌图像、车牌颜色
识别效果:
3 总结
综上所述,使用Hyperlpr库和SVM(支持向量机)算法进行车牌识别各有其特点和区别。
Hyperlpr库:
- 技术基础:基于深度学习技术,特别是卷积神经网络(CNN),进行车牌识别。
- 特点:
- 高准确性:通过大规模数据集训练,能够学习到车牌的复杂特征和模式,实现较高的识别准确率。
- 鲁棒性强:应用了图像处理、图像增强和预处理技术,对光照变化、部分遮挡等情况有一定的适应能力。
- 多类型支持:支持多种车牌类型,包括中国大陆的普通车牌、港澳台车牌、特种车牌等。
- 易用性:提供了丰富的API接口和示例代码,支持多种编程语言和平台,方便开发者集成和使用。
SVM算法:
- 技术基础:一种传统的机器学习算法,通过寻找一个最优超平面来划分数据类别。
- 特点:
- 经典但有限制:在车牌识别中,SVM算法需要依赖手工设计的特征提取器,这限制了其处理复杂车牌图像的能力。
- 计算效率:在特征维度不高的情况下,SVM算法的计算效率较高。
- 适应性:对于简单场景和清晰的车牌图像,SVM算法可能取得不错的识别效果,但面对复杂多变的实际场景,其性能可能受限。
如果是在做毕业设计时,项目需要处理复杂多变的车牌图像,追求高准确性和鲁棒性,建议选择Hyperlpr库。 如果项目侧重于机器学习算法的研究与实现,且车牌图像相对简单,可以考虑使用SVM算法。 所以,在选择使用Hyperlpr库还是SVM算法进行车牌识别时,需要综合考虑项目需求、技术栈以及个人技术储备和发展方向。对于大多数毕业设计项目而言,如果追求高准确性和鲁棒性,且资源允许,建议使用Hyperlpr库进行车牌识别。