简介:这份资源是面向计算机视觉初学者与目标检测实践者的共享单车检测数据集,可直接用于YOLO系列算法的训练、验证与调参实验,帮助解决城市共享单车识别与计数等实际场景问题。压缩包共272个文件,包含136张jpg实拍图片与136个xml标注文件,图片采集自不同城市环境,覆盖多种光照、视角与背景,单车在颜色、品牌和使用状态上差异明显;xml文件采用VOC格式,逐张记录边界框坐标与类别信息,便于直接接入常见检测框架。资源包整体约90.08MB,规模适中,适合快速搭建实验流程。目前已有107人学习下载。借助这套图片与标签组合,读者可完成数据加载、模型训练、精度评估与部署验证的完整链路,并针对尺度变化、目标密集等难点优化泛化能力,为智能交通监管与共享单车调度提供可复用的数据基础。
1. 共享单车检测数据集:从一堆散图到 YOLO 可训的 VOC 标签
共享单车检测数据集这个方向,真正卡住人的往往不是模型结构,而是数据。你拿到手的可能是一批街拍图,标签是 VOC 格式的 XML,每张图里横七竖八停着几辆到几十辆单车,遮挡、逆光、密集堆叠全都有。这份资源就是冲着这个场景来的:图片加 VOC 格式标签打包,专门用于 YOLO 目标检测训练。它解决的是「没有现成标注数据、自己标又太慢」的问题,适合做智慧交通、城市治理、园区车辆管理的从业者,也适合刚入门想跑通一个真实检测任务的新手。VOC 转 YOLO 是绕不开的第一步,下面从数据本身讲到训练落地。
2. 拆开压缩包先看什么:VOC 结构与共享单车标注特点
2.1 VOC 目录长什么样,先确认三件事
解压之后,常见做法是看到Annotations、JPEGImages、ImageSets三个目录。Annotations放 XML,JPEGImages放原图,ImageSets/Main里通常有train.txt、val.txt。先别急着写训练脚本,打开一个 XML 确认三件事:文件名和图片是否一一对应、<object>里的name是不是统一写成bicycle或bike、<bndbox>的坐标有没有越界或为负。
# 统计图片数、XML 数,快速判断有没有缺标签 ls JPEGImages | wc -l ls Annotations | wc -l # 找出没有对应 XML 的图片 for f in JPEGImages/*.jpg; do base=$(basename "$f" .jpg) [ -f "Annotations/$base.xml" ] || echo "缺标签: $base" done这段脚本做的是集合差集检查。basename去掉路径和后缀拿到主名,再去Annotations里找同名 XML。缺标签的图如果直接进训练,YOLO 会把它当负样本,共享单车密集场景里这会明显拉低召回。参数上没什么可调的,重点是先跑一遍,把缺标签和多余 XML 都清掉。
2.2 共享单车标注的三个真实特点
第一是类别单一但实例极多。一张路口图里十几辆单车是常态,VOC 的 XML 会逐个列<object>,转 YOLO 时不能漏。第二是遮挡严重,车把、车座、车轮互相压叠,标注框往往只框可见部分,训练时要靠数据增强补。第三是尺度跨度大,近处一辆车占半张图,远处一排车每辆只有几十像素,这对 YOLO 的多尺度检测头是个考验。
提示:先抽 20 张图用标注工具过一遍,确认框的松紧是否一致。标注风格不统一比数量少更致命。
2.3 为什么选 VOC 而不是直接给 YOLO 格式
VOC 是通用中间格式,LabelImg、CVAT 这些工具默认就导出它,社区里大量脚本也是围绕 VOC 写的。拿到 VOC 意味着你可以随时转成 YOLO、COCO 或别的格式,不被单一框架绑死。代价就是多一步转换,但这一步脚本化之后一劳永逸。常见做法是把转换脚本和划分脚本都固化下来,换数据集只改路径。
3. VOC 转 YOLO:转换脚本、坐标归一化与数据集划分
3.1 坐标归一化的原理
VOC 的<bndbox>给的是绝对像素坐标xmin, ymin, xmax, ymax。YOLO 要的是归一化后的中心点加宽高:x_center, y_center, w, h,全部除以图片宽高,落在 0 到 1 之间。转换时要用PIL或cv2读一次图片拿真实尺寸,不能凭 XML 里的<size>字段,因为有些标注工具的宽高和实际图片对不上,这是血泪经验。
import os import xml.etree.ElementTree as ET from PIL import Image CLASSES = ["bicycle"] # 按你的实际类别改 def voc_to_yolo(xml_path, img_path, out_path): tree = ET.parse(xml_path) root = tree.getroot() # 用真实图片尺寸,别信 XML 里的 size with Image.open(img_path) as im: iw, ih = im.size lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLASSES: continue cls_id = CLASSES.index(name) 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) # 裁剪到图内,防止越界 xmin, ymin = max(0, xmin), max(0, ymin) xmax, ymax = min(iw, xmax), min(ih, ymax) if xmax <= xmin or ymax <= ymin: continue # 丢弃无效框 xc = (xmin + xmax) / 2 / iw yc = (ymin + ymax) / 2 / ih w = (xmax - xmin) / iw h = (ymax - ymin) / ih lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") with open(out_path, "w") as f: f.write("\n".join(lines))逻辑上分四步:读 XML、拿真实尺寸、逐框归一化、写 txt。CLASSES列表决定类别索引,必须和训练时的data.yaml完全一致,顺序错了模型学出来的类就是乱的。坐标裁剪那两行是后悔药,标注越界在共享单车数据里很常见,不裁会在训练时触发断言或产生诡异框。:.6f保留六位小数是 YOLO 官方脚本的习惯,精度足够。
3.2 批量转换与数据集划分
单张转换只是验证逻辑,真正要跑的是全量加划分。常见做法是 8:2 分训练验证,共享单车这种密集场景可以再留一点做测试。
import glob, random, os random.seed(42) imgs = sorted(glob.glob("JPEGImages/*.jpg")) random.shuffle(imgs) split = int(len(imgs) * 0.8) train, val = imgs[:split], imgs[split:] os.makedirs("labels/train", exist_ok=True) os.makedirs("labels/val", exist_ok=True) os.makedirs("images/train", exist_ok=True) os.makedirs("images/val", exist_ok=True) for phase, items in [("train", train), ("val", val)]: for img in items: base = os.path.splitext(os.path.basename(img))[0] xml = f"Annotations/{base}.xml" if not os.path.exists(xml): continue voc_to_yolo(xml, img, f"labels/{phase}/{base}.txt") # 图片软链或复制到对应目录 os.system(f"cp {img} images/{phase}/")random.seed(42)固定划分,保证每次复现一致。split控制比例,数据量小的时候可以调到 0.9。注意 YOLO 要求图片和标签同名同目录结构,images/train对应labels/train,路径在data.yaml里配。
3.3 data.yaml 怎么写
path: /your/dataset/root train: images/train val: images/val nc: 1 names: ["bicycle"]nc是类别数,共享单车单类就写 1。names顺序必须和转换脚本里的CLASSES一致。path用绝对路径最稳,相对路径在不同工作目录下容易翻车。
4. 用 YOLO 训练这份数据:环境、参数与显存权衡
4.1 环境配置的最小闭环
新手最容易在环境上耗掉半天。常见做法是用 conda 建一个干净环境,装 ultralytics 和 torch,别在 base 里折腾。
conda create -n bike python=3.10 -y conda activate bike pip install ultralytics # 验证 yolo checksyolo checks会打印 torch、CUDA、显卡信息。如果 CUDA 不可用,先确认驱动和 torch 版本匹配,别急着怀疑数据集。CPU 也能训,就是慢,小数据集验证流程够用。
4.2 训练命令与关键参数
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs/bike \ name=exp1model选yolov8n是轻量起点,共享单车单类任务通常够用,想提精度换yolov8s或更大。imgsz=640是通用值,远处小车多的话可以试 960,但显存翻倍。batch根据显存调,8G 卡上 640 分辨率一般能到 16。epochs100 起步,看验证集 mAP 是否还在涨。
4.3 显存不够时的三个降级手段
第一降batch,从 16 到 8 再到 4。第二降imgsz,640 到 512。第三换更小的模型。三者对精度的影响依次递减,优先动 batch。训练时用nvidia-smi -l 1盯着显存,别等 OOM 了才回头调。
注意:共享单车密集场景下,
imgsz降太多会让远处车辆糊成一团,mAP 掉得比想象中快。
5. 训练避坑与排查:从 loss 不降到底层数据错误
5.1 现象:loss 一直不降,mAP 接近 0
原因通常是标签路径没对上,YOLO 找不到 label 就当全是背景。解决:检查labels/train里 txt 数量是否和images/train一致,打开一个 txt 看内容格式对不对,类别索引是否越界。
5.2 现象:训练报「Label class x exceeds nc」
原因:转换脚本里的类别索引大于data.yaml的nc。比如脚本里 bicycle 是 0,但你不小心写了别的类。解决:统一CLASSES和names,重新生成标签。
5.3 现象:验证集 mAP 高但实际推理漏检严重
原因:训练验证同分布,实际场景光照、角度差异大。解决:划分时按场景分层抽样,别纯随机;推理时适当降conf阈值看召回变化。
5.4 现象:框位置整体偏移
原因:转换时用了 XML 里的<size>而非真实图片尺寸,两者不一致。解决:回到 3.1 的脚本,强制用PIL读真实宽高。
5.5 现象:同一辆车被检出多个框
原因:密集场景 NMS 阈值不合适。解决:推理时调iou参数,默认 0.7,密集场景可降到 0.5 左右试。
6. 进阶:用验证脚本量化数据质量,再决定要不要补标
训练跑通只是开始,真正决定上限的是数据质量。我一般会写一个验证脚本,在训练前就把问题暴露出来,而不是等 mAP 不理想再回头猜。
import glob, os from PIL import Image bad = [] for txt in glob.glob("labels/train/*.txt"): base = os.path.splitext(os.path.basename(txt))[0] img = f"images/train/{base}.jpg" if not os.path.exists(img): bad.append(("缺图", base)); continue with Image.open(img) as im: iw, ih = im.size with open(txt) as f: for i, line in enumerate(f): parts = line.split() if len(parts) != 5: bad.append(("字段数错", base, i)); continue _, xc, yc, w, h = map(float, parts) if not (0 <= xc <= 1 and 0 <= yc <= 1 and 0 < w <= 1 and 0 < h <= 1): bad.append(("越界", base, i)) print(f"问题样本 {len(bad)} 条") for b in bad[:20]: print(b)这个脚本查三类问题:图片缺失、字段数不对、归一化值越界。跑完看问题条数,超过总量 1% 就说明转换环节有系统性错误,先修脚本再训。参数上没什么可调的,重点是把它加进你的数据流水线,每次换数据集都跑一遍。
再进一步,可以统计每张图的框数量分布,共享单车场景里如果某张图框数超过 50,多半是标注重复或误标,值得人工复核。还可以把框的宽高比画出来,正常单车框宽高比集中在一个区间,出现极端值就是异常。
从那以后我每次拿到新数据集,都强制先跑一遍这个验证脚本再开训,省下的返工时间远比写脚本的十分钟多。希望帮到你。
本文还有配套的精品资源,点击获取