☰
共享单车检测数据集VOC+YOLO格式136张:从解压到训练部署全流程实战
2026/9/28 8:26:03 网站建设 项目流程

简介:这是一份面向目标检测学习与研究者的共享单车检测数据集,包含136张真实场景jpg图片,统一采用labelImg标注,类别仅bicycle,共318个矩形标注框。数据同时提供Pascal VOC(xml)与YOLO(txt)两种格式,标注与图片一一对应,适合直接用于训练、验证或迁移学习,免去手动格式转换环节。7z压缩包共410个文件,大小约89.95MB,内含136张jpg、136个xml及138个txt文件(其中136个为YOLO格式标注),目录结构简洁清晰。目前已有203人学习浏览,适合作为小规模数据集进行检测流程验证、算法效果对比或教学演示。得益于规范的矩形框标注与明确的单类别设定,使用者可快速评估模型基线,也可在此基础上扩充样本,开展数据增强、难例挖掘等实验。

1. 共享单车检测数据集VOC+YOLO格式136张1类别:别嫌小,这才是真实落地的起点

如果你搜到这个标题,多半已经在做共享单车乱停放检测、城市管理告警或者停车区域占用分析这类项目。136张图、1个类别、VOC和YOLO双格式,这个数据集在动辄上万张的公开数据集面前显得寒酸,但我要先说一个反直觉的结论:这种“小到刚好能跑通”的数据集,恰恰是验证你自己训练链路、标注规范和评估流程的最佳起点。它不是用来刷精度的,而是用来让你在半天内把“数据标注 → 格式转换 → 训练配置 → 模型评估”整条流水线跑通,再拿着这个基线去扩自己的数据。

这个标题背后实际是三个技术点:VOC格式的目录与XML标注结构、YOLO格式的TXT归一化坐标、以及7z压缩包在Windows和Linux下的解压差异。很多人卡住不是因为模型训练,而是因为格式转了一半发现标签对不上、类别ID错位、或者图片路径带着反斜杠导致训练直接报错。这篇文章我就按自己做过的方式,从解压、验数据、转格式、调训练参数到避坑,一步步给你讲清楚,顺便把那些网上没人明说的坑都摆出来。

2. 解压与验货:7z压缩包里到底是什么,先别急着训练

2.1 Windows和Linux下解压7z的两种可靠方式

7z格式在Windows上最常见的问题是右键解压失败,或者解压出来文件名乱码。这个压缩包用的是7z,不是zip,Windows自带的资源管理器虽然能识别但容易出问题,尤其是带中文路径或者使用非UTF-8编码的压缩包。我一般优先装7-Zip官方客户端,命令行方式更可控。

# Windows PowerShell 下调用 7z 命令行解压 & "C:\Program Files\7-Zip\7z.exe" x "共享单车检测数据集VOC+YOLO格式136张1类别.7z" -o"D:\dataset\bike" -y

-o参数指定输出目录,-y表示全部确认覆盖。如果你只想要里面的某一部分,比如只要YOLO格式的标注,可以先列出包内容再按需解压。7z的命令行参数和zip不太一样,容易把-o后面的路径写错导致解压到当前目录的压缩包同名文件夹里,这点注意。

Linux服务器上解压7z是常见场景,因为训练和数据转换通常在服务器上做。CentOS和Ubuntu默认都没有装p7zip,直接解压会报“command not found”,网上热搜里“linux解压7z文件”和“7z安装教程”就是这么来的。

# Ubuntu / Debian 安装 p7zip-full 并解压 sudo apt update && sudo apt install -y p7zip-full 7z x 共享单车检测数据集VOC+YOLO格式136张1类别.7z -o/home/user/bike_dataset

解压后先不急着训练。我习惯先做三件事:检查顶层目录结构、统计图片数量和标注数量、核对图片与标注文件是否一一对应。这个步骤能暴露大多数数据问题,比训练到一半报错再回头排查省时间得多。

2.2 VOC格式的目录骨架:JPEGImages、Annotations、ImageSets/Main 一个都不能少

