简介:三轮车数据集包含559张真实场景图片,聚焦“tricycle”单类别目标检测,适合计算机视觉初学者与研究者用于YOLO、SSD等模型的训练与效果验证。所有图片均通过labelImg手工标注,画框准确,累计659个标注框,并提供Pascal VOC和YOLO两种格式的标签文件,可无缝对接主流检测框架,省去格式转换的繁琐步骤。资源包整体约25.22MB,内含1679个文件,包括559个jpg图片、559个xml标注、559个yolo格式txt标注及少量附加说明文本,目录结构简洁,按需取用即可。目前已有641人浏览学习,数据集标注质量可靠,可直接用于目标检测实战项目或毕业设计,帮助学习者专注模型调优而非数据整理。
1. 559张三轮车数据集:VOC+YOLO双格式与labelImg手工标注的组合价值
城市道路检测里,三轮车是典型的小众类别:公开数据集基本不覆盖,自己采集又要承担标注成本。这套559张三轮车数据集把VOC与YOLO格式一次给齐,全部由labelImg人工逐框标注,拿到手可以直接进yolo训练流程。
双格式意味着少踩转换坑:VOC的XML喂给经典框架,YOLO的txt归一化坐标直接进yolo目标检测流程。手工标注相比自动标注,边界框更干净,漏框与偏移这类批量污染少得多。
下面的路径适合做三轮车、快递车等非机动车检测的工程师,也适合刚入门yolo训练、需要干净样本练手的新手:先拆格式,再复现标注,然后划分与校验,最后反向排查漏标。
2. VOC与YOLO双格式拆解:三轮车标注的XML结构、txt归一化坐标与互转公式
拿到559张数据的第一件事不是开训,而是确认两种格式的目录与字段对得上。VOC和YOLO描述的是同一个边界框,坐标系定义却完全不同,这个差异会在训练阶段以各种隐蔽方式暴露出来。
2.1 先看VOC的XML:annotation、size与bndbox三个关键节点
VOC格式源自PASCAL VOC竞赛,每张jpg对应一个同名xml。以一张1280x720的三轮车样本为例,结构固定为annotation根节点下挂filename、size和若干个object块。
<annotation> <folder>tricycle_images</folder> <filename>img_0042.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>tricycle</name> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>112</xmin> <ymin>240</ymin> <xmax>486</xmax> <ymax>612</ymax> </bndbox> </object> </annotation>逻辑说明:size里的width与height是整个文件的坐标基准,bndbox的四个值都是像素绝对坐标,原点在图像左上角,xmin/ymin是边框左上角,xmax/ymax是右下角。图像里出现几辆三轮车就有几个object块,一图多框时块与块之间没有强制排序,labelImg按画框先后生成顺序。
参数说明:truncated表示目标是否被画面边缘截断,手工标注时三轮车只有半边进图,这个值应置1;difficult表示目标因遮挡或模糊难以辨认,训练流程通常会忽略difficult=1的框。检查这套数据时重点看这两个字段是否与图像内容一致,如果所有图都写0但画面里明显有截断目标,说明标注时没处理边界语义,训练出的框会整体偏大或偏小。
2.2 YOLO的txt归一化坐标:class_id、cx/cy与宽高的存储约定
YOLO格式下每张jpg对应一个同名txt,每行一个目标,内容是五个数字:
0 0.2336 0.5917 0.2922 0.5167 0 0.7250 0.3819 0.1805 0.3333逻辑说明:第一个数字是类别编号class_id,从0开始。后面四个是边界框中心点x、y以及宽w、高h,全部除以图像宽高做了归一化。换算关系是cx=(xmin+xmax)/(2*width),w=(xmax-xmin)/width,y与h同理。这份数据如果只标了tricycle一个类别,所有行class_id都是0;多类别时class_id范围在0到nc-1之间,越界属于标注事故。
参数说明:归一化坐标必须落在0到1区间。训练时YOLO直接读txt进损失函数,w或h出现负值会触发NaN,某个坐标大于1会让anchor匹配错乱。拿到数据集先全量扫一遍数值范围,比训练到一半报错再查快得多。
2.3 VOC转YOLO的换算公式与边界情况处理
已知图像宽W、高H,VOC的坐标换算关系为cx=(xmin+xmax)/(2W),cy=(ymin+ymax)/(2H),w=(xmax-xmin)/W,h=(ymax-ymin)/H。反向转换则xmin=(cx-w/2)*W,xmax=(cx+w/2)*W,ymin与ymax同理。换算里最容易忽略的是浮点精度:float64转成6位小数的txt再转回像素坐标,会产生1到2像素偏差,对imgsz=640的训练影响可忽略,但做像素级评测时需保留更多小数位。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_map): # 解析XML,先拿size作为归一化基准 tree = ET.parse(xml_path) root = tree.getroot() width = int(root.find('size/width').text) height = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_map: 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) # 中心点与宽高全部除以图宽高,保留6位小数 cx = (xmin + xmax) / 2 / width cy = (ymin + ymax) / 2 / height bw = (xmax - xmin) / width bh = (ymax - ymin) / height lines.append(f"{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") base = os.path.basename(xml_path).replace('.xml', '.txt') with open(os.path.join(out_dir, base), 'w') as f: f.write('\n'.join(lines))逻辑说明:脚本先解析size得到基准宽高,再遍历每个object,把bndbox的像素坐标换算成归一化值,按class_id cx cy w h的顺序写成txt。class_map参数把类别名映射成数字,单类别三轮车数据集传{"tricycle": 0}即可。
参数说明:保留6位小数是兼顾精度与文件体积的惯例。如果xml里有difficult=1的框,通常先过滤不写入txt。反向转换YOLO到VOC时用round而不是int,直接int截断小数会产生系统性偏移。
三种常见标注格式的坐标语义差异,用一张对照表收敛:
| 格式 | 文件后缀 | 坐标基准 | 每行/块结构 | 典型消费方 |
|---|---|---|---|---|
| VOC | xml | 像素,左上角原点 | object块内含xmin/ymin/xmax/ymax | Faster R-CNN、SSD、labelImg默认 |
| YOLO | txt | 归一化 | class_id cx cy w h | YOLOv5到v11、Ultralytics |
| KITTI | txt | 像素,字段带语义标签 | class truncated occluded alpha 加bbox | 自动驾驶,需字段级映射 |
常见的坑是KITTI标注转YOLO:KITTI的2D框虽然是像素坐标,但每行开头是类别、截断程度、遮挡程度,直接按YOLO的行序解析会把类别当成坐标。转换脚本必须逐字段映射,不能只按空格切分后取后四位。任何数据集到手先确认坐标系定义,再谈训练。
3. labelImg手工标注559张三轮车的实战流程:环境配置、快捷键与bbox判定标准
标题里写的labellmg是LabelImg的常见手误拼法,实际工具名是labelImg,目标检测领域最常用的bbox标注器。559张这个量级完全适合手工标注,按单张平均一个目标算,熟练后一小时能标30到40张,关键是把流程、类别文件和判定标准先定死。
3.1 labelImg的安装与启动参数:预定义类别文件怎么传
yolo环境配置里最轻的一环就是labelImg安装,Python环境直接pip装,装完用两条命令启动:
pip install labelImg # 第一个参数是图片目录,第二个是预定义类别文件 labelImg tricycle_images tricycle_classes.txt逻辑说明:第一个位置参数是图片目录,第二个是预定义类别文件,每行一个类名。对559张三轮车数据,classes.txt里写一行tricycle就够了,标注时拉框后直接确认默认类别,不用每次弹窗选。如果不传类别文件,labelImg会进入自由输入模式,同一个类别名大小写写法不一致时,后面转YOLO会得到两个class_id,数据集类别数直接翻倍,这是手工标注最常见的事故之一。
参数说明:labelImg还有几个常用参数,比如-d指定xml/txt输出目录、-l指定预标注导入目录。更关键的是保存格式,界面里的PascalVOC与YOLO两种模式输出文件后缀不同,一个存xml一个存txt。我一般全程用VOC模式标注,标完后统一跑一次第2.3节的转换脚本生成YOLO格式,避免两种格式文件混在同一个目录里,后续校验逻辑翻倍。
提示:多人协作标注时,所有人必须共用同一份tricycle_classes.txt并传给labelImg,而不是各自新建,能根除类别名不一致问题。
3.2 提高手工标注效率的快捷键组合与自动保存
labelImg的标注节奏是W键拉框、松手确认类别、D键翻页、自动保存落盘,核心快捷键如下:
| 快捷键 | 功能 | 使用场景 |
|---|---|---|
| W | 进入拉框模式 | 每张图开始的第一次操作 |
| A / D | 上一张 / 下一张 | 翻图浏览,配合自动保存 |
| Ctrl+S | 手动保存 | 关闭自动保存时使用 |
| Delete | 删除选中框 | 误拉背景框时直接删 |
| Ctrl+滚轮 | 缩放画布 | 小目标放大后再框,精度更高 |
效率提升的常见做法是勾选View菜单里的Auto Save mode,每次翻页自动落盘上一张的标注。手工标注最怕标了十几张程序崩溃,自动保存配合A/D翻页能把丢标注的概率降到接近零。另一个习惯是每标完50张把标注目录复制一份做增量备份,样本量到559张时备份目录也就几十MB,成本很低。
参数说明:Ctrl+滚轮缩放之后,拉框坐标仍然是图像原始像素坐标,不受画布缩放影响,可以放心放大到局部再拖框。对有遮挡的三轮车场景,放大后贴轮廓画框比整图直接拉更准,xmin这类数值能控制在1到2像素波动内。
3.3 三轮车bbox的边界判定标准:框车身还是连车斗一起框
手工标注一致性问题最常出现在边界框语义模糊。同一辆三轮车,一个人标成只框驾驶位,另一个人标成连车斗全框,训练出来的预测框就会忽大忽小。推荐做法是把整车语义定成最小外接矩形:车斗、车轮、骑行者的主体躯干全部算进框内,伸出去的手或腿不强行纳入。遮挡场景按可见轮廓画框,两辆三轮车重叠时允许框相交,被完全挡死的位置不做猜测,不在图像里的部分就地截断。
三种常见误标要尽量避免。一是把单独行人误当骑行者,骑行者与三轮车的分界在躯干与车把、座垫的接触关系,只盯人没看车就会把路人拉进框。二是框沿卡在车轮外沿内侧,三轮车的框应包含车轮触地点,车轮被压线等于框整体偏小。三是重复框同一目标,一辆车被画了两个相交框,训练时产生两个高置信度预测,NMS后保留哪一个完全随机。
判定标准定完后做一次快速抽检:随机抽50张,看同一目标被不同人画的框IoU是否都在0.8以上,低于0.8说明标准没执行到位,需要回炉。
4. 用559张三轮车数据训练YOLO前的数据准备:yolo数据集划分、格式校验与训练参数对照
进入yolo训练自己的数据集的标准准备阶段。559张不能全量灌进训练,要先做yolo数据集划分,再校验标注与图像的对应关系,最后把data.yaml和训练参数按小样本量调整。漏掉任何一步,排查loss曲线异常的时间都会加倍。
4.1 按8:1:1划分train/val/test的Python脚本
Ultralytics的目录约定是images与labels各分train、val、test三个子目录,txt与jpg同名配对:
tricycle/ images/train/*.jpg images/val/*.jpg images/test/*.jpg labels/train/*.txt labels/val/*.txt labels/test/*.txt data.yaml划分脚本最省事的写法是shuffle后切片,而不是按文件名顺序取前80%:
import os, random, shutil from glob import glob random.seed(42) # 固定随机种子,保证划分可复现 imgs = sorted(glob('tricycle/images/*.jpg')) random.shuffle(imgs) n = len(imgs) parts = { 'train': imgs[:int(n * 0.8)], 'val': imgs[int(n * 0.8):int(n * 0.9)], 'test': imgs[int(n * 0.9):], } for split, paths in parts.items(): img_out = f'tricycle/{split}/images' lbl_out = f'tricycle/{split}/labels' os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for p in paths: base = os.path.splitext(os.path.basename(p))[0] shutil.copy(p, f'{img_out}/{base}.jpg') lbl = f'tricycle/labels/{base}.txt' if os.path.exists(lbl): shutil.copy(lbl, f'{lbl_out}/{base}.txt')逻辑说明:先shuffle再切片,避免同一路段连续拍摄的三轮车图片全部落进train,导致val与train场景过度相似,mAP虚高。固定seed=42保证每次重跑得到的划分完全一致,实验结果可以复现。
参数说明:559张按8:1:1得到447张train、56张val与56张test。test集划分出来后就固定不动,后续做增强消融只动val。单类别三轮车数据集用不到分层抽样;如果以后扩到多类别且数量不均衡,再把shuffle改成按类别分层。
4.2 校验txt标注与图像对应关系的快速脚本
标注文件与图片不匹配是手工数据集的常见病,写一个全量校验脚本,跑一次把所有问题一次性列出来:
from glob import glob imgs = {p.split('/')[-1][:-4] for p in glob('tricycle/images/*.jpg')} lbls = {p.split('/')[-1][:-4] for p in glob('tricycle/labels/*.txt')} print('缺标注图片数:', len(imgs - lbls)) print('孤儿标注数:', len(lbls - imgs)) for f in glob('tricycle/labels/*.txt'): for line in open(f): parts = line.split() if len(parts) != 5: print('字段数错误:', f, line) break cls, cx, cy, w, h = map(float, parts) # 单类别class_id只允许0,坐标必须在0~1之间 if not (0 <= cls <= 1) or not (0 <= cx <= 1) or not (0 <= cy <= 1): print('坐标越界:', f, line)逻辑说明:集合差集分别找出没有txt的图片和没有图片的txt;逐行检查字段数与数值范围,字段数不等于5说明YOLO行被污染,坐标大于1说明归一化用了错误的分辨率基准。
参数说明:单类别数据class_id只有0,所以判断范围是0到1;多类别时把上界改成nc-1。孤儿标注主要是删图时忘了删txt,Ultralytics训练遇到孤儿txt会报missing image,提前清理能省一次训练中断。VOC格式的XML还要额外检查每个object是否含完整四值bndbox和合法类名。
4.3 data.yaml配置与三轮车数据集的训练参数建议
目录就绪后写data.yaml,表明类别数量与类别名,路径用相对path指到data.yaml所在目录:
path: /data/tricycle # 数据集根目录 train: images/train # 训练图片相对路径 val: images/val # 验证图片相对路径 test: images/test # 测试图片相对路径 nc: 1 # 类别数 names: 0: tricycle # 类别编号与名称训练命令与参数按小样本场景调整:
yolo detect train model=yolo11n.pt data=/data/tricycle/data.yaml \ epochs=300 imgsz=640 batch=16 patience=50 workers=8参数说明:imgsz=640与常见标注分辨率接近,缩放倍数最小,边界框坐标误差也最小;lr0从默认的0.01降到0.005,小数据集初期不容易震荡;patience=50让验证指标连续50轮不升时提前停,省算力同时避免过拟合。559张里如果平均每张不到2个目标,正样本比例偏低,损失函数里背景占绝大多数,模型容易学成保守预测,所以用yolo11n这种轻量权重先跑通pipeline,确认val曲线正常后再换yolo11s提精度。
关键参数推荐值与依据整理成表:
| 参数 | 三轮车数据集建议值 | 依据 |
|---|---|---|
| imgsz | 640 | 缩放倍率小,手工标注的1到2像素误差不被放大 |
| batch | 16 | 8到12G显存的安全值,30系以上可提至32 |
| epochs | 300 | 小样本需要较长训练配合早停 |
| patience | 50 | 防止val过拟合后继续空转 |
| lr0 | 0.005 | 目标稀疏,初始学习率过大会发散 |
| cache | True | 559张可全量缓存进内存,省掉IO等待 |
逻辑说明:cache=True对559张这种量级几乎不占内存,却能把每个epoch的数据读取时间从秒级压到毫秒级,一次完整训练能省下可观的墙钟时间。如果手上只有VOC格式的XML,先跑第2.3节的转换脚本转成txt再训练,Ultralytics主流程按txt读取,绕开转换直接把XML目录喂进去不是常规做法。
5. 漏标反向排查:用50轮快速训练定位559张三轮车数据里的人工疏漏
手工标注559张图,漏标几乎是必然事件。与其逐张瞪眼复查,不如让模型自己把可疑位置指出来,这套反向排查思路能显著压缩质检时间。
5.1 用短训练加高置信度预测找出没有标注的图片
先用第4章的data.yaml训一个只跑50轮的yolo11n,权重不追求精度,只要模型对三轮车有基本响应。用这个半成品预测train集,重点看那些ground truth为空、但模型置信度很高的图片:
from ultralytics import YOLO import os model = YOLO('runs/detect/train/weights/last.pt') results = model.predict(source='tricycle/images/train', conf=0.25, save_txt=True, save_conf=True) for r in results: if r.boxes is None or len(r.boxes) == 0: continue lbl = r.path.replace('images', 'labels').replace('.jpg', '.txt') gt_n = 0 if os.path.exists(lbl): gt_n = sum(1 for _ in open(lbl)) # 真实标注为0且模型置信度超过0.4,标记为疑似漏标 if gt_n == 0 and r.boxes.conf.max() > 0.4: print('疑似漏标:', r.path, round(float(r.boxes.conf.max()), 3))逻辑说明:模型在train集上见过这些图像,如果在labelImg明确没画框的位置给出持续高置信度预测,最可能的解释是人工漏标。只取置信度大于阈值的框,相当于在完整yolo目标检测流程里提前做了一轮后处理过滤。输出聚焦gt_n==0的图片,这类图片即使漏一辆三轮车,也会让训练时该图的损失少算一个正样本。
参数说明:conf=0.25先过滤低质量预测,判断漏标用0.4而不是0.5,给阈值留余量。如果输出里大面积图片命中,说明标注整体质量有问题,先检查标注文件是否配对,别急着补框;如果命中集中在某几张图且置信度超0.6,基本可以定位到真实漏标。
5.2 SAM2做语义确认后再回labelImg补框
对怀疑漏标的图片,下一步用SAM2对预测框做语义确认。把可疑框作为box prompt输入SAM2,分割出的mask边缘贴合三轮车轮廓,说明该位置确有目标;mask发散到背景,多半是模型幻觉,直接忽略。
SAM2在这里只承担确认角色,不直接产生检测框。三轮车密集重叠时SAM2可能把两辆车并成一个mask,直接采用会引入新的边框误差。确认后的图片回到labelImg打开,手动补一个符合第3.3节判定标准的框,比自动生成框再修更省时间。
补框完成后,重新跑一次第4.1节的划分脚本,把新增标注同步进train/val/test,再回到第4.3节启动正式训练。按这个流程跑完,500张级别的数据通常能抓出5到15个漏标框,补完再重跑划分脚本,后续正式训练前就不需要人工逐张复查了。
本文还有配套的精品资源,点击获取