尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

YOLOv5实战:从VOC数据集转换到边缘端部署完整教程

YOLOv5实战:从VOC数据集转换到边缘端部署完整教程 简介目标检测是计算机视觉领域的核心任务之一广泛应用于安防监控、智慧交通和工业质检等场景。YOLOv5作为当前工业界最成熟的目标检测框架之一凭借其高效的推理速度、较低的硬件门槛和丰富的社区资源成为许多工程落地项目的首选方案。在实际项目中数据格式的适配往往是模型训练的第一步常见的VOC标注格式需要通过脚本转换为YOLO支持的txt格式同时还要进行数据清洗、类别平衡与划分等预处理工作。本文从这一基础流程切入结合一个非机动车违规停放检测的实际项目详细讲解如何利用YOLOv5训练自定义数据集包括数据集解析、环境搭建、超参数调整、训练曲线评估以及最终导出ONNX并转换RKNN在RK3568等边缘设备上部署的完整链路帮助开发者快速上手端侧目标检测项目。 最近在帮一个园区做非机动车违规停放的视觉识别核心就是通过监控摄像头自动发现乱停、占道的三轮车和电动车。项目里用的算法是YOLOv5数据集是一份已经标注好的三轮车图片集——tricycle8_images_xmls。折腾了一周多从数据清洗、格式转换到模型训练、边缘端部署踩了不少坑。这篇文章把整条链路完整拆一遍适合刚接触YOLOv5、手里有VOC标注数据、想快速跑通自己的检测项目的朋友参考。由于我实际手头的算力卡是有限的所以整个方案里也特意考虑了后续往RK3568这类边缘设备上迁移的问题后面会细说。1. 项目整体设计与思路拆解1.1 为什么选YOLOv5这套组合先说选型。非机动车违规停放检测属于典型的端侧实时视觉任务摄像头部署在园区、小区、商业街出入口要求尽量低成本、低延迟、离线也能跑。目前可选的目标检测方案不少Faster R-CNN精度高但速度太慢SSD速度尚可但小目标效果一般YOLOX和YOLOv8也很优秀但YOLOv5生态最成熟、社区资料最多训练部署教程满地都是遇到问题随便一搜就有答案。尤其是手头这份数据集是标准的VOC XML标注YOLOv5官方脚本可以直接做格式转换省了不少前期工作。再一个原因是YOLOv5对硬件要求非常友好。我的训练机器是一张RTX 306012G显存用yolov5s模型跑640分辨率batch_size调到16都能稳定训练。如果要部署到RK3568这种带NPU的板子上YOLOv5的ONNX导出和RKNN转换都有完整工具链官方也默认支持踩坑的周期比别的模型短得多。对像我这种偏向应用落地的工程师来说稳定、省心比什么都重要。1.2 项目目标与技术链路这个项目的核心目标是识别监控画面中违规停放的三轮车后续扩展到电动车、自行车并在检测到目标时触发告警或人工复核。技术链路大概是这样的摄像头RTSP拉流 - 视频帧抽取 - YOLOv5推理 - 目标框坐标映射到实际场景区域 - 判断是否落在禁停区域 - 触发记录。其中最关键的第一步就是训练出可靠的三轮车检测模型这也是为什么我要把dataset.yaml、训练参数、以及部署转换这些细节都分享出来。另外要注意非机动车违规停放和普通车辆违停有个明显区别非机动车体积小、遮挡多、停放姿态千奇百怪有横着的、竖着的、甚至叠在一起的。所以训练数据里必须包含足够多的俯拍和不同角度画面。tricycle8_images_xmls这份数据集虽然名字叫“tricycle8”但实际目录里除三轮车外还有不少自行车和电动车样本后面我会讲怎么处理这部分。1.3 数据集来源与场景适配先明确一下tricycle8_images_xmls并不是一个庞大的数据集我手里这份大概只有几百张图片。几百张图片对深度学习目标检测来说属于典型的小样本。小样本训练最怕过拟合尤其是模型参数多、数据量少的情况下。所以后面我做了很多数据增强和微调策略这也是整个项目里最花时间的一步。数据集的格式是imagesXML文件即VOC格式。每张图片对应一个同名XML文件里面用bndbox框出目标位置category名称是tricycle。如果你直接把VOC格式丢给YOLOv5跑它会报错因为YOLOv5需要的是归一化后的txt坐标格式。所以第一步必须进行格式转换。转换本身不复杂但有几个细节不注意就会翻车下面展开讲。2. 数据集解析tricycle8_images_xmls到底怎么用2.1 数据集目录结构与标注格式拿到手的第一步先看目录结构。典型的tricycle8_images_xmls目录是这样组织的tricycle8_images_xmls/ ├── images/ │ ├── 001.jpg │ ├── 002.jpg │ └── ... └── xmls/ ├── 001.xml ├── 002.xml └── ...注意有的版本是images和Annotations放在一起有的是图片和XML分开放还有的XML文件名和图片名不一致。拿到数据后第一件事就是把文件名对齐我建议统一重命名为六位数字编号000001.jpg、000001.xml这样后面处理脚本好写得多。打开一个XML文件里面是标准的Pascal VOC结构annotation folderimages/folder filename001.jpg/filename size width1920/width height1080/height depth3/depth /size object nametricycle/name bndbox xmin100/xmin ymin200/ymin xmax300/xmax ymax450/ymax /bndbox /object /annotation核心信息就是object节点下的name和bndbox。一个图片里可能多个object比如一个画面里停着两辆三轮车那就有两个object节点。2.2 将VOC格式XML转成YOLO训练格式YOLOv5的训练标签是txt文件每行表示一个目标格式为class_id x_center y_center width height其中x_center、y_center、width、height是相对于图片宽高的归一化坐标取值范围0~1。class_id是从0开始的整数。写转换脚本时我用的是Python的xml.etree.ElementTree核心逻辑如下import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) out_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 防止边界越界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h out_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if out_lines: 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(out_lines))转换过程中我踩过的坑有两个。第一个是XML里缺少size字段有些标注工具不写图片尺寸那么你在转换时就要用PIL或cv2手动读取图片尺寸。第二个是bndbox坐标可能出现负数或者xmin、xmax写反了这种情况需要做边界裁剪和合法校验。我用上面的max/min处理避免程序崩溃但如果坐标反了还是会导致无意义的异常建议转换后画框检查一遍。2.3 数据清洗与类别平衡转换完成之后别急着训练先清洗数据。我打开每张图对应的txt文件检查目标数量发现有些图片可能一张图像里没有三轮车或者只有很小的目标。这类图片对训练没帮助反而容易误导模型尤其是数据集本身只有几百张的情况下。我一般用一个小脚本统计所有目标的数量和尺寸分布。把目标宽高相对于图片的比例打印出来如果目标太小比如宽度低于图片宽度的1%这种样本很难学习可以选择剔除或后续依靠多尺度训练增强。对于类别tricycle8_images_xmls的标注可能有分类不完全统一的情况有的标注成了car有的是trcycle如果你把这些当作不同类别训练模型就会非常混乱。我的做法是用脚本把所有标注name映射成目标类别三轮车统一为tricycle其他车辆如果需要检测也可以统一为vehicle然后修改class_names列表。当然如果只想检测三轮车那么其他类别的object就直接跳过不写入txt这也就是很多人说“我只保留一个类别的标注”的做法。清洗后我还做了一次数据划分按8:1:1拆分训练集、验证集、测试集。注意要用随机种子保证可复现。划分时务必保证同一场景的图片尽量只出现在同一个集合里不然会带来严重的信息泄漏导致验证集精度虚高。3. YOLOv5环境搭建与训练自己的数据集3.1 YOLOv5下载与依赖安装YOLOv5的源码在GitHub上直接用git克隆下来就行。如果你网络不方便也可以下载压缩包解压。记得切换版本标签我用的v6.0版本比较稳定网上大部分部署资料都基于这个版本。git clone -b v6.0 https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtrequirements.txt里会安装torch、opencv等一堆依赖。如果你的机器有NVIDIA显卡建议提前装好CUDA版的PyTorch而不是用pip默认装的CPU版。我习惯先到PyTorch官网复制对应的命令安装再执行requirements.txt里剩余的部分。这里有个细节YOLOv5官方代码会默认下载预训练权重比如yolov5s.pt。如果你网络有障碍可以单独下载权重文件放进weights目录。权重文件不大几十兆但如果你不下载训练脚本可能会卡在下载环节。3.2 数据配置与模型配置修改数据准备好后需要在yolov5目录下新建一个数据集配置文件比如tricycle.yaml内容如下# 数据集配置 path: /path/to/tricycle8_dataset # 数据集根目录 train: images/train # 训练图片文件夹 val: images/val # 验证图片文件夹 test: images/test # 测试图片文件夹可选 # 类别 names: 0: tricycle注意这里train和val指向的是图片文件夹而不是txt文件夹。YOLOv5会自动到同名目录下找标签默认要求标签放在与images同级目录下的labels文件夹里。目录结构是这样tricycle8_dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── ... │ └── val/ │ ├── 000010.jpg │ └── ... └── labels/ ├── train/ │ ├── 000001.txt │ └── ... └── val/ ├── 000010.txt └── ...如果你之前把图片和txt混在一起建议先整理成这个标准结构。YOLOv5代码里是硬性通过路径后缀找标签的只要结构不对就会出现标签文件找不到的警告而且不会报错停止害得我一度以为训练正常结果模型什么都没学到。3.3 超参数设定与训练命令我用的是yolov5s模型作为基础因为它在精度和速度之间最平衡。训练命令如下python train.py --data tricycle.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --cache解释一下几个关键参数--img 640输入图片缩放尺寸。原始图片是1920x1080YOLOv5会在训练时letterbox到640x640。如果你场景里目标很小可以调到896或1024但显存占用会暴涨。我实测3060跑640最合适。--batch 16根据显存调整。如果显存不够跑12或8也行。--cache把图片缓存到内存里加快训练速度。如果你的图像总数太大内存不够就不要加。--epochs 100对于小数据集100轮足够了我甚至发现50轮之后损失就不再下降。--weights yolov5s.pt使用预训练权重做迁移学习能加快收敛效果比随机初始化好很多。超参数文件默认是data/hyps/hyp.scratch-low.yaml其中包含学习率、动量、增强参数等。对于小数据集我喜欢把mosaic和mixup保持开启增强它们对提升泛化能力很有帮助。如果训练时发现loss不稳定可以将lr0从0.01降为0.005在命令里用--hyp指定自定义yaml文件实现。我调试时还会加一个--patience参数比如--patience 20意思是20轮没有提升就提前停止。这样能节省时间不用每次都跑满100轮。3.4 训练单通道灰度图像的特别处理可能有人会问我的监控相机是黑白的或者源头是红外相机能不能直接用单通道图训练这个我专门试过。YOLOv5的默认数据加载器在读取图片时用的是PIL的RGB模式单通道灰度图会被复制成三个通道即每个通道的数值相同。所以理论上你直接把灰度图放进数据集训练也能正常进行因为模型输入还是三通道。但这种方式会在推理时增加无意义的冗余计算而且如果训练集是灰度、测试集是彩色模型性能会下降。如果你确定整个部署环境都是单通道或灰度源有两个做法第一个做法是保持默认三通道但数据预处理阶段把图片统一转成灰度再复制三通道让训练和推理的数据分布一致。实现方式是在自定义的dataset类里给图片做灰度转换或者直接在送入训练前用cv2.cvtColor转一遍。这个方法最简单不用动模型结构推荐新手使用。第二个做法是修改模型第一层卷积的输入通道数。YOLOv5的模型定义文件models/yolov5s.yaml里第一层是Focus或者Conv不同版本不一样把输入通道从3改成1同时加载预训练权重时第一层的权重无法直接对齐需要跳过或做均值化处理。这个操作比较麻烦而且收益不大。我后来实测发现在边缘设备NPU上三通道输入的推理耗时和单通道差异可以忽略不计真正影响速度的是算子和量化优化。所以建议无脑用第一种方案。4. 训练过程实战记录与评估4.1 训练曲线怎么看训练结束后会在runs/train/exp目录下生成result.png等文件包含训练损失、验证损失、mAP等曲线。我的经验是重点看验证集上的Box P、Box R、mAP50这些指标。如果训练损失持续下降但验证损失在某个epoch后反弹这就是过拟合的典型信号。对于几百张图片的数据集过拟合几乎必然会发生关键是什么时候早停。我那次训练大概到第70轮时mAP50已经在0.92附近徘徊验证损失不再下降果断在第85轮结束训练。看一下result.png里的P曲线和R曲线会发现最后的10轮基本走平说明模型容量已经够了。如果你发现mAP50怎么也上不去别急着调模型先回头检查标签格式和分类是否有误。4.2 模型效果评估与阈值调节训练完成后我做一个实拍测试。把没参与训练的图片拿出来或者直接拿摄像头画面截图测试。执行推理python detect.py --weights runs/train/exp/weights/best.pt --source test.jpg --conf-thres 0.25 --iou-thres 0.45--conf-thres是置信度阈值默认0.25。实际应用中因为我们的目标是违规停放监测漏检的代价比误报低因为漏检导致违规行为没被发现而误报只是产生少量人工复核。所以可以适当降低阈值到0.15在保证精度可接受的前提下尽可能不漏目标。但如果阈值太低可能会出现大量误框比如把行人当成三轮车这时需要根据现场情况调整。另外YOLOv5的NMS阈值iou-thres一般保持默认0.45即可。如果检测目标相互重叠严重可能需要稍微降低到0.4避免两个同类目标被合并成一个框。这个参数需要根据实际场景测试调整。4.3 在RK3568及RV1106上部署的落地经验项目最终要跑到边缘设备上我主要试了RK3568。部署流程大概分四步导出ONNX、转换RKNN、集成推理、优化后处理。导出ONNX用的是官方脚本python export.py --weights best.pt --include onnx --opset 11转换RKNN这一步比较关键。需要安装rknn-toolkit2版本和芯片的NPU驱动要对应。我的做法是在x86的Docker容器里跑转换生成.rknn文件后再拷贝到板子上。转换时最需要注意的是量化。默认转为int8量化后模型大小缩小到1/4左右推理速度快很多但精度会掉一些。如果mAP下降得厉害可以尝试混合量化把最后几层保留为fp16。我在三轮车检测上int8量化后mAP50从0.92下降到0.88但速度从30毫秒/帧提升到12毫秒/帧性价比很高。RV1106也是瑞芯微的芯片它的NPU算力比RK3568弱但做轻量级检测也够用。转换流程类似只是rknn-toolkit里需要指定target platform为rv1106。如果量化后精度不够换yolov5n模型速度比yolov5s快一倍精度其实也够。这里遇到一个坑RKNN转换时由于YOLOv5的输出包含三个不同尺度的特征图后处理需要在板端实现。使用rknn-toolkit自带的YOLOv5 demo代码大概率要在解码时调整anchor和stride。如果你导出时用的是动态shape部署时有些坑最简单的是固定输入尺寸为640x640这样一切问题都少很多。5. 常见问题与排查技巧实录5.1 数据转换和路径问题很多新手在转换VOC到YOLO后训练时看到警告“WARNING: 0 labels found in ...”训练也正常启动了但损失一直不降。这种情况99%是标签路径不对。检查yaml文件里train和val路径是否定位到图片目录检查图片对应的txt文件是否在labels目录下且文件内容非空。还要注意YOLOv5要求标签文件名必须与图片文件名完全一致大小写敏感。如果你的图片是jpg标签是txtJPG后缀不同也可能找不到。还有一个问题多个文件重名。我遇到过一次图片是001.jpg但XML文件是001.xml转出来的txt是001.txt可是另一个文件夹里恰好还有一个001.jpg导致训练时标签匹配到了错误的图片。建议用脚本把所有图片统一重命名比如tricycle_0001.jpg、tricycle_0002.jpg避免歧义。5.2 训练不收敛或震荡训练不收敛的原因各异。如果是loss直接变成nan先检查学习率是否过大或者数据里是否有异常的标签宽高为0。宽高为0的情况通常来自XML中xmin和xmax相等或者分母图片尺寸为0。我在清洗脚本里加了一个过滤条件凡是宽高小于0的我直接丢弃该目标避免训练崩溃。如果是损失震荡但总体下降可以尝试增大batch size或者关闭一些强数据增强。小数据集上我经常在hyp.yaml里把hsv_h、hsv_s等颜色增强参数调小因为这些增强会让本来有限的颜色特征变得不稳定。5.3 检测漏检误检的调优建议模型跑起来之后最常见的抱怨是“老是误检”。这里我建议先收集20~30张现场实际抓拍的图片单独做一个测试集测一下模型在这些图片上的表现。如果误检来自远处的行人可以在后处理增加一个目标最小宽高过滤比如小于一定像素的框直接删掉。如果漏检是目标被遮挡可以考虑调整推理时的多尺度测试比如--augment。但要注意多尺度测试在边缘设备上很难实现尽量用训练时的强mosaic增强来提高对遮挡的鲁棒性。另一个经验是类别不平衡。如果数据集中三轮车数量远大于自行车模型会倾向于预测三轮车。解决办法可以是先单独训练一个三轮车检测器再逐步增加其他类别也可以对数量少的类别做过采样在数据划分时把重复图片加进去。但我个人建议尽量保持单一类别减少误检风险毕竟实际业务里违规停放主要关注三轮车拉货占道其他车辆可以后续再扩展。6. 后续扩展思路与个人经验6.1 小技巧扩展数据集的方式数据集太小的问题怎么解决除了用mosaic增加训练样本多样性之外我还有一个笨但有效的办法把视频抽帧。找几段园区不同角度的监控视频按每秒一帧抽图抽出来之后用已有的模型先做自动标注再人工审核修改。这样能快速扩充到上千张图片。自动标注的工具有labelimg、makesense.ai配合YOLOv5的推理结果生成pre-label人工修正成本低很多。另外如果实际场景和训练集场景差异大比如训练集是俯拍部署位置却是平拍那么模型的泛化能力会很差。最好的方式是重新采集部署点位附近的图片加入训练集重新训练。我做过一次加入新点位混合训练后误检率下降了50%以上。6.2 个人体会这套项目做下来说实话最大的收获不是YOLOv5本身而是明白了一个道理数据质量和场景匹配度远比模型结构重要。tricycle8_images_xmls这种小数据集如果不仔细清洗、不做目标尺寸分析、不针对部署场景做增强哪怕换成YOLOv8也救不回来。反过来数据整理好了用yolov5s也能在端侧跑出实用效果。最后再分享一个小技巧训练完成后保留好训练用的yaml文件和超参数文件放在模型目录里一起保存。因为部署阶段你永远不知道什么时候需要重新训练或者调试有了完整的训练配置复现起来会非常方便。我在RK3568上验证时就因为需要知道原有训练用的anchors大小翻回去找到了配置文件省了很多时间。项目后续扩展时可以考虑增加一个禁停区域检测分支也就是把目标检测结果映射到预先划定的多边形区域内区域内检测到非机动车就判定为违停。这个功能用opencv的pointPolygonTest就能实现跟YOLOv5结合也不复杂。等哪天我把那个部分也调好了再单独写一篇分享。本文还有配套的精品资源点击获取
返回列表