
简介目标检测是计算机视觉领域的核心任务之一其目标是在图像或视频中定位特定物体并给出类别信息。在智能交通与安防场景中车牌识别作为典型应用依赖高精度的目标检测技术从复杂背景中提取车牌区域。YOLOv5凭借轻量级网络结构与成熟的工程生态成为车牌检测的主流方案之一尤其适合停车场道闸、卡口监控等实时性要求高的场景。围绕YOLOv5开发者需要掌握从数据集标注、数据增强、模型训练调参到字符识别串联、ONNX导出及TensorRT加速的完整链路。同时边缘计算设备的普及对模型部署提出了更高要求如何在Jetson、RK系列等硬件上实现高效推理是工程落地的关键。本文结合车牌识别项目的真实经验梳理从数据准备到部署优化的全流程技术细节帮助开发者理解目标检测技术在垂直场景中的实践方法并高效搭建一套可用的车牌识别系统。 做车牌识别这个方向绕不开YOLOv5。不管你是做毕业设计、公司安防项目还是自己捣鼓一套停车场道闸系统这个名字总会出现在各路教程和开源仓库里。我在这个领域踩过不少坑从数据集标注到模型调参再到最后部署到边缘设备整理了一条比较完整的路线把关键的技术决策和实操细节都摊开来讲希望能帮你少走弯路。这个项目的核心目标很明确输入一张图像或一段视频输出画面中所有车牌的位置并识别出车牌上的字符内容。适用场景包括停车场出入口、道路卡口、小区门禁等。适合对深度学习有一定基础、想完整走通“数据-训练-部署”全流程的开发者参考。1. 项目整体设计与技术拆解1.1 车牌识别系统的完整链路车牌识别在工程上从来不是单一模型能搞定的事它是一条完整的链路。最经典的做法是两阶段方案先做目标检测框出车牌位置再做字符识别读出车牌内容。为什么要拆成两段而不是直接端到端因为检测和识别的任务难度不同分开优化会让整个系统的容错性更好。先说检测端。车牌在图像中只占很小一片区域尤其是道路监控画面里车牌可能只有几十个像素宽。这种小目标检测对模型的要求很高既要抓得准又要保证速度能跟上视频流的帧率。而识别端处理的是已经裁剪好的车牌小图任务变成序列文本识别和OCR类似。YOLOv5在这个链路里负责的是第一步——车牌定位。它的输出是若干个边界框每个框包含中心坐标、宽高和置信度。在拿到这些框之后再根据框的坐标把车牌区域裁剪下来送入识别模块。1.2 为什么在这个场景选YOLOv5你可能要问现在YOLOv8、YOLOv9都出来了为什么还要用YOLOv5坦白说如果只追求指标上的最优YOLOv8确实在COCO数据集上有更好的表现。但落到车牌识别这个具体场景YOLOv5有两个不可替代的优势。第一个是生态成熟度。YOLOv5的社区太大了无论是数据标注工具的支持、模型部署的教程还是各种边缘设备的适配你遇到的问题几乎都能搜到现成答案。这一点在工程落地时太重要了。第二个是硬件友好性。YOLOv5s只有大概7.2M参数计算量在16.5 GFLOPs左右这意味着它可以在不牺牲太多精度的前提下轻松跑到实时甚至超实时。拿我实际测试的数据来说在一块中端显卡上YOLOv5s处理一张1080P图像大约需要8毫秒加上预处理和后处理也就11毫秒左右完全满足视频流25帧每秒的需求。如果用TensorRT做加速在Jetson Nano这类边缘设备上也能跑到40毫秒以内。1.3 需求边界与设计取舍在设计系统之前必须想清楚一个问题你要做的车牌识别面对的输入是什么样的是大角度俯拍的停车场入口还是平视视角的电子警察还是可移动的手持终端不同场景对模型架构的影响很大。我最初做的时候犯过一个错误直接拿通用目标检测的权重去跑车牌图片结果漏检率特别高。后来分析原因停车场出入口的照片里车牌经常是倾斜的而且受光照影响严重逆光时车牌几乎变成一团黑影。所以后来在数据集的构建阶段我刻意加入了大角度旋转、强逆光、模糊样本。另一个取舍是是否需要识别新能源绿牌。新能源车牌是8位字符而传统蓝牌是7位这会影响后续字符分割和识别的设计。如果你的系统需要兼容两种车牌从数据准备阶段就要把两种情况都纳入并且在识别模块里处理好变长序列的问题。2. 数据集准备与标注规范2.1 数据来源与公开数据集推荐训练数据是车牌识别项目里最耗时也最关键的环节。公开数据集首选CCPDChinese City Parking Dataset这是中科大收集的国内停车场大规模数据集包含超过25万张图像覆盖了蓝牌、绿牌、黄牌以及各种复杂场景。另一个是CRPDChinese Road Plate Dataset偏道路场景图像质量更高。用公开数据集有个需要注意的地方它的图像尺寸偏大很多是720P以上的街景图车牌在其中占比较小。直接拿去训练也不是不行但最好做一次统计分析看看你自己的目标场景和数据集分布是否匹配。如果你的场景是小区地下车库而数据集是路边停车位那光照和角度差异就会很大泛化效果会打折扣。我建议的补充方式是用自己的摄像头去停车场、路边、小区门口录制半小时视频抽帧后挑出有效样本加入训练集。这个操作说起来简单但对最终精度的提升非常明显。我自己的模型在加入大约2000张现场采集的样本后检测精度mAP0.5从88%提升到了93%以上。2.2 YOLO标注格式详解YOLOv5使用的标注格式是txt文件每一行代表一个目标共5个数格式为class_id x_center y_center width height注意这四个坐标值都是归一化的也就是用像素坐标除以图像的宽和高得到0到1之间的数值。举例来说一张1280x720的图像里如果车牌中心点在(640, 360)宽200高60那么标注内容就是0 0.5 0.5 0.15625 0.08333这里0.15625是200除以12800.08333是60除以720。标注工具我用的是LabelImg和X-AnyLabeling。如果是标注车牌这类单一目标LabelImg足够用了它生成的标注文件可以直接做格式转换。在标注的时候有两点经验第一框一定要紧贴车牌边缘不要留太多背景否则模型学到的特征会包含大量无关信息第二对于遮挡或模糊的车牌如果人眼都很难确认位置建议直接跳过不标注不要硬标否则会引入噪声样本。2.3 数据增强策略YOLOv5内置了丰富的数据增强策略这些增强在训练时随机执行相当于免费扩充了数据集。核心的几种增强方式包括Mosaic增强将4张图拼接成1张增强模型对小目标的感知能力对车牌这种小目标特别有效HSV色域变换随机调整图像的色调、饱和度和明度提升模型在不同光照条件下的鲁棒性随机水平翻转车牌字符顺序会反转但这种翻转在训练中会让模型学到更本质的纹理特征实际推理时对正立车牌的识别没有负面影响不过在车牌识别场景里有一个增强参数需要特殊处理——旋转角度。YOLOv5默认的旋转增强范围是-10度到10度但对车牌来说超过30度的旋转在实际中也很常见比如坡道停车场的摄像头车牌在画面里往往是倾斜的。我在训练时把旋转角度调到了30到-30度。代价是训练时间变长了一些但换来了更好的角度鲁棒性我觉得值。3. 环境搭建与依赖配置3.1 深度学习环境安装要点这部分是最劝退新手的环节我当年在这里卡了快一周。先说结论环境配置的核心是让PyTorch、CUDA和显卡驱动三者版本匹配任何一环对不上都会出现奇奇怪怪的错误。如果你用的是NVIDIA显卡第一步装驱动别操之过急。Ubuntu系统下用ubuntu-drivers devices命令查看推荐版本然后直接用sudo apt install nvidia-driver-550这类命令安装。有些教程会让你去官网下载.run文件手动装新手别碰这个一旦装错图形界面起不来排查成本特别高。装好驱动后用nvidia-smi验证能看到显卡信息就说明驱动正常了。接下来装CUDA Toolkit和cuDNN。这里有个容易让人困惑的地方PyTorch官方提供的whl包里面已经捆绑了运行时所需的CUDA库所以很多情况下你根本不需要单独安装完整的CUDA Toolkit只需要确保驱动版本足够新。我的建议是用conda创建独立环境Python版本3.9或3.10然后跑pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118这个命令会安装支持CUDA 11.8的PyTorch。装完后在Python里执行import torch print(torch.cuda.is_available())如果输出True恭喜环境通了。3.2 YOLOv5源码获取与依赖安装YOLOv5的安装流程相当标准化。先拉取官方代码库git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txtrequirements.txt里列的依赖比较多包括opencv-python、matplotlib、pandas、tqdm等。正常情况下几分钟就能装完。如果网络不好导致某些包下载失败建议pip换用国内镜像源pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple需要提醒的是YOLOv5版本更新很快每个版本的API会有细微变化。我在项目里用的是6.2版本这个版本最稳定网上教程也最多。不建议直接拉最新的master分支新的不一定好稳定才重要。3.3 WSL2环境特别说明如果你的主力机是Windows又想用Linux环境跑训练WSL2是个不错的选择但有一个前提条件必须开启主板BIOS中的虚拟化功能。运行WSL2报错“此计算机上未启用虚拟化”十有八九就是BIOS里没开。开启方法是重启电脑进入BIOS找到Intel Virtualization Technology或SVM Mode改成Enabled。保存重启后在Windows的“启用或关闭Windows功能”中勾选“适用于Linux的Windows子系统”和“虚拟机平台”再安装WSL2即可。不过说实话如果你是做深度学习训练我强烈不建议长期用WSL2磁盘IO的损耗对数据读取速度影响很大。WSL2适合调试代码真正的模型训练还是放到纯Linux环境或者远程服务器上跑更靠谱。4. 训练配置与模型调优4.1 数据集目录结构与配置文件YOLOv5要求数据集按固定的目录结构存放datasets/ └── plate/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/images和labels对应放图像和标注文件文件名必须一一对应否则训练时会提示找不到标签。我习惯用一个Python脚本自动划分数据集按70%训练、15%验证、15%测试的比例随机分配并确保同一条视频里抽出来的帧不要全部跑进训练集否则验证集的结果会虚高。接下来在YOLOv5的data目录下创建plate.yamltrain: /path/to/datasets/plate/images/train val: /path/to/datasets/plate/images/val nc: 1 names: [license_plate]nc1表示只有车牌一个类别names列表里的名称自己定不影响训练过程。4.2 关键训练参数解读训练命令的基本格式是python train.py --data data/plate.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100每个参数都有讲究。img是输入分辨率YOLOv5默认用640x640。车牌识别场景中如果原始图像的尺寸很大且车牌很小可以把输入分辨率提高到960甚至1280这会直接改善小目标的检测效果但代价是显存占用和推理时间都会上升。我自己的经验是普通停车场场景用640就够卡口相机抓拍的高清大图建议用960。batch大小的选择取决于你的显存大小。24GB显存跑YOLOv5s可以开到32甚至更大8GB显存建议用16。batch太小会导致梯度估计不准确训练过程容易震荡loss曲线锯齿感很强。epochs表示训练轮数。车牌是单类目标任务相对简单100轮基本足够。我在训练时会在第80轮左右检查一下验证集mAP如果不再明显提升就提前终止没必要白白烧电。4.3 超参数调整细节YOLOv5提供了data/hyps/hyp.scratch-low.yaml这样的超参数文件里面定义了学习率、动量、数据增强系数等一系列参数。新手不需要大改但有几个参数必须关注。学习率lr0默认为0.01。车牌数据集小的情况下初始学习率太大会导致前期loss飙升。我在小数据集上通常把lr0降到0.001让模型平稳收敛。mosaic默认是1.0也就是每张训练图都会做Mosaic增强。Mosaic对提升小目标检测很有效但如果训练集很干净、目标位置相对固定可以考虑降到0.5降低增强强度让模型更快拟合真实分布。anchor也是一个值得讲的话题。YOLOv5默认的anchor是在COCO数据集上聚类得到的对车牌这种长宽比特别大的目标通常长宽比在3:1到4:1之间并不完全适配。好在YOLOv5训练时会自动计算数据集的anchor你不需要手动调整只需确保训练时控制台输出的新anchor不是和默认值偏离太远就行。4.4 训练过程监控与评估训练过程中loss曲线是最直接的反馈。正常情况下box_loss、obj_loss和cls_loss都应该呈下降趋势并趋于平稳。如果出现loss持续不降甚至上升大概率是学习率太大或数据集有问题。每个epoch结束后验证集会计算mAP0.5和mAP0.5:0.95两个指标。对车牌检测来说主要看mAP0.5因为这个指标反映的是IoU大于0.5时检测框的准确率与实际应用场景的容忍度基本一致。我训练的模型mAP0.5通常在0.92到0.95之间。训练完成后模型权重保存在runs/train/exp/weights/目录下最好的是best.pt这是验证集表现最优的那个权重文件。5. 车牌字符识别与系统整合5.1 字符识别的方案选择检测到车牌后落地到实际项目还需要字符识别。这块有两条技术路线一是用专门的轻量级OCR模型二是自己训练分类网络做单字符识别。路线一用的是LPRNet或CRNN这类序列文本识别模型。它们的输入是车牌小图输出是字符序列。LPRNet的一个优势是不需要做字符分割直接端到端识别对倾斜和模糊的车牌也相对鲁棒。我用的就是LPRNet的一个PyTorch开源实现在裁剪好的车牌图上单张识别时间不到5毫秒精度在95%以上。路线二的做法是先做字符分割把车牌切成单个字符然后送入一个分类网络。这个方案理解起来直观但实现细节很繁琐。字符分割在车牌有铆钉、边框或者倾斜的情况下经常出错一个分割失误就导致整个识别失败。我现在基本不推荐这种做法。5.2 识别模型的数据处理字符识别模型需要单独的训练数据。公开方案中合成数据充当了主力。用程序在纯色背景上渲染车牌字符包括中国各省简称汉字、英文字母和数字然后叠加随机噪声、模糊、透视变换生成几万张合成图像。关键在于合成数据必须尽量贴近真实车牌的字体。车牌的字体不是随便一个系统字库就行的需要找车牌专用字体或者直接用真实车牌的图像做数据增强。我在项目中是先用合成数据预训练再用真实车牌图像微调这个组合效果最好。识别模型的输出还需要后处理主要是正则化校验。中国车牌的格式通常是一个省份汉字加一个发牌机关字母后面是5位或6位数字字母混合。我在识别结果上加了正则过滤比如第二个字符必须是字母如果模型输出的是数字我会修正为最接近的字母比如0修正为O、1修正为I但要注意车牌字符中O和I通常不使用所以这类情况一般是模型输出错误。5.3 从检测到识别的完整流程串联实际项目的推理流程我的实现是这样的# 检测阶段 results model(frame) boxes results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2, conf, cls box if conf 0.5: continue crop frame[int(y1):int(y2), int(x1):int(x2)] # 预处理车牌图像 plate cv2.resize(crop, (168, 48)) plate cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) # 字符识别 text lprnet(plate) print(text)这里的关键是检测框到识别输入的尺寸映射。不同车型的车牌宽高比会有差异统一resize到168x48是因为这个比例接近普通蓝牌的比例如果检测到的是新能源绿牌宽高比会略大一些需要根据检测框本身的长宽比决定是否保持比例缩放到固定高度。5.4 性能优化与错误处理系统整合后还要考虑性能。我的方案里检测模型的推理时间约10毫秒识别模型约3毫秒整个流程大约15毫秒在视频流场景下完全够用。错误处理方面最容易出问题的是识别置信度低的情况。当识别结果的置信度低于0.7时我选择丢掉这一帧的识别结果而不是强行输出一个可能错误的车牌号。在道闸等需要高准确率的场景中连续三帧识别结果一致才认为识别成功这样能有效避免误判。6. 模型导出与边缘设备部署6.1 ONNX导出与TensorRT加速训练好的PyTorch模型无法直接在边缘设备上高效运行必须先做格式转换。YOLOv5官方提供了导出脚本python export.py --weights runs/train/exp/weights/best.pt --include onnx导出ONNX后可以再转成TensorRT的engine文件在NVIDIA设备上获得更大的速度提升。我自己在Jetson Nano上测试过PyTorch模型推理一帧大约需要90毫秒转成TensorRT INT8后降到35毫秒左右提升非常明显。6.2 瑞芯微平台部署要点如果你用的是瑞芯微RK3568这类国产边缘计算板子部署流程稍微特殊一点。RK3568有自己的NPU工具链RKNN不能直接跑ONNX。流程是先导出ONNX模型再用rknn-toolkit2将其转换为RKNN格式。我在RK3568上跑YOLOv5s的实测帧率大约在15到20 FPS如果输入分辨率降到416x416可以跑到30 FPS。想在更低成本的RV1106上跑则需要把模型压得非常轻量YOLOv5s已经是上限了通常会改用YOLOv5n或者更小的变体。RKNN转换过程有个常见坑PyTorch模型中某些算子尤其是涉及动态shape的操作RKNN不支持需要手动修改网络结构或者修改导出方式。遇到这种情况别慌用固定的输入尺寸导出ONNX基本都能绕过去。6.3 轻量化部署与摄像头实时推理对于普通PC部署我习惯直接把训练好的权重和推理脚本封装成一个Python类用OpenCV读取摄像头帧逐帧调用模型推理并用PIL或OpenCV在画面上叠加检测框和识别结果。cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break text plate_recognize(frame) cv2.putText(frame, text, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(plate, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()实际测试中CPU推理YOLOv5s大约300毫秒一帧GPU则十几毫秒差距非常大。如果必须在CPU上实时跑可以选择YOLOv5n或者蒸馏一个轻量模型。如果是通用x86盒子也可以考虑用OpenVINO加速Intel家的CPU用这个方案能压到50毫秒左右。7. 常见问题与排查技巧7.1 安装与环境问题速查我把自己遇到过的和身边朋友问过最多的环境问题整理成了一张速查表。问题现象可能原因解决办法torch.cuda.is_available()为False驱动版本或PyTorch版本不匹配用nvidia-smi检查驱动重新安装对应CUDA版本的PyTorchOpenCV无法读取视频流解码库缺失pip install opencv-python-headless或安装ffmpeg训练时显存不足(OOM)batch过大或图像分辨率过高减小batch降低img尺寸用梯度累积WSL2启动报虚拟化错误BIOS未开启虚拟化重启进入BIOS开启VT-x/SVM7.2 训练与精度问题模型训练不收敛或精度差的坑我踩过很多说几个高频的。第一个是loss降不下去且震荡剧烈。检查一下数据集的标注坐标有没有问题特别是归一化后坐标值超过1的情况。我遇到过一批新标注的数据x_center写成了像素值而不是归一化值训练结果直接崩掉。第二个是mAP_0.5很高但跑到真实场景就漏检。这种情况通常是训练数据和实际场景的分布差异太大。建议回到数据部分多采集自己目标场景的图像做补充训练。不要指望一个通用模型搞定所有场景。第三个是车牌被Truncated或被遮挡。如果目标车牌在图像边缘被切掉一半YOLOv5的检测框通常会给出一个裁剪后的框识别阶段会失败。解决办法是在预处理阶段对检测框做一定范围的扩展把被切掉的边缘补回来。我一般把检测框每个方向外扩10%到20%再裁剪识别成功率会好很多。7.3 部署与性能优化部署阶段的性能瓶颈很多时候不在模型推理而在图像解码和预处理。我在RK3568上第一次测帧率只有8 FPS仔细排查发现视频解码用的是软解码CPU直接跑满。换成硬解码接口RKMPP之后帧率直接翻倍。另一个很容易忽略的点是NPU算力分配。如果你在RK3568上同时跑检测和识别两个模型它们会争抢NPU资源导致两边都变慢。我后来把识别模型改到CPU上用RKNN的混合量化模式跑推理时间从15毫秒增到30毫秒但检测模型从30毫秒降到15毫秒整体帧率反而提升了。7.4 我认为最容易踩的“隐形坑”最后说几个不太容易被记录在文档里的问题。预训练权重版本不一致YOLOv5官方提供的yolov5s.pt有6.0和6.2等不同版本如果你的代码版本和权重版本对不上训练时虽然能跑但结果会非常诡异。解决方法是用官方配套脚本下载权重别手动去第三方渠道拿。中文路径问题。数据集的路径以及数据集内部的图像路径如果包含中文或空格在读取时会报找不到文件的错误。这种问题排查起来很绕因为报错信息通常指向的是某个随机图片文件你不会第一时间联想到是根路径有中文。因此所有数据目录的名称尽量统一用英文。关于单通道灰度图训练YOLOv5默认输入是三通道RGB图像如果你网上找到的很多历史车牌数据是老式黑白摄像头拍的灰度图需要把单通道复制成三通道再训练。YOLOv5本身没有直接支持单通道输入的模型配置不要尝试去改网络第一层的输入通道数那样会破坏预训练权重的加载逻辑迁移学习的效果会大打折扣。8. 项目延伸与实战体会做完一个基本能用的车牌识别系统之后如果你还想要更顺滑的体验有一些很自然的扩展方向。角度矫正。检测到的车牌经常是倾斜的尤其是路边停车或坡道入口。在识别前用仿射变换把车牌转正能极大提升字符识别准确率。我最初忽略了这一步后来加入简单的透视校正后识别准确率提升了2个百分点对于识别这种精度敏感的任务这一步值得做。夜间场景优化。夜间车牌识别是个老难题图像亮度低、噪点多。可以在数据增强时加入低光照模拟也可以接一个轻量的图像增强模块作为预处理。或者换一个思路用红外摄像头。很多停车场道闸实际用的是红外补光摄像头灰度图为主这样反而减少了颜色干扰车牌字符更清晰。多车牌追踪。如果场景中有多辆车同时经过需要把检测框和车辆ID进行关联。这一步可以结合DeepSORT之类的多目标跟踪算法确定每个车牌的轨迹和唯一性避免同一辆车被重复识别计费。在我实际做过的几个项目里最深的体会是一个实用的车牌识别系统重心未必在模型结构上而是在数据质量和工程细节上。YOLOv5只是给你提供了一个好用的工具工具本身的好坏影响有限真正决定系统上限的是你对数据分布的理解、对场景的把握以及对各种边界情况的处理。如果你是从零开始做我的建议是先用别人训练好的通用车牌模型跑通流程理解整个系统的运转逻辑再逐步训练自己的模型。这个过程中你会深刻理解到深度学习项目里最花时间的不是写代码而是调数据和排bug。耐心一点把每一步都走扎实这个项目的价值不只是一套能跑的车牌识别系统更是你理解目标检测技术从训练到部署全流程的绝佳实践。本文还有配套的精品资源点击获取