FEATURED · 精选文章

复杂环境下车牌识别实战:YOLO检测、透视校正与LPRNet识别的完整技术方案

发布时间 / 2026/9/17 1:17:38
来源 / 创域科博编辑部
栏目 / 资讯中心
复杂环境下车牌识别实战:YOLO检测、透视校正与LPRNet识别的完整技术方案 先聊点实际的。停车场出口那根道闸、高速路口的电子屏、违章抓拍系统背后都离不开车牌识别。做了这么多年计算机视觉项目我越来越觉得车牌识别是个很“诚实”的方向你在标准数据集上跑出99%的准确率不算本事放到真实环境下——逆光、夜间、倾斜、模糊、泥污——还能稳定输出正确车牌那才是真功夫。今天这篇博文是对我做过的“基于复杂环境下的车牌识别”项目做一次完整复盘。我会把整个项目的技术路线、模块设计、训练细节、踩坑过程都摊开来讲包括为什么传统图像处理方法在复杂环境下不顶用、怎么用YOLO做车牌检测、怎么用透视几何把歪斜的车牌摆正、以及怎么训练一个不依赖字符分割的整牌识别网络。如果你正准备做计算机视觉大作业或者刚入门CV想找一个能串起检测、几何校正、序列识别、工程部署的完整项目又或者在一线做落地时被真实场景折磨得够呛那这篇内容应该能给你省下不少时间。1. 项目定位与技术路线选型1.1 复杂环境到底复杂在哪很多人第一次做车牌识别都是从网上下一个干净的车牌图片集跑一跑模板匹配或者调一下OCR库看到识别成功就觉得自己已经会了。但真实场景里的车牌识别难点根本不在于“看清楚车牌”而在于“在乱七八糟的环境里看清车牌”。我把实际项目里遇到的环境干扰归成四类每类都会让算法崩溃环境因素典型表现对算法的影响光照问题白天强反光、夜间低照度、逆光、灯箱照射二值化阈值失效、字符对比度低、颜色信息不可靠几何问题相机俯仰角大、车牌相对镜头有明显透视畸变检测框内车牌是平行四边形/梯形字符拉伸变形成像质量运动模糊、雨雾天气、车牌泥污、低分辨率字符边缘模糊、细节丢失分割和识别都受影响背景干扰车身广告文字、保险杠纹理、周围行人和树木检测模块产生误检把非车牌区域当车牌四类问题叠在一起才是真实场景的常态。比如夜间地库入口车灯直射镜头、车牌反光严重同时相机安装角度很高、车牌在画面里还是个斜的。这种场景跑一次就知道了标准流程基本全线崩溃。1.2 传统图像处理为什么搞不定先说说过去教科书里最经典的流程灰度化 → 边缘检测Sobel/Canny→ 形态学闭运算 → 轮廓查找 → 字符分割 → 模板匹配。这条路我在课程设计和早期项目里都走过原理清晰、计算量小、不依赖GPU做一个教学演示很合适。但它的短板太明显了。整个流程里有大量阈值参数边缘检测的阈值、形态学核的大小、二值化的阈值、连通域筛选的面积范围……这些参数是针对某一类固定场景调的一旦换环境就得重新调。夜间车灯一照Canny边缘图直接糊成一片车牌稍微倾斜一点字符分割就切歪汉字笔画稍复杂模板匹配就分不清“京”和“津”。我见过不少用Matlab做的车牌识别课程设计演示视频里效果还挺好因为演示环境是固定的。但拿到真实停车场一测识别率从95%掉到80%以下是很常见的有些场景甚至直接识别不出来。传统方法像拿着手电筒在黑屋子里找钥匙能照亮一条路但换个屋子、换个角度就找不到了。复杂环境下做车牌识别开局就得定调以深度学习为主体传统方法只做辅助。1.3 深度学习方案的整体框架我最终选定的技术路线是“检测 校正 识别”三段式车牌检测用YOLOv5s定位图像中的车牌区域输出一个外接矩形框和置信度。透视校正在检测框内通过透视几何分析把倾斜、变形的车牌区域摆正恢复成标准矩形。车牌识别用轻量级序列识别网络LPRNet对校正后的车牌图像输出字符序列附带颜色分类。为什么不选端到端的单阶段模型比如检测完直接输出车牌号因为工程上拆开更有利于调试。检测不准可以单独补检测数据识别不准可以单独去增强识别数据中间校正效果差也可以单独优化几何模块。三个模块各自可替换、可迭代这在项目迭代里非常重要。detect 用 YOLOv5s是因为它轻量、生态好能直接用COCO预训练权重做迁移学习识别用 LPRNet是因为中文车牌字符序列长度固定蓝牌7位、新能源绿牌8位用 CTC 损失做定长序列输出正好合适。选型逻辑后面细讲。2. 车牌检测模块从数据到模型训练2.1 数据集选择与标注格式转换做检测任务数据是第一道坎。中文车牌识别有一个绕不开的开源数据集——CCPDChinese City Parking Dataset包含超过20万张真实场景图片覆盖多种天气、光照、距离、角度并且有独立的 blur、rotate、weather 等子集非常适合用来研究复杂环境下的检测和识别。如果不想从零开始攒图CCPD 是个很好的起点。如果要用自己的数据就得走一遍标注流程。用 LabelImg 或 labelme 框出车牌区域导出 VOC 格式的 XML再转成 YOLO 训练需要的 txt 格式。转换公式不复杂def voc_to_yolo(x1, y1, x2, y2, img_w, img_h): x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h return x_center, y_center, w, h这里有个容易踩的坑划分训练集和验证集的时候要按图片所属场景混着切不能简单地按文件夹一刀切。比如 CCPD 里不同子集代表不同环境如果某个天气子集全被划进验证集训练时模型从没见过这种环境验证结果就会很难看你会误以为模型训练不到位其实只是数据划分不科学。类别就一类license_plate。单独一类的好处是检测模块聚焦在“找到车牌区域”这一个任务上负样本问题放到后处理阶段去管。2.2 YOLOv5s 训练的关键参数YOLOv5s 训练其实不玄学关键参数就那么几个我踩过一遍之后基本固定了输入分辨率默认 640但如果你场景里的车牌是中小目标比如远距离抓拍建议直接上 960 甚至 1280。分辨率越高小目标召回率越好代价是训练和推理变慢。预训练权重从yolov5s.ptCOCO 预训练开始迁移不要从零训练。车牌检测虽然是个新任务但底层特征边缘、纹理、对比度是通用的。超参数epoch 100~150batch size 16~32优化器用 SGD初始学习率 0.01。YOLOv5 自带的 mosaic、HSV 增强默认开着就够用了我再额外加了随机旋转 ±10° 和轻微透视扰动用来模拟相机安装角度差异。锚框不要一上来就调 anchorYOLOv5 会自动计算也可以直接用默认值。检测率上不去的原因大多是数据或分辨率而不是 anchor 没调好。训练过程中要重点盯这几个指标mAP0.5、mAP0.5:0.95、precision、recall。如果漏检多先提高分辨率或补充该场景的数据如果误检多先考虑加负样本或者在推理时把置信度阈值从 0.25 提到 0.4。2.3 检测后处理与部署注意项检测模型输出的是矩形框但这里有个细节车牌检测框允许适当外扩不要紧贴车牌边缘。因为下一步要做透视校正和字符识别框太紧会切掉部分字符宁可框大一点给后续模块留点操作空间。后处理方面NMS 的 IoU 阈值通常设在 0.45~0.5置信度阈值看具体场景。如果是固定机位、背景简单的道闸场景置信度阈值可以提到 0.5 以上来压误检如果是复杂街道场景还是保持在 0.25~0.3 比较保险避免漏检。在视频流里检测模块不要每帧都跑。配合一个轻量跟踪器比如 ByteTrack对检测框做跟踪只在跟踪丢失时才重新检测计算量能省下不少后面我会专门讲。3. 透视校正用透视几何把歪车牌摆正3.1 为什么不校正是真不行检测模块给出的通常是一个轴对齐的矩形框但真实场景里车牌在图像中往往是平行四边形或梯形。原因很简单相机光轴和车牌平面不垂直产生了透视变形。如果直接把这个歪斜的四边形区域裁出来送进识别网络字体会被拉伸、压扁、倾斜识别精度会肉眼可见地崩掉。极端情况下比如高机位俯拍的停车场相机车牌在画面里被压成一条细扁条不校正的话就算人眼都费劲。这里就用到计算机视觉里一个核心的几何概念透视变换。它的本质是把图像中的一个任意四边形映射成另一个四边形我们用固定点对把歪车牌“拉”成一个标准矩形。3.2 怎么拿到车牌四个角点要做透视校正第一步是找到车牌区域的四个角点。实际项目里常用的有三种方式语义分割 / 关键点检测网络直接回归4个角点。精度高但需要额外标注关键点数据。传统图像处理在检测框内做 Canny 边缘检测 轮廓查找 approxPolyDP多边形拟合找到最外层四边形。基于检测框坐标做外围扩边二值化后找最大轮廓再提取角点。我建议分两种情况对待。如果是课程设计或者大作业走思路2就够了原理好讲、代码量适中、效果可视化强。如果是真实工程场景车牌边框、铆钉、字符笔画都会干扰轮廓提取传统方法做出来的角点经常不稳定这时候直接上角点回归网络反而更省心。这个项目里我在前期原型验证时用思路2快速跑通了流程后期为了提升鲁棒性切到了关键点回归的方案。3.3 单应矩阵原理与 OpenCV 实现透视校正背后是单应矩阵Homography。它是一个 3x3 的矩阵8 个自由度4 对匹配点就能解出来。OpenCV 提供了现成的函数cv2.getPerspectiveTransform输入源4点和目标4点返回单应矩阵再用cv2.warpPerspective对图像做重采样。可以这样理解你站在不同角度拍同一张海报海报在画面里是各种不规则的四边形但只要你知道海报本身是矩形的就能算出从拍摄画面到海报正面的映射关系把它“拉正”。车牌校正也是同理。核心代码大概长这样import cv2 import numpy as np def perspective_correct(plate_crop, src_pts): # src_pts: 检测/轮廓得到的车牌4个角点顺序需固定左上、右上、右下、左下 h, w plate_crop.shape[:2] dst_pts np.float32([[0, 0], [w, 0], [w, h], [0, h]]) # 由4对点计算单应矩阵 H cv2.getPerspectiveTransform(src_pts, dst_pts) # 应用透视变换 warped cv2.warpPerspective(plate_crop, H, (w, h)) return warped如果轮廓查找得到的角点顺序乱记得按坐标排序统一否则映射结果会错乱。实际使用时校正后的车牌图我会统一 resize 到宽 240、高 80作为识别网络的输入尺寸。3.4 角点检测的坑与备选方案角点检测这一块是我在这个项目里花时间最多的地方坑也最多。第一个坑是车牌边框干扰。很多车牌外面有一圈白色或银色的边框Canny 检测后边框的直线和字符的笔画混在一起轮廓拟合出来的四边形可能框到边框外沿也可能框到字符区域。我的经验是先对检测框内区域做一次轻度腐蚀把细边框腐蚀掉再做边缘检测成功率会高不少。第二个坑是汉字轮廓干扰。车牌第一个字符是汉字笔画复杂在二值化后会产生大量小轮廓approxPolyDP拟合时容易选错四边形。解决办法是限定轮廓的面积比例只保留占整个检测框面积 30%~80% 的凸四边形同时检查四边形的四个角是否接近直角。第三个坑最隐蔽车牌被遮挡或者脏污角点根本提不出来。这种情况我选择直接退化成普通矩形裁切只做仿射变换或者干脆不校正别让校正模块变成整个识别链路的瓶颈。识别网络有一定鲁棒性能容忍少量变形强行校正出一个错误结果反而更糟。4. 车牌字符识别分割还是整牌识别4.1 曾经的字符分割法现在为什么不吃香了传统的字符识别思路是先分割再分类二值化 → 垂直投影 → 找波谷切分字符 → 模板匹配或小分类器识别。这个思路在规整的合成车牌上很好用但一到真实场景就露馅。核心痛点在于汉字。像“京”这种左右结构“津”这种左右偏旁分离的垂直投影很容易在中间断裂一个汉字被切成两块而像“川”“鄂”这种本身笔画离散的汉字投影波谷和字符边界混在一起分割点会漂移。再加上光照不均导致二值化断裂、模糊导致字符粘连分割法在复杂环境下没有稳定解。不是说分割法一无是处。当识别网络对超长宽比、特别窄的图像表现不稳定时分割 单字符分类可以作为兜底方案但要作为主方案建议慎重。4.2 LPRNet / CRNN 二选一我为什么选 LPRNet既然字符分割不可靠那就绕开分割用序列识别网络直接整牌识别。这个方向上有两个主流选项LPRNet 和 CRNN。LPRNet 是专门为车牌识别设计的轻量网络用 CNN 提取特征 CTC 损失输入通常是 94x24 的灰度或彩色图直接输出定长 7 位或 8 位字符序列。它最大的优势是模型小、速度快、不依赖字符分割。CRNN 是通用序列识别框架CNN 提取特征后接 BiLSTM 建模序列关系再用 CTC 对齐。它在不定长文本识别上非常强比如英文街景文字但模型比 LPRNet 重一些。模型输入尺寸是否需字符分割模型体积适用场景LPRNet94x24 固定否约几 MB中文定长车牌、边缘设备部署CRNN高 32、宽可变否中等中英文字符序列、不定长场景中文民用车牌字符集大概是 31 个省份简称汉字 24 个字母不含 I 和 O 10 个数字加起来 65 类左右。因为蓝牌固定 7 位、新能源绿牌固定 8 位LPRNet 的定长输出正好匹配所以我最终选了 LPRNet。实测下来在同样的数据集上LPRNet 的整牌准确率不输 CRNN但推理速度快一截部署也更方便。4.3 识别数据准备与训练经验识别网络的数据来源有三个一是 CCPD 里按检测框裁出来的车牌区域二是自己收集的真实场景车牌图三是合成数据。合成车牌图是个很好用的补充手段用字体渲染车牌内容再叠加随机背景、光照变化、噪声、模糊可以快速生成几万张难例专门针对数据不足的省份简称做补充。训练时几个点分享一下输入统一 resize 到 94x24注意不要拉伸变形太夸张保持车牌长宽比。损失函数用 CTC Loss优化器用 Adam初始学习率 0.001配合余弦退火。易混淆字符是训练重点0/O、1/I、8/B、2/Z 这类形状接近的字符在数据增强时适当提高它们的采样权重。评估指标不要只看整牌准确率还要看字符准确率。整牌准确率会被次要因素拖累字符准确率能反映网络对易混淆字符的学习情况。我还会在识别网络后面挂一个简单的颜色分类头输出蓝牌、黄牌、绿牌、白牌。颜色分类和字符识别是并行任务网络共享特征提取层几乎不增加额外计算量但对业务系统区分燃油车和新能源车很有用。5. 实战 Pipeline检测→校正→识别串联5.1 完整流程与模块接口设计整个系统跑通以后流程非常清晰视频帧或图片 → 车牌检测 → 置信度过滤 → 透视校正 → 归一化 → 车牌识别 → 后处理输出。设计模块接口时我给每个环节都定了明确的输入输出detect(frame)返回bbox(x1,y1,x2,y2) 列表 score列表。correct(plate_crop)返回校正后的车牌图像240x80。recognize(corrected_img)返回车牌字符串、颜色、置信度。接口拆分的好处是每个模块都可以独立替换和测试。比如你想把 YOLOv5 换成 YOLOv8只改detect函数的内部实现其他模块完全不用动。5.2 串联代码示例与逐行解析下面是整个 Pipeline 的简化版实现我把每一步的注释写得比较细import cv2 import numpy as np def process_frame(frame, detector, recognizer): # 1. 检测车牌区域 boxes, scores detector(frame) # boxes: [[x1,y1,x2,y2], ...] results [] for box, score in zip(boxes, scores): if score 0.4: continue x1, y1, x2, y2 [int(v) for v in box] # 适当外扩避免切掉边缘字符 x1 max(0, x1 - 5); y1 max(0, y1 - 5) x2 min(frame.shape[1], x2 5); y2 min(frame.shape[0], y2 5) plate_crop frame[y1:y2, x1:x2] # 2. 透视校正这里假设已实现角点提取函数 get_four_points src_pts get_four_points(plate_crop) # 4x2 点集 if src_pts is not None: corrected perspective_correct(plate_crop, src_pts) else: corrected plate_crop # 角点提取失败退化为直接裁切 # 3. 统一尺寸送识别网络 corrected cv2.resize(corrected, (240, 80)) text, color, conf recognizer(corrected) results.append({ bbox: [x1, y1, x2, y2], plate: text, color: color, confidence: conf }) return results几个关键细节值得单独拎出来说检测模型如果用了 letterboxYOLO 标准预处理推理完的坐标要映射回原图坐标不能直接拿模型输出的坐标去裁原图否则框会偏移。校正后统一 resize 到 240x80这一步不只是为了满足网络输入要求更重要的是消除不同距离下车牌大小差异让识别网络看到一致的尺度。CTC 解码时要注意去重。CTC 会输出包含 blank 的字符序列需要把重复且非 blank 的字符折叠否则一个字符会被输出多次。5.3 业务后处理从单帧到视频流单张图片识别跑通之后视频流才是真正的战场。视频里同一辆车会出现在连续几十帧里如果每一帧都独立识别结果会在不同字符间来回跳比如这一帧识别出“粤B12345”下一帧变成“粤B12346”画面会闪个不停。我用的是滑动窗口投票对同一个跟踪目标维护最近 N 帧比如 10 帧的识别结果取出现频率最高的字符串作为最终输出。这个逻辑实现简单但对消除单帧误识别特别有效。跟踪模块我用 ByteTrack它轻量且不需要额外的 ReID 模型。有了跟踪之后检测频率可以从每帧一次降到每 3~5 帧一次中间帧直接沿用上一帧的 bbox可以显著降低计算压力。最终输出给业务系统的 JSON 长这样{ plate: 粤B12345, color: blue, confidence: 0.96, bbox: [312, 214, 465, 253], timestamp: 1690000000 }停车场道闸、监控告警、收费系统拿这个 JSON 就能直接做业务对接和识别系统完全解耦。6. 复杂环境专项优化与踩坑实录6.1 光照不均与夜间场景光照问题是最常见的复杂环境干扰也是传统方法最先崩溃的地方。我的优化思路分两条线走训练层面数据增强里把亮度扰动范围加大加上随机 gamma 变换、模拟夜间低照度。同时从 CCPD 等数据集里把夜间样本单独挑出来做一次针对性微调。推理层面在检测之前加一层 CLAHE自适应直方图均衡化它对局部光照不均比如一边亮一边暗的改善非常明显计算量也不大不影响实时性。识别前可以做非常轻的 gamma 校正把暗部稍微提亮但不要太重否则会引入噪点。夜间还有另一个问题很多监控相机是黑白模式图像没有颜色信息。这时候颜色分类模块会失效需要靠字符识别模块单独工作业务系统要做好颜色缺失的兼容。6.2 模糊与雨雾天气运动模糊和雨雾会让字符边缘信息丢失。我的做法是训练时直接模拟这些退化在数据增强里随机加高斯模糊、运动模糊、雾噪让模型提前见过这类输入。推理时我一般不额外做去模糊处理因为去模糊算法比如超分计算量大实时场景扛不住。雨雾天气有个可选的预处理方案是暗通道先验去雾效果直观但容易产生 halo 伪影而且对车牌这种高对比度区域有时会适得其反。我的建议是如果业务场景允许一定的预处理耗时可以加如果追求实时性就不要加靠训练数据增强去扛。6.3 严重倾斜与大角度怎么救检测模型对角度不敏感但识别模型对校正后的插值模糊很敏感。车牌倾角越大透视校正时的重采样损失越大字符越容易糊。我在这个项目里想到的办法是利用多帧信息同一辆车在视频流的不同位置会有不同角度跟踪模块把多帧识别结果串起来后可以只选取“最正”的那一帧做识别也就是车牌区域宽高比最接近 3:1、校正后置信度最高的那一帧。实测下来这个方法比单帧硬识别准确率提升不少。6.4 常见问题速查表项目过程中踩过不少坑我整理成一张速查表方便排查问题问题现象可能原因解决方案检测漏检多车牌目标太小、对比度低提高输入分辨率、补充对应场景数据、降低置信度阈值检测误检多车身广告文字被当车牌增加负样本、提高置信度阈值校正后字符歪角点提取不准切换到角点回归网络、二值化前先去边框识别易混淆O/0、8/B 分不清收集该类样本重训、调整样本采样权重夜间识别率低暗光下字符信息少训练增强模拟暗光、推理加 CLAHE、收集夜间数据微调视频结果闪烁单帧误识别滑动窗口投票、跟踪目标帧间关联7. 从大作业到落地扩展方向与部署优化7.1 给课程设计 / 毕设的扩展建议如果你做这个方向是为了交大作业或者毕设在有基础流程之后还可以往几个方向加亮点加一个可视化界面用 PyQt 或 Streamlit 做个简单的上传图片/视频的演示工具把检测框、校正结果、识别字符都显示出来效果直观很多。加一个环境对比模块专门做一组测试亮光、暗光、倾斜、模糊四组样本把识别率做成曲线让老师一眼看到系统针对复杂环境做了什么优化。写报告的时候把失败的尝试也写进去。比如“最初用字符分割法在模糊车牌上怎么调都不准后来切到 CTC 序列识别才解决”这种真实的调试过程比全篇讲成功更有说服力。7.2 工程化部署的关键点模型选型上YOLOv5s LPRNet 天然适合部署。训练好的模型先导出 ONNX再转 TensorRTFP16 精度就能跑得很快。如果再上 INT8 量化模型体积和推理延迟还能进一步压缩代价是精度小幅下降需要针对业务数据做校准和验证。边缘设备上Jetson Nano 级别的主板跑这两个模型检测 识别整体可以做到 20~30 FPS满足道闸等中低速场景的要求。如果机位固定、算力紧张还可以进一步裁掉检测频率配合跟踪器降到 10FPS 的检测速率计算压力能大幅下降。部署时别忘了加一个失败样本留存机制识别置信度低于阈值的结果自动把图像和识别文本存下来。积累一段时间后这些失败样本就是你下一轮迭代最宝贵的数据来源。做这个项目给我最大的感受是计算机视觉项目真正的天花板往往不在模型结构而在数据和工程细节。模型选型其实很简单YOLO 加 LPRNet 这种组合跑几天就能出结果但要把夜间、倾斜、模糊这些复杂场景一个个啃下来靠的是耐心收集数据、反复迭代、一个坑一个坑地填。车牌识别这个方向好就好在它足够“具体”从检测、校正到识别链路完整每块都有明确的评价标准非常适合作为练手项目。如果之后有精力我建议你在这个基础上继续往下做加一个车辆跟踪和轨迹聚合把多帧识别信息综合起来用这比单独优化每一帧的识别结果提升效果往往更明显。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