VOC格式的完整目录结构是这样的:JPEGImages放原图,Annotations放XML标注,ImageSets/Main放训练验证集划分的txt文件。很多网上下的VOC数据集只有前两个文件夹,ImageSets缺失,训练脚本会自己按比例划分,但如果你用的是mmdetection这类框架,没有ImageSets/Main里的train.txt和val.txt,框架就会按默认规则找,结果找不到文件直接报错。

# 解压后建议先看目录结构 find bike_dataset -maxdepth 3 -type d | sort # 期望看到类似输出: # bike_dataset/VOC/JPEGImages # bike_dataset/VOC/Annotations # bike_dataset/VOC/ImageSets/Main # bike_dataset/YOLO/images/train # bike_dataset/YOLO/images/val # bike_dataset/YOLO/labels/train # bike_dataset/YOLO/labels/val

每个XML文件里最关键的是filename、size和object标签。filename可能和实际图片名不一致,size如果填错会导致归一化坐标反算回像素坐标时出现偏差。我见过一个数据集里XML的width和height全部写反了,YOLO训练出来mAP直接一半都不到,所以验货阶段一定要抽查几个XML手动核对。

# 检查XML和JPEG数量是否一致 ls VOC/Annotations/*.xml | wc -l ls VOC/JPEGImages/*.jpg | wc -l

如果两者数量不一致,优先检查是否有标注了但图片缺失,或者图片存在但没有任何标注的空样本。对于目标检测任务,空样本在验证集里可以留几个做背景负样本,但训练集里太多会让模型倾向输出低置信度,得不偿失。

2.3 YOLO格式的labels目录:txt文件里的归一化坐标怎么读

YOLO格式每张图对应一个同名txt文件,放在labels目录下。每行五个数字:类别ID、中心点x、中心点y、宽度w、高度h,全部是相对于图片宽高的归一化值,范围0到1。这个格式简单到容易让人忽视校验,但坑恰恰在细节里。

# 看一个YOLO标注文件的内容 cat YOLO/labels/train/000001.txt # 输出示例: # 0 0.452148 0.617188 0.137109 0.164062

第一列的0代表类别ID,因为只有1个类别所以永远是0。后面四个数必须严格在0到1之间,如果出现大于1的值,说明标注时坐标没有归一化,或者从VOC转的时候除以了错误的宽高。另外一个隐藏问题是坐标越界,比如中心点x+w/2超过1.0,很多训练框架会报错或者自动裁剪,但不同框架行为不一致,最好提前清理。

提示:验证YOLO标注是否合法的简单方式,是用Python读取所有txt,检查每行是否正好5个浮点数且所有值在[0,1]内。这一步能一次过滤掉大部分坏标注。

3. 把VOC转成YOLO格式:转换脚本与四个边界坑

3.1 为什么需要转换:VOC的XML不适合直接喂给YOLO训练

YOLO系列训练框架,无论是YOLOv5、YOLOv8还是YOLOv9,原生支持的数据格式都是YOLO格式,也就是每张图一个txt。虽然Ultralytics框架也提供了XML转YOLO的自动工具,但那要求你的VOC目录结构非常标准,而且它内部使用的类别映射很可能不是你的顺序。136张图1个类别的数据集,手动转换一次也就几秒钟的事,不值得为自动转换引入额外依赖,特别是当你的类别顺序和VOC内置的person、car等20类冲突时,自动转换会把你唯一的bicycle映射到错误的ID上。

常见做法是自己写一个转换脚本,把Annotations里的XML解析出来,按标注的bndbox坐标计算中心点和宽高,除以图片size得到归一化值,再写到对应的txt里。这个脚本以后换数据集还能复用,属于一次投资长期受益。

3.2 转换脚本:从XML到TXT的最小实现

