十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

11类中国车牌检测识别:YOLOv8n+CRNN多类别实战方案

11类中国车牌检测识别:YOLOv8n+CRNN多类别实战方案 简介这是一套面向计算机视觉初学者与进阶开发者的中文车牌检测与识别实战源码聚焦蓝牌、黄牌、新能源、港澳及特种车牌警车、校车、教练车等的端到端识别任务适用于智能交通、安防监控、教学实验等场景。资源共159个文件含42个核心Python脚本含模型训练、推理、预处理模块、36张实测车牌图像如IMG_2199.jpg、1811AS.jpg等、19个配置用YAML文件、8个可视化PNG图及4个模型权重文件.pth、.pt另有Dockerfile、CCPD2019数据集子目录及C扩展编译文件box_overlaps.c整体包大小36.51MB结构完整便于复现与二次开发。已有513人学习下载提供从图像预处理OpenCV灰度化、二值化、Canny边缘检测、车牌定位轮廓分析Hough变换、字符分割垂直投影到CNN字符识别的全流程实现附带CCPD数据集适配与新能源车牌颜色/布局特化处理逻辑是深入理解车牌识别工业级落地细节的优质实践范例。1. 为什么你训练的车牌检测模型在真实路口总漏检蓝牌、黄牌却对新能源车牌“视而不见”这不是模型不够深而是你没意识到国内车牌体系根本不是单一类别——它有11种法定制式小型汽车蓝牌、大型汽车黄牌、挂车黄牌、双层黄牌如工程车、农用车牌绿色边框黑字、警用车牌白底黑字红“警”、校车白底红字“校车”、教练车黄底黑字“学”、港澳入出境车黑底白字粤Z/港/澳、使领馆车牌黑底白字使/领、新能源绿牌渐变绿底黑字/白字。每类在字符高度、边框宽度、反光材质、安装角度、夜间补光响应上差异极大。用YOLOv5只训“plate”一个类别等于让模型硬记11套完全不同的视觉规律——结果就是蓝牌识别率92%黄牌掉到63%新能源牌直接归零。本方案不堆模型而是用一套可插拔的检测识别流水线把车牌类型先分组再专精检测阶段用改进的YOLOv8n做多类别定位输出带type_id的bbox识别阶段用CRNNAttention分通道处理不同字符集蓝牌7位、新能源8位、使领馆含英文数字汉字。源码已实测跑通海康IPC抓拍图、卡口低照度图像、手机拍摄倾斜图三类真实数据支持OpenVINO加速部署到边缘盒子不依赖GPU也能跑30FPS。适合想快速落地停车场、高速ETC、交管稽查系统的Python工程师尤其适合被“黄牌识别不准”“新能源车牌漏字”反复折磨的团队。2. 用YOLOv8n实现11类车牌的高精度检测从数据标注到模型微调2.1 为什么必须用YOLOv8n而不是YOLOv5或YOLOv7YOLOv5对小目标如双层黄牌下层字符召回率差YOLOv7在多尺度特征融合时易混淆农用车牌绿色边框与背景植被YOLOv8n则通过Task-Aligned Assigner替代Anchor-based匹配在训练初期就强制模型关注车牌四角关键点对倾斜、遮挡、反光车牌的定位鲁棒性提升明显。更重要的是其Backbone中C2f模块的梯度流设计让11类长尾分布警车、使领馆样本极少的loss收敛更稳定——我们在自建数据集上对比YOLOv5s在黄牌mAP0.5为71.2%YOLOv8n达79.6%新能源车牌因字符间距大、底色渐变YOLOv5s仅64.3%YOLOv8n达75.1%。实际选型时我们放弃YOLOv8s参数量翻倍但mAP仅1.2%用YOLOv8n平衡速度与精度。2.2 数据准备11类车牌的标注规范与增强策略标注必须严格遵循《GA 36-2018》标准重点处理三类易错点双层黄牌上层为汉字字母数字如“粤B12345”下层为纯数字如“678”需标注两个独立bbox且type_id3双层黄牌新能源绿牌小型新能源为“粤B D12345”大型为“粤B D123456”注意“D/F”字母后接6/7位数字type_id10港澳车牌粤Z开头后接4位数字1个英文字母如“粤Z KH999”但“港”“澳”字样在车牌右下角独立小标需单独标注type_id8/9。我们采用LabelImg 自定义插件生成YOLO格式标签关键代码如下# convert_to_yolo.py: 将GA36标准标注转为YOLOv8要求的txt格式 import xml.etree.ElementTree as ET import os def ga36_to_yolo(xml_path, img_w, img_h, class_map): tree ET.parse(xml_path) root tree.getroot() yolo_lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in class_map: continue # 跳过非11类车牌 cls_id class_map[cls_name] bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # YOLO格式cls_id center_x center_y width height (归一化) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return yolo_lines # class_map示例按YOLOv8要求索引0~10对应11类 class_map { blue: 0, # 小型汽车蓝牌 yellow: 1, # 大型汽车黄牌 trailer_yellow: 2, # 挂车黄牌 double_yellow: 3, # 双层黄牌 agricultural: 4, # 农用车牌 police: 5, # 警车 school_bus: 6, # 校车 coach: 7, # 教练车 hongkong: 8, # 港澳车牌 embassy: 9, # 使领馆 new_energy: 10 # 新能源车牌 }提示class_map顺序必须与data.yaml中names列表严格一致否则训练时类别错位。我们实测发现若将“new_energy”放在索引0模型会把所有绿牌预测为蓝牌——因为权重初始化默认偏向高频类别错序导致先验偏移。2.3 训练配置针对11类长尾分布的关键参数调整YOLOv8默认配置对长尾类别不友好我们修改data.yaml和训练命令# data.yaml train: ../datasets/train/images val: ../datasets/val/images nc: 11 # 必须为11不可省略 names: [blue, yellow, trailer_yellow, double_yellow, agricultural, police, school_bus, coach, hongkong, embassy, new_energy]训练命令关键参数yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs150 \ batch32 \ imgsz640 \ lr00.01 \ lrf0.01 \ cos_lrTrue \ augmentTrue \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ degrees10 \ translate0.1 \ scale0.5 \ shear0 \ perspective0.0005 \ mosaic1.0 \ mixup0.1 \ copy_paste0.1 \ auto_augmentrandaugment \ fliplr0.5 \ flipud0.0 \ erasing0.4 \ crop_fraction0.8 \ close_mosaic10 \ save_period10 \ patience30 \ device0 \ workers8 \ projectruns/detect \ nameplate_11class参数说明mosaic1.0mixup0.1强制启用马赛克增强解决小目标双层黄牌下层漏检erasing0.4随机擦除40%区域模拟车牌被泥污、树枝遮挡hsv_s0.7hsv_v0.4大幅增强饱和度与明度扰动对抗夜间补光不均导致的黄牌发灰、绿牌褪色close_mosaic10最后10轮关闭马赛克让模型专注学习单图细节patience30早停耐心设为30轮避免在长尾类别上过早终止验证集mAP波动大。我们实测发现若关闭erasing农用车牌常被泥土覆盖的召回率下降12.3%若hsv_v设为0.1则夜间黄牌检测F1-score从0.72跌至0.58。3. CRNNAttention车牌识别如何让模型区分“粤B D12345”和“粤B D123456”3.1 为什么不用端到端检测识别如PP-OCR而选CRNNAttentionPP-OCR在通用场景强但对车牌字符集有硬伤它预置字符集含26字母10数字31省市简称共67字符而中国车牌实际需支持汉字京、津、冀…31个 “警”“学”“使”“领”4个 “港”“澳”2个 37个字母除I、O外24个防混淆 新能源专用D/F 26个数字0~910个特殊符号无。总计73字符PP-OCR默认字典缺“港”“澳”“使”“领”且无法区分“粤Z KH999”中“KH”是英文字母还是汉字“科”“合”需按位置约束。CRNNAttention可自定义字符集且Attention机制能强制模型按“左→右”顺序聚焦每个字符对新能源8位长序列如“粤B D123456”的识别准确率比CTC高9.2%。3.2 字符集构建与Attention解码器设计我们定义字符集CHARS为73字符顺序必须与模型输出logits维度严格对应# crnn_model.py CHARS [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, 京,沪,津,渝,冀,晋,辽,吉,黑,苏, 浙,皖,闽,赣,鲁,豫,鄂,湘,粤,桂, 琼,川,贵,云,藏,陕,甘,青,宁,新, 警,学,使,领,港,澳] # 注意I/O被剔除港澳放在末尾确保Attention decoder能正确对齐Attention解码器核心逻辑PyTorchclass AttentionDecoder(nn.Module): def __init__(self, input_size, hidden_size, num_classes, max_len10): super().__init__() self.hidden_size hidden_size self.num_classes num_classes self.max_len max_len self.embedding nn.Embedding(num_classes, hidden_size) # 字符嵌入 self.lstm nn.LSTM(hidden_size, hidden_size, batch_firstTrue) self.attention nn.Sequential( nn.Linear(hidden_size * 2, 256), nn.Tanh(), nn.Linear(256, 1) ) self.classifier nn.Linear(hidden_size * 2, num_classes) def forward(self, encoder_out, targetsNone): # encoder_out: [B, T, C] - CNN提取的特征序列 batch_size encoder_out.size(0) # 初始化decoder输入GO token (索引0) decoder_input torch.zeros(batch_size, dtypetorch.long, deviceencoder_out.device) decoder_hidden (torch.zeros(1, batch_size, self.hidden_size, deviceencoder_out.device), torch.zeros(1, batch_size, self.hidden_size, deviceencoder_out.device)) outputs [] for t in range(self.max_len): # Step 1: Embed current char embedded self.embedding(decoder_input) # [B, H] embedded embedded.unsqueeze(1) # [B, 1, H] # Step 2: LSTM with attention lstm_out, decoder_hidden self.lstm(embedded, decoder_hidden) # [B, 1, H] # Compute attention weights over encoder features attn_weights self._attention_weights(lstm_out.squeeze(1), encoder_out) # [B, T] context torch.bmm(attn_weights.unsqueeze(1), encoder_out).squeeze(1) # [B, C] # Step 3: Concatenate and classify output self.classifier(torch.cat([lstm_out.squeeze(1), context], dim1)) # [B, num_classes] outputs.append(output) # Step 4: Teacher forcing or greedy decode if targets is not None and random.random() 0.5: decoder_input targets[:, t] # 使用真实标签 else: decoder_input output.argmax(dim1) # 使用预测 return torch.stack(outputs, dim1) # [B, max_len, num_classes] def _attention_weights(self, decoder_hidden, encoder_out): # decoder_hidden: [B, H], encoder_out: [B, T, C] # Expand decoder_hidden to [B, T, H] for concat decoder_expanded decoder_hidden.unsqueeze(1).expand(-1, encoder_out.size(1), -1) # Concat and compute attention score energy torch.cat([decoder_expanded, encoder_out], dim2) # [B, T, HC] attn_energies self.attention(energy).squeeze(2) # [B, T] return F.softmax(attn_energies, dim1)注意max_len10是安全上限最长为港澳车牌“粤Z KH999”共8字符2个空格实际解码时用EOS截断。我们测试发现若max_len设为8新能源8位车牌如“粤B D123456”末位“6”常被截断——因为Attention在第8步尚未收敛需留2位缓冲。3.3 针对11类车牌的字符长度约束与后处理不同车牌字符数固定可作为强约束提升准确率车牌类型字符数示例约束逻辑蓝牌7粤B12345前1汉字1字母5数字黄牌7粤B12345同蓝牌但字体更大双层黄牌上层7下层3~4粤B12345678分开识别下层仅数字新能源8粤B D12345前1汉字1字母1空格1字母5数字港澳8粤Z KH999前2字母1空格2字母3数字使领馆7使123456“使/领”6数字后处理代码强制校验def post_process(recognized, plate_type): 根据plate_type修正识别结果 if plate_type blue or plate_type yellow: # 强制7位汉字字母5数字 if len(recognized) ! 7: recognized recognized[:7].ljust(7, 0)[:7] # 修正常见错误0-O, 1-I recognized recognized.replace(O, 0).replace(I, 1) return recognized[:1] recognized[1:2] recognized[2:].replace( , ) elif plate_type new_energy: # 新能源汉字字母空格字母5/6数字 → 统一取8位 if len(recognized) 8: recognized recognized.ljust(8, 0)[:8] elif len(recognized) 8: recognized recognized[:8] # 确保第3位为空格第4位为D/F if len(recognized) 4 and recognized[3] not in [D, F]: recognized recognized[:3] D recognized[4:] return recognized elif plate_type hongkong: # 港澳粤Z空格2字母3数字 → 共8字符 if len(recognized) 8: recognized recognized.ljust(8, 0)[:8] # 强制格式粤Z KH999 parts recognized.split() if len(parts) 2: prefix parts[0][:4].ljust(4, Z) # 确保粤Z suffix parts[1][:5].rjust(5, 0)[:5] # 2字母3数字 recognized f{prefix} {suffix} return recognized return recognized我们实测加入此约束后新能源车牌识别准确率从82.3%升至91.7%港澳车牌从76.5%升至88.2%——因为模型本身易把“粤Z KH999”识别成“粤ZK H999”后处理强制空格位置直接修复。4. 多类别车牌检测与识别的避坑指南血泪经验总结4.1 现象模型在验证集上蓝牌mAP 95%但部署到路口摄像头后黄牌漏检严重原因训练数据用高清合成图而真实黄牌在阴天反光弱、字体粗RGB直方图偏黄偏暗。YOLOv8默认HSV增强中hsv_h0.015色相扰动太小无法覆盖黄牌在不同光照下的色相漂移实测黄牌色相角从20°到45°波动。解决将hsv_h从0.015提升至0.05并在数据加载时添加RandomGamma增强gamma∈[0.7,1.3]模拟不同曝光补偿。4.2 现象双层黄牌下层数字识别为“6789”但实际是“678”原因标注时将上下层合并为一个bbox模型学到的是“整体纹理”而非“两行独立文本”。CRNN输入是整块ROI未做行分割。解决检测阶段输出两个bboxtype_id3时强制拆分识别阶段对下层ROI单独送入CRNN且下层字符集仅限数字[0,1,...,9]减少干扰。4.3 现象新能源车牌“粤B D123456”识别成“粤B D12345”丢最后一位原因Attention decoder在第8步注意力权重分散且max_len8无缓冲CTC loss对末位惩罚不足。解决max_len设为10训练时用PAD填充至10位推理时取前8位但用Beam Searchbeam_width3替代Greedy Decode提升末位置信度。4.4 现象使领馆车牌“使123456”被识别为“便123456”原因“使”字在训练集中样本仅23张远少于蓝牌的12万张且字体与“便”相似手写体易混淆。模型未学到“使”字结构特征。解决对“使”“领”“警”“学”等稀有字用StyleGAN2生成1000张不同字体、大小、模糊度的合成图加入训练集同时在损失函数中为这些类别加权weight3.0。4.5 现象OpenVINO部署后FPS从30掉到12CPU占用率98%原因默认FP32推理未启用INT8量化且YOLOv8n的Detect层未做算子融合。解决用pot工具进行Accuracy-aware Quantization非简单INT8保留关键层FP16在OpenVINO Model Optimizer中添加--reverse_input_channels适配BGR输入将CRNN的LSTM替换为ONNX Runtime支持的GRUYOLOv8nCRNN GRU组合在i5-1135G7上达28FPS。5. 实战技巧如何用30行代码完成11类车牌的端到端推理与结果可视化5.1 构建最小可运行推理管道核心是解耦检测与识别避免模型耦合导致调试困难。我们封装为PlateDetector和PlateRecognizer两个类# infer_pipeline.py import cv2 import numpy as np from ultralytics import YOLO from crnn_model import CRNNRecognizer class PlateDetector: def __init__(self, weightsweights/yolov8n_plate.pt): self.model YOLO(weights) self.class_names [blue, yellow, trailer_yellow, double_yellow, agricultural, police, school_bus, coach, hongkong, embassy, new_energy] def detect(self, img): results self.model(img, conf0.4, iou0.5, verboseFalse) boxes [] for r in results: for box in r.boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) cls_id int(box.cls[0]) conf float(box.conf[0]) if cls_id len(self.class_names): boxes.append({ bbox: [x1, y1, x2, y2], type: self.class_names[cls_id], conf: conf, cls_id: cls_id }) return boxes class PlateRecognizer: def __init__(self, weightsweights/crnn_atten.onnx): self.session ort.InferenceSession(weights) self.chars [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 recognize(self, roi_img, plate_type): # 预处理统一尺寸灰度归一化 roi_gray cv2.cvtColor(roi_img, cv2.COLOR_BGR2GRAY) roi_resized cv2.resize(roi_gray, (128, 32)) # CRNN输入尺寸 roi_norm roi_resized.astype(np.float32) / 255.0 roi_input np.expand_dims(np.expand_dims(roi_norm, 0), 0) # [1,1,32,128] # ONNX推理 pred self.session.run(None, {input: roi_input})[0] # [1,10,73] pred_text for t in range(10): char_idx np.argmax(pred[0, t]) if char_idx len(self.chars) and self.chars[char_idx] ! PAD: pred_text self.chars[char_idx] return post_process(pred_text, plate_type) # 调用3.3节后处理 # 使用示例 detector PlateDetector() recognizer PlateRecognizer() img cv2.imread(test.jpg) boxes detector.detect(img) for box in boxes: x1, y1, x2, y2 box[bbox] roi img[y1:y2, x1:x2] text recognizer.recognize(roi, box[type]) # 可视化 cv2.rectangle(img, (x1, y1), (x2, y2), (0,255,0), 2) cv2.putText(img, f{box[type]}:{text}, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2) cv2.imwrite(result.jpg, img)关键点post_process必须传入box[type]否则无法应用字符长度约束。我们曾因忘记传参导致所有车牌都按蓝牌7位截断新能源车牌全错。5.2 可视化增强用颜色编码区分11类车牌为快速验证检测效果我们为11类车牌分配专属颜色基于HSV色环均匀采样类型BGR颜色说明blue(255,0,0)纯蓝yellow(0,255,255)黄double_yellow(0,165,255)橙new_energy(0,255,0)绿hongkong(128,0,128)紫embassy(255,0,255)品红police(0,0,255)红在绘制bbox时动态选择COLOR_MAP { blue: (255,0,0), yellow: (0,255,255), trailer_yellow: (0,165,255), double_yellow: (0,165,255), agricultural: (0,255,0), police: (0,0,255), school_bus: (255,165,0), coach: (255,215,0), hongkong: (128,0,128), embassy: (255,0,255), new_energy: (0,255,0) } # 绘制时 color COLOR_MAP.get(box[type], (255,255,255)) # 默认白色 cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)这样在监控画面中一眼就能看出黄牌黄色框是否被误检为蓝牌蓝色框新能源绿牌绿色框是否被漏检。5.3 性能压测在Jetson Nano上跑通11类车牌全流程我们实测Jetson Nano2GB RAM4核ARM A57的极限输入1280×720视频流H.264检测YOLOv8n INT8模型24FPS识别CRNN GRU ONNX INT8单ROI平均120ms瓶颈识别耗时占总Pipeline 83%故采用异步识别——检测出bbox后用多线程队列预取下一帧ROI当前帧识别与下一帧检测并行。最终达成平均FPS18.3含IO等待CPU占用82%未超频内存占用1.6GB关键技巧ROI裁剪用cv2.getRectSubPix替代切片减少内存拷贝CRNN输入尺寸从128×32改为96×32精度降0.7%速度升35%对同一车牌连续3帧识别结果投票避免单帧抖动。我坚持在交付前必做三件事用手机拍100张真实场景图含雨雾、逆光、夜间、用海康IPC导出2小时卡口录像抽帧测试、在Jetson Nano上连续跑48小时压力测试。去年有个项目客户说“黄牌识别准”我坚持加测了凌晨3点的低照度路段结果发现黄牌mAP从82%暴跌到41%——连夜加了hsv_v0.4和RandomGamma才保住交付。希望帮到你。本文还有配套的精品资源点击获取
返回列表