ARTICLE DETAIL

资讯详情

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

中文车牌识别实战:10类车牌检测与CRNN+CTC识别方案

中文车牌识别实战:10类车牌检测与CRNN+CTC识别方案 简介这是一套基于Python实现的中文多类型车牌检测与识别系统源码面向计算机视觉初学者、智能交通项目开发者及深度学习实践者解决复杂场景下蓝牌、黄牌、双层黄牌、农用车牌、警车、校车、教练车、港澳车牌、使领馆车牌及新能源绿牌等10余类车牌的端到端识别问题适用于交通监控、停车场管理、执法辅助等实际场景。资源包共159个文件含42个核心Python脚本含模型训练、推理、预处理模块、36张实测车牌图像JPG格式、19个配置YAML文件定义数据集路径、模型超参等、8个PNG图标与界面资源以及Dockerfile、CCPD2019数据集子目录、.pth模型权重和C扩展模块box_overlaps.c等整体压缩包大小为36.51MB。已有513人学习下载提供完整可运行流程从OpenCV图像预处理、Hough/Canny边缘定位、轮廓筛选裁剪到CNN字符识别与新能源车牌特化适配代码结构清晰、模块解耦附带README说明与典型测试样例便于理解算法逻辑、调试优化及二次开发。1. 这不是“调个OCR就能用”的玩具项目Python中文车牌检测和识别系统真要跑通蓝牌、黄牌、双层黄牌、农用车、警车、校车、教练车、港澳、使领馆、新能源等10类车牌得过三关——检测框得准尤其双层黄牌易切错、字符分割得稳新能源车牌“粤B D12345F”里那个F常被切飞、识别模型得泛化农用车牌锈蚀警车反光校车泥点同一套权重根本扛不住我去年在高速ETC后端做车牌复核时踩过坑用OpenCVTesseract硬上对标准蓝牌准确率82%但一遇到广东双层黄牌上黄下黑就漏检换YOLOv5s训了3000张图检测框倒是准了可字符识别环节把“粤Z HK123*港”里的星号识别成“0”导致港澳车牌全量误判最后落地的方案是检测用PP-YOLOEPaddleDetection开源模型对小目标和多尺度车牌鲁棒性强识别用CRNNCTC自建中文字符集含“粤Z”“使”“领”“警”“学”“教”“农”“港”“澳”及新能源专用字母“D/F”再加一层规则引擎兜底——比如检测到“粤Z”开头末尾带“港/澳”字强制走港澳分支。这套组合不是炫技是实打实为10类车牌的物理差异尺寸、反光、锈蚀、双层结构、字体间距设计的。适合想落地真实场景的工程师不适合只跑通demo就交差的学生作业。2. 检测模型选型与训练为什么PP-YOLOE比YOLOv5/v8更适合中文车牌多类别场景2.1 多类别车牌的检测难点直接决定模型选型生死线中文车牌类型差异极大蓝牌小型汽车440mm×140mm字符紧凑边缘清晰黄牌大型车/挂车440mm×220mm常有污损反光强双层黄牌如工程车上层黄底黑字440×100下层黄底黑字440×100两行间距不固定YOLO系列易将两行合并为单框或漏检下层新能源车牌绿牌480mm×140mm首字母固定为“D/F”但“D”与“O”、“F”与“E”在低分辨率下极易混淆港澳车牌粤Z HK123港含星号“”、中英文混排、无分隔符传统CTPN类文本检测器会把“*”当噪声过滤。PP-YOLOEPaddlePaddle官方维护在这些场景有硬优势内置Anchor-Free 自适应Anchor生成机制对双层黄牌这种非标宽高比目标召回率比YOLOv5高12.7%实测mAP0.5ESE Attention模块对反光区域警车镀铬边框、校车金属漆面特征增强明显误检率降低9.3%PaddleDetection框架原生支持多任务头可同时输出检测框车牌朝向角用于后续矫正省去OpenCV仿射变换的玄学参数调试。2.2 数据准备10类车牌标注规范与增强策略必须按PaddleDetection要求组织数据dataset/ ├── train/ │ ├── images/ # JPG格式命名无空格例blue_001.jpg │ └── labels/ # TXT格式每行class_id center_x center_y width height归一化坐标 ├── val/ │ ├── images/ │ └── labels/ └── label_list.txt # 每行一个类别顺序必须与label文件class_id严格对应label_list.txt内容10类严格按此顺序blue yellow double_yellow agricultural police school_bus coach_car hk_macao embassy_consulate new_energy提示双层黄牌不能标成两个框必须标为单个大框覆盖上下两行否则PP-YOLOE的Anchor-Free机制会因目标尺度突变而震荡。我们实测发现标单框后续用OCR定位字符行比标双框拼接更稳定。关键增强策略写入configs/ppyoloe/ppyoloe_crn_l_300e_coco.ymlTrainDataset: dataset: type: VOCDetection dataset_dir: dataset anno_path: train.txt # 内容images/xxx.jpg labels/xxx.txt label_list: label_list.txt transforms: - type: MixUp # 解决农用车牌样本少问题mixup ratio0.5 - type: Mosaic # 强化小目标使领馆车牌常占画面3% - type: RandomDistort # 色彩抖动模拟不同光照校车晨雾/警车正午强光 - type: RandomRotate # ±5°旋转防双层黄牌倾斜漏检 - type: Resize # 固定640×640避免PP-YOLOE多尺度训练时显存爆炸2.3 训练命令与关键参数调优# 安装PaddlePaddleCUDA 11.2 cuDNN 8.2避坑见第4章 pip install paddlepaddle-gpu2.4.2.post112 # 克隆PaddleDetectionv2.5分支兼容PP-YOLOE git clone https://github.com/PaddlePaddle/PaddleDetection.git cd PaddleDetection # 启动训练单卡V100batch_size8 python tools/train.py \ -c configs/ppyoloe/ppyoloe_crn_l_300e_coco.yml \ --eval \ --use_vdl \ --save_interval 10 \ --seed 42 \ --optimal_batch_size 8 \ --enable_ce False必须调整的3个参数--optimal_batch_size 8PP-YOLOE默认batch_size16在V100上OOM设为8后显存占用从23GB降至16GB--seed 42车牌检测对随机性敏感固定seed保证结果可复现尤其双层黄牌漏检率波动±5%--enable_ce False关闭CEContinuous Evaluation模式否则val阶段会卡死——这是PaddleDetection v2.5已知bug。训练300 epoch后验证集指标mAP0.5应达类别mAP0.5关键问题blue98.2%无double_yellow92.7%下层字符模糊时易漏检需靠OCR补救hk_macao89.1%“*”符号识别率低依赖后续OCR字符级修正new_energy95.3%“D/F”混淆率3.2%靠规则引擎拦截3. 字符识别流水线CRNNCTC为何比端到端模型更适配10类车牌混合场景3.1 为什么不用端到端如YOLOv8OCR血泪经验告诉你去年试过YOLOv8nPaddleOCR的端到端方案检测识别一步到位。结果在使领馆车牌上翻车——“使”字在低分辨率下被识别为“便”“领”字被切进相邻字符。原因在于YOLOv8的检测头对“使领馆”这种窄高型字符宽高比≈1:3定位不准框偏移导致OCR输入图歪斜PaddleOCR的DBNet文本检测器对车牌这种无空白分隔的连续字符串如“京A12345”易过切把“12”切成“1”和“2”中间插入空格新能源车牌“粤B D12345F”的空格位置不固定有空格/无空格/空格在D前端到端模型无法泛化。CRNNCTC方案拆解为三步检测框矫正用HoughLinesP检测车牌四边形透视变换归一化为480×120字符分割基于投影法水平投影找字符行垂直投影切单字对双层黄牌先分上下行再逐行切字CRNN识别CNN提取特征 RNN建模时序 CTC解码天然适配不定长字符串“粤Z HK123*港”共10字符“京A12345”仅7字符。3.2 CRNN模型构建字符集必须覆盖10类车牌所有可能字符字符集dict.txt共79个字符不可增删京沪津渝冀晋蒙辽吉黑苏浙皖闽赣鲁豫鄂湘粤桂琼川贵云藏陕甘青宁新兵团使领馆粤港澳学警教农D F 0123456789ABCDEFGHJKLMNPQRSTUVWXYZ注意“I”和“O”不包含在内——国内车牌禁用这两个易混淆字母“粤Z HK123港”中的“”必须保留且位置固定在倒数第二位港澳车牌规则。CRNN网络结构PyTorch实现crnn_model.pyimport torch.nn as nn class CRNN(nn.Module): def __init__(self, nclass, nh256): # nclass79, nhRNN隐藏层维度 super(CRNN, self).__init__() self.cnn nn.Sequential( nn.Conv2d(1, 64, 3, 1, 1), nn.ReLU(True), nn.MaxPool2d(2, 2), nn.Conv2d(64, 128, 3, 1, 1), nn.ReLU(True), nn.MaxPool2d(2, 2), nn.Conv2d(128, 256, 3, 1, 1), nn.BatchNorm2d(256), nn.ReLU(True), nn.Conv2d(256, 256, 3, 1, 1), nn.ReLU(True), nn.MaxPool2d((2, 1)), # 关键纵向压缩保留字符高度信息 nn.Conv2d(256, 512, 3, 1, 1), nn.BatchNorm2d(512), nn.ReLU(True), nn.Conv2d(512, 512, 3, 1, 1), nn.ReLU(True), nn.MaxPool2d((2, 1)), nn.Conv2d(512, 512, 2, 1, 0), nn.BatchNorm2d(512), nn.ReLU(True) # 输出512x1xWW为时间步 ) self.rnn nn.Sequential( nn.LSTM(512, nh, bidirectionalTrue, batch_firstTrue), nn.LSTM(nh*2, nh, bidirectionalTrue, batch_firstTrue) ) self.embedding nn.Linear(nh*2, nclass) # CTC需要nclass1blank def forward(self, x): x self.cnn(x) # [B, 512, 1, W] → [B, 512, W] x x.squeeze(2).permute(0, 2, 1) # [B, W, 512] x, _ self.rnn(x) # [B, W, nh*2] x self.embedding(x) # [B, W, nclass1] return x关键设计点CNN末层Conv2d(512, 512, 2, 1, 0)卷积核2×2精准压缩至1行特征避免传统ResNet导致的W维度不可控LSTM双层双向捕获字符间强依赖如“粤Z”必连“港”必在末尾embedding输出维度为nclass1CTC解码必需blank标签占位。3.3 训练与推理CTC Loss与解码规则必须手写训练脚本核心逻辑train_crnn.pyimport torch.nn.functional as F # CTC Loss计算注意targets需pad到统一长度 loss F.ctc_loss( log_probslogits, # [W, B, nclass1]W为时间步需log_softmax targetstargets, # [B, max_len]值为0~nclass-1不含blank input_lengthsinput_lengths, # [B]每个样本实际时间步 target_lengthstarget_lengths, # [B]每个样本真实字符数 blanknclass, # blank索引nclass即最后一维 reductionmean ) # 推理时CTC解码去除blank重复 def ctc_decode(pred): pred pred.argmax(-1).cpu().numpy() # [W] tokens [] for t in pred: if t ! nclass and (len(tokens) 0 or t ! tokens[-1]): # 去blank去重 tokens.append(t) return .join([char_dict[i] for i in tokens])字符字典映射char_dict必须与dict.txt行号严格对应with open(dict.txt, r, encodingutf-8) as f: char_dict {i: ch.strip() for i, ch in enumerate(f.readlines())} # char_dict[0]京, char_dict[1]沪, ..., char_dict[78]F4. 避坑指南10类车牌识别中踩过的5个致命坑第3个让团队加班三天4.1 现象双层黄牌检测框完美但OCR识别结果只有上层字符下层全丢原因PP-YOLOE输出的检测框是矩形但双层黄牌实际是两个独立文本行。直接crop矩形区域会导致下层字符被截断尤其当车牌倾斜时。解决在检测后增加HoughLinesP直线检测拟合车牌四边形再用cv2.getPerspectiveTransform做透视矫正。代码片段def warp_perspective(img, box): # box: [(x1,y1), (x2,y2), (x3,y3), (x4,y4)] 顺时针四点 pts1 np.float32(box) pts2 np.float32([[0,0], [480,0], [480,120], [0,120]]) # 统一归一化尺寸 M cv2.getPerspectiveTransform(pts1, pts2) warped cv2.warpPerspective(img, M, (480,120)) return warped # 调用前确保box四点按顺时针排序用cv2.minAreaRect返回的box需手动排序4.2 现象新能源车牌“粤B D12345F”识别成“粤B D123450”F→0原因训练数据中“F”样本不足仅占新能源车牌3.2%且“F”与“0”在低分辨率下纹理相似。解决在数据增强中加入RandomAffine(degrees0, translate(0.1,0.1), scale(0.8,1.2), shear0)重点增强“F”的形变鲁棒性规则引擎兜底识别后检查末尾字符若为“0”且前一位是数字则触发二次识别裁剪末尾区域单独送CRNN。4.3 现象港澳车牌“粤Z HK123港”识别为“粤Z HK123港”星号“”消失原因CTC解码时模型对“”的置信度低于blank被自动过滤。解决修改CTC解码逻辑**强制保留“”位置**def ctc_decode_with_star(pred): pred pred.argmax(-1).cpu().numpy() tokens [] for t in pred: if t nclass: continue # skip blank if t char_dict_inv[*]: # char_dict_inv{*:77} tokens.append(t) elif len(tokens) 0 or t ! tokens[-1]: tokens.append(t) # 强制在倒数第二位插入*港澳车牌规则 if len(tokens) 2 and tokens[-1] in [char_dict_inv[港], char_dict_inv[澳]]: tokens.insert(-1, char_dict_inv[*]) return .join([char_dict[i] for i in tokens])4.4 现象农用车牌锈蚀严重时检测框飘移OCR输入图全是噪点原因PP-YOLOE的ESE Attention对锈蚀区域特征抑制过度导致框定位偏移。解决在检测后增加形态学闭运算预处理kernel np.ones((3,3), np.uint8) img_clean cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) # 仅对检测框内区域做闭运算避免影响全局 x1,y1,x2,y2 int(box[0]), int(box[1]), int(box[2]), int(box[3]) roi img_clean[y1:y2, x1:x2] roi_clean cv2.morphologyEx(roi, cv2.MORPH_CLOSE, kernel) img_clean[y1:y2, x1:x2] roi_clean4.5 现象使领馆车牌“使A12345”识别为“便A12345”“使”→“便”原因“使”字在车牌字体中笔画粘连投影法分割时与“A”连成一体。解决字符分割阶段引入轻量级UNet训练一个二分类模型前景/背景输出字符掩膜再用cv2.connectedComponents精确提取单字轮廓。UNet输入为归一化车牌图输出为单通道概率图阈值0.5二值化。5. 规则引擎与业务闭环如何用200行代码把10类车牌识别准确率从89%拉到98.7%5.1 规则引擎设计原则只纠“确定性错误”不碰“模糊地带”规则引擎不是万能的它的使命是拦截模型明确犯错但人类一眼能判的case。例如检测到“使”字开头但识别结果不含“使” → 强制重识别新能源车牌末尾是“D/F”但识别结果末尾是数字 → 触发末尾字符重识别港澳车牌含“粤Z”但识别结果无“粤Z” → 直接返回“粤Z XXXX*港”模板需人工复核。绝不做对“京A12345”和“京A12346”这种相似结果做概率修正模型置信度已足够用规则强行改写“粤B D12345F”为“粤B D12345F”冗余。5.2 核心规则表10类车牌的硬约束与软约束类别硬约束违反则重识别软约束触发置信度加权blue长度7首字符∈[京沪津...]第2字符∈[A-Z]后5字符∈[0-9A-Z]第2字符为“A”时置信度×1.2蓝牌A号段占比63%double_yellow长度7含“挂”字或末尾为“挂”上下两行字符数差≤1双层结构对称性hk_macao必含“粤Z”“*”“港/澳”总长10“*”位置必须为倒数第二位new_energy首字符∈[粤苏浙...]第2字符D/F末字符∈[D/F0-9]D/F后必须接数字“粤B D12345F”合法“粤B DF12345”非法police必含“警”字且“警”在首或第二位“警”字后紧跟数字“警123456”5.3 规则引擎代码实现rule_engine.pydef apply_rules(recognized_text, plate_type, confidence): # 输入识别文本、车牌类型、各字符置信度列表 if plate_type hk_macao: if 粤Z not in recognized_text or * not in recognized_text or not (recognized_text.endswith(港) or recognized_text.endswith(澳)): # 硬约束失败触发重识别 return re_recognize_hk_macao() if plate_type new_energy: if len(recognized_text) 7: return re_recognize_new_energy() # 检查D/F位置 if recognized_text[1] not in [D, F]: # 软约束D/F应在第二位否则降低置信度 confidence * 0.7 if plate_type police: if 警 not in recognized_text[:3]: # “警”应在前3位 confidence * 0.5 # 所有规则通过返回原始结果 return recognized_text, confidence # 重识别函数只针对特定case优化 def re_recognize_hk_macao(): # 1. 强制在“粤Z”后切分单独识别“HK123*港” # 2. 对“*”区域做超分辨率重建ESRGAN轻量版 # 3. CRNN识别时targets强制包含“*”标签 pass5.4 准确率提升实测对比某省高速卡口2000张真实图指标无规则引擎规则引擎提升整体准确率89.2%98.7%9.5%港澳车牌准确率76.3%94.1%17.8%双层黄牌准确率83.5%92.9%9.4%平均耗时320ms/张345ms/张25ms可接受我的习惯是规则引擎代码写完后用100张最难样本锈蚀/反光/倾斜做压力测试确保每条规则只触发该修的case。曾有一条“末尾为‘挂’则判双层黄牌”的规则误伤了蓝牌“京A12345挂”某地二手车临时牌照花两天回溯日志才定位。现在我的规则上线前必过三关1规则逻辑白板推演2100张badcase回归3AB测试流量1%灰度。希望帮到你。本文还有配套的精品资源点击获取
返回列表