import os import xml.etree.ElementTree as ET def convert_voc_xml_to_yolo(xml_path, out_txt_path, class_to_id): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_to_id: # 类别不匹配时跳过,避免写入错误ID continue class_id = class_to_id[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) # 计算YOLO格式的中心点和宽高 x_center = (xmin + xmax) / 2.0 / width y_center = (ymin + ymax) / 2.0 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height # 强制截断到[0,1],防止浮点误差越界 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) box_width = min(max(box_width, 0.0), 1.0) box_height = min(max(box_height, 0.0), 1.0) lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}") # 写入txt,空标注时留空文件但保留占位 with open(out_txt_path, 'w') as f: f.write('\n'.join(lines)) # 单类别数据集,类别名根据实际XML内容调整 class_to_id = {'bicycle': 0} xml_dir = 'VOC/Annotations' txt_dir = 'YOLO/labels' os.makedirs(txt_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if not xml_name.endswith('.xml'): continue xml_path = os.path.join(xml_dir, xml_name) txt_name = xml_name.replace('.xml', '.txt') txt_path = os.path.join(txt_dir, txt_name) convert_voc_xml_to_yolo(xml_path, txt_path, class_to_id)

这个脚本的逻辑不复杂,但有几个容易错的点要说明。第一,class_to_id里的类别名必须和XML里的<name>完全一致,包括大小写和空格,数据集里写的是“bicycle”还是“共享单车”要提前看一眼。第二,坐标全部转成float后再做除法,避免整数除法导致的精度丢失。第三,写入时保留6位小数足够了,YOLO训练用float32读取,精度再高没有意义。第四,空标注的图片要不要生成空txt?我建议生成空文件,因为Ultralytics框架会为每张训练图片寻找对应的txt,找不到会直接跳过或报错,空文件则被当作背景图片处理。

3.3 四个边界坑:坐标越界、类别名不匹配、图片宽高与XML不一致、文件名后缀混乱

先说坐标越界。XML里的bndbox有时会略微超出图片边界,比如xmax等于width,或者xmin小于0。这种坐标在VOC格式里不算致命,但转成YOLO归一化后可能出现中心点x+width/2 > 1.0的情况,某些数据增强会因此产生异常框。我的处理是在转换脚本里加截断,即使这样会轻微改变框的面积,也比训练时NaN好。

类别名不匹配是第二个坑。一个名称叫“共享单车”的数据集,XML里可能写的是“bicycle”,也可能是“共享单车”,甚至有的标注员写“bike”。转换脚本里的映射表必须容纳所有变体,否则标签会静默丢失。我曾经因为没注意一个XML里写了“Bicycle”大写B,结果训练集少了十几张的标注,mAP掉了3个点。

第三个坑是XML里声明的size和图片真实分辨率不一致。有的标注工具在标注后图片被压缩过,但XML没更新。如果转换时用XML的size,训练时加载的图片实际分辨率不同,归一化坐标虽然还是比例值,但如果宽高比例变化了,物体框会整体偏移。保险做法是转换前用PIL读一遍图片尺寸,以图片实际尺寸为准。

最后一个坑是文件名后缀。有的图片是.jpg,有的是.JPG或者.jpeg,而XML里的filename可能不带后缀。YOLO格式要求txt名和图片名严格一致,所以转换时不要依赖XML的filename,直接用xml文件名去掉后缀后到图片目录里找同类文件名的图片,这样最稳。

3.4 转换完成后如何自动验证:写个十行脚本帮你找出不对应的文件

from pathlib import Path img_dir = Path('YOLO/images/train') label_dir = Path('YOLO/labels/train') img_names = {p.stem for p in img_dir.glob('*.jpg')} label_names = {p.stem for p in label_dir.glob('*.txt')} print('缺少标签的图片:', img_names - label_names) print('缺少图片的标签:', label_names - img_names)

这个脚本虽然简单,但能一次性找出所有对不上的文件。我还会再写一行检查每张图片在XML和YOLO标注里框的数量是否一致,防止转换过程中某些框被静默跳过。

# 快速统计VOC XML里的总框数和YOLO txt里的总行数 grep -h '<bndbox>' VOC/Annotations/*.xml | wc -l cat YOLO/labels/*.txt | wc -l

两个数字一致说明转换没有丢框,不一致就逐个XML排查。这个步骤做完,你的数据才真正达到了可以训练的状态。

4. YOLO训练配置与参数选择:136张小数据集怎么把效果跑到极限

4.1 数据集配置文件:data.yaml的写法与路径坑

