简介面向目标检测训练的数据集收录370张鹅类实景图片并由labelImg工具完成标注类别统一为goose。图片均为jpg格式大小为1-500KB兼顾清晰度与读取效率标注文件同时提供VOC的xml和YOLO的txt两种格式适配主流检测框架适合入门练习或小规模迁移学习。标注过程遵循目标完整框选、不遗漏、多轮一致性检查等规范质量较可靠。资源共1111个文件包含370个jpg原图、370个xml标注和371个txt标注压缩包约26.82MBrar格式解压后三个文件夹分类清晰。目前已有85人学习/下载。对于需要快速获取标准化鹅类目标数据的学习者这是一份即下即用的数据集可直接用于YOLO、Faster R-CNN等模型的训练与效果验证。1. 鹅数据集是个什么方案370张、VOC与YOLO双格式专为鹅检测训练铺路养鹅场需要自动清点、驱赶计数、异常行为识别但拿通用目标检测模型去测鹅效果往往很差——鹅整体是白色、喜欢成群扎堆、互相遮挡小目标又多通用coco权重根本不认识“鹅”这个类别。这时最缺的不是更好的模型结构而是一份带标注的鹅目标数据集。这套鹅数据集提供了370张真实图片同时给了VOC和YOLO两种格式的目标标注直接对应LabelImg导出和YOLO训练输入属于“拿到就能开始训练”的落地资源。适合正在做养殖场景检测、动物检测或小样本目标检测验证的工程师。比起重新标几千张先拿这370张跑通数据链路、验证损失函数和模型收敛情况性价比高很多。2. VOC格式的鹅标注长什么样目录、XML字段与人工复核2.1 目录结构JPEGImages、Annotations、ImageSets三者缺一不可VOC格式是目标检测领域流传最广的标注格式之一它的目录约定带有明显的“数据集”属性不只是把图片和标注放在一起那么简单。常见做法是把图片放在JPEGImages目录把XML标注放在Annotations目录把数据划分清单放在ImageSets/Main目录下。这套鹅数据集按VOC风格组织时也有同样的三层结构缺一个目录都会导致后续转换脚本或训练流程报错。JPEGImages里存放的是原始图片一般保持拍摄现场的真实分辨率比如鹅群在水面、草地、围栏边的照片。Annotations里每张图片对应一个同名XML文件记录图片尺寸、通道数、目标类别和边界框坐标。ImageSets/Main里放的是train.txt、val.txt这类清单文件每一行写一张图片的文件名不含扩展名YOLO训练时并不直接读VOC的这四个目录而是靠这些清单去索引图片路径。实际转换时很多人上来就写脚本遍历Annotations结果发现转换后txt数量对不上大概率是ImageSets/Main里的train/val划分列表没同步或者Annotations里有空标注文件的图片被漏掉了。370张听起来不多但一旦漏了某个目录训练时“No labels found”或“AssertionError”就会立刻出现。2.2 XML标注字段数据字典与核心参数说明VOC的XML标注文件是树状结构根节点是annotation下面挂filename、size、object等子节点。下面这段是一个最小化的鹅标注XML片段你可以直接对照着手里的标注文件看annotation foldergoose_dataset/folder filenamegoose_001.jpg/filename source databasegoose_voc/database /source size width1920/width height1080/height depth3/depth /size segmented0/segmented object namegoose/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin280/xmin ymin140/ymin xmax780/xmax ymax620/ymax /bndbox /object /annotation逻辑说明解析时最关心的是size节点下的width和height它决定了后续坐标归一化时除以什么值bndbox下的四个整数表示左上角(xmin, ymin)和右下角(xmax, ymax)这是VOC格式的绝对像素坐标。object节点可能有多组每组对应图片里的一个鹅个体或一个密集鹅群。参数说明150x150像素以下的鹅在1920x1080的大图里占比很小这种小目标在后续YOLO训练时需要使用合理的分辨率否则细微标注偏差会被放大。difficult字段在这里一般标注为0表示目标清晰可辨认truncated表示目标是否被图片边缘截断。转换脚本如果忽略这两个字段数据层面没有任何问题但如果后续做按难度筛选保留这两个字段会更灵活。2.3 LabelImg复核标注看框比看数字更直接拿到这套鹅数据集后第一件事不该是写转换脚本而是用LabelImg打开几张图肉眼核对标注框是否紧贴鹅的轮廓。LabelImg默认导出的就是VOC XML格式实际上就是来自同一生态不存在兼容问题。打开LabelImg后要点开“Open Dir”选择JPEGImages目录再点“Change Save Dir”选择Annotations目录在视图里把默认的PascalVOC格式保持住。然后逐张检查有的鹅翅膀张开时标注框是否把翅膀算进去几只鹅重叠时框之间是否互相覆盖太多有没有把水面倒影或者远处杂物误标成鹅。我一般会按图片文件名随机抽10%到15%的样本复核370张实际抽40张左右大概半小时能看完。如果发现框体普遍偏大或偏小就直接在标注阶段修正因为成品数据集的框质量直接影响YOLO损失函数的收敛起点。3. 把VOC转成YOLO格式转换脚本与数据划分的完整链路3.1 转换脚本解析XML、归一化坐标、写出txtYOLO系列的训练标签要求每张图片对应一个同名txt文件文件名与图片名完全一致扩展名不同txt的每一行代表一个目标格式为类别id 中心点x归一化 中心点y归一化 宽度归一化 高度归一化。直接把VOC的XML交给YOLO训练器是不可行的必须做一次有损转换。import xml.etree.ElementTree as ET import os # 注意这个列表的顺序决定了YOLO txt里写的类别id classes [goose] def voc_xml_to_yolo_txt(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) if img_w 0 or img_h 0: raise ValueError(fimage size is zero: {xml_path}) lines [] for obj in root.iter(object): name obj.find(name).text 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) # 中心点坐标与宽高都除以图片宽高得到0~1之间的归一化坐标 x_center ((xmin xmax) / 2.0) / img_w y_center ((ymin ymax) / 2.0) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h # 防止转换结果越界 x_center max(0.0, min(1.0, x_center)) y_center max(0.0, min(1.0, y_center)) box_w max(0.0, min(1.0, box_w)) box_h max(0.0, min(1.0, box_h)) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))逻辑说明脚本先解析XML里的size节点取到原始图片宽高这是归一化的基准。然后遍历每一个object节点取name字段到classes列表中查索引得到YOLO需要的类别id。核心计算是求bbox中心点坐标和宽高再统一除以图片宽高。最后把结果保留6位小数写进txt文件。参数说明classes列表的顺序必须与你之后训练时的data.yaml中的类别顺序完全一致。这里只有一种类别看起来不容易出错但如果你继续往数据集里加类别比如增加“天鹅”或“大雁”顺序一旦写死历史标注就全部错位。另外一个容易忽略的点是如果一张图片没有目标转换后也要生成一个空txt文件而不是跳过。YOLO训练时会根据图片清单找同名txt缺文件会直接抛异常。3.2 数据划分与txt清单保证train/val不重叠目标检测训练需要把图片分成训练集和验证集。370张的数据量不大常见的划分比例是训练集90%、验证集10%或者80%/20%具体要看你的验证指标稳定性。最关键的点是划分要发生在转换之前或至少在文件层面一一对应否则极易出现同图片同时进入训练集和验证集的情况。import os import random random.seed(2024) image_dir JPEGImages annotation_dir Annotations label_dir labels train_txt train.txt val_txt val.txt train_ratio 0.9 os.makedirs(label_dir, exist_okTrue) # 列出所有图片文件名不带扩展名但保留原始扩展名用于路径拼接 image_files [f for f in os.listdir(image_dir) if f.lower().endswith((.jpg, .jpeg, .png))] image_files.sort() random.shuffle(image_files) split_idx int(len(image_files) * train_ratio) train_files image_files[:split_idx] val_files image_files[split_idx:] def write_image_list(file_list, list_path): with open(list_path, w, encodingutf-8) as f: for name in file_list: img_path os.path.join(image_dir, name) f.write(img_path \n) write_image_list(train_files, train_txt) write_image_list(val_files, val_txt) # 逐个转换XML为YOLO txt for name in image_files: stem os.path.splitext(name)[0] xml_path os.path.join(annotation_dir, stem .xml) txt_path os.path.join(label_dir, stem .txt) voc_xml_to_yolo_txt(xml_path, txt_path)逻辑说明先用随机打乱后的图片列表按训练比例切分然后同时生成train.txt和val.txt再把每张图片对应的XML转换成YOLO txt。因为三个动作基于同一个image_files列表所以图片、XML、txt三者天然一一对应不容易错位。参数说明random.seed(2024)固定随机种子保证别人在你机器上复现时训练集和验证集划分完全一致。train.txt里写的是图片的绝对路径YOLO项目在训练时会去读这个文件得到图片路径并从相同位置推导labels目录如果你的路径里带中文或空格部分老版本YOLO会读不进来保险起见转成纯英文路径。3.3 验证转换结果检查文件数量与内容格式转换完成后先不要急着训练做两步快速校验。第一步比较Annotations目录下的XML数量和labels目录下的txt数量理论上应该相等370张图对应370个xml和370个txt。第二步随机打开几个txt文件确认每一行只有5个数字且所有数值都在0到1之间。import os xml_dir Annotations label_dir labels xml_names [f for f in os.listdir(xml_dir) if f.endswith(.xml)] txt_names [f for f in os.listdir(label_dir) if f.endswith(.txt)] print(fxml count: {len(xml_names)}) print(ftxt count: {len(txt_names)}) bad_count 0 for txt in txt_names: with open(os.path.join(label_dir, txt)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad_count 1 break for v in parts[1:]: if not (0.0 float(v) 1.0): bad_count 1 break if bad_count 0: print(label format seems ok)逻辑说明这段脚本不算训练逻辑的一部分但值得养成习惯。检查文件数量能发现漏转换检查数值能发现某个XML里出现了极端坐标值比如xmax比xmin还小或者某个坐标超出图片尺寸。这些脏数据在损失函数计算时会产生无穷大梯度训练直接崩。4. VOC与YOLO标注格式的实质差异为什么转换后更像“黑匣子”4.1 字段映射关系对照表VOC和YOLO的标注本质上是同一件事的两种表达方式差异体现在坐标参照系和类别表达上。下面这张表可以帮你建立映射直觉维度VOCXMLYOLOtxt坐标基准图像左上角为原点单位为像素图像左上角为原点单位为图片宽高比例框的表达左上角(xmin, ymin)右下角(xmax, ymax)中心点(x_center, y_center) 宽高(w, h)类别表达类别名称字符串如goose类别索引整数按classes列表顺序从0开始文件组织一张图对应一个XML一张图对应一个同名txt人工可读性可直接打开阅读数字序列不借助工具不易判断这张表的意义在于你排查问题时的思考方向会完全不同。VOC格式里看到name字段是goose第一反应是类别名写错了YOLO格式里看到第一个数字是0要立刻去对照classes.yaml里第0个类别是不是goose。很多人在这个环节把类别id从1开始数导致所有标注整体错一位训练出来的模型完全没有检测能力。4.2 归一化坐标的隐式约定与显式陷阱YOLO的txt里所有坐标都是0到1之间的小数很多人把它理解为“相对坐标”这个理解并不完整。它其实是相对于图片宽度和高度的比例值而不是预设的固定网格坐标。比如一个宽度为1920像素的图片里一个框的左边界xmin是960像素归一化后x_center就是0.5。只要图片原尺寸不变这个比例就是唯一的。这里就容易出问题如果你用OpenCV读取图片格式为BGR但某个标注工具对图片做了exif旋转或自动裁剪那么图片实际尺寸和XML里记录的width/height就不再一致。转换脚本按照XML里的宽高归一化可训练时YOLO读到的实际图片尺寸可能被预处理改大了或改小了坐标就会整体偏移。所以在VOC转YOLO之前我一般会在脚本里加一个断言从图片文件本身读一遍宽高与XML里记录的宽高做比对不一致立刻报错。这个检查能挡掉绝大部分因图片处理和转码导致的坐标错位问题。import cv2 def check_image_size_matches(img_path, xml_root): img cv2.imread(img_path) if img is None: raise FileNotFoundError(fcannot read image: {img_path}) img_h, img_w img.shape[:2] xml_w int(xml_root.find(size/width).text) xml_h int(xml_root.find(size/height).text) if img_w ! xml_w or img_h ! xml_h: raise ValueError( fsize mismatch: {img_path} fimage({img_w},{img_h}) xml({xml_w},{xml_h}) )这段脚本我在每次批量转换前都会跑一遍。如果发现尺寸不匹配优先去检查图片本身是否被修改过而不是在转换脚本里强行覆盖XML里的宽高。覆盖宽高等于让标注坐标适应错误基准等于拿噪声当信号。5. 鹅数据集转换与训练中的5个常见坑现象、原因、解决5.1 训练loss变成nan停在第一步现象用转换后的标签直接开训前几个epoch还能看到loss下降第三个epoch开始loss突然变成nan训练进程没有崩溃但输出完全无效。原因YOLO损失函数对边界框回归项非常敏感txt中的某个坐标值虽然保持在0到1之间但出现了“xmax小于xmin”这类逻辑错误导致框的宽度是负数。归一化后虽然数值不越界但物理意义错误梯度方向错乱。解决在转换脚本里增加几何断言每转换一个框就检查xmax xmin、ymax ymin、w 0、h 0。检查失败立即打印XML文件名和具体坐标值。这比训练启动后在损失函数阶段排查要省力得多。5.2 所有目标都被识别成同一个类别现象训练完成后测试无论图片里是鹅还是远处的水鸟模型都输出类别0置信度还很高看起来像是“什么都检但什么都叫鹅”。原因classes列表顺序和data.yaml里的names顺序不一致。转换时classes [goose]只有一类看不出问题一旦你在转换脚本里写的是[goose, waterbird]而训练配置里写的是[waterbird, goose]同一个id就指向不同类别模型学到的类别特征和标注语义完全错位。解决把类别列表抽成单独的classes.txt或data.yaml转换脚本和训练配置都从这个文件读取。不要在两处分别手写类别顺序靠人工维护必然漏改。改动类别后需要重新转换全部标注旧的txt必须删除不能混着用。5.3 画框验证时左边界相差几十像素现象用可视化脚本把txt画回原图发现框的位置和真实目标有明显偏移偏移量固定几乎每隔一张图偏移方向一致。原因图片读取库不一致。转换脚本用了PIL读取图片尺寸记录为宽高但可视化时用OpenCV读取同一张图片两个库对某些格式的图片解码结果可能不同尤其在带有旋转信息的JPEG图片上会表现得很明显。OpenCV默认不做exif旋转PIL会自动应用exif于是同一个文件两个库读出的宽高可能互换。解决统一用OpenCV或统一用PIL。如果转换脚本用PIL解析XML那训练前做图片尺寸校验时也用PIL如果训练时YOLO用的pytorch读取方式是PIL标注归一化就应该按PIL读到的尺寸来做。更稳妥的做法是把所有训练图片先统一转成无exif信息的标准JPEG再做标注从源头消除歧义。5.4 mAP虚高但实际检测效果差现象训练结束时验证集mAP很高接近0.95但拿到真实场景测试漏检率明显偏高。原因train.txt和val.txt划分时数据泄漏。370张图片的划分如果直接在文件列表上随机切分没有先按场景或时间戳分组同一个场地同一个机位的画面很可能被同时分到训练集和验证集验证集失去了独立评估的意义。解决划分时先按图片文件名前缀或拍摄时间分桶比如同一时间片段拍的照片只能进训练集或验证集不能横跨两边。370张的规模下验证集可能只有30到40张分布偏差大是常态。更合理的做法是不设独立测试集直接用训练集做五折交叉验证看每一折的验证loss稳定性。5.5 JPEG图片损坏导致转换中断现象批量转换跑到第269张时报错提示无法读取图片或XML节点不存在。原因鹅数据集的图片来自不同拍摄设备个别文件传输损坏或者部分XML缺失目标节点。这类问题在370张的小数据集上非常显眼因为只要一张图片出问题整个训练管道就被卡住。解决转换脚本里加try/except捕获单个文件的异常并记录到坏文件清单跳过继续执行。转换结束后单独统计坏文件数量决定是找数据补充还是直接剔除这批图片。剔除的同时也要把train.txt和val.txt里的对应行删掉否则训练时还是会报错。6. 最后一招训练前做可视化画框验证比看任何指标都可靠把txt标注画回原图是验证整个转换流程最直观的手段。下面这段脚本读取图片和对应YOLO标签画框并保存到输出目录你可以把验证结果逐张翻看。import cv2 import os classes [goose] def draw_yolo_label(img_path, txt_path, output_path): img cv2.imread(img_path) if img is None: return False h, w img.shape[:2] with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh parts cls_id int(cls_id) xc, yc, bw, bh float(xc), float(yc), float(bw), float(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) # 画框前裁剪到图像边界内否则看不见框边缘 x1 max(0, min(w - 1, x1)) y1 max(0, min(h - 1, y1)) x2 max(0, min(w - 1, x2)) y2 max(0, min(h - 1, y2)) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) label classes[cls_id] if cls_id len(classes) else str(cls_id) cv2.putText(img, label, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(output_path, img) return True for filename in os.listdir(images): stem os.path.splitext(filename)[0] txt os.path.join(labels, stem .txt) if not os.path.exists(txt): print(fmissing label: {filename}) continue draw_yolo_label( os.path.join(images, filename), txt, os.path.join(check_imgs, stem _check.jpg) )画框不是看个热闹每一帧都要自问三个问题框是不是正好包住鹅的身体鹅头伸出水面时框是否漏了头两只鹅紧挨时框有没有错位如果有问题优先回到XML标注处修改后重新转换而不是在txt上做手动修正。370张图片在目标检测任务里属于小型数据集转换和校验通过后训练阶段建议直接用迁移学习加载YOLOv8的coco预训练权重做微调batch size不宜设得太大一般16到32在单卡上够用。训练完成后导出ONNX模型再接TensorRT做推理部署370张产出的模型在特定场景下会比通用模型可靠得多。我第一次做这个转换链路时跳过可视化验证直接开训loss看起来很正常但到第100轮后推理结果框全部偏离目标位置回头排查才发现是图片尺寸不一致导致坐标系统性偏移白跑了三天训练流程。从那以后可视化画框检查成了我的固定习惯步骤再麻烦也不省略。这个数据集的整理思路不只在鹅身上有用换成猪、牛、果园水果都适用希望帮到你。本文还有配套的精品资源点击获取