1. 项目概述
车牌识别系统是现代智能交通和安防领域的关键技术之一。作为一名计算机视觉工程师,我经常需要从零开始搭建完整的车牌识别流程。这个项目将带你从摄像头采集开始,一步步实现车牌检测、字符分割和字符识别的全流程开发。
在实际工程中,车牌识别系统通常包含四个核心环节:图像采集→车牌定位→字符分割→字符识别。每个环节都有其技术难点,比如光照变化、车牌倾斜、字符粘连等问题。本文将重点介绍如何使用传统图像处理结合深度学习的方法,构建一个鲁棒性较强的车牌识别系统。
2. 核心流程与技术实现
2.1 图像采集与预处理
硬件选型建议:
- 工业级摄像头(如海康威视DS-2CD3系列)
- 分辨率至少200万像素(1920×1080)
- 支持自动曝光和宽动态范围(WDR)
python复制import cv2
# 初始化摄像头
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
while True:
ret, frame = cap.read()
if not ret:
break
# 转换为灰度图
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 直方图均衡化
equalized = cv2.equalizeHist(gray)
预处理关键步骤:
- 高斯模糊去噪(kernel size 3×3)
- 直方图均衡化增强对比度
- 边缘保留滤波(如双边滤波)
注意:避免过度模糊导致边缘信息丢失,建议sigma值设为1.0-1.5
2.2 车牌定位检测
方法一:基于颜色的定位(适合中国蓝牌/黄牌)
python复制def color_based_detection(img):
# HSV颜色空间转换
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 蓝色车牌范围
lower_blue = np.array([100, 80, 80])
upper_blue = np.array([140, 255, 255])
# 黄色车牌范围
lower_yellow = np.array([15, 80, 80])
upper_yellow = np.array([40, 255, 255])
# 颜色阈值分割
mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
# 形态学处理
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
mask_blue = cv2.morphologyEx(mask_blue, cv2.MORPH_CLOSE, kernel, iterations=2)
# 查找轮廓
contours, _ = cv2.findContours(mask_blue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 筛选符合车牌长宽比的轮廓
plates = []
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
ratio = w/h
if 2.5 < ratio < 4.5 and w > 100: # 典型车牌长宽比
plates.append((x,y,w,h))
return plates
方法二:基于边缘的定位(通用性更强)
python复制def edge_based_detection(gray_img):
# Sobel边缘检测
sobelx = cv2.Sobel(gray_img, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray_img, cv2.CV_64F, 0, 1, ksize=3)
magnitude = np.sqrt(sobelx**2 + sobely**2)
# 二值化
_, binary = cv2.threshold(magnitude, 50, 255, cv2.THRESH_BINARY)
# 闭运算连接边缘
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 2))
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# 查找轮廓并筛选
contours, _ = cv2.findContours(closed.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
plates = []
for cnt in contours:
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
# 计算长宽比和面积
width, height = rect[1]
if width > height:
width, height = height, width
ratio = width / height
if 2.5 < ratio < 4.5 and width*height > 5000:
plates.append(box)
return plates
定位效果优化技巧:
- 多方法融合:先尝试颜色定位,失败时改用边缘定位
- 非极大值抑制(NMS)消除重复检测
- 角度校正:通过minAreaRect获取旋转角度并校正
2.3 字符分割
关键步骤实现
python复制def char_segmentation(plate_img):
# 转为灰度图并二值化
gray = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 水平投影分割行
horizontal_proj = np.sum(binary, axis=1)
row_ranges = find_peaks(horizontal_proj, min_height=plate_img.shape[1]*0.3)
# 垂直投影分割字符
vertical_proj = np.sum(binary[row_ranges[0]:row_ranges[1], :], axis=0)
char_ranges = find_peaks(vertical_proj, min_height=(row_ranges[1]-row_ranges[0])*0.5)
# 字符归一化
chars = []
for i in range(0, len(char_ranges), 2):
char_img = binary[row_ranges[0]:row_ranges[1], char_ranges[i]:char_ranges[i+1]]
char_img = cv2.resize(char_img, (20, 40)) # 统一到相同尺寸
chars.append(char_img)
return chars
def find_peaks(proj, min_height):
peaks = []
in_peak = False
for i, val in enumerate(proj):
if val > min_height and not in_peak:
in_peak = True
peaks.append(i)
elif val <= min_height and in_peak:
in_peak = False
peaks.append(i-1)
return peaks
处理特殊情况的技巧:
- 中文字符分割:首个字符通常较宽,需特殊处理
- 字符粘连:使用连通域分析+投影法组合处理
- 倾斜校正:在分割前先进行仿射变换
2.4 字符识别
基于CNN的识别模型
python复制import tensorflow as tf
from tensorflow.keras import layers
def build_cnn_model(num_classes):
model = tf.keras.Sequential([
layers.Conv2D(32, (3,3), activation='relu', input_shape=(40, 20, 1)),
layers.MaxPooling2D((2,2)),
layers.Conv2D(64, (3,3), activation='relu'),
layers.MaxPooling2D((2,2)),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(num_classes, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
# 示例类别:0-9, A-Z, 各省简称
char_classes = 34
model = build_cnn_model(char_classes)
数据增强策略
python复制train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
rotation_range=10,
width_shift_range=0.1,
height_shift_range=0.1,
zoom_range=0.1,
shear_range=0.1
)
模型训练建议:
- 使用公开数据集(如CCPD)预训练
- 针对实际场景采集数据微调
- 平衡各类字符的样本数量
3. 性能优化与工程实践
3.1 多线程处理框架
python复制import threading
from queue import Queue
class PlateRecognizer:
def __init__(self):
self.frame_queue = Queue(maxsize=10)
self.result_queue = Queue(maxsize=10)
def capture_thread(self):
while True:
ret, frame = self.cap.read()
if ret:
self.frame_queue.put(frame)
def process_thread(self):
while True:
frame = self.frame_queue.get()
plates = detect_plates(frame)
for plate in plates:
chars = segment_chars(plate)
result = recognize_chars(chars)
self.result_queue.put(result)
def start(self):
t1 = threading.Thread(target=self.capture_thread)
t2 = threading.Thread(target=self.process_thread)
t1.daemon = True
t2.daemon = True
t1.start()
t2.start()
3.2 准确率提升技巧
-
多模型集成:
- 训练多个不同结构的CNN模型
- 采用投票机制综合结果
-
上下文校验:
- 利用车牌编码规则校验结果
- 例如:第二位通常是字母,最后四位是数字等
-
时序融合:
- 对视频流采用多帧结果融合
- 加权平均或投票机制
4. 常见问题与解决方案
4.1 车牌检测失败场景
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 完全检测不到 | 光照条件差 | 增加补光灯/使用红外摄像头 |
| 误检率高 | 背景复杂 | 调整颜色/边缘检测阈值 |
| 定位不准确 | 车牌倾斜 | 改进minAreaRect处理 |
4.2 字符识别错误分析
-
数字混淆:
- '0'与'D'混淆 → 增加倾斜样本训练
- '8'与'B'混淆 → 改进网络感受野
-
中文识别困难:
- 收集更多省份简称样本
- 单独训练中文分类器
-
模糊字符处理:
- 加入运动模糊数据增强
- 采用超分辨率重建预处理
5. 完整实现示例
python复制import cv2
import numpy as np
import tensorflow as tf
class LPRSystem:
def __init__(self, model_path):
self.plate_detector = PlateDetector()
self.char_recognizer = tf.keras.models.load_model(model_path)
self.char_labels = ['0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','J','K',
'L','M','N','P','Q','R','S','T','U','V',
'W','X','Y','Z']
def process_frame(self, frame):
# 车牌检测
plates = self.plate_detector.detect(frame)
results = []
for plate in plates:
# 字符分割
chars = self.segment_chars(plate)
# 字符识别
plate_str = ""
for char_img in chars:
char_img = cv2.resize(char_img, (20, 40))
char_img = np.expand_dims(char_img, axis=-1)
char_img = np.expand_dims(char_img, axis=0)
pred = self.char_recognizer.predict(char_img)
plate_str += self.char_labels[np.argmax(pred)]
results.append({
'plate_rect': plate,
'plate_number': plate_str
})
return results
# 使用示例
lpr = LPRSystem('char_cnn.h5')
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
results = lpr.process_frame(frame)
for res in results:
x,y,w,h = res['plate_rect']
cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2)
cv2.putText(frame, res['plate_number'], (x,y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
cv2.imshow('LPR Demo', frame)
if cv2.waitKey(1) == 27:
break
cap.release()
cv2.destroyAllWindows()
6. 进阶优化方向
-
端到端深度学习:
- 采用YOLO+CRNN的联合模型
- 实现检测识别一体化
-
嵌入式部署:
- 使用TensorRT加速
- 在Jetson Nano等设备部署
-
多车牌处理:
- 改进NMS算法
- 支持同一画面多个车牌
在实际项目中,车牌识别系统的性能往往取决于细节处理。建议从以下方面持续优化:
- 增加异常样本收集(污损、遮挡车牌)
- 优化预处理流水线
- 定期更新训练数据集
