1. 项目概述
车牌检测与识别系统是智能交通领域的重要技术支撑,广泛应用于高速公路收费、停车场管理、违章抓拍等场景。传统基于图像处理的车牌识别方法在理想环境下表现尚可,但在复杂光照、角度变化、遮挡等实际场景中往往难以达到实用要求。近年来,深度学习技术的突破为这一领域带来了新的解决方案。
我最近完成了一个基于YOLOv3和卷积神经网络的车牌检测与识别系统,通过多尺度特征融合和端到端训练,在CCPD数据集上实现了98.7%的车牌检测准确率和96.2%的字符识别准确率。这个系统最大的特点是采用了轻量化网络设计,在保持高精度的同时,单张图像处理时间控制在50ms以内,完全可以满足实时性要求。
2. 系统架构设计
2.1 整体技术路线
系统采用经典的检测-识别两阶段架构。第一阶段使用改进的YOLOv3网络进行车牌检测,输出车牌位置和类型;第二阶段将检测到的车牌区域送入专门设计的字符识别网络,输出车牌号码。这种解耦设计有以下优势:
- 模块化程度高,可以独立优化每个模块
- 检测网络可以专注于位置回归,识别网络专注于特征提取
- 便于针对不同车牌类型定制识别策略
2.2 关键技术选型
在检测网络选择上,我们对比了Faster R-CNN、SSD和YOLO系列算法。实测结果表明,YOLOv3在精度和速度上取得了最佳平衡。特别是其多尺度预测特性,非常适合处理不同尺寸的车牌目标。
对于识别网络,我们放弃了传统的LSTM+CTC方案,转而采用全卷积网络结构。这种设计有以下考虑:
- 车牌字符具有固定位置关系,不需要序列建模
- 全卷积网络计算效率更高,参数更少
- 多标签分类框架可以并行预测所有字符
3. 核心算法实现
3.1 改进的YOLOv3检测网络
我们在原始YOLOv3基础上做了三点重要改进:
- 引入SPP模块增强感受野
python复制class SPP(nn.Module):
def __init__(self):
super().__init__()
self.maxpool1 = nn.MaxPool2d(5, stride=1, padding=2)
self.maxpool2 = nn.MaxPool2d(9, stride=1, padding=4)
self.maxpool3 = nn.MaxPool2d(13, stride=1, padding=6)
def forward(self, x):
y1 = self.maxpool1(x)
y2 = self.maxpool2(x)
y3 = self.maxpool3(x)
return torch.cat([x, y1, y2, y3], dim=1)
- 使用CIoU Loss替代原生的IoU Loss
python复制def ciou_loss(pred, target):
# 计算中心点距离
rho2 = (pred[:,0] - target[:,0])**2 + (pred[:,1] - target[:,1])**2
# 计算最小包围框对角线长度
c2 = (torch.max(pred[:,2], target[:,2]) - torch.min(pred[:,0], target[:,0]))**2 + \
(torch.max(pred[:,3], target[:,3]) - torch.min(pred[:,1], target[:,1]))**2
# 计算宽高比的惩罚项
v = (4/(math.pi**2)) * torch.pow(torch.atan(target[:,2]/target[:,3]) -
torch.atan(pred[:,2]/pred[:,3]), 2)
alpha = v / (1 - (pred[:,4] * target[:,4]) + v)
return 1 - (pred[:,4] * target[:,4]) - (rho2/c2) + alpha*v
- 添加注意力机制增强小目标检测能力
python复制class CBAM(nn.Module):
def __init__(self, channels):
super().__init__()
self.channel_attention = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, channels//8, 1),
nn.ReLU(),
nn.Conv2d(channels//8, channels, 1),
nn.Sigmoid()
)
self.spatial_attention = nn.Sequential(
nn.Conv2d(2, 1, 7, padding=3),
nn.Sigmoid()
)
def forward(self, x):
# 通道注意力
ca = self.channel_attention(x)
x = x * ca
# 空间注意力
sa = torch.cat([torch.max(x, dim=1)[0].unsqueeze(1),
torch.mean(x, dim=1).unsqueeze(1)], dim=1)
sa = self.spatial_attention(sa)
return x * sa
3.2 多标签字符识别网络
字符识别网络采用全卷积结构,主要创新点包括:
- 使用深度可分离卷积减少参数量
python复制class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size):
super().__init__()
self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size,
groups=in_channels, padding=kernel_size//2)
self.pointwise = nn.Conv2d(in_channels, out_channels, 1)
def forward(self, x):
x = self.depthwise(x)
return self.pointwise(x)
- 引入位置编码增强空间感知
python复制class PositionEncoding(nn.Module):
def __init__(self, d_model, height, width):
super().__init__()
pe = torch.zeros(d_model, height, width)
y_pos = torch.arange(height).float().unsqueeze(1)
x_pos = torch.arange(width).float().unsqueeze(0)
div_term = torch.exp(torch.arange(0, d_model, 2).float() *
-(math.log(10000.0) / d_model))
pe[0::2, :, :] = torch.sin(x_pos * div_term).unsqueeze(0)
pe[1::2, :, :] = torch.sin(y_pos * div_term).unsqueeze(1)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
return x + self.pe[:, :, :x.size(2), :x.size(3)]
- 多任务损失函数设计
python复制class MultiTaskLoss(nn.Module):
def __init__(self, num_chars):
super().__init__()
self.num_chars = num_chars
self.ce_loss = nn.CrossEntropyLoss()
def forward(self, pred, target):
total_loss = 0
for i in range(self.num_chars):
total_loss += self.ce_loss(pred[:,i,:], target[:,i])
return total_loss / self.num_chars
4. 工程实现细节
4.1 数据增强策略
我们设计了一套针对车牌识别的数据增强方案:
- 几何变换:随机旋转(±15°)、透视变换(最大20%)、缩放(0.8-1.2倍)
- 颜色扰动:HSV空间随机调整(H±30,S±0.5,V±0.5)
- 模拟遮挡:随机添加矩形遮挡(最多3块,面积不超过20%)
- 运动模糊:随机添加线性运动模糊(最大15像素)
python复制class LicensePlateAugmentation:
def __call__(self, image, bboxes):
# 随机旋转
if random.random() < 0.5:
angle = random.uniform(-15, 15)
image, bboxes = rotate_image(image, angle, bboxes)
# 透视变换
if random.random() < 0.3:
image, bboxes = perspective_transform(image, bboxes)
# 颜色扰动
image = hsv_transform(image)
# 添加遮挡
if random.random() < 0.4:
image = add_occlusion(image)
return image, bboxes
4.2 模型优化技巧
- 混合精度训练
python复制scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
outputs = model(inputs)
loss = criterion(outputs, targets)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
- 知识蒸馏
python复制teacher_model.eval()
with torch.no_grad():
teacher_logits = teacher_model(inputs)
student_logits = student_model(inputs)
loss = alpha * criterion(student_logits, targets) + \
(1-alpha) * KLDivLoss(student_logits, teacher_logits)
- 模型量化
python复制model = torch.quantization.quantize_dynamic(
model,
{torch.nn.Linear, torch.nn.Conv2d},
dtype=torch.qint8
)
5. 性能优化方案
5.1 检测阶段优化
- 多尺度测试增强
python复制def multi_scale_test(image, scales=[0.5, 1.0, 1.5]):
detections = []
for scale in scales:
resized_img = cv2.resize(image, None, fx=scale, fy=scale)
det = detect(resized_img)
det[:, :4] /= scale
detections.append(det)
return non_max_suppression(np.concatenate(detections))
- 模型剪枝
python复制parameters_to_prune = []
for module in model.modules():
if isinstance(module, nn.Conv2d):
parameters_to_prune.append((module, 'weight'))
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured,
amount=0.3
)
5.2 识别阶段优化
- 字符级语言模型
python复制class CharLanguageModel(nn.Module):
def __init__(self, vocab_size):
super().__init__()
self.embedding = nn.Embedding(vocab_size, 64)
self.rnn = nn.GRU(64, 128, bidirectional=True)
self.fc = nn.Linear(256, vocab_size)
def forward(self, x):
x = self.embedding(x)
x, _ = self.rnn(x)
return self.fc(x)
- 结果后处理
python复制def post_process(preds, lang_model):
# 初始预测
chars = preds.argmax(dim=-1)
# 语言模型修正
for i in range(1, len(chars)-1):
if preds[i].max() < 0.8: # 低置信度字符
context = torch.cat([chars[i-1:i+2]])
scores = lang_model(context.unsqueeze(0))
chars[i] = scores.argmax()
return chars
6. 部署实践
6.1 TensorRT加速
python复制# 转换模型为ONNX格式
torch.onnx.export(model, dummy_input, "model.onnx")
# 使用TensorRT优化
trt_logger = trt.Logger(trt.Logger.INFO)
with trt.Builder(trt_logger) as builder:
network = builder.create_network()
parser = trt.OnnxParser(network, trt_logger)
with open("model.onnx", "rb") as f:
parser.parse(f.read())
config = builder.create_builder_config()
config.set_flag(trt.BuilderFlag.FP16)
engine = builder.build_engine(network, config)
6.2 边缘设备部署
在Jetson Xavier NX上的优化策略:
- 使用TensorRT加速
- 启用DLA核心
- 调整电源模式为MAXN
- 使用INT8量化
- 多线程流水线处理
python复制# 设置DLA核心
config.default_device_type = trt.DeviceType.DLA
config.DLA_core = 0
# INT8量化
config.set_flag(trt.BuilderFlag.INT8)
config.int8_calibrator = calibrator
7. 常见问题解决
7.1 检测失败场景处理
- 低光照情况:添加光照补偿算法
python复制def adaptive_gamma_correction(img):
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
# CLAHE增强
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
l = clahe.apply(l)
# Gamma校正
mean = np.mean(l)
gamma = math.log(0.5)/math.log(mean/255)
l = np.power(l/255.0, gamma) * 255
lab = cv2.merge((l, a, b))
return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
- 模糊车牌:添加超分辨率重建
python复制class SuperResolution(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, 9, padding=4)
self.conv2 = nn.Conv2d(64, 32, 1)
self.conv3 = nn.Conv2d(32, 3, 5, padding=2)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
return torch.sigmoid(self.conv3(x))
7.2 识别错误处理
- 相似字符区分:添加注意力机制
python复制class CharAttention(nn.Module):
def __init__(self, channels):
super().__init__()
self.query = nn.Conv2d(channels, channels//8, 1)
self.key = nn.Conv2d(channels, channels//8, 1)
self.value = nn.Conv2d(channels, channels, 1)
self.gamma = nn.Parameter(torch.zeros(1))
def forward(self, x):
b, c, h, w = x.size()
q = self.query(x).view(b, -1, h*w).permute(0, 2, 1)
k = self.key(x).view(b, -1, h*w)
v = self.value(x).view(b, -1, h*w)
attn = torch.bmm(q, k)
attn = F.softmax(attn, dim=-1)
out = torch.bmm(v, attn.permute(0, 2, 1))
out = out.view(b, c, h, w)
return self.gamma*out + x
- 多车牌处理:添加跟踪算法
python复制class SortTracker:
def __init__(self, max_age=5):
self.max_age = max_age
self.tracks = []
self.next_id = 0
def update(self, detections):
# 匈牙利算法匹配
matches, unmatched_dets = self._match(detections)
# 更新已有轨迹
for t, d in matches.items():
self.tracks[t].update(detections[d])
# 创建新轨迹
for d in unmatched_dets:
self.tracks.append(Track(detections[d], self.next_id))
self.next_id += 1
# 删除丢失的轨迹
self.tracks = [t for t in self.tracks if not t.time_since_update > self.max_age]
return self.tracks
在实际部署中,我们发现模型对倾斜车牌的识别准确率仍有提升空间。通过添加空间变换网络(STN)模块,可以显著改善这一情况:
python复制class STN(nn.Module):
def __init__(self):
super().__init__()
self.localization = nn.Sequential(
nn.Conv2d(3, 8, kernel_size=7),
nn.MaxPool2d(2, stride=2),
nn.ReLU(True),
nn.Conv2d(8, 10, kernel_size=5),
nn.MaxPool2d(2, stride=2),
nn.ReLU(True)
)
self.fc_loc = nn.Sequential(
nn.Linear(10*24*24, 32),
nn.ReLU(True),
nn.Linear(32, 6)
)
# 初始化变换参数为恒等变换
self.fc_loc[2].weight.data.zero_()
self.fc_loc[2].bias.data.copy_(torch.tensor([1,0,0,0,1,0], dtype=torch.float))
def forward(self, x):
xs = self.localization(x)
xs = xs.view(-1, 10*24*24)
theta = self.fc_loc(xs)
theta = theta.view(-1, 2, 3)
grid = F.affine_grid(theta, x.size())
return F.grid_sample(x, grid)
这个项目从算法设计到工程落地共耗时3个月,最大的收获是认识到工业级AI应用不仅需要好的算法,更需要扎实的工程实现能力。特别是在模型优化和部署环节,往往需要根据具体硬件平台做大量定制化工作。