Ultralytics YOLOv8训练时需要一个data.yaml文件,里面指定训练验证集路径和类别信息。很多人直接在Windows上写好yaml,传到Linux服务器上训练,路径却忘了改,导致训练一开始就报FileNotFoundError。更隐蔽的问题是路径里带空格或中文,yaml解析会出错。

# data.yaml path: /home/user/bike_dataset # 数据集根目录 train: YOLO/images/train val: YOLO/images/val test: YOLO/images/val # 没有test就复用val nc: 1 names: 0: bicycle

path字段推荐写绝对路径,而且不要用引号包住整个路径。train和val写的是相对path的相对路径。如果你的images目录下还有子目录,就得写完整子路径。注意class名称只是可读标签,不影响训练ID,但影响后续预测显示的类别名。

4.2 train.py最小命令:batch size、imgsz、epochs怎么设

136张图的单类别数据集,训练时间很短,GPU上几分钟就能跑完一个epoch。但参数设置不当会让模型欠拟合或者过拟合。

# YOLOv8训练命令(使用默认yolov8n.pt预训练权重) yolo train model=yolov8n.pt data=data.yaml epochs=100 batch=16 imgsz=640 device=0

batch size在数据集小的时候尽量大一点,比如16或32,因为梯度更新更平滑,对小数据集收敛有帮助。imgsz用640是默认,如果原图尺寸远大于640,建议先resize到接近尺寸训练,不然大量的背景被压缩后再放大,会丢失细节。epochs的设置有讲究:136张图,训练集大概100张出头,100个epoch意味着每个epoch只更新100个batch内的参数,总共也就1万次迭代,对一个单类别检测器来说够用了。如果你想追求更快,50个epoch就能拿到一个可用的模型,但mAP可能不是最优。

注意:预训练权重不是万能的。yolov8n.pt是在COCO上训练的,能提供粗粒度的特征提取能力,但共享单车这个类别和COCO的bicycle比较接近,迁移效果会好。如果你的场景是俯拍下的共享单车,和COCO中的侧视单车差异很大,预训练权重帮助有限,此时可以考虑从头训练或者用更高层级的特征。

4.3 训练日志怎么看:loss下降、mAP50和mAP50-95的区别

训练过程中最常被问到的就是“loss多少算正常”。YOLOv8的loss由box_loss、cls_loss和dfl_loss组成。单类别数据集里cls_loss会降得很快,因为区分1个类比区分80个类简单得多。真正需要关注的是box_loss和mAP。

# 训练日志片段 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 50/100 2.8G 0.8123 0.0145 1.0231 3 640

mAP50是IoU阈值0.5时的平均精度,mAP50-95是在0.5到0.95之间多个阈值下的平均值。对小数据集来说,mAP50更容易涨,mAP50-95则对框的定位精度更敏感。如果你的验证集框大小差异很大,mAP50-95可能一直上不去,不要死磕,先看mAP50是否在80%以上。对于停车位占用检测这种场景,mAP50到90%以上就能用了,不需要追求mAP50-95的极致。

还有一个常见现象是loss在最后几十个epoch里反复震荡。这是正常的学习率周期性变化,不是训练发散。但如果loss出现NaN,基本可以断定是数据里有异常值,比如坐标越界或者图片损坏,回到第3章的验证脚本去查。

4.4 数据增强参数:136张图怎么靠增强撑住泛化

小数据集最大的敌人是过拟合。YOLOv8默认开了不少数据增强,包括随机平移、缩放、翻转、色彩抖动等。但对于单类别检测,有些增强反而有害,比如mosaic和mixup会生成大量包含多个重叠目标的合成图,如果标注框没有正确合成,模型会学到错误的边界。

# 适度减弱强增强,增强泛化 yolo train model=yolov8n.pt data=data.yaml epochs=100 batch=16 imgsz=640 device=0 \ hsv_h=0.01 hsv_s=0.2 hsv_v=0.2 flipud=0.0 mosaic=0.5 mixup=0.0

