ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

8张图训YOLOv5?三轮车识别与边缘部署实践

8张图训YOLOv5?三轮车识别与边缘部署实践 简介本资源是面向智能交通与城市管理场景的YOLOv5目标检测训练专用数据集聚焦非机动车违规停放识别任务特别适用于三轮车细粒度分类与定位模型开发。资源包含784张高质量三轮车图像JPG及配套PASCAL VOC格式XML标注文件772个共1556个文件总大小73.87MB标注涵盖万虎、宝岛等主流品牌三轮车属完整三轮车子类tricycle8数据集。已有237人学习下载适合计算机视觉初学者至中级开发者开展模型训练、数据增强与部署验证。用户可直接用于YOLOv5训练流程无需额外标注处理XML文件含精确边界框与类别标签支持一键转换为YOLO格式数据已按品牌细分便于构建多类别识别或违规停放行为分析模型显著降低算法研发门槛。 前段时间我接了一个园区管理需求用机器视觉识别非机动车违规停放尤其要盯三轮车。技术方案很快定了——YOLOv5 已标注数据集做监督训练数据这边拿到一份名为 tricycle8_images_xmls 的VOC格式标注包里面是 8 张三轮车图片加 8 个 XML 标注文件。光看数量确实有点寒酸但项目周期紧、预算有限我们先用它把完整链路跑通后面再考虑扩数据。这篇文章把从环境搭建、数据转换、模型训练到边缘部署的完整过程记录下来主要给同样拿到少量标注数据、需要在 RK3568 或 RV1106 这类边缘设备上跑 YOLOv5 的兄弟们做个参考。1. 一个很现实的场景非机动车乱停靠什么识别1.1 城市治理里的硬需求三轮车为什么是重点小区门口、批发市场周边、学校附近三轮车乱停是真正的老大难。两轮车虽然也乱停但挪动成本低保安搬一次就能解决三轮车体积大、自重高尤其是带斗的平板车或者封闭式货箱一旦堵在消防通道、无障碍坡道或者单元门口人工清理效率极低。更麻烦的是不少三轮车是长期停放的“僵尸车”一停就是好几天物业和城管都头疼。过去主要靠人盯监控大屏一屏一屏轮播漏看是常态。现在摄像头硬件基本普及缺的是最前面那道自动识别算法。项目目标很直接一台普通监控摄像头能在画面里直接圈出“这有一辆三轮车停在禁停区域”然后弹告警管理员再去现场处理。这个需求不需要多么复杂的语义理解核心就是可靠的目标检测。1.2 技术选型定调YOLOv5、机器视觉识别与已标注数据的三角关系选 YOLOv5 有几个现实原因。第一检测速度快在 RK3568、RV1106 这类 AI 边缘盒子上有大量现成部署案例社区资料多踩了坑容易找到答案。第二生态成熟不管是数据标注、训练还是导出 ONNX/RKNN流程都很顺手。第三项目参与的人未必都是算法出身YOLOv5 工程化程度高懂 Python 基本能上手。机器视觉识别在这里做的事本质上是目标检测输入一帧画面输出每个目标的位置框和类别。要让模型学会认三轮车就必须用已标注数据集做监督训练。tricycle8_images_xmls 就是这样一个标注包图片负责给模型看长什么样XML 负责告诉模型“三轮车在画面里的哪个位置”。数据和算法是绑在一起的——没有标注数据YOLOv5 再能打也只是个空壳。2. tricycle8_images_xmls 数据集到底什么状态2.1 目录与标注格式VOC XML里藏着哪些信息拿到数据集第一件事别急着训练先翻目录和标注文件。这个包的结构比较简单通常是tricycle8_images_xmls/ ├── IMG_0001.jpg ├── IMG_0001.xml ├── IMG_0002.jpg ├── IMG_0002.xml ...XML 是 VOC 格式这是 LabelImg 这类标注工具默认输出的格式。打开一个 XML核心结构长这样annotation folderimages/folder filenameIMG_0001.jpg/filename size width1280/width height720/height depth3/depth /size object nametricycle/name bndbox xmin214/xmin ymin128/ymin xmax893/xmax ymax651/ymax /bndbox /object /annotationxmin、ymin、xmax、ymax 是目标框左上角和右下角的像素坐标name 是类别名。在只有一个类别 tricycle 的情况下格式处理很直接如果以后扩展再加入两轮电动车、行人等类别XML 里会多出几个 object 节点解析逻辑要同步适配。2.2 8张图够不够数据增强与迁移学习如何兜底说句实在话8 张图做训练属于“证明能跑通”的级别远达不到生产环境的精度要求。但实践中确实有办法把这个项目做成。关键是两个兜底手段。一是迁移学习。YOLOv5 官方提供的 yolov5s.pt 权重是在 COCO 数据集上预训练过的COCO 里有 bicycle、motorcycle 这些类虽然三轮车不完全一样但“带轮子的交通工具”的底层特征轮子、车身、轮廓、纹理是共通的。在这个基础上微调模型不需要从零学8 张图也能把三轮车这个类别拟合出来。二是数据增强。YOLOv5 默认就开了 mosaic、hsv 色域扰动、随机翻转、缩放等一系列增强8 张图会在线生成大量变化的样本来喂给网络相当于变相把数据规模放大。注意这只是缓解不是根治真正要提高精度还是得扩数据。另外8 张图里建议拆出 1 到 2 张作为验证集。虽然训练集只剩 6 张左右但保留一点独立样本能观察训练收敛趋势和过拟合信号。验证集不是为了看绝对精度而是给训练过程装一个温度计。3. YOLOv5 环境搭建装到能跑 detect.py 为止3.1 conda环境与依赖安装顺序环境搭建这块顺序对了能省很多事。我先建一个独立的 conda 环境避免和系统里的 Python 包互相污染。conda create -n yolov5 python3.9 -y conda activate yolov5然后装 PyTorch。官网会根据 CUDA 版本生成对应的安装命令这里以 CUDA 11.8 为例pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118如果机器没有 NVIDIA 显卡就装 CPU 版训练慢点但流程能通。接着拉 YOLOv5 代码并装依赖git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simplerequirements.txt 里已经列好了 opencv-python、numpy、matplotlib、scipy、pyyaml 这些基础库。装完先别急着训练用官方权重验证一下环境python detect.py --weights yolov5s.pt --source data/images/bus.jpg能跑出一张带检测框的 bus.jpg说明 torch、opencv、YOLOv5 工程这一套都正常了。3.2 安装路上的三个报错和对应解法第一个常见报错是 OpenCV 导入失败。有的部署环境默认装了老版本 numpy比如 1.21 以下opencv 和它配合会报各种奇怪错误。解决办法是固定版本组合pip install numpy1.23.5 opencv-python4.7.0.68第二个是缺少 thop。老版本 YOLOv5 在 profile 模型结构时要用到 thop直接补装就行。第三个是 Windows 下训练时 DataLoader 卡死。默认 workers8Windows 跑多进程数据加载经常出问题把 workers 调成 0或者在入口处用if __name__ __main__:包住训练调用。我在笔记本上跑的时候还遇到过显存不足把 batch 调小就解决了。环境跑通之后整个项目最不可控的部分基本就消除了。后面所有操作都建立在这个能跑的基础之上再出问题基本都是工程问题而不是环境问题。4. 格式转换从VOC XML到YOLO txt4.1 逐字段解析XML并输出归一化坐标YOLOv5 训练用的标签不是 XML而是 YOLO 格式的 txt一行一个目标格式是class_id x_center y_center width height四个坐标全部归一化到 0 到 1 之间。为什么要转因为 YOLOv5 的 DataLoader 默认读 txt只有转过去才能直接喂进训练管线。归一化本身不复杂核心逻辑是“把像素坐标除以图片宽高”。我写了一个转换脚本直接能跑import xml.etree.ElementTree as ET import os classes [tricycle] def convert(size, box): dw 1.0 / size[0] dh 1.0 / size[1] x ((box[0] box[1]) / 2.0) * dw y ((box[2] box[3]) / 2.0) * dh w (box[1] - box[0]) * dw h (box[3] - box[2]) * dh return x, y, w, h xml_dir tricycle8_images_xmls out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) out_txt os.path.join(out_dir, xml_file.replace(.xml, .txt)) with open(out_txt, w) as f: for obj in root.findall(object): name obj.find(name).text.strip() if name not in classes: continue box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) cx, cy, w, h convert((width, height), (xmin, ymin, xmax, ymax)) class_id classes.index(name) f.write(f{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n)转换之后每个 txt 里应该看到一行类似0 0.431641 0.541667 0.529687 0.726389的内容。检查一下坐标是否都在 0 到 1 区间内如果出现负数或大于 1多半是 XML 里的 bndbox 坐标越界了得回源数据核对。4.2 数据集目录规划与data.yaml编写目录规划直接决定训练参数怎么填。下面的目录结构是 YOLOv5 的标准习惯datasets/tricycle8/ ├── images/ │ ├── train/ │ │ ├── IMG_0001.jpg │ │ ├── IMG_0002.jpg │ │ └── ... │ └── val/ │ ├── IMG_0003.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── IMG_0001.txt │ │ ├── IMG_0002.txt │ │ └── ... │ └── val/ │ ├── IMG_0003.txt │ └── ... └── data.yaml注意txt 文件名必须和 jpg 文件名一一对应大小写也要一致否则训练时找不到标签。data.yaml 写起来很简单path: datasets/tricycle8 train: images/train val: images/val nc: 1 names: [tricycle]path 是相对 YOLOv5 工程根目录的位置。如果数据集放在别处直接写绝对路径更省心不用折腾相对路径带来的各种问题。5. 训练配置与结果评估把8张图的模型训练出来5.1 train.py训练命令与超参数选择数据就位后开始训练。在 YOLOv5 根目录下执行python train.py \ --data datasets/tricycle8/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 80 \ --patience 20 \ --save-period 20 \ --name tricycle8每个关键参数说下我的取舍。--weights用官方的 yolov5s.pt 做迁移学习起点而不是默认的 yolov5s.yaml这是 8 张图能训下去的核心原因。--img 640是 YOLOv5 的默认输入尺寸也是后面导出 ONNX/RKNN 时要固定的尺寸先定好后面不用改。--batch 16在 8 张数据下看起来有点“虚”但因为增强会在线生成变化batch 大一点 loss 曲线更平稳显存不够就调 8、4。--epochs 80对 8 张图来说足够了。8 张图跑 300 个 epoch模型几乎必然过拟合训练 loss 可能很低但验证 loss 会翘头。--patience 20是早停机制连续 20 个 epoch 没有改善就自动停能省时间。如果希望保留中间权重方便后面挑选加--save-period 10每隔 10 个 epoch 存一次。8 张图这个量级超参数不建议大改用默认的 hyp.scratch-low 就行。我自己只有在数据扩充到 50 张以上之后才把 hyp 换成 hyp.scratch-high 并调高 lr0效果才明显。数据少的时候过大的学习率和增强反而让训练不稳定。5.2 从results.csv看训练质量并完成基础检测验证训练结束后所有输出在runs/train/tricycle8/目录下。看三个东西。第一是 results.png 里的 loss 曲线。box_loss 和 obj_loss 在下降说明模型在正常学习如果训练后期 val 曲线明显向上翘就是过拟合信号。第二是 results.csv。它的列比较长重点看metrics/mAP_0.5和metrics/mAP_0.5:0.95。因为验证集只有 1 到 2 张图这个数值的参考意义有限我不建议用它来衡量模型好坏更该关注的是真实检测效果。第三是 weights 目录下的 best.pt 和 last.pt。best.pt 是早停机制认为最好的权重直接拿来验证python detect.py --weights runs/train/tricycle8/weights/best.pt \ --source datasets/tricycle8/images/val/IMG_0003.jpg \ --conf-thres 0.5输出的结果图在 runs/detect 下。看到三轮车被框住置信度哪怕只有 0.6、0.7也说明这条链已经跑通了。我见过不少第一次做这个的人到这一步会很有成就感——确实从零散图片到能自动识别最难的部分已经过去了。6. 从PC到边缘盒子RK3568与RV1106的部署路径6.1 模型导出ONNX与RKNN转换训练是 PC 上的事真正落地往往要装到边缘盒子。RK3568 和 RV1106 都是瑞芯微的 AI SoC这两个平台不能直接用 PyTorch 跑需要把模型转成 RKNN 格式。第一步把 PyTorch 模型导出为 ONNXpython export.py --weights runs/train/tricycle8/weights/best.pt \ --include onnx \ --img 640 \ --opset 11--img必须和训练时保持一致不然后处理坐标会错位。因为数据集是 8 张图第二步要准备一个量化校准用的图片目录放几十张有代表性的画面不一定要带标注但要尽量覆盖实际场景的光照和角度。第三步写转换脚本用瑞芯微的 rknn-toolkit2 完成转换from rknn.api import RKNN rknn RKNN() rknn.config(mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3568) rknn.load_onnx(modelbest.onnx) rknn.build(do_quantizationTrue, datasetquant_dataset.txt) rknn.export_rknn(best.rknn)target_platform 根据实际芯片改成rv1106或rv1103。转换时最容易踩的坑是动态输入尺寸如果 ONNX 导出时没有固定输入尺寸RKNN 前端会报错所以导出时带--img 640是必须的。6.2 边缘部署的量化与帧率权衡RKNN 支持 FP16 和 INT8 量化。RK3568 大约有 2 TOPS 算力跑 YOLOv5s 的 INT8 量化模型640 输入下实测大概 20 到 35 帧对监控场景足够。RV1106 算力弱不少只有 0.5 TOPS 左右建议直接用 YOLOv5n 版本或者把输入降到 320帧率才能到 15 到 20。量化是典型的“用精度换速度”操作。我遇到过量化后检测框偏移的情况小目标掉点尤其明显。解决办法有两个一是校准图片一定要选真实场景画面多放几种光线二是如果掉点太严重可以把模型最后几层跳过量化做混合精度。板端推理代码如下以 RKNNLite 为例from rknnlite.api import RKNNLite rknn_lite RKNNLite() rknn_lite.load_rknn(best.rknn) rknn_lite.init_runtime() # img 尺寸要与训练一致 outputs rknn_lite.inference(inputs[img])拿到输出后YOLOv5 的解码、NMS 后处理都需要自己实现或者参考 rknn_model_zoo 里的 YOLOv5 Cython 后处理代码。这一步是部署里最容易被低估的工作量真正写起来比 PC 端复杂得多保守起见留出两三天时间单独调试。7. 复盘这套识别方案距离可用还差几步7.1 8张图项目的实践边界这个项目做完最想说的一句是8 张图能证明链路不能证明精度。我们最后用 best.pt 在真实监控画面上跑识别正对镜头、光线好的三轮车没问题一旦车辆出现在侧面、逆光、或者车斗里堆满货物漏检率和误检率明显上升。因为训练数据没有覆盖这些难样本。要做成可交付的产品得有组织地扩充数据从真实摄像头按小时截图挑出包含三轮车的画面标注时把“载货状态”“封闭车厢”“侧后方视角”分别建立样例。一个类别至少做到几百张甚至上千张模型才有泛化能力。我还踩过一个关于类别界定的坑三轮车形态差异太大有敞篷的、有封闭箱体的、有带顶棚的如果把所有形态都塞进一个 tricycle 类模型容易糊涂。数据多了以后最好拆成几个子类或者增加特征明显的样本权重否则训练曲线看似漂亮实测都在“四不像”样本上翻车。7.2 从检测到判定违规停放多帧跟踪与ROI区域另一个容易踩的“语义坑”是检测到三轮车不等于判定违规停放。“停车”是一个时间概念单帧画面里三轮车可能只是在正常行驶只是路过禁停区。直接对单帧检测框触发告警会疯狂误报。我的做法是加两层逻辑。第一层划定禁停 ROI 区域比如小区门口的黄网格范围。第二层做目标跟踪用 ByteTrack 或者 DeepSORT 给每辆检测到的三轮车分配 ID当同一个 ID 的中心点落在 ROI 内且停留时长超过设定的阈值比如 60 秒再触发一次违规告警。这样还能天然做告警去重一个目标只报一次管理端不会被刷屏。跟踪加 ROI 判定这套逻辑才真正把“识别”变成了“治理”。根据我的实测经验如果把多帧跟踪也纳入项目范围整体工作量大约要增加四成。纯检测模型可能一周就训完加上跟踪和告警逻辑连同边缘端的视频流接入至少再留出两周时间。这套方案目前的定位是半自动辅助系统先筛掉百分之八九十的正常画面人工只需要看剩下的可疑片段管理效率已经比纯盯屏高出一大截。后续再扩充数据、优化部署性能方向都已经很清晰了。本文还有配套的精品资源点击获取
返回列表