简介基于YOLOv5的车牌检测与识别完整实现面向计算机视觉开发者、智能交通及安防监控领域的算法工程师可解决车辆场景下车牌自动定位与字符识别的落地问题。资源共84个文件压缩包78.16MB涵盖PyTorch权重文件.pt/.pth、Python训练与推理脚本、YOLOv5系列模型配置yaml、车牌样例图片jpg/jpeg/png以及XML标注文件从模型定义、数据准备到检测识别均有完整覆盖。包内还包含LPRNet字符识别模块、模型导出脚本和多种网络结构配置可支持从训练、验证到导出的全流程操作并配有测试图片和识别结果示例便于直接运行体验或基于自有数据集进行二次开发与参数调优。已有2057人学习下载适合希望快速上手YOLOv5车牌识别项目、研究检测与OCR结合方案的读者。1. 车牌识别不是目标检测的简单加法先想清楚YOLOv5在这条链上干什么一辆车从远处开进停车场道闸的识别区几百毫秒之内要把车牌号码读出来并抬杆放行——这是YOLOv5车牌检测和识别最典型的落地场景。YOLOv5在这里负责的是目标检测从整张画面里把车牌这个目标框出来而「框里到底是什么字符」是识别阶段的事通常由另一段网络或字符级检测完成。这两段是串行关系检测的框质量直接决定识别的上限所以很多人拿YOLOv5只做检测、把识别交给OCR这个分工是合理的。做这个方向的人多半是手里有摄像头和GPU的工程师想用yolov5训练自己的车牌数据集、从环境配置到部署完整走一遍。先搞清楚这个分工后面踩坑才知道坑在哪一层。2. 环境与数据用conda把YOLOv5跑起来车牌数据集怎么选、怎么转格式先把环境装好。这一步别图省事直接往系统Python里塞包conda隔离环境是踩出来的习惯。yolov5环境配置的核心是版本对齐Python版本、PyTorch版本、CUDA版本三者要匹配否则后面训练时莫名其妙报CUDA错误排查起来非常消耗耐心。装环境这件事看着简单但每天都有大量时间耗在「torch装好了但是cuda不可用」这类问题上所以花十分钟把环境弄干净比后面反复返工划算得多。2.1 conda创建yolov5环境与依赖安装conda create -n yolov5 python3.9 -y conda activate yolov5 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt第一行创建名为yolov5的conda环境并指定Python 3.9。Python版本不能太低也不能太高3.8以下很多新版本PyTorch不再支持3.10以上部分依赖编译时容易报错3.9是目前兼容性最稳的选择。第三行从GitHub拉取YOLOv5源码官方仓库一直维护了比较久拉默认分支即可。最后一行安装requirements.txt里锁定的依赖包含torch、torchvision、opencv-python、numpy等核心库。装完之后验证一下环境python -c import torch; print(torch.__version__, torch.cuda.is_available())这行命令确认PyTorch装好且CUDA可用。输出应该类似2.1.0 True如果后面是False说明torch装成了CPU版或者CUDA驱动没对上。常见做法是去PyTorch官网用pip安装对应CUDA版本的torch而不是靠requirements.txt里的默认源。conda yolov5环境装好后这一层基本不会在训练阶段再出问题后面所有操作都在这套环境里做别混用。2.2 车牌数据集怎么选CCPD、CRPD还是自己标YOLOv5训练自己的数据集第一步是解决数据来源。车牌识别领域公开数据集主要是CCPD中国城市停车场数据集和CRPD中国车牌数据集。CCPD是国内停车场场景采集的特点是场景多样包含蓝牌和绿牌新能源有大量倾斜、逆光、夜间样本。它每个样本存储的是整张停车场画面车牌位置以json形式给出适合做YOLOv5检测训练。CRPD是另一个车牌检测与识别数据集覆盖省份更多有些版本直接给了车牌字符级标注适合做识别阶段的训练。如果项目要求是特定场景——比如只有地下车库的暖光环境或者只有单位门口的单车道——公开数据集其实不太够用因为停车场公开数据和你的摄像头视角差异很大。我一般建议先拿CCPD跑通流程再采集自己场景的几百张图做增量训练。标注工具用LabelImg或者x-anylabeling都可以前者老牌稳定后者对旋转框支持更好。车牌经常是倾斜的这一点后面会专门讲。2.3 VOC标注转YOLO格式转换脚本与四个边界坑LabelImg默认导出VOC XML格式而YOLOv5需要的是txt格式每行一个目标格式为class_id center_x center_y width height坐标是相对图片宽高的归一化值。转换脚本是绕不开的一步。import xml.etree.ElementTree as ET import os def xml_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text cls_id class_names.index(name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 转成YOLO格式中心点坐标 宽高归一化到[0,1] cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) os.makedirs(out_dir, exist_okTrue) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines))这段脚本把VOC XML里的目标框转换成YOLO训练要的txt标注。核心逻辑是把左上角(x1,y1)、右下角(x2,y2)的坐标换算成中心点(cx,cy)和宽高(w,h)再除以图片宽高做归一化。cls_id是类别在names里的下标车牌检测只有一个类别plate所以取值恒为0。转换时有四个边界坑要留意第一img_w和img_h取的是xml里size节点的值不是图片实际尺寸。如果标注工具版本不一致xml里的宽高和图片实际像素对不上坐标就会整体偏移模型训练出来检测框全部靠左上角。解决办法是转换前用PIL或cv2重新读一遍图片尺寸以实际像素为准。第二XML里的坐标可能带小数点也可能是负数。带小数点还好float()直接解析负数一般是标注工具误操作产生的越界框转换时要做clip操作把坐标约束在[0,img_w]和[0,img_h]内。第三标注文件里可能存在difficult标记的困难样本这类目标通常很模糊直接混进训练集会拉低mAP。转换时加个判断difficult1的跳过。第四回车换行符。Linux和Windows混用标注文件时txt里可能出现\r字符训练时读标注会报格式错误。用open写入时指定newline\n或者转换后统一用dos2unix清理一遍。数据集目录结构也要按YOLOv5约定摆好train和val两个文件夹下面分images和labels两个子目录。images目录放图片labels目录放对应的txt。YAML文件里分别指向train和val的images路径即可。3. 训练车牌检测模型yolov5超参数与训练日志解读数据就绪之后开始训练。这一章讲三个东西训练命令怎么写、yolov5超参数怎么调、训练日志和mAP怎么看。这一层直接决定检测模型能不能用不能盲目跑默认参数。3.1 训练命令与数据集YAML# plate.yaml train: /home/user/plate_dataset/train/images val: /home/user/plate_dataset/val/images nc: 1 names: [plate]先写数据集配置文件。train和val字段指向图片目录YOLOv5会从同级的labels目录自动找标注txt。nc是类别数这里只有车牌一个类写1。names是类别名列表。python train.py --data plate.yaml --weights yolov5s.pt --epochs 300 --batch-size 16 --img 640 --device 0这是最常用的车牌检测训练命令。--weights yolov5s.pt表示加载YOLOv5s预训练权重做迁移学习不要小看这一步从零训练在车牌这种单类目标上收敛极慢mAP也很难上去。--img 640是输入分辨率车牌通常占画面比例不大建议至少640起步。--batch-size 16要看显存12G显存用yolov5s跑640分辨率16这个值刚好。--device 0指定第一块GPU。如果只有CPU去掉这个参数或者写--device cpu但训练速度会慢一到两个数量级300个epoch等不起。3.2 yolov5超参数必调的5个位置YOLOv5的超参数在data/hyp.scratch.yaml这类文件里训练时刻用--hyp指定。车牌检测这种任务我任务最值得调的5个参数是lr0: 0.01 # 初始学习率 lrf: 0.2 # 最终学习率 lr0 * lrf warmup_epochs: 3 # 预热epoch数 fl_gamma: 0.0 # focal loss强度 mosaic: 1.0 # mosaic增强概率lr0是初始学习率预训练权重迁移时0.01是安全的起点如果loss前20个epoch就震荡降到0.005再看。warmup_epochs预热让模型前几个epoch用很小的学习率慢慢进入状态车牌数据集如果只有几千张warmup可以适当加长到5。fl_gamma是focal loss的gamma参数用于解决正负样本不均衡。车牌目标在画面里通常只有一个不那么极端默认0.0即可但如果你自己做数据时每张图平均不到0.5个车牌目标很多空背景图可以调到0.5。mosaic是马赛克增强把4张图拼在一起训练对小目标检测尤其有效。车牌属于中小目标mosaic保持默认1.0。但要小心车牌本身是矩形且字符密集mosaic时如果拼接边恰好切到车牌会产生大量切了一半的车牌正样本模型容易学到「半个车牌也能算正例」的坏习惯。真遇到识别阶段经常把半截车牌框出来就把mosaic降到0.5甚至0.3这是最直接的后悔药。3.3 训练日志与mAP怎么看训练结束后终端会输出每个类别的P精确率、R召回率、mAP50和mAP50-95。对车牌检测场景我最看重的是R而不是mAP50-95因为漏检一辆车意味着道闸不抬杆比误检更影响体验。R要做到95%以上才敢上线。训练过程中还有一个容易忽略的细节results.png里val_box_loss曲线。如果val的box loss在epoch 200之后还在缓慢下降说明数据还有学习空间可以加epoch。如果val loss已经平稳甚至反弹再多的epoch只会过拟合到训练集此时看best.pt和last.pt的差别就知道模型有没有飘——best.pt在验证集上表现最好last.pt是最后一个epoch的权重两者mAP差距超过2个点说明后半段训练已经在过拟合。另一个常被忽略的文件是labels.png它可视化标注分布框中心点散布在图像中央区域宽高集中在一个范围。如果看到标注框的width/height出现离群值比如某个框宽度归一化值超过0.5说明有标注错的样本混进来了优先清洗数据而不是调参——训练数据里的脏框靠调参永远调不干净。4. 检测完之后怎么读字符车牌识别两条可行路线YOLOv5检测模型输出的是车牌位置框接下来要把框里的字符读出来。这一步是车牌识别LPR常见做法有两条一是检测车牌后裁剪图像交给LPRNet这类轻量OCR网络直接输出字符串二是再用YOLOv5训练一个字符级检测模型检测出每个字符的位置后按顺序拼接。两条路线在工程上都有大量落地选哪条取决于你的字符集规模和部署算力。4.1 路线一YOLOv5检测车牌 LPRNet字符序列识别LPRNet是专门为车牌识别设计的轻量网络输入车牌的裁剪图比如宽高94x24输出是字符串序列不需要预先做字符分割。它内部用CTC损失做序列建模训练数据是字符级别的标注省份汉字、字母、数字。常见做法是检测模型每帧输出车牌框把框内区域用OpenCV裁出来resize到LPRNet的输入尺寸前向一次直接得到「京A12345」这样的结果。import cv2 import torch # plate_det是训练好的YOLOv5检测模型lprnet是训练好的识别模型 def detect_and_recognize(img, plate_det, lprnet): results plate_det(img) # YOLOv5前向得到车牌框 for det in results.xyxy[0]: x1, y1, x2, y2, conf, cls det.cpu().numpy() if conf 0.6: continue crop img[int(y1):int(y2), int(x1):int(x2)] # 裁剪车牌区域 crop cv2.resize(crop, (94, 24)) # 对齐LPRNet输入尺寸 crop torch.from_numpy(crop).permute(2, 0, 1).float() / 255.0 plate_text lprnet(crop.unsqueeze(0)) # 前向得到字符串 print(fplate: {plate_text}, conf: {conf:.2f})这段代码把检测和识别串起来。核心在于resize到(94,24)这一步LPRNet的输入设计是宽94高24固定尺寸字符长宽比会被拉伸但CTC训练时已经见过这种拉伸所以问题不大。裁剪前建议先做一次透视矫正框是斜的时候直接resize会拉伸变形识别准确率明显下降矫正方法在4.3节讲。conf阈值取0.6是经验值道闸场景漏一拍比误抬杆好阈值可以提到0.7。4.2 路线二用YOLOv5做字符级检测后拼接如果不想额外维护一个LPRNet网络也可以把YOLOv5用在字符粒度上把车牌图里每个字符当成一个目标类类别是「省份汉字字母数字」训练一个字符检测器。识别时先把大图上的车牌框出来裁剪再对裁剪图跑字符检测最后按x坐标从左到右排序拼接。python train.py --data char.yaml --weights yolov5s.pt --epochs 200 --batch-size 32 --img 320char.yaml里的nc是字符类别数常见设置是31个省份汉字24个字母I和O通常不用10个数字共65类。输入分辨率降到320因为字符在裁剪图里已经是大目标不需要640。这条路线对排布不标准的车牌比如新能源车牌的8位字符更友好LPRNet在长字符序列上偶尔会丢字符字符级检测配合排序拼接则不会。代价是工程链路变长要按x坐标排序、要处理粘连字符的NMS合并还要处理检测框重叠时字符被重复计数的问题。我见过不少项目在这条路线的后处理上加了一堆规则最后效果不稳定。纯从省心角度数据规模不大时优先考虑LPRNet只有字符集里有特殊字符、或者车牌样式特别不规整时才值得上字符级检测。4.3 车牌透视矫正识别前的一步关键预处理无论走哪条识别路线裁剪出来的车牌区域都可能带透视形变。道闸摄像头的安装角度决定了车牌不会总是正对镜头尤其是车停在坡道或车头偏离时车牌是一个平行四边形。直接送进识别网络字符被压缩识别率掉得厉害。import cv2 import numpy as np def rectify_plate(crop_gray): # 边缘检测找到车牌四边形的四个角点 edges cv2.Canny(crop_gray, 100, 200) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnt max(contours, keycv2.contourArea) # 车牌是四边形用approxPolyDP拟合出四个角 epsilon 0.02 * cv2.arcLength(cnt, True) approx cv2.approxPolyDP(cnt, epsilon, True) if len(approx) ! 4: return crop_gray # 拟合不出四边形原样返回 # 按左上、右上、右下、左下排序后做透视变换 pts approx.reshape(4, 2).astype(np.float32) dst np.float32([[0, 0], [94, 0], [94, 24], [0, 24]]) M cv2.getPerspectiveTransform(pts, dst) return cv2.warpPerspective(crop_gray, M, (94, 24))这段代码用Canny边缘检测拿到车牌的边缘轮廓再用approxPolyDP把轮廓拟合成四边形最后用getPerspectiveTransform计算透视变换矩阵并矫正。核心是透视变换把任意四边形映射到固定尺寸(94,24)的矩形。提示矫正的目标矩形不要设置成正方形车牌的长宽比大约是3.7:1保持这个比例字符才不会被拉伸。这一步对识别效果的提升非常明显。我第一次调通LPRNet时直接拿倾斜车牌测试识别率只有70%出头加上矫正之后到96%左右。当时还在怀疑是不是模型训练问题后来才发现是前处理少了这一步。5. YOLOv5车牌项目避坑指南5个高频问题的现象、原因与解决从数据到模型再到部署车牌场景有自己特有的坑。这一章写5个实测高频问题每条按「现象→原因→解决」的结构说。5.1 蓝牌绿牌混训导致漏检率翻倍现象训练集里蓝牌和绿牌各一半训练完成后蓝牌检测正常绿牌漏检严重。原因绿牌是渐变底色字符对比度比蓝牌低模型在底层特征上更难区分另外CCPD数据集中蓝牌数量远大于绿牌类别内样本不均衡是常见的翻车点。解决把绿牌数据单独抽出来数一遍保证训练集里绿牌数量不低于总样本的30%不够就做数据增强把蓝牌样本通过HSV变换转成偏绿底色调加进训练集这是懒人办法但实测有效。5.2 车牌倾斜时检测框和字符识别一起出问题现象检测框框住了车牌但框得很松边缘有多余背景识别阶段字符大量错乱尤其两端的汉字和最后的数字。原因标注框是正矩形倾斜车牌被框成斜矩形resize的同时把字符压扁了识别网络看到的字符和训练时的形状分布对不上。解决采集数据时优先用带旋转框能力的标注工具导出旋转框后扩展成YOLO的正矩形框或者训练后用4.3节的透视矫正兜底。注意矫正解决的是识别错乱解决不了检测框松的问题检测框松要从标注侧改。5.3 训练loss前20个epoch不降反升现象loss从1.x涨到2.x然后一直不动。原因最常见是学习率过大。YOLOv5默认lr00.01是配合COCO这种大数据集的车牌数据量小0.01偏大。解决把lr0改到0.005warmup_epochs从3提到5。如果还是不行用--weights 从零训练对比如果从零训练loss反而降得更快说明预训练权重的特征跟车牌场景差太多那就老老实实从零训练或者换yolov5n这种更轻的预训练模型。5.4 远距离小目标车牌总是漏检现象道闸前3米内车牌秒检距离超过10米就开始闪烁漏检。原因摄像头画面里远距离车牌只有几十个像素宽640分辨率下特征太弱。解决训练时把--img从640提高到1280检测时也按1280推理。显存不够就在训练和推理时用--rect保持原图比例减少无效padding。另外可以从yolov5s换到yolov5m深度增加对小目标的特征表达能力有实打实的提升。5.5 识别阶段省份汉字误识别成其他字现象「京」被识别成「青」或「冀」「湘」被识别成「浙」频率还不低。原因LPRNet这类OCR网络的训练数据里省份汉字类别分布很不均衡北京、山东、广东的样本多青海、湖南的样本少模型对低频汉字基本是瞎猜。解决第一条路是训练时对低频省份做过采样把「青」「湘」这类样本重复加载到和北京一个量级第二条路是不训练省份汉字识别结果里前两位只做校验不参与业务判断很多停车场系统根本没用到省份信息可以砍掉。字符集缩小之后识别率会整体上升这个是立竿见影的。6. 部署到边缘设备导出ONNX与TensorRT加速的验证习惯模型在GPU上跑通了下一步是部署。车牌检测和识别的典型部署目标是道闸工控机或边缘盒子NVIDIA平台用TensorRT树莓派5这类ARM平台用ONNX Runtime导出流程大同小异。python export.py --weights best.pt --include onnx --img 640YOLOv5官方就带导出脚本--include onnx导出ONNX格式--img 640必须和训练时保持一致。导出后用onnxruntime跑一次推理对比原模型和ONNX模型的输出差异。差异超过0.1个置信度阈值就要检查是否开了--dynamic动态输入。车牌场景输入尺寸是固定的关掉动态维度能让模型更稳定。TensorRT加速时有一个关键验证习惯用同一段测试视频跑原模型和TensorRT引擎逐帧对比检测框坐标。坐标偏差超过2个像素就要警惕量化精度掉了此时把FP16去掉改用FP32视觉任务上FP16的加速收益远小于精度损失风险。部署验收不要只看识别率要看整条链路的端到端耗时。道闸场景从图像输入到输出字符串50毫秒以内是可接受的超出200毫秒就要拆链路逐段计时图像采集、检测前处理、检测推理、裁剪矫正、识别推理、后处理拼接哪段耗时长就优化哪段。我见过很多项目栽在识别推理上——LPRNet虽轻但如果每帧都跑两次依然会吃掉算力预算。车牌识别项目的调优顺序我一直按「先数据、再输入尺度、最后引擎」来。数据错了调什么都没用输入分辨率不够再好模型也白搭引擎优化永远放在最后。这个习惯帮我避掉了不止一次返工。希望帮到你。本文还有配套的精品资源点击获取