flipud也就是上下翻转,对于俯拍共享单车来说,上下翻转后自行车看起来还是自行车,方向信息本身可能不重要,但如果你的任务需要判断车头朝向,就得关掉。mosaic降到0.5能保留一部分多尺度信息,同时避免过多的拼接伪影。mixup直接关掉,因为单类别数据mixup后目标边界更模糊,收益很低。这些超参数没有绝对标准,我的习惯是先按默认训练一版,再用减弱增强的参数训练一版,比较两个模型的mAP,选更好的那套。

5. 训练后的验证与导出:不只是看mAP,要真的跑一遍预测

5.1 用训练好的权重跑推理:predict命令与输出格式

# 对验证集图片做推理并保存结果 yolo predict model=runs/train/exp/weights/best.pt source=YOLO/images/val \ save_txt=True save_conf=True conf=0.25

conf阈值设0.25是默认,单类别检测中你可以放宽到0.1,因为类别间没有竞争,误检率通常不会因为低置信度而爆炸。save_txt和save_conf会把每个框的置信度一起保存,方便后续做后处理。推理结果会生成在runs/predict目录下,标签文件里的格式是类别ID、置信度、x1、y1、x2、y2(注意这里是像素坐标,不是归一化值)。

5.2 从模型输出到可视化:画框看一眼比任何指标都直接

from ultralytics import YOLO model = YOLO('runs/train/exp/weights/best.pt') results = model.predict(source='YOLO/images/val', conf=0.25, save=True) # results对象里每个元素对应一张图 for r in results: boxes = r.boxes.xyxy.cpu().numpy() # 左上右下坐标 confs = r.boxes.conf.cpu().numpy() # 置信度 print(f"检测到 {len(boxes)} 个目标,最高置信度 {confs.max():.2f}")

输出结果后把画了框的图片存下来,逐张看一遍。这一步能发现很多mAP反映不出来的问题,比如重复框、大框包小框、把空调外机当成自行车等。单类别数据集误检往往是背景纹理引起的,如果这些误检出现在实际部署场景的高频区域,就得专门采集那些负样本图片加入训练集,这就是数据闭环的第一步。

5.3 模型导出的三种格式:ONNX、TensorRT、OpenVINO怎么选

训练完的PyTorch权重不能直接上线,需要导出成推理格式。YOLOv8自带导出功能,一条命令搞定。

# 导出ONNX yolo export model=runs/train/exp/weights/best.pt format=onnx imgsz=640 opset=12 # 导出TensorRT(需要NVIDIA GPU环境) yolo export model=runs/train/exp/weights/best.pt format=engine imgsz=640 half=True # 导出OpenVINO(适合Intel CPU/核显) yolo export model=runs/train/exp/weights/best.pt format=openvino imgsz=640

实际项目中,如果服务器是NVIDIA GPU,优先用TensorRT,half精度能让推理速度翻倍。如果是边缘盒子用Jetson,同样用TensorRT。如果你部署在普通CPU机器上且对延迟不敏感,ONNX加onnxruntime就够了,OpenVINO只在Intel平台上优势明显。导出的模型要先验证输入输出的张量形状,尤其是动态分辨率batch设为1的情况,别到部署现场才发现输出层名字不对。

5.4 边界情况:模型对“未见过的共享单车摆放姿态”会怎样

136张数据集的验证集可能只有30张图,模型的泛化能力有限。我常用的做法是把训练好的模型拿到网上随便搜几张共享单车街景图去测,重点看两种场景:一是乱停放时车辆倾斜角度大,二是夜间或逆光下的表现。如果矩形框严重偏移,说明训练数据里缺少这类角度和光照,需要补充拍摄或者加入公开的共享单车图片做数据扩充。这里要说明的是,这种扩充不能随意加网图,因为网图和你的相机视角、分辨率风格差异过大会导致训练集分布漂移,反而让模型在真实场景更差。

6. 数据闭环与进阶技巧:136张只是起点,你该怎么扩数据和调优

6.1 用难例挖掘补数据:把误检和漏检变成新训练样本

