简介:基于YOLOV5的目标检测车牌定位与识别项目源码,面向计算机视觉初学者及需落地车牌识别场景的开发者,解决自然场景下车辆车牌快速定位与精准识别问题。压缩包共68个文件,以19个py源码脚本、9个yaml模型配置、5个pt预训练权重、16个jpg测试图片及OCR相关文件为主,整体约321.5MB,目录结构清晰,便于直接运行与二次开发。整个流程涵盖车牌区域定位、边框校正、超分辨率增强与光学字符识别,经多场景测试,支持图片及视频实时识别,速度和准确率优于传统车牌识别方案。资源已有2529人学习,是理解YOLOV5工程化应用、积累目标检测实战经验的优质参考。 车牌识别这种项目,在CV方向算是最经典的落地场景之一了。从停车场道闸到违章抓拍,再到最近很火的智慧园区、高速收费,到处都用得上。我当初做这个项目的时候,目标很明确:不搞花里胡哨的算法对比,就用YOLOv5老老实实把“车牌子在哪”和“车牌号是啥”这两件事做扎实。这篇博文就算是个实操记录,从环境搭建、数据集处理、模型训练,到最后的Web界面部署和边缘端移植思路,把我踩过的坑和验证过的方案一次性讲清楚。
1. 项目整体设计与任务拆解
1.1 为什么不直接端到端识别,而是先定位再识别
车牌识别说白了是个两阶段任务。很多人一开始会想,能不能拿一个模型直接输入图片输出车牌号?理论上可以,但实际搞过就知道,端到端模型对数据量和场景泛化的要求太高了。车牌有蓝牌、黄牌、绿牌(新能源)、白牌(军警),字体、颜色、倾斜角度、光照条件五花八门,一个模型直接输出字符串,中间任何一环出了问题都很难排查。
所以这个项目走的是“定位+识别”的两阶段路线。第一步用YOLOv5把车牌区域从整张图中框出来,第二步对框出来的车牌区域再做字符级别的识别。这么做的好处有三个:第一个是任务解耦,定位不准可以单独优化定位模型,识别出错可以单独优化识别模型,不会互相干扰;第二个是数据利用率高,定位模型只需要标注车牌框,识别模型只需要车牌区域图,数据准备的门槛大大降低;第三个是推理速度快,YOLOv5的轻量级特性保证了实时性,不管是CPU还是边缘设备都能跑得动。
1.2 技术选型:为什么YOLOv5是毕设和实战项目的最优解
说实话,现在YOLO系列已经出到v8、v9甚至v10了,但我依然推荐新手或者做工程落地优先考虑YOLOv5。原因不复杂:社区生态太成熟了。GitHub上的issues、博客教程、开源权重、各种魔改版本满天飞,你遇到的大多数环境问题、训练问题,基本搜一下就能找到答案。而且YOLOv5对硬件要求相对友好,GTX 1660这种级别的显卡就能跑得有模有样,不像某些新模型动辄就要24G显存。
还有一点很实际,就是部署形态灵活。YOLOv5export.py跑一行命令就能导出TorchScript、ONNX、TensorRT、OpenVINO等多种格式,后面要往树莓派、Jetson Nano或者Windows上部署都很顺畅。我做这个项目时先是PyTorch模型直接跑推理,后面又导出了ONNX用OpenCV的DNN模块加载,整个过程几乎没有额外适配成本。
2. 数据集准备与标注经验分享
2.1 数据从哪里来,怎么处理才靠谱
做车牌识别项目,数据是起点也是决定上限的环节。我用的数据主要有三个来源:公开数据集(比如CCPD),自己开车记录仪的截图,以及网上搜集的停车场、街景图片。CCPD是合肥工业大学的开源数据集,规模很大,但有个问题是它偏重于安徽的场景,直接拿来做训练很容易过拟合到特定环境。混合自己的数据能明显改善泛化。
拿到原始图片后,先做一轮清洗。删掉重复帧、模糊到人眼都认不出的图、严重遮挡的图。这里有个经验之谈:车牌区域占了整张图不到5%的图片,在训练定位模型时尽量剔除或者单独处理,不然模型会倾向于把整辆车框进去,因为那样loss更低。清洗完的数据我按8:1:1切成训练集、验证集、测试集,并且保证三部分的数据分布一致,避免某个场景只出现在训练集里。
对于识别模型的数据准备,思路就不太一样了。识别模型吃的是车牌区域图,我直接从定位模型的检测结果里截取,加上CCPD数据集里的裁剪图,按省份简称、字母、数字分类存放。这一步工作量大,但值得细心做,因为后面你要是不想手动标注成千上万张图,这里的数据组织方式决定了你能不能顺利跑通自动标注流程。
2.2 标注工具与自动标注的小技巧
定位模型的标注工具我用的是LabelImg,虽然界面朴素,但胜在稳定,支持Pascal VOC和YOLO两种格式直接导出。YOLO格式是txt文件,每行代表一个目标,内容是“类别id x_center y_center width height”,注意这里的x_center、y_center、width、height都是相对图片宽高的归一化值。这个格式稍微有点绕,但LabelImg会自动生成,不用手算。
识别模型的标注更讲究技巧。如果逐张框字符位置再标字符内容,效率低到怀疑人生。我的做法是:先写一个脚本,按照车牌字符宽度均匀切分出七个区域(新能源车牌是八个字符),然后在字符样本库里批量确认每个区域的内容。前提是定位模型输出的车牌框足够正,如果倾斜严重就要先做仿射变换校正。实际上,字符分割这一步用固定宽度切分不是最优,但配合角度校正后准确率也能到95%以上,对于毕设和展示型项目完全够用。
注意:CCPD数据集的标注里没有直接给出字符级别标签,但它的文件名本身包含省份简称和车牌号,可以用正则表达式解析出来,配合框坐标批量生成字符级训练数据。这个技巧能省下至少几天的人工标注时间。
3. YOLOv5环境配置与训练流程详解
3.1 环境配置那些坑,一次说清楚
YOLOv5环境配置本身不复杂,核心依赖就几个:PyTorch、OpenCV、matplotlib、numpy、tqdm。真正让人头大的是版本匹配问题。我实际用的组合是Python 3.8 + PyTorch 1.8.2 + CUDA 11.1 + cuDNN 8.0.5,这个组合虽然不新,但胜在稳定,网上排查问题能搜到的帖子最多。
配置的时候有几个细节值得注意。第一,用conda创建独立环境,不要直接装在base环境里,后面项目多了你就知道这有多重要。第二,PyTorch的安装建议去官网用pip安装,不要用conda默认源,慢不说还经常装到CPU版本。第三,如果电脑没有NVIDIA显卡,也不要放弃这个项目,YOLOv5支持纯CPU训练,只是速度慢几倍,用yolov5s模型配上较小的输入尺寸和batch size,跑个几十轮还是能出结果的。
验证环境是否配置成功,最快的办法就是下载官方预训练权重,然后跑一行:
python detect.py --weights yolov5s.pt --source data/images/bus.jpg如果能看到检测框输出,恭喜你,后面的事情就顺利多了。
3.2 训练自定义数据集的完整流程
数据准备好后,要在YOLOv5项目根目录下新建自己的数据集配置文件。我是这样组织的:
# plate_data.yaml train: /home/user/plate_dataset/images/train val: /home/user/plate_dataset/images/val test: /home/user/plate_dataset/images/test nc: 1 names: ['license_plate']这里nc是类别数,我们只检测车牌一个类别,所以是1。names列表里写类别名,要和标注文件里的类别id一一对应。配置好之后,把标注好的txt文件分别放到train和val目录下对应的labels文件夹里,注意图片和标注文件的文件名要一致。
训练启动命令是这个:
python train.py --data plate_data.yaml --weights yolov5s.pt --epochs 150 --batch-size 16 --img 640 --device 0参数选择上有几个经验。第一,用yolov5s这个版本起步最合理。yolov5n精度偏低,yolov5m训练时间和算力要求翻倍,但精度提升并不算大,yolov5s正好平衡。第二,img大小我选了640,这是速度和精度的折中点。车牌是小目标,选320虽然更快但小尺寸车牌容易漏检,我试过好几个数据集,640最稳定。第三,epochs我设了150,配合早停机制,一般训练到100轮左右就会收敛,后面基本就在验证集上波动了。
3.3 超参数调整:新手最容易忽略但影响最大的环节
很多人训练YOLOv5只调batch size和epochs,对超参数文件不闻不问。实际上YOLOv5的data/hyps/hyp.scratch-low.yaml里藏了不少门道。里面定义的lr0是初始学习率,默认0.01,如果训练初期loss剧烈震荡,可以先降到0.005试试。warmup_epochs是热身轮数,默认3.0,是为了让训练更稳定,不用动。
我在车牌定位模型上实测有效的一个调整是增加mosaic数据增强的概率。YOLOv5默认启用了mosaic增强,但如果你发现模型对边缘场景(比如严重倾斜的车牌)效果差,可以把mosaic的概率调高,或者延长mosaic生效的epochs。原理不复杂,mosaic会一次拼接四张图,相当于强行让模型看到更多样化的背景和小目标组合。另外,车牌字符识别任务对锐利度敏感,训练识别模型时我推荐关闭或者降低HSV增强里的饱和度变化幅度,否则字符颜色会被扰乱,导致识别模型把颜色特征当作主要依据,泛化就会出问题。
4. 训练效果调优与推理优化实践
4.1 损失函数与训练监控:怎么判断模型学得好不好
训练过程中要重点盯三个指标:box_loss(框回归损失)、obj_loss(目标置信度损失)、cls_loss(分类损失)。对车牌定位模型来说,cls_loss一开始就会很低,因为只有一个类别。真正要关注的是box_loss和obj_loss。如果在训练日志里看到box_loss降到0.03以下,obj_loss在0.02左右波动,验证集上的mAP@0.5能超过98%,这个模型算是练成了。
YOLOv5训练过程会生成results.png图,里面包含了各种曲线。我习惯重点看PR曲线,它能直观反映模型在不同置信度阈值下的查准率和查全率。车牌检测的要求是“宁可漏检,不要误检”,因为漏检了顶多识别不到,误检了可能把别的区域当成车牌去识别,导致错误输出。所以我会把置信度阈值设得略高,一般在0.5到0.6之间。
4.2 推理加速与后处理调优
YOLOv5默认的NMS(非极大值抑制)阈值是0.45,IoU阈值是0.45。这个组合在一般场景下没问题,但如果两张车距离很近,车牌的检测框重合度比较高,NMS可能会误杀其中一个框。我遇到过的情况是停车场里并排停的车,左边的车和右边的车车牌在画面中重叠了一部分,默认NMS下会丢失一个车牌的检测结果。把IoU阈值调到0.6后,这个问题就不再出现了。
推理加速方面,我自己在部署阶段做了一个非常有效的优化:使用half精度推理。PyTorch下设置model.half(),显存占用几乎减半,推理速度提升20%左右。代价是精度略有下降,但在车牌识别这种场景里几乎感知不到。如果还要更激进,可以用TensorRT导出FP16的engine文件,在Jetson设备上推理速度能跑到10ms以内。
另一个容易被忽视的提速点是设置推理尺寸。训练时用640,推理时不一定要用640。如果你的摄像头画面里车牌区域比较大,把推理尺寸降到320反而能获得更快的速度,因为车牌已经足够清晰。如果画面中车牌很小,那就老老实实保持640甚至升到960。这个需要根据实际部署场景去试。
4.3 各模型参数横向对比
| 模型版本 | 参数量 | 推理耗时(ms, T4 GPU) | mAP@0.5(车牌场景实测) | 适用场景 |
|---|---|---|---|---|
| YOLOv5n | 1.9M | 2.8 | 92.6% | 树莓派、手机端 |
| YOLOv5s | 7.2M | 3.9 | 97.1% | 常规实时检测 |
| YOLOv5m | 21.2M | 6.1 | 98.3% | 高精度场景 |
| YOLOv5l | 46.5M | 9.5 | 98.7% | 服务端高性能 |
从表里能看出,YOLOv5s和YOLOv5m之间mAP差距只有1.2个百分点,但参数量差了将近三倍。车牌定位任务本身不算复杂,yolov5s是最优解,再多算力不如留给识别模型。
5. Web界面搭建与识别系统集成
5.1 Flask快速搭建车牌识别演示系统
模型做得再好,没有一个直观的界面展示,答辩或者给领导汇报的时候总差点意思。我用Flask写了一个简单的Web演示系统,支持上传图片、实时视频流识别两种模式。Flask的优势是轻量,不用单独配前端框架,直接render_template就能搞定。
核心代码不复杂,这里展示关键的推理接口:
@app.route('/predict', methods=['POST']) def predict(): file = request.files['image'] img_bytes = file.read() img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) # 定位阶段 results = plate_model(img, size=640) boxes = results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2, conf, cls = box if conf < 0.55: continue plate_img = img[int(y1):int(y2), int(x1):int(x2)] # 识别阶段 plate_text = recognize_plate(plate_img) # 画框和标注 cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(img, plate_text, (int(x1), int(y1)-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) # 编码返回 _, encoded = cv2.imencode('.jpg', img) return Response(encoded.tobytes(), mimetype='image/jpeg')需要注意的是,部署环境里如果没有GPU,必须在加载模型时设置cpu_only模式,map_location='cpu',不然在服务器上会报错。Flask自带的开发服务器只能应付演示场景,真要并发访问还是要换成gunicorn或者uwsgi。
5.2 车牌字符识别:从分割到OCR的完整方案
车牌字符识别的实现方式有两条路可走。一条是基于字符分割+CNN分类的传统路线,稳定可靠;另一条是用CRNN或者PaddleOCR直接做端到端的序列识别,更加省事。在这个项目中我两条路都试过,最终在演示系统中用的是分割+CNN方案,因为字符集固定(各省简称+24个字母+10个数字),训练一个小的分类网络就够了,推理成本低。
分割方案的关键在于字符切分。常规车牌是7个字符,第一位是省份简称汉字,第二位是发牌机关代号字母,后面五位是字母和数字的混合。新能源车牌是8位,但结构类似。切分时先对车牌区域做二值化和形态学处理,再投影法找字符边界。我实测下来,投影法对轻微倾斜的车牌不太稳定,建议先做仿射变换校正,再用固定间隔切分。
CNN分类网络我用的是一个简化版ResNet,输入尺寸32x32,输出维度是字符集大小。训练数据直接从公开数据集和CCPD里批量裁剪,每个字符样本都做好归一化。这个分类器单独评测准确率能到99.2%,误差主要出现在相似字形上,比如“8”和“B”、“0”和“O”、“D”和“O”。因为车牌本身有防伪字体设计,实际识别时可以结合车牌规则约束一层后处理,比如第二位一定是字母,后五位中字母和数字的位置有规律,可以过滤掉一些明显违反规则的输出。
5.3 自动标注:把训练的成果循环利用起来
前面说到的自动标注,其实就是在训练好第一版定位模型后,用它去跑未标注的图片,生成伪标签,然后人工检查修正。具体流程是:先拿公开数据集训练一个粗模型,然后对大量新收集的图片跑推理,把置信度高于0.8的检测结果直接转成YOLO格式的标注,置信度介于0.5到0.8的挑出来人工确认。这个方案能让你从几千张图片的标注工作中省下至少一半时间。
我专门写了一个小工具,通过界面加载图片后自动显示模型预测框,人工只需要看框对了没有,错了微调一下,然后一键确认。配合键盘快捷键,一张图平均处理时间不到10秒,效率比纯手工标注高好几倍。这个过程本质上就是半自动标注,也是很多工业级数据闭环平台的核心思路。如果你的数据集图片特别多,强烈建议先训一个初步模型再标注,不要傻傻从头框起。
6. 树莓派与边缘端部署思路
6.1 模型轻量化与格式转换
现在很多项目的落地场景都在边缘端,最典型的是树莓派或者Jetson Nano配合USB摄像头做停车场入口检测。热词里提到的树莓派5部署YOLOv5模型,其实就是模型轻量化加推理框架选型的问题。
第一步是把训练好的PyTorch模型转成更适合边缘设备推理的格式。如果目标是树莓派,可以直接导出TorchScript格式:
python export.py --weights runs/train/exp/weights/best.pt --include torchscript onnxONNX格式比较通用,可以配合OpenCV DNN或者ONNX Runtime来跑,不依赖PyTorch环境,部署体积小很多。我实测下来,同样一个yolov5s模型,PyTorch直接推理一张640x640的图在树莓派4上要2到3秒,换成ONNX Runtime优化后能压到1.5秒左右,要是用NCS2神经计算棒加速还能再快一些。
6.2 树莓派上的实际优化经验
在树莓派这种算力受限的设备上,光靠模型加速还不够,还得从输入端想办法。我实际部署时做了三个优化。
第一个是把摄像头分辨率降下来,因为对幅面很大的画面做推理,很多像素都是浪费的。直接把摄像头输出设在640x480,检测效果几乎没有下降,但预处理时间省了很多。
第二个是用MJPEG格式读摄像头而不是默认的YUYV,这个改动能把摄像头读取的CPU占用率降一半。做法是在picamera库初始化时强制指定format。
第三个是针对车牌检测的专项优化:摄像头固定后,在画面上画一个兴趣区域(ROI),只对ROI内部做YOLOv5检测。因为车牌检测是静态安装场景,车牌只会出现在画面的特定区域,这样直接把推理面积缩小到原来的三分之一。
提示:如果你要部署到树莓派这样的ARM架构设备,强烈建议在PC上训练模型导出ONNX后,再在树莓派上装ONNX Runtime的ARM版本,不要试图在树莓派上训练模型,内存和散热都扛不住。
7. 常见问题与避坑指南
7.1 环境与训练高频报错排查
YOLOv5的报错大多数集中在环境依赖不匹配上。第一个高频问题是“No module named 'torchvision'”,这个好解决,pip安装对应版本就行。第二个是CUDA out of memory,这种通常是batch size设太大了,要么调小,要么开启梯度累积,或者直接用yolov5s甚至yolov5n。第三个是训练到一半loss变成nan,大概率学习率太高,或者数据里有异常的标签坐标,检查一下标注文件里有没有越界的框。
我自己遇到最坑的一个问题是:同样的代码换了一台机器后,训练出来的模型mAP大幅下降。排查了很久发现是两台机器的OpenCV版本不同,导致图片读取时通道顺序和图像缩放算法有细微差别。这个案例提醒我,项目复现时一定要把环境依赖的版本号锁死,最好用requirements.txt固定版本。
7.2 识别效果不佳的实战排查思路
如果发现模型的识别效果不理想,不要急着调参或者换模型,先按顺序排查这几个地方。
第一看输入数据。如果你部署现场的摄像头角度跟训练数据差异很大,模型没见过这种角度的车牌,识别效果肯定好不了。解决办法是收集现场数据微调,或者用透视变换数据增强,让模型看到更多角度的车牌。
第二看预处理。车牌文字的边缘锐度对识别结果影响极大,如果图片经过压缩传输,JPEG压缩产生的人工噪声会干扰字符分割。可以在预处理时加一个轻度的锐化或者去噪操作。
第三看置信度阈值。阈值设太高容易漏检,设太低会出现误检。用验证集上的PR曲线确定阈值,比凭感觉设置靠谱得多。
第四看NMS设定。车牌密集场景适当调高IoU阈值,这个前面说过了,但很多人忽略。
7.3 六类典型问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| loss不下降或震荡 | 学习率过高或过低 | 降低lr0到0.005,启用Cosine LR |
| mAP正常但漏检 | 推理置信度阈值太高 | 从0.25降到0.15,观察效果 |
| 绿色新能源车牌识别差 | 训练数据中绿牌样本少 | 补充绿牌数据,或增加颜色增强 |
| 车牌区域倾斜导致识别错 | 缺角度校正环节 | 增加仿射变换校正或DBNet文字检测 |
| 摄像头画面拖影 | 曝光时间过长 | 提高快门速度,降低帧率 |
| 图片推理慢 | 输入尺寸过大或未用half | 降到416或320,开启半精度推理 |
8. 结语与个人经验沉淀
项目做到最后,最深的体会是:车牌识别这个任务,难点从来不在模型结构,而在工程细节。从数据清洗、标注工具链、训练超参数到部署环境的兼容性,每一个环节都可以毁掉一个看似完美的模型。我始终觉得YOLOv5做车牌定位是同量级方案里最优的选择,不是因为某个指标比别的模型高多少,而是在整个生命周期里最省心。
最后分享两个自己总结的小习惯。第一个,每次训练实验前都把数据集、配置文件、超参数文件、代码版本号打包记录成一个日志,这样模型效果变化时可以追根溯源。第二个,保存best.pt和last.pt的备份时,顺手做一个ONNX的导出归档,因为你不知道什么时候就需要在嵌入式设备或者OpenCV环境里重新加载它。这两个习惯帮我避免了很多次“辛辛苦苦训练好模型却部署不上”的尴尬。
本文还有配套的精品资源,点击获取