简介:面向采用YOLOv5进行机器视觉识别的开发者,这份已标注数据集子包聚焦非机动车违规停放场景,包含三轮车第一类(tricycle1)的九百五十七张图片及对应标注文件,适用于智慧城市与交通管理项目中的违规停放检测、模型微调或算法效果评估。压缩包共一千九百一十个文件,其中九百五十七张JPG图片与九百五十三个XML标注文件,总体积约八十二MB,图片与标注一一对应,可直接用于模型训练与验证;目前已有二百九十五人学习或下载。三轮车数据集整体划分为八个类别,此子包为其中第一类,方便按需与其他类别组合扩充。数据已预先标注完成,免去人工标注环节,标注信息包含目标框与类别标签,既可作为理解非机动车违规停放检测流程的入手指南,也可作为更大规模识别任务的基础子集。
1. 三轮车违停识别第一步:957张已标注图够不够用
把 yolov5 和非机动车违规停放做到能落地,真正卡人的不是模型结构,是数据。这份三轮车 tricycle1 数据集一共 957 张实拍图片,每张带同名 XML 标注,属于三轮车八个分类里的第一类,拿过来就能直接进入 YOLOv5 训练流程。它解决的是违停识别最头疼的"标注从哪来"问题:不用自己画框,不用清洗格式,按 VOC 结构组织好,训练、验证、部署能一次打通。适合正在做非机动车违停识别、需要三轮车检测基线模型的从业者,也适合刚上手 YOLOv5、想用真实标注数据跑通全流程的新手。下面按我实际拆这套数据的顺序,从目录结构、标注转换、训练参数讲到违停判定和排坑。
2. 拆解 tricycle1 数据集:XML 标注结构与格式转换
拿到资源第一步,别急着训练,先把目录和标注文件看明白。这份三轮车数据的图片是 jpg,标注是同名 xml,命名规则很简单:三轮车_751.jpg 对应三轮车_751.xml。这种一一对应的命名是后期批量处理和划分训练集的前提。我一般会先写一段脚本统计总数、抽查标注内容,确认没有空标注和损坏文件,再进训练流程。如果这一步省略,后面训练时经常出现"找得到图找不到标签"的怪问题,排查起来比现在多花三倍时间。
2.1 文件命名与 XML 关键字段
文件名编号从三位数到四位数都有,前缀统一是"三轮车",拉个列表能看到 957 张图片全部有配对 xml。这里有个容易忽略的细节:文件名里的中文在 Linux 环境下是 UTF-8 编码,直接 glob 匹配没问题,但在 Windows 下用 GBK 编码写脚本,有可能出现乱码导致找不到配对文件。我的习惯是训练前把所有文件重命名为纯数字编号,顺便把中文路径兼容问题一次解决。特别是后续要合并八个分类一起训练时,文件名前缀各不相同,统一编号能避免不同子类之间的命名冲突。
XML 标注是典型的 Pascal VOC 结构,每个文件里包含这样几类关键字段:
| 字段 | 路径 | 含义 | 使用注意 |
|---|---|---|---|
| filename | /filename | 图片文件名 | 要跟实际 jpg 对应,否则找不到图 |
| width | /size/width | 图片宽度(像素) | 归一化分母,错了框全偏 |
| height | /size/height | 图片高度(像素) | 同上 |
| name | /object/name | 目标类别名 | tricycle1 里的值可能是"三轮车"或 "tricycle",打开一个 XML 确认 |
| bndbox | /object/bndbox | xmin/ymin/xmax/ymax | 左上右下坐标,未归一化 |
这里最关键的是 bndbox 的四个值,它们是原始像素坐标,YOLOv5 训练要的是归一化后的中心点坐标和宽高,所以 XML 不能直接喂进去,必须先转换。另外注意 name 字段,整个资源是三轮车八个子分类的数据集,每类有自己的类别名,这份 tricycle1 只是第一类。后续如果要合并八个分类一起训,类别名和 id 映射表必须提前统一,不然后面改映射会非常痛苦,我就是吃过这个亏才把它写在前面的。
2.2 VOC 标注转 YOLO 格式:一次跑通的转换脚本
转换逻辑不复杂,就是把 XML 的 bndbox 像素坐标换算成 0~1 的归一化值。我常用的脚本长这样:
import os import xml.etree.ElementTree as ET # 类别表:tricycle1 按单类处理,索引从 0 开始 # 后续合并八个分类时,这里扩展成完整类别列表即可 classes = ["tricycle"] def convert(xml_path, out_txt_path): 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.findall("object"): name = obj.find("name").text if name not in classes: print(f"skip unknown class: {name} in {xml_path}") continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 转成中心点坐标加宽高,再归一化到 0~1 x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # YOLO 格式:class_id x_center y_center width height lines.append(f"{classes.index(name)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 批量转换:遍历 tricycle1 的 xml 目录 for xml_file in os.listdir("tricycle1/xmls"): if not xml_file.endswith(".xml"): continue base = os.path.splitext(xml_file)[0] convert( os.path.join("tricycle1/xmls", xml_file), os.path.join("tricycle1/labels", base + ".txt"), )逻辑说明:脚本先取 size 字段定分母,再遍历每个 object 取 bndbox,算中心点与宽高后归一化,最后按 YOLO 格式写 txt。参数说明里有两个坑:一是 classes.index(name) 决定了类别 id,多个类时一定要和后续 data yaml 里的 names 顺序一致,否则类别错位,模型会拿三轮车的数据去拟合错误标签;二是浮点保留 6 位足够,超过这个精度对 loss 没有实际帮助。转换完我建议随机打印三个 txt 的前几行,人工核对坐标是否在 0~1 之间,顺便确认没有 NaN 或者负数。
提示:转换脚本跑完后,把 labels 目录和 images 目录放成同级,yolov5 训练时才能自动按同名文件去 labels 里找标注。
2.3 训练集与验证集划分
957 张图不算多,我习惯按 8:2 划训练集和验证集。有个原则:划分前先按场景视频段分组,如果同一段视频的连续帧既进了 train 又进了 val,mAP 会虚高,现场复用性反而差。简单做法是随机划分后用文件名序列检查连续编号的图片是否跨集,发现连续 5 帧以上散布在两边就重新划过。这一步不写进训练脚本,但直接影响你对模型真实水平的判断。
划分脚本本身不复杂,用 random.shuffle 加一个固定随机种子保证可复现。划完之后把 train.txt 和 val.txt 各存一份,训练时用。另一个细节是模型训练完做验证时,val 集合里要保留少量难例,比如遮挡严重、逆光的图片,不然验证结果一派祥和,部署到现场就露馅。957 张图分出来的 val 集大约 190 张,足够看出问题所在。
3. YOLOv5 训练三轮车检测模型:环境、参数与训练命令
数据准备好之后,训练本身是流水线活。yolov5 这个仓库维护比较成熟,训练、验证、导出一条命令走完,重点其实是参数选择和数据组织方式。这份三轮车数据集是单类检测,目标尺寸偏大、特征明显,训练门槛不高,但环境配置和参数含义还是要理清楚,不然报错都不知道往哪个方向查。
3.1 环境准备与依赖安装
环境上我建议直接用 Python 3.8 以上版本,PyTorch 按显卡的 CUDA 版本装。我的习惯是先装 torch,再装 yolov5 的 requirements,避免依赖解析冲突:
# 拉取 yolov5 官方仓库,常见做法是 clone 主分支 git clone https://github.com/ultralytics/yolov5.git cd yolov5 # 先装 PyTorch,再装剩余依赖 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install -r requirements.txt参数说明:--index-url 指定 CUDA 12.1 的 wheel 源,如果你的显卡驱动是 CUDA 11.8 就换成 cu118,装错版本的表现是 torch.cuda.is_available() 返回 False,训练时直接掉回 CPU。requirements.txt 里包含 opencv、matplotlib、seaborn 这些库,缺哪个补哪个,不建议一次全部升级到最新,yolov5 对某些库版本有隐性要求,升级后偶发报错反而不好排查。机器上没 GPU 也能训,用 CPU 就是慢,957 张单类数据 CPU 训 100 轮大概要十几个小时,有 GPU 的话一张 3060 大约 40 分钟就能跑完。
3.2 数据集配置与训练参数调优
yolov5 训练前要把数据描述文件写好,这个 yaml 决定了训练集、验证集路径和类别数。我的 tricycle.yaml 长这样:
# 路径相对于 yolov5 仓库根目录 train: data/tricycle/images/train val: data/tricycle/images/val nc: 1 names: ['tricycle']参数说明:train 和 val 指向存放 jpg 的目录,注意这里指的是图片目录,yolov5 会自动去同名 labels 目录找 txt,所以 labels 目录必须和 images 目录同级且前缀一致。nc 是类别数,单类就是 1。names 顺序必须和转换脚本里的 classes 顺序一致,这一点错位了,检测结果就会张冠李戴。
然后是训练命令。最省事的配置是先用 COCO 预训练权重做迁移学习:
python train.py \ --data data/tricycle.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img 640 \ --device 0 \ --name tricycle_run参数说明:--weights 用 yolov5s.pt 是平衡速度和精度的选择,三轮车目标大、特征明显,s 模型足够;如果追求更高精度可以换 yolov5m,但显存和推理时间都会涨。--img 640 是输入分辨率,训练和推理要保持一致,现场摄像头画面如果是 1080p,推理时也会缩到 640。--batch-size 根据显存调,16 在 8GB 显存上比较稳,显存小就降到 8。训练过程重点看两个指标:val 的 mAP@0.5 是否在 60 轮后进入平台期,以及 loss 曲线是否持续下降。三轮车这类目标不算难,一般 80~100 轮就能收敛。
超参数这块,yolov5 默认的 hyp.scratch-low.yaml 在单类检测上通常够用。如果发现小目标漏检,我一般会动两个值:mosaic 保持 1.0 别关,它让模型看到更多裁剪组合;再用 --hyp 指定一份改过的 yaml 把 hsv_h、hsv_s 调低一点,因为三轮车颜色是品牌识别的特征,色相增强太猛会把颜色信息洗掉。这个属于玄学区域,调之前先跑一版默认参数,有明确短板再动,不要一上来就改一堆超参,改了也不知道是哪个起的作用。
4. 违停判定后处理:从检测框到违规结论
训练出模型只是第一步,违停识别要的是"这辆车停在了不该停的位置"这个结论。yolov5 输出的是检测框,违规判定必须自己在后处理里实现。这里的设计直接决定误报率,同一个模型装到两套后处理逻辑里,效果能差出一倍。
4.1 禁停区域划定:矩形框还是多边形
实际场景里禁停区域很少是正矩形,人行道、消防通道、弯道附近都是不规则多边形。我一般用 OpenCV 先画多边形标定禁停区,存成顶点数组。判定时用 pointPolygonTest 判断点是否落在区域内:
import cv2 import numpy as np # 在画面坐标系里手工标定的禁停多边形顶点(像素坐标) # 顶点顺序要按顺时针或逆时针统一,不然判定结果会混乱 forbidden_zone = np.array( [[210, 380], [520, 360], [560, 720], [180, 750]], dtype=np.int32, ) def point_in_zone(px, py, polygon): # 返回 >= 0 表示点在多边形内部或边界上 return cv2.pointPolygonTest(polygon, (px, py), False) >= 0逻辑说明:pointPolygonTest 的第三个参数传 False 表示只返回位置关系,不计算距离,速度快,适合视频帧实时处理。参数说明里要注意坐标系的统一,如果检测框坐标来自模型输出的原始像素坐标,多边形顶点也要用同一分辨率下的坐标标定;现场部署时摄像头安装角度变了,多边形要重新标,这是最容易漏的一步。我见过有人标定完禁停区,换了个摄像头分辨率,所有判定全部失效,就是因为坐标系没跟着换算。
4.2 违规判定逻辑:中心点判定与多帧确认
单帧检测直接判违规会带来抖动问题:目标被遮挡一帧、置信度波动一下,画面里违停告警就会闪。我的做法是先取检测框中心点做落区判定,再加多帧确认窗口:
def judge_violation(frame_id, dets, forbidden_polygons, history): results = [] for det in dets: # det 结构: x1, y1, x2, y2, conf, cls x1, y1, x2, y2, conf, cls = det cx = (x1 + x2) / 2 cy = (y1 + y2) / 2 in_zone = any( cv2.pointPolygonTest(poly, (cx, cy), False) >= 0 for poly in forbidden_polygons ) # 中心点落在禁停区作为候选条件,置信度再卡一道 if in_zone and conf >= 0.35: history[frame_id % 30] = 1 else: history[frame_id % 30] = 0 # 连续 5 帧命中才输出违规,滤掉瞬时误检 if sum(history) >= 5: results.append((cx, cy, conf)) return results逻辑说明:中心点判定比整框 IoU 判定更稳,因为框的边界受遮挡影响大,中心点反而稳定。多帧确认的窗口按 30 帧(1 秒)滚动,连续 5 帧命中才告警,能滤掉大部分遮挡导致的闪烁误报。参数说明里 conf 阈值 0.35 是个经验值,训练集质量好可以放宽到 0.3,场景复杂就提到 0.45。另外,如果现场画面里三轮车会移动,建议叠一个简单的 IoU 跟踪,记录同一目标的中心点位移,位移大于一个车身宽度时判定为行驶中,不触发违停。这个逻辑对"车停在禁停区"和"车刚好路过禁停区"的区分非常关键,不做区分的话,早晚高峰的误报能把值班室淹了。
注意:多帧确认的窗口长度要和摄像头帧率匹配,15 帧的摄像头用 30 帧窗口就是 2 秒,告警延时会比 1 秒的方案慢一倍。实时性要求高的场景,窗口缩到 10 帧以内。
5. 三轮车检测训练与部署避坑:五条血泪经验
这部分每一条都是我实际跑这套数据时踩过的,按"现象 → 原因 → 解决"列出来,遇到同症状可以直接对症下药。有些问题在官方文档里根本找不到,属于典型的数据和场景耦合出来的坑。
5.1 训练与部署中的五个常见问题
现象一:训练跑起来了,但 mAP 一直是 0,val 阶段没有检测框输出。 原因:XML 转 txt 时类别 id 和 names 列表对不上,或者有人把 bndbox 的 xmin、ymin、xmax、ymax 直接当 x_center、y_center 写进去了,归一化后的值大量大于 1。 解决:转换完先 cat 一个 txt 看内容,所有值应在 0~1 之间;再跑一段校验脚本,把 txt 的框画回原图,肉眼比对偏移情况。画框这一步花五分钟,能省后面一小时的排查,我现在每次转换完都强制做。
现象二:训练到 30 轮左右 CUDA out of memory。 原因:batch-size 16 在显存 6GB 的卡上超出上限,或者开了 --cache 把图片全量缓存进显存,957 张图在高分辨率下缓存量不小。 解决:batch-size 降到 8 或 4,img 尺寸从 640 降到 512;如果必须用 640,就把 --cache 改成 --cache ram(缓存到内存而不是显存)。3080 级别的显卡跑这套单类数据,batch 32 都毫无压力,瓶颈通常出在显存小的卡上。
现象三:模型训练正常,但检测框明显偏大,把旁边的电动车也框进来了。 原因:XML 里 bndbox 标注本身框得松,标注员把车身外围的阴影或背景也划进去了。这种问题在人工标注的数据里很常见,框松紧不一是常态。 解决:这是数据质量问题,不是训练问题。我一般会统计所有框的宽高比,找出偏离均值超过两个标准差的样本重点检查,再统一对 bndbox 做 2%~5% 的内缩。框稍微收紧后,mAP 反而会涨,因为 IoU 计算时正样本的框更准了。
现象四:现场部署后,行驶中的三轮车频繁误报违停。 原因:单帧检测没有运动判定,只要检测框中心点落在禁停区就触发,而过路的车在画面里停了一两帧就会被算法当成停放。监控场景里车速慢,一帧一帧看每帧都有框,人眼觉得明显在动,算法却不知道。 解决:按第 4 章的多帧确认逻辑,连续 5 帧以上中心点稳定在禁停区才告警;再叠加中心点位移阈值,帧间位移超过车身宽度的 1/3 判定为运动目标,直接跳过违规判断。这两层叠加之后,误报基本能压到可接受范围。
现象五:同一套模型换了个摄像头角度,漏检率明显上升。 原因:训练数据视角比较单一,大多为平视或略俯视,新摄像头是高位俯视,三轮车的投影形状和训练样本差异大。模型学到的是训练视角下的特征分布,换视角就是换了一个分布。 解决:收集新角度的图片做增量训练,不需要重新标 957 张,挑 200 张新视角图片标注后,在现有模型权重上继续 fine-tune 50 轮。这是成本最低的兜底方案。另外推理时开启 --augment 做左右翻转和尺度增强,能稍微弥补视角差异,但会加推理时间,实时性要求高的场景慎用。
6. 模型验证的一线手法:mAP 之外还要看什么
模型训完,val 集上 mAP 看着不错,不等于现场能用。我验证这套三轮车模型的固定流程有三步:第一步,用脚本跑一遍全部验证集图片,把每个漏检和误检的框画出来存图,人工翻一遍,确认漏检集中在哪些场景——比如逆光、车身被遮挡、远处小目标;第二步,跑一段现场禁停区的视频,统计 10 分钟内误报的次数和每次持续时间,误报率超过每 3 分钟一次就不合格;第三步,把模型导出成 TensorRT 或 ONNX 做推理时间测试,确认帧率满足现场摄像头并发路数。
这里有个实用技巧:yolov5 的 val.py 会输出 confusion matrix 图,重点看 tricycle 这一类对角线上的值。如果误检那一列有值,说明把背景或者其他物体认成了三轮车;如果漏检行有值,说明三轮车被当成背景忽略。这两类问题处理方法完全不同,前者要调置信度阈值,后者要补数据或增强样本。我还习惯用 --save-txt 把检测结果落成文件,写一段统计脚本算检测框中心点在画面里的分布热力图,如果大量框集中在画面角落,多半是训练样本里目标位置分布不均,模型有位置偏好。
从那以后,我每次拿到新数据集,都强制先跑一遍"标注合法性与一致性校验",再做转换和训练,最后用现场视频做验收测试,这套流程让翻车率低了很多。这份 tricycle1 的 957 张标注图作为三轮车检测的起点,够你完整走一遍 yolov5 违停识别链路,后续八个分类合并、品牌细分的扩展空间也是现成的。希望帮到你。
本文还有配套的精品资源,点击获取