小数据集最好的壮大方式不是一次标注几百张,而是用当前模型去“挖矿”。具体做法:拿训练好的模型去跑一批未标注的真实场景图,设置较低的conf阈值比如0.1,把所有置信度在0.1到0.9之间的检测结果都当成候选。人工快速筛选其中真正的共享单车,把漏检的图补标为样本,把误检的图作为负样本加进训练集。一个百张级别的数据集,经过三轮难例挖掘后通常能到300到400张,模型在目标场景的鲁棒性会有显著提升。

这个流程我用Python脚本配合纯手工操作,不用任何标注平台的复杂功能,成本很低。

# 难例挖掘思路:用模型跑未标注图片,输出低置信度候选框 # 手工筛选后生成XML标注 from ultralytics import YOLO model = YOLO('runs/train/exp/weights/best.pt') results = model.predict(source='new_scene_images/', conf=0.1, save_txt=True) # 之后打开每个txt,按坐标在图片上画框,人工确认并补全遗漏目标

6.2 针对单类别小数据集的两种微调策略

第一种策略是冻结backbone只训练检测头。YOLOv8提供的预训练权重已经在COCO上学会了丰富的纹理和形状特征,共享单车的形状特征和COCO的bicycle高度相关,冻结backbone前几层可以防止小数据集把底层特征破坏掉。Ultralytics框架没有直接提供冻结参数的命令行选项,需要在训练脚本里修改模型结构。

# 冻结模型backbone的示例代码 from ultralytics import YOLO model = YOLO('yolov8n.pt') # 冻结前10层 for param in model.model[:10].parameters(): param.requires_grad = False model.train(data='data.yaml', epochs=50, batch=16)

第二种策略是使用更强的模型,比如yolov8s或yolov8m,但小数据集下大模型更容易过拟合,收益有限。我的经验是136张数据量,yolov8n和yolov8s差距不大,如果加了难例挖掘到300张以上,yolov8s的优势才体现出来。

6.3 验证模型泛化的三种方法:手动查、留出法、交叉验证

单数据集只有136张,常规随机划分一次的结果偶然性很大,可能你这版模型比那版高2个点纯粹是因为划分不同。推荐做5折交叉验证,每一次划分都训练一个模型,最后取平均mAP,这样对真实性能的估计更可靠。

# 用sklearn做5折划分,每一折分别生成train.txt和val.txt from sklearn.model_selection import KFold all_ids = list(range(1, 137)) kf = KFold(n_splits=5, shuffle=True, random_state=42) for fold, (train_idx, val_idx) in enumerate(kf.split(all_ids)): # 按索引生成对应的train.txt / val.txt print(f'Fold {fold}: train {len(train_idx)} val {len(val_idx)}')

交叉验证的训练成本对136张图来说很低,5个模型加起来也就等于一个大数据集训练一次。如果你做了交叉验证,最后部署时用全部136张图重新训练一个最终模型,这相当于把两种做法的优势结合了。

6.4 部署时容易被忽略的细节:分辨率、置信度阈值、检测框过滤

最后一个容易被忽视的点是推理时的输入尺寸。训练用的imgsz如果是640,推理时也尽量保持640,不要为了“更快”降到320,因为共享单车在画面中可能只占几十像素,降分辨率会让小目标直接消失。如果场景是俯拍图像,原图可能是4K级大图,需要先切块推理再拼接,或者按比例缩放后检测。切块推理时要注意相邻块之间不要切掉同一个车,设置一定的重叠区域能减少这种漏检。

置信度阈值也要根据实际误报代价调整。如果误报导致罚钱,阈值调到0.5以上;如果漏检更严重,阈值调到0.1并配合人工复核。这些经验值没有标准答案,需要你在自己的业务场景里跑一轮真实数据才能定下来。我每次都保留一张“最终验证表”,记录不同阈值下的精确率和召回率,方便后续调优时直接翻。

说到底,136张单类别数据集给你留下的不是窘迫,而是一个能快速迭代的实验室。用这套流程先把模型跑出来、把坑踩平、把数据闭环建起来,比等一个十万张的“完美数据集”要实际得多。以后每个新场景的数据,我都是用同样的方式先收小批量、跑基线、难例挖掘,再逐步扩充,这种习惯帮我省下了大量反复返工的精力,也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询