
简介面向目标检测任务的摩托车检测数据集从COCO2017中提取整理而来共3661张JPEG原图并配套3662个txt与3661个xml两种格式的标签文件类别统一为motorcycle可直接用于YOLO系列等主流检测算法的训练与验证同时覆盖YOLO与VOC两种常用标注格式需求。资源包总文件数2000个压缩后约683.87MB包含图片、标签及目录说明结构清晰无需额外格式转换即可上手使用。目前已有189人浏览学习适合计算机视觉入门学习者、算法工程师以及需要快速获取摩托车检测样本的科研人员。数据基于公开权威的COCO2017标注质量稳定可靠既能作为训练集也能作为验证集帮助使用者省去自行采集、清洗与标注数据的流程显著缩短模型开发前期的数据准备周期便于把精力集中在模型调优与业务落地环节。1. 摩托车检测数据集从COCO2017里拆出来的3661张现成标注做摩托车检测的人应该都体会过这种尴尬想训练一个目标检测模型第一反应是拿COCO全量数据但151个类别、十几万张图光下载就要折腾半天更别提标注文件动不动几个G预处理脚本还没写完热情就凉了。这份摩托车检测数据集就是冲这个痛点来的——它把COCO2017里motorcycle类别单独抠了出来一共3661张图同时给出txt和xml两套标签YOLO系和Pascal VOC系算法直接就能吃。适合谁做两轮车检测、城市交通目标识别、电动车违章抓拍的开发者以及想快速验证检测模型而不想花时间清数据的从业者。它解决的其实就是一件事把筛选类别、解析COCO JSON、转换标注格式这三步成本一次性省掉。2. 数据格式先看清txt与xml两套标注的生成逻辑2.1 文件来源与命名规则COCO原始ID保留的利与弊这个数据集的图片来自COCO2017 train2017和val2017文件名就是COCO原始的12位ID比如000000399942.jpg。对应的标签文件是000000399942.txt和000000399942.xml三者前缀完全一致。这个设计有个好处你在做数据增强或预处理时只要维护一个前缀列表就能同步操作图片和标签不用额外建映射表。坏处是文件名没有语义信息你光看文件名判断不了场景必须靠可视化工具配合。COCO2017的标注原始文件是instances_train2017.json和instances_val2017.json里面是嵌套的JSON结构。提取motorcycle类别的常规思路是遍历annotations数组筛出category_id对应的目标再通过image_id反查图片信息。转换时要注意COCO的坐标是绝对像素值x, y, w, h而YOLO的txt要求归一化到0~1区间这个换算如果直接截断小数精度就会受损。资源类型文件后缀格式体系主要字段图片.jpgCOCO原始图无标注信息标签.txtYOLO / Darknetclass_id, x_center, y_center, width, height标签.xmlPascal VOCname, xmin, ymin, xmax, ymax类别定义无单独文件需自行创建motorcycle 对应 class_id 02.2 txt标签格式归一化坐标里的小数精度陷阱txt每行代表一个目标框格式固定为五个数字类别ID、中心点x、中心点y、框宽、框高全部归一化。例如某行0 0.516967 0.463678 0.143867 0.162851含义是类别motorcycle框中心在图片宽度的51.7%、高度的46.4%处框宽占整张图宽的14.4%、高占16.3%。这套格式是YOLOv5、YOLOv8、YOLOv9这些模型的默认输入。转换公式是x_center (x w/2) / image_widthy_center (y h/2) / image_height宽高同理除以对应尺寸。这里有个很容易忽略的点COCO的bbox是左上角坐标加宽高而VOC的xml是左上角右下角坐标两者换算不能直接套同一个减法。数据集的制作者把jpg的标准尺寸信息写进了xml的size节点你如果要重新生成别的格式优先从xml读原始像素值而不是从txt反推因为小数保留位数会引入误差。另一个检查点txt里的归一化值如果出现负数或大于1说明原图在做resize时没有同步更新标注或者是某些边缘目标本来就越界了。这个数据集我从抽查的标注看坐标范围是正常的但你自己在训练前最好还是跑一遍范围校验成本很低。2.3 xml标签格式VOC体系下的可编辑性和转码便利xml文件是标准的Pascal VOC结构annotation节点下包含folder、filename、size然后是一个或多个object节点每个object里有namemotorcycle/name、difficult0/difficult以及bndbox里的四个坐标值。坐标是绝对像素的xmin、ymin、xmax、ymax不需要归一化。为什么同一个数据集要做两套标注我的理解是覆盖两类使用场景用YOLO训练直接读txt速度最快用SSD、Faster R-CNN、Detectron2这类框架或者要导入LabelImg做人工修正时xml更通用。而且以后如果想把摩托车类并进其他数据集xml格式用脚本合并比txt容易得多——VOC本来就是为多类别标注设计的。还有一点xml里保留了图片尺寸信息这对后面做Mosaic增强或自定义预处理是个很好的参考基准。不过要注意txt和xml是同一批标注导出的但如果你从外部工具修改了xml并重新导出txt务必确认转换脚本对小数位数的处理。有的脚本默认保留3位小数对1920×1080的大图来说每个坐标会偏移约1~2个像素对小目标检测影响明显。3. 塞进YOLO训练管线目录组织、配置文件与首次训练参数3.1 标准目录组织数据集目录结构与划分脚本拿这份数据集训练YOLOv8第一步是把数据组织成YOLO要求的目录结构。常见做法是images和labels两个平级目录各自再按train和val分。我之前踩过坑把train和val的图片混在一起靠txt来区分结果训练时报错找不到标签文件排查了半天才发现是目录映射没对上。# 建立标准目录结构 mkdir -p motorcycle_dataset/{images/{train,val},labels/{train,val}} # 将数据集按需分配示例前80%训练后20%验证 # 假设图片和标签存放在 extract/ 目录下 total$(ls extract/*.jpg | wc -l) train_count$((total * 80 / 100)) i0 for img in extract/*.jpg; do if [ $i -lt $train_count ]; then cp $img motorcycle_dataset/images/train/ prefix$(basename $img .jpg) cp extract/${prefix}.txt motorcycle_dataset/labels/train/ else cp $img motorcycle_dataset/images/val/ prefix$(basename $img .jpg) cp extract/${prefix}.txt motorcycle_dataset/labels/val/ fi i$((i 1)) done这段脚本做的事很简单先统计图片总数按百分比算出训练集数量然后循环复制图片和同名txt到对应目录。注意train和val的划分用了固定的80/20比例实际项目中我更建议按场景来分比如同一条路的照片不要同时出现在训练集和验证集里否则验证mAP虚高。排序后即使这个数据集只有3661张也要保证images/train和labels/train里的文件一一对应缺一个都会在训练时报FileNotFoundError。拷完后跑一句ls | wc -l分别检查两个目录数量是否一致这是最朴素的校验方法。3.2 类别配置文件从零写一个data.yamlYOLOv8训练需要一份YAML配置文件指定训练集、验证集路径和类别名。这个数据集只有一个类类别ID固定为0对应名称motorcycle。注意不要写错成motor或者motorbikeYOLO框架读取的是你在yaml里定义的names列表索引顺序必须和txt里的class_id对应。# motorcycle.yaml path: ./motorcycle_dataset train: images/train val: images/val nc: 1 names: 0: motorcycle这份配置的关键点path是相对于你执行训练命令的工作目录建议用绝对路径或者让train和val字段直接写完整路径避免因为工作目录不同导致找不到图片。nc是类别数这里必须是1因为txt里只有类别0。如果你从txt数据里意外混入了其他类别的标注这里的nc和names就对不上了训练时会出现index out of range或类别错乱的报错。3.3 一次训练的完整命令与参数解读YOLOv8训练命令不长但参数值要根据数据量调整。3661张图算小型数据集batch size不宜太大否则模型快速收敛后开始过拟合。我一般用batch 16、imgsz 640起步先跑50个epoch看损失曲线。# 训练YOLOv8s模型 yolo detect train \ modelyolov8s.pt \ datamotorcycle.yaml \ epochs50 \ batch16 \ imgsz640 \ patience10 \ projectmotorcycle_yolo \ nameexp1参数含义拆开说modelyolov8s.pt表示加载COCO预训练权重这里有个迁移学习的优势——COCO里本来就包含motorcycle类预训练模型对这个类别已有一定先验patience10是早停参数如果连续10个epoch验证集mAP没提升训练自动停止省时间imgsz640是输入分辨率如果原图里摩托车占比小、目标尺寸偏小可以改成960甚至1280但显存占用会成倍增长。首次跑建议用s版本m和l版本在这个数据规模下容易过拟合不是模型越大越好。4. 训练之外的处理数据集划分、类别均衡与标签可视化4.1 划分train/val/test按场景还是按文件前缀前面用bash脚本做了一个80/20的粗暴划分这节说更规范的做法。数据划分的核心原则是避免数据泄露——同一个人、同一辆车、同一场景出现在训练集和验证集中会导致验证指标虚高部署后实际效果打折扣。COCO原始图片有不同的场景和角度但毕竟只有3661张如果你在意评估可信度建议划分成train 70%、val 15%、test 15%。test集只在最终评估时用一次不要参与训练和调参。import os import random import shutil random.seed(42) src extract train_dir motorcycle_dataset/images/train val_dir motorcycle_dataset/images/val test_dir motorcycle_dataset/images/test all_files [f for f in os.listdir(src) if f.endswith(.jpg)] random.shuffle(all_files) n len(all_files) train_files all_files[:int(n * 0.7)] val_files all_files[int(n * 0.7):int(n * 0.85)] test_files all_files[int(n * 0.85):] for file_list, target_dir in [ (train_files, train_dir), (val_files, val_dir), (test_files, test_dir) ]: os.makedirs(target_dir, exist_okTrue) for f in file_list: prefix os.path.splitext(f)[0] shutil.copy(os.path.join(src, f), os.path.join(target_dir, f)) shutil.copy(os.path.join(src, prefix .txt), os.path.join(target_dir.replace(images, labels), prefix .txt))这段代码做了随机打乱后按比例切分random.seed(42)保证每次运行划分结果一致便于复现实验。注意replace(images, labels)这行是关键——它把图片目录路径中的images替换成labels确保标签复制到对应的labels目录。如果你是Windows环境路径分隔符是反斜杠replace时最好统一成os.sep做拼接我在这里踩过一次坑Linux跑完的脚本换到Windows就跑不通了。4.2 类别分布统计一张图里有多辆摩托车的处理这份数据的标注粒度是实例级的一张图里可能不止一个motorcycle目标。训练前统计一下每张图的标注框数分布能帮你判断模型的上限——如果大量图片只有一个小目标那么用小输入尺寸训练的模型必然在远距离检测上翻车。写个脚本统计每个txt文件的行数再画个分布图就行。import os import numpy as np label_dir motorcycle_dataset/labels/train counts [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), r) as fp: lines fp.readlines() counts.append(len(lines)) counts np.array(counts) print(f标注文件总数: {len(counts)}) print(f平均每图目标数: {counts.mean():.2f}) print(f单图最多目标数: {counts.max()}) print(f单目标图片占比: {(counts 1).mean() * 100:.1f}%)逻辑很简单读每个txt的行数行数就是目标数。对于这个数据集如果统计出来的单目标占比很高说明大多数场景是常见的街拍摩托车多目标的密集场景偏少。如果你后续想提升密集场景检测能力就要额外补充多目标图片而不是单纯加大训练epoch。COCO里同一张图出现多辆摩托车的场景其实不少从数据分布看平均每张图的目标数在1.2左右属于比较典型的目标检测数据集结构。4.3 标注可视化抽检把人工检查变成自动化训练前最值得做的一件事是把标注框画到图上人工快速翻一遍。以前我都是直接在图片上叠加PIL画的矩形效率很低。后来改成把所有抽检图片拼到一张大图上一次看几十张几秒钟就能发现标注错位、坐标越界这类问题。import cv2 import os import math def draw_boxes(img_path, txt_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f.readlines(): parts line.strip().split() if len(parts) ! 5: continue cls, xc, yc, bw, bh map(float, parts) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) return img image_dir motorcycle_dataset/images/val label_dir motorcycle_dataset/labels/val out_dir visual_check os.makedirs(out_dir, exist_okTrue) images sorted(os.listdir(image_dir))[:8] tiles [] for img_file in images: prefix os.path.splitext(img_file)[0] txt_file os.path.join(label_dir, prefix .txt) img draw_boxes(os.path.join(image_dir, img_file), txt_file) tiles.append(img) # 拼接2x4网格 grid np.hstack([np.vstack(tiles[:4]), np.vstack(tiles[4:8])]) cv2.imwrite(os.path.join(out_dir, grid_check.jpg), grid)这段代码的核心是把txt的归一化坐标乘以图片实际宽高还原成像素坐标再画矩形。归一化坐标的好处在这里体现——不管图片原始尺寸是多少解析逻辑都一样。抽检时重点看两类问题一是框和摩托车的轮廓是否贴合二是同一张图里多个框是否重叠异常。如果发现txt里坐标正常但框画出来明显偏移多半是原图被resize过但标注没跟着改这种图在训练时是负样本要尽早剔除。5. 实战避坑转换、训练和验证环节的五个高频问题5.1 现象标注txt坐标出现负值或大于1有次我拿一个从别的来源整理的摩托车数据集训练loss降到一定程度就卡住验证集的mAP始终在0.3左右徘徊。排查时发现部分txt文件里的中心点坐标是1.023或者-0.015这类越界坐标大多数是图像在做翻转增强时标注没同步更新造成的。原因YOLO系训练时Mosaic和随机仿射变换都会对坐标做重映射如果原始标注就存在边界越界增强后的坐标会进一步偏移导致损失函数震荡。解决训练前统一跑一遍坐标范围检查。代码很简单读每行txt判断0 xc 1且0 yc 1且bw 0且bh 0不满足的要么剔除该目标要么用xml里的原始值重新归一化。5.2 现象图片和标签对不上训练时报FileNotFoundError在YOLOv5的train.py里labels目录下缺了某个txt文件训练直接中断。这个数据集本身是配对的但你自己做划分、拷贝、清洗时很容易弄丢标签。原因我犯过的错误是用shell的find命令按图片列表过滤标签结果find的路径写错把一部分标签过滤没了。解决在划分脚本里强制加入配对校验。处理数据的标准做法是所有对图片的操作都必须同时操作同名txt不要用独立脚本分别处理。写一个简单的自检函数遍历图片目录逐个检查labels目录里是否存在对应前缀的txt缺失的集中报告。每次跑新数据集前我都强制走一遍这个检查耗时不到一分钟能省掉后面排查数小时的麻烦。5.3 现象用COCO预训练权重迁移加载时报类别数量不匹配用yolov8s.pt训练时如果改了nc但没改names有的框架版本会直接加载失败或者加载成功但训练时类别错乱。还有的人图省事直接换yolov8s.yaml把nc改成1结果预训练权重里的分类头维度对不上前几个epoch的loss异常高。原因迁移学习的逻辑是权重和模型结构必须匹配。YOLOv8会自动适配nc的差异但如果你用的不是官方模型文件而是自己改过结构的自定义模型兼容性就没保障。解决统一用官方发布的预训练权重只改data.yaml里的nc和names不要动模型yaml。训练命令里model参数填yolov8s.pt框架会自动调整检测头的输出维度。5.4 现象3661张训练样本训练到后期明显过拟合训练30个epoch后训练集loss接近0但验证集mAP不涨反降。这是小数据集的通病。原因模型容量大数据多样性不够。单体摩托车的姿态、颜色、背景变化有限模型很快记住了训练集里具体的车而不是抽象出“摩托车”这个概念。解决一是加强数据增强YOLOv8的hsv_h、hsv_s、degrees这些增强参数可以调大一点二是用更小的模型版本比如从s降到n三是做迁移学习时冻结backbone的前几层只训练检测头等loss稳定后再解冻全部层做微调。我在这份数据上试过冻结backbone训练20个epoch解冻再训练20个epoch比直接端到端训练50个epoch的mAP高3~5个点。5.5 现象小目标摩托车漏检远距离场景mAP很低验证集中的摩托车骑手如果距离远框只有二三十像素大模型几乎全部漏检。这和数据集本身的构成有关COCO里小目标占比天然偏低提取出来的单类别数据也同样继承了这个特性。原因输入分辨率是640小目标在特征图上只占几个像素FPN的高层特征对小目标响应本身就弱。解决把imgsz从640改成960或1280代价是训练时间翻倍或者用SAHI这类切片推理工具把大图切成若干小图分别检测再合并对远距离小目标效果立竿见影。如果显存不足可以把batch降到8。6. 进阶技巧用mAP曲线和可视化抽检把数据集价值榨干6.1 用mAP0.5和mAP0.5:0.95区分数据集质量问题训练结束后YOLO会在runs/detect/exp目录下输出results.csv和confusion_matrix.png。我的习惯是先看mAP0.5:0.95这个指标对框定位精度很敏感。如果mAP0.5高但mAP0.5:0.95明显低说明模型能找到摩托车但框的位置不够准大概率是标注框本身有偏移。这种情况下再去调模型参数学费不讨好正确的做法是重新审视标注质量。import pandas as pd import matplotlib.pyplot as plt # 读取训练日志 df pd.read_csv(motorcycle_yolo/exp1/results.csv) plt.figure(figsize(10, 6)) plt.plot(df[epoch], df[metrics/mAP50(B)], labelmAP0.5) plt.plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP0.5:0.95) plt.xlabel(epoch) plt.ylabel(mAP) plt.legend() plt.grid(True) plt.savefig(mAP_curve.png)这个脚本把两个mAP画在同一张图里观察两条曲线的间隔。间隔一直在0.2以上基本可以判断标注框精度不够。正常数据下两个指标的差一般在0.1~0.15之间。间隔过大的时候我一般会重新抽50张图做可视化检查看看是整体偏移还是个别标注漏了边角再用LabelImg人工修正一批修正后再重训mAP50-95通常能回升不少。6.2 标注质量抽检批量导出带框图片并排序最后一招是我比较依赖的“土办法”把验证集的预测结果和Ground Truth同时画在图上按置信度排序置信度低但GT存在的图片往往藏着标注问题。YOLO预测输出的txt直接解析即可和前面的可视化脚本几乎一样只是把txt路径换成runs/detect/exp/labels下的预测结果。import cv2 import numpy as np from pathlib import Path # 预测结果可视化 pred_dir Path(motorcycle_yolo/exp1/labels) img_dir Path(motorcycle_dataset/images/val) out_dir Path(pred_visual) out_dir.mkdir(exist_okTrue) for txt_file in pred_dir.glob(*.txt): # 只保留置信度大于0.5的预测框 boxes [] with open(txt_file, r) as f: for line in f.readlines(): parts line.strip().split() conf float(parts[4]) if conf 0.5: boxes.append(parts) if not boxes: continue img cv2.imread(str(img_dir / (txt_file.stem .jpg))) h, w img.shape[:2] for parts in boxes: cls, xc, yc, bw, bh, conf parts xc, yc, bw, bh map(float, [xc, yc, bw, bh]) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(str(out_dir / (txt_file.stem .jpg)), img)画出来的图里红色框是模型预测绿色框是GT。我一般按“预测框比GT大一圈”和“预测框偏在一侧”这两个特征找问题图。尤其是前者模型倾向于输出更大的框来覆盖不确定区域如果大量图片都是这种情况说明训练数据里部分GT框太贴近目标边缘模型学着学着就把边界模糊了。从那以后我每次拿到新的检测数据集都强制先走一遍这套流程检查txt坐标范围、核对图片标签配对、画一批可视化抽检图。跑模型反而是最后一步数据本身没过关后面的调参全是玄学。这份摩托车数据集虽然规模不大但胜在格式标准、来源干净把它用透比盲目堆数据更能帮你厘清检测流程里每一步的坑在哪。希望帮到你。本文还有配套的精品资源点击获取