ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

风筝数据集2260张VOC+YOLO格式目标检测训练全流程解析

风筝数据集2260张VOC+YOLO格式目标检测训练全流程解析 简介风筝目标检测数据集面向计算机视觉入门与目标检测模型训练需求提供2260张真实场景风筝图片以及对应的Pascal VOC格式XML与YOLO格式TXT标注文件类别统一为kite共8790个目标框可直接用于YOLO、Faster R-CNN等目标检测模型的训练与验证。单个类别设计可排除多类别干扰适合新手快速掌握数据标注与检测流程。7z压缩包共2000个文件以XML标注文件为主附说明txt整体大小268.06MB标注工具为labelImg采用矩形框规则框选准确、格式规范使用时可省去数据整理和格式转换步骤。数据集聚焦单类别检测文件名按序号排列便于批量读取与按索引检索适合开展风筝识别、天空目标检测等任务的教学实践、算法对比或项目预研。目前已有361人学习下载标注仅覆盖目标框不包含分割标签与模型权重可自由用于自定义训练流程与模型精度评估。1. 风筝数据集2260张VOCYOLO格式天空目标检测的起点样本做目标检测的人都知道通用数据集里大把的猫狗汽车行人可一旦把场景切到天空样本立刻稀缺。风筝检测就是这个典型飞行器识别、无人机避障、户外赛事监控都绕不开这个目标。而这个「风筝数据集2260张VOCYOLO格式」恰恰把最麻烦的三件事提前解决了——图片集齐了、VOC标注有了、YOLO训练要的txt格式也直接给了省掉自己标注和转格式的体力活。拿到这种数据集第一反应别急着开训。你要先搞清楚它内部怎么组织、标注质量如何、类别平衡怎样再决定要不要补数据、要不要做增强。本文就按「读数据 → 转格式 → 配参数 → 踩坑 → 验证提升」这条路把这个数据集从磁盘一路用到验证集。适合正在跑YOLO系列、又缺垂直场景数据的你。2. 摸清2260张的底细目录结构、标注内容与一致性核查2.1 拿到手先做三件事看目录、数文件、量标注数据集解压后第一件事不是看图片而是把目录结构整个列出来。常见做法是VOC原始目录和YOLO输出目录分开放你自己的工程里不要动不动改它的内部结构宁可写脚本复制一份再动。目录长这样kite_dataset/ ├── VOC/ │ ├── JPEGImages/ # 2260张jpg原图 │ ├── Annotations/ # 2260个xml标注 │ ├── ImageSets/ │ │ └── Main/ │ │ ├── train.txt # 训练集图片名列表 │ │ ├── val.txt # 验证集图片名列表 │ │ └── test.txt # 测试集图片名列表 ├── YOLO/ │ ├── images/ # 全部图片或按train/val再分 │ ├── labels/ # 每张图对应一个txt │ └── data.yaml # 类别定义与路径配置 └── README.md拿到目录后立刻数文件别信宣传。2260张是总数你得确认JPEGImages里有2260个jpg、Annotations里有2260个xml而且两边文件名一一对应。任何一个对不上后面训练时yolo会报image not found或者干脆跳过这张图白折腾。通常用一条bash命令就能核对ls VOC/JPEGImages | wc -l ls VOC/Annotations | wc -l comm -3 (ls VOC/JPEGImages | sed s/\.jpg//) (ls VOC/Annotations | sed s/\.xml//)第三条comm -3输出两边不一致的文件名正常情况应该什么都不打印。如果打印了那说明这个数据集里有孤儿图片或孤儿标注再训练之前得先筛掉。2.2 xml标注内容字段逐个看class、bbox、difficult的含义VOC格式的xml标注一个文件对应一张图核心字段就那么几个。拿一个典型标注文件来说annotation folderVOC/folder filenamekite_0117.jpg/filename size width1920/width height1080/height depth3/depth /size object namekite/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin412/xmin ymin266/ymin xmax889/xmax ymax603/ymax /bndbox /object /annotation这里要重点核查的是difficult字段。VOC原始定义里difficult1表示这个目标很难辨认比赛评测时会忽略它。但很多第三方数据集这个字段是乱填的甚至干脆不写。如果你拿到的数据里大量目标difficult1转YOLO格式时默认会把它们丢掉可能导致实际参与训练的目标数远小于标注数。另一个要留意的是单张图的目标数。2260张图、每张图一个风筝那总共就是2260个目标。如果平均每张图有2到3个风筝说明这个数据集带密集目标适合做遮挡和多目标训练如果每张就一个那泛化到多风筝场景时要小心。跑一段Python就能知道分布import xml.etree.ElementTree as ET import os from collections import Counter ann_dir VOC/Annotations obj_counts Counter() class_counts Counter() for xml_name in os.listdir(ann_dir): tree ET.parse(os.path.join(ann_dir, xml_name)) root tree.getroot() objs root.findall(object) obj_counts[len(objs)] 1 for obj in objs: cls obj.findtext(name) diff obj.findtext(difficult) class_counts[(cls, diff)] 1 print(每张图的目标数分布:, dict(sorted(obj_counts.items()))) print(类别与difficult分布:, class_counts)这段代码直接帮你画出目标分布。如果出现某张图有十几个目标、大部分图只有一个那就注意密集场景下的性能。如果difficult的分布异常比如一半以上目标都标难例建议转YOLO格式时别用默认剔除逻辑先保留再人工抽检。2.3 YOLO格式的txt究竟存了什么归一化坐标不是玄学YOLO格式的标注文件每行表示一个目标格式固定为class x_center y_center width height后四个值全部是相对图片宽高的归一化比例。这个设计是为了让模型在不同分辨率下训练时标注不随图片缩放而失效。0 0.339062 0.402315 0.248958 0.311574 0 0.681771 0.513889 0.214062 0.257407上面这个txt里两行0是类别ID对应data.yaml里第一个类别。如果是单类别数据集所有行都是0如果是多类别就得去yaml文件里数顺序。用OpenCV或PIL读图后验证一下框是否落在图内import cv2 img_path YOLO/images/kite_0117.jpg label_path YOLO/labels/kite_0117.txt img cv2.imread(img_path) h, w img.shape[:2] print(f图片尺寸: {w} x {h}) with open(label_path) as f: for line in f.readlines(): cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) print(f类别{int(cls)} 框: ({x1},{y1}) - ({x2},{y2})) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check_kite_0117.jpg, img)跑完之后打开check_kite_0117.jpg肉眼扫一遍框是否贴合风筝轮廓。框明显偏向或框不到目标说明原始标注不准框跑到图片外面去了说明归一化换算或者坐标解析有bug千万别拿这种数据直接开训。3. 把VOC转成YOLO能用的txt拆解转换脚本与四个边界坑3.1 为什么不直接用VOC训而非要转成txtYOLO系训练管线对标注的读取路径是dataset脚本读label文件拿归一化坐标算损失。虽然ultralytics也提供了从VOC直接读的适配器但性能上不如原生txt稳定。常见做法是先把VOC格式转成YOLO的labels让数据集根目录结构完全符合ultralytics默认预期。这样后续换backbone、换预训练权重都不会在数据读取上翻车。3.2 一套转换脚本从xml到txt全流程核心转换逻辑不复杂无非是读取bndbox四个像素坐标除以图片宽高得到归一化值。要注意的点是必须用xml里size的宽高不能自己打开图片再量。因为有的数据集图片被重采样过但xml没更新两者对不上时转出来的框就会集体偏移。安全做法是两套都读以图片实际尺寸为准并警告偏差import xml.etree.ElementTree as ET import os import cv2 voc_img_dir VOC/JPEGImages voc_ann_dir VOC/Annotations yolo_img_dir YOLO/images yolo_label_dir YOLO/labels class_names [kite] # 按你的data.yaml类别顺序排列 os.makedirs(yolo_img_dir, exist_okTrue) os.makedirs(yolo_label_dir, exist_okTrue) for xml_name in os.listdir(voc_ann_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(voc_ann_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() # 优先用xml里声明的宽高动态检测为0时用真实图片宽高兜底 size root.find(size) xml_w int(size.findtext(width)) xml_h int(size.findtext(height)) img_name root.findtext(filename) img_path os.path.join(voc_img_dir, img_name) img cv2.imread(img_path) if img is None: print(f图片无法读取跳过: {img_path}) continue real_h, real_w img.shape[:2] if xml_w ! real_w or xml_h ! real_h: print(f警告 {img_name}: xml尺寸({xml_w}x{xml_h})与真实尺寸({real_w}x{real_h})不一致使用真实尺寸) base os.path.splitext(img_name)[0] label_out os.path.join(yolo_label_dir, base .txt) img_out os.path.join(yolo_img_dir, img_name) # 复制图片到YOLO目录保持目录干净 cv2.imwrite(img_out, img) lines [] for obj in root.findall(object): cls_name obj.findtext(name) if cls_name not in class_names: print(f跳过未定义类别: {cls_name}) continue cls_id class_names.index(cls_name) diff int(obj.findtext(difficult) or 0) if diff 1: continue # 难例默认剔除可以删掉这行保留难例 bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # 坐标裁剪到图内防止越界导致的训练异常 xmin max(0, min(xmin, real_w - 1)) xmax max(0, min(xmax, real_w - 1)) ymin max(0, min(ymin, real_h - 1)) ymax max(0, min(ymax, real_h - 1)) if xmax xmin or ymax ymin: print(f跳过宽高为0的框: {img_name}) continue x_center (xmin xmax) / 2 / real_w y_center (ymin ymax) / 2 / real_h box_w (xmax - xmin) / real_w box_h (ymax - ymin) / real_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(label_out, w) as f: f.write(\n.join(lines)) print(转换完成)这段脚本的意图很明确xml只是参考真实图片尺寸才是坐标计算的基准。后面的cv2.imwrite直接拷贝图片保证了YOLO目录里的images和labels文件对本一一对应。坐标裁剪那一步是很多人忽略的xml里的xmax偶尔会等于图片宽度归一化后变成1.0正好卡在边界上部分YOLO版本训练时bn层就崩了。3.3 转换中四个边界坑difficult丢目标、坐标越界、类别ID错位、空标注文件第一个坑是difficult目标被静默丢弃。上面代码里if diff 1: continue这一行如果数据集的difficult标注不可靠会造成实际参与训练的目标数只剩一半。你训练完看每张图预测都很准但验证集mAP不高原因可能就是验证标注里困难目标全被剔除了数量对不上。第二个坑是框越界。风筝在天空背景下经常贴近图片边缘标注员一马虎框就出了边界。YOLO训练时遇到越界框轻则loss变成nan重则bn层参数漂移训练到一半指标狂掉。所以转换脚本里必须做clip操作宁可让框缩一点点也别让它越界。第三个坑是类别ID错位。如果你的数据集是多类别而data.yaml里类的顺序和xml里name字符串顺序不一致训练时所有标签都会张冠李戴。转换脚本里用class_names.index(cls_name)动态映射而不是硬编码数字就是要避免这件事。第四个坑是空标注文件。有些图片里所有目标都被判为difficult脚本会生成一个0字节的txt。YOLO训练遇到空txt有些版本直接报错有些版本跳过这张图。处理办法是转换后扫一遍labels目录把所有0字节文件对应的图片也一并从训练集里剔除find YOLO/labels -name *.txt -size 0 | while read f; do imgYOLO/images/$(basename ${f%.txt}).jpg echo 删除 $img 及其空标注 $f rm -f $img $f done跑完这条命令后再核对一次图片数和标注数两者必须完全相等这才是合格的训练数据。3.4 train/val/test切分别让同场景的风筝同时出现在训练集和验证集2260张图按7:2:1切分是常见做法。但这里有个隐藏坑如果图片是按某个时间序列采集的比如同一片天空同一时段连续拍了20张直接随机切分训练集和验证集里会出现几乎一样的内容验证指标虚高部署到新场景就露馅。稳妥做法是按场景分组切分。你拿不到采集元信息也没关系可以用一个简单启发式把文件名前缀相同的图片视作同组同组内不能跨训练/验证边界。写切分脚本时按组为单位放进对应集合import os import random from collections import defaultdict random.seed(42) base YOLO images os.listdir(os.path.join(base, images)) # 按文件名前缀分组通常是采集场景标识 groups defaultdict(list) for img in images: prefix img.rsplit(_, 1)[0] # 如 kite_0117.jpg - kite groups[prefix].append(img) group_names list(groups.keys()) random.shuffle(group_names) n_total len(group_names) n_train int(n_total * 0.7) n_val int(n_total * 0.2) train_groups set(group_names[:n_train]) val_groups set(group_names[n_train:n_train n_val]) train_imgs [] val_imgs [] test_imgs [] for gname, gimgs in groups.items(): if gname in train_groups: train_imgs.extend(gimgs) elif gname in val_groups: val_imgs.extend(gimgs) else: test_imgs.extend(gimgs) for split, imgs in [(train, train_imgs), (val, val_imgs), (test, test_imgs)]: with open(os.path.join(base, f{split}.txt), w) as f: for img in sorted(imgs): f.write(os.path.join(base, images, img) \n) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)}, test: {len(test_imgs)})分组切分比随机切分更接近真实部署场景。如果你的数据集里前缀没有规律可循退而求其次用随机切分也行但要记得固定random seed方便复现实验。4. yolov8训练自己的数据集配置、参数与踩坑实录4.1 data.yaml与模型选型小数据集该用哪个尺寸ultralytics的YOLOv8读数据靠一个yaml文件把训练集、验证集、类别名串起来。以这个2260张风筝数据集为例我的推荐配置是path: ./kite_dataset/YOLO train: train.txt val: val.txt test: test.txt nc: 1 names: 0: kitepath是相对于你执行训练命令的根目录。train和val指向的是之前切分生成的txt文件txt里存的是每张图片的绝对或相对路径。nc是类别数单类别就是1。names的索引顺序必须和labels txt里的class ID一致对不上就全乱了。模型选型上2260张图属于中小规模数据集第一原则是别贪大。yolov8m、yolov8l确实精度可能更高但训练数据量不够时大模型反而更容易过拟合发挥不出参数量优势。常见做法是先跑yolov8s把baseline钉住再看混淆矩阵决定要不要上更大模型。4.2 训练命令与关键参数epochs、batch、imgsz怎么给训练命令本身不复杂yolo detect train \ modelyolov8s.pt \ datakite_dataset/YOLO/data.yaml \ epochs100 \ batch16 \ imgsz640 \ patience20 \ projectruns/kite \ nameyolov8s_640epochs100对于2260张图来说配合早停patience20足够跑出好结果。batch16看你的显存如果12GB显存跑不动降到8如果24GB可以上32。imgsz640是默认推理分辨率风筝目标在天空背景下通常较小如果你发现验证集大风筝全检测到了、小风筝漏检多把imgsz提到960或1280往往有效。有个细节很多人不知道yolov8s.pt是COCO预训练权重COCO里没有kite这个类别所以最后一层分类头会被随机初始化。你不用担心这会拉低效果前面backbone学到的边缘、纹理、形状特征完全可以迁移过来风筝虽然不在COCO里但它边缘纹理跟很多COCO目标共享低级特征。4.3 避坑风筝类小目标、bn崩溃、loss变成nan现象到解决先说你大概率会碰到的第一个坑训练到一半loss突然变成nan。这个现象我见过不下五次原因多半出在标注上。转格式时空txt没清理干净、或者某个框坐标算错了导致中心点落在图外都会让损失计算时出现除零或对数取负数。我的排查顺序是——先检查labels目录是不是每张图都有对应的非空txt再随机抽样50个标注框画到图上肉眼看最后跑一个torch.nan出现前的batch数据喂到模型里定位。第二个坑是yolo训练中bn崩溃。现象是训练前十几轮正常后面mAP突然掉到接近0plot出来的PR曲线像被腰斩。常见原因是batch size太小比如只有4或8同时又开了大批量增强bn层的统计量在mini-batch里波动太大参数漂移后就崩了。解决方法是把batch提到至少16或改用batch-1让程序按显存自动找最大值再不行就把mosaic增强关掉看是否稳定。第三个坑是验证集指标虚高但实测一塌糊涂。这是数据划分的问题如果同一条时间序列里相邻帧被分到训练和验证两个集合模型在验证集上看到的几乎是训练时见过的内容。这个数据集如果按连续帧采集一定要用前面说的分组切分重新划分。划分完再去训练别嫌麻烦。第四个坑是小风筝目标漏检严重。现象是验证集mAP看着还行但把测试图放大看远处的小风筝全没框出来。这类场景有个取巧的补救办法训练时把imgsz设成960推理时也设成960对比640输入小目标召回常有肉眼可见的提升。如果还不行就按目标面积统计一下标注框的分布看看小目标占比是否真的少少的话该补数据就补数据别硬调模型。4.4 训练失败时看什么runs目录下的日志与权重恢复训练中断或指标异常时第一手信息都在runs/kite/yolov8s_640/下面。weights/best.pt和weights/last.pt是关键best.pt按验证集mAP保存最优权重last.pt是最后一个epoch的权重。如果训练中断用last.pt续训yolo detect train \ modelruns/kite/yolov8s_640/weights/last.pt \ datakite_dataset/YOLO/data.yaml \ epochs100 \ resumeTrueresumeTrue会从上一次中断的位置接着跑。训练日志文件train目录下的csv记录每个epoch的loss、mAP50、mAP50-95用Excel或pandas打开重点看train/box_loss和val/box_loss两条曲线。如果训练loss持续下降、验证loss先降后升那不用等训练跑完就知道过拟合已经开始了回滚到验证loss最低点的best.pt即可。5. 验证模型与进阶调优混淆矩阵、PR曲线和小目标增强训练跑完别急着部署。先跑一遍验证集看指标全貌yolo detect val \ modelruns/kite/yolov8s_640/weights/best.pt \ datakite_dataset/YOLO/data.yaml控制台会打印mAP50和mAP50-95。mAP50是IoU阈值0.5的均值风筝检测场景这个值比较直观mAP50-95更严格反映框的定位精度。单类别目标看这两个数就够了。进阶验证看两张图。第一张是confusion_matrix.png虽然单类别下只有两行两列但能看出有多少真实目标被漏检、多少背景被误检为风筝。第二张是PR_curve.png正常曲线应该靠近右上角。如果曲线在召回率0.8附近陡然下落说明模型对难例召回不够这时候回去检查是不是小目标占比高别急着加数据先试imgsz960推理。最后一招是针对性数据增强。yolov8默认开启了mosaic和mixup但风筝是天空背景下的小目标这两个增强有时反而会把目标裁掉一半。在超参文件里调低或关闭部分增强比如mosaic: 0.5 mixup: 0.0 hsv_h: 0.015 hsv_s: 0.5 hsv_v: 0.3风筝对颜色不敏感但对形状完整度敏感把mosaic概率从1.0降到0.5mixup直接关掉通常能在小目标场景换来2到3个点的mAP提升。我的习惯是每次调整只改一个变量改完跑一次验证集做对比别同时动三个参数改坏了都不知道是谁的锅。这个数据集拿到手最花时间的其实不是训练而是把数据检查清楚。标注核查、分组切分、空文件清理每一步省五分钟后面训练就跑偏半小时。希望我的这些踩坑记录能帮你少走一段弯路。本文还有配套的精品资源点击获取
返回列表