拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

鹅数据集VOC和YOLO格式目标标注370张:直接进训练管线

鹅数据集VOC和YOLO格式目标标注370张:直接进训练管线

简介:本资源为一份面向目标检测学习者的鹅类图像数据集,采用VOC与YOLO双格式标注,适合从事禽类识别、农业智能化或计算机视觉入门实践的开发者与研究人员使用。压缩包共1111个文件,包含370张jpg图片、370个xml标注文件与371个txt标注文件,整体约26.82MB,图片大小在1-500KB之间,解压后即可直接用于训练与验证。所有图片均由labelImg人工标注,类别统一为goose,标注过程强调边界框选准确、目标覆盖完整,并通过一致性检查减少漏标与误标。目前已有85人学习下载。数据集目录结构清晰,图片、xml与txt分文件夹存放,便于快速接入YOLO或VOC标准流程,也可用于格式转换、模型微调与检测效果对比等场景,为鹅类目标检测任务提供即用型数据基础。

1. 鹅数据集 VOC 和 YOLO 格式目标标注 370 张:一份能直接进训练管线的现成货

手上有个鹅群计数或者鹅只行为分析的需求,卡在第一步——没有标注数据。自己扛着相机去养殖场拍两天,回来再用 labelImg 一张张框,370 张图够你框到怀疑人生。这份「鹅数据集 VOC 和 YOLO 格式目标标注 370 张」就是冲着这个痛点来的:370 张 jpg 原图,配齐了 VOC 的 xml 和 YOLO 的 txt 两套标注,类别只有一个——goose。压缩包解开就是三个文件夹,图片、xml、txt 各归各的,不用装额外工具就能直接喂给 YOLOv5/v8 或者转成 COCO 跑别的框架。适合谁?做禽类检测的算法同学、搞智慧养殖落地的工程团队,以及拿目标检测练手但不想在标注上耗时间的人。它不解决模型结构问题,但能让你把精力从「造数据」挪到「调模型」上,这个价值对一线来说很实在。

2. 拆开压缩包先看什么:目录结构、标注格式与选型理由

2.1 三个文件夹的职责划分与文件对应关系

解压之后你会看到类似这样的结构,不同人打包习惯略有差异,但核心就三块:

goose_dataset/ ├── JPEGImages/ # 370 张 jpg 原图,命名如 goose_276.jpg ├── Annotations/ # 370 个 VOC 格式 xml,与图片同名 └── labels/ # 370 个 YOLO 格式 txt,与图片同名

图片命名是goose_数字.jpg这种风格,数字不连续,说明是从更大池子里筛出来的,不是摆拍连拍。三个文件夹靠文件名对齐:goose_41.jpg对应goose_41.xml和goose_41.txt。拿到手第一件事不是急着训练,而是抽查对齐率——用下面这段脚本跑一遍,确认没有孤儿文件。

import os img_dir = "goose_dataset/JPEGImages" xml_dir = "goose_dataset/Annotations" txt_dir = "goose_dataset/labels" imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")} xmls = {os.path.splitext(f)[0] for f in os.listdir(xml_dir) if f.endswith(".xml")} txts = {os.path.splitext(f)[0] for f in os.listdir(txt_dir) if f.endswith(".txt")} print("图片数:", len(imgs), "xml数:", len(xmls), "txt数:", len(txts)) print("有图无xml:", imgs - xmls) print("有图无txt:", imgs - txts) print("有xml无图:", xmls - imgs)

逻辑很直白:用文件名主干做集合运算,把三类文件各自的主干集合拿出来比对。参数上唯一要注意的是扩展名大小写——有些打包工具会把.JPG写成大写,endswith(".jpg")就漏了,稳妥点可以统一f.lower().endswith(".jpg")。跑完如果三个集合完全相等,说明这份数据整理得干净,可以直接进下一步;如果有差异,先别删文件,大概率是命名带空格或者多了个副本,手动核一下。

2.2 VOC 与 YOLO 双格式并存的实际意义

很多人会问:既然要训 YOLO,为什么还留一份 VOC 的 xml?这不是冗余,是留后路。VOC 的 xml 是「原始标注记录」,里面存的是绝对像素坐标xmin, ymin, xmax, ymax,还带图片宽高、类别名、甚至标注时的难度标记。YOLO 的 txt 是「训练就绪格式」,存的是归一化后的class_id cx cy w h,中心点加宽高,全部除以图宽图高。两者的关系是单向可逆的——xml 能无损转成 txt,但 txt 转回 xml 会丢掉图片尺寸以外的元信息。

实际干活时这个双格式能省事:你想换框架,比如从 YOLO 切到 Detectron2 或者 MMDetection,它们吃 COCO 或 VOC,这时候直接拿 xml 转,不用回头重新标注。反过来,如果你只想快速跑 YOLOv8,txt 已经躺在那儿了,改个data.yaml路径就能开训。选型上我的建议是:以 xml 为存档,以 txt 为消耗品。txt 训练时可能被各种脚本改写、增强、切分,改坏了不心疼,xml 留着兜底。

2.3 用 labelImg 标注的痕迹与质量初判

这份数据是用 labelImg 标的,从 xml 里能看出典型特征:<folder>标签通常写的是图片所在文件夹名,<path>是标注时的绝对路径,<size>里有 width、height、depth。这些字段对训练没用,但能帮你判断标注是否规范。比如<size>里的宽高如果和实际图片对不上,说明标注时图片被缩放过,坐标就偏了。

快速抽检可以写个小脚本,把 xml 里的框画回图上,肉眼扫几张:

import xml.etree.ElementTree as ET import cv2 def draw_voc(img_path, xml_path, out_path): tree = ET.parse(xml_path) root = tree.getroot() img = cv2.imread(img_path) for obj in root.findall("object"): name = obj.find("name").text bbox = obj.find("bndbox") x1 = int(bbox.find("xmin").text) y1 = int(bbox.find("ymin").text) x2 = int(bbox.find("xmax").text) y2 = int(bbox.find("ymax").text) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, name, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(out_path, img) draw_voc("goose_dataset/JPEGImages/goose_276.jpg", "goose_dataset/Annotations/goose_276.xml", "check_276.jpg")

这段代码把 xml 里的框和类别名画到原图上,输出一张检查图。参数上cv2.rectangle的线宽设 2 够看清,putText的位置放在框上方避免遮挡。抽 10 到 20 张不同编号的图跑一遍,重点看三件事:框有没有明显偏移、有没有漏标(图里有鹅但没框)、有没有多标(框里没鹅)。标注遵循里提到的「一致性检查」就是这个意思,只不过原话是让人对比,咱们用脚本先过一遍效率更高。

3. 从 VOC 到 YOLO:转换脚本、data.yaml 配置与训练接入

3.1 手写 xml 转 txt 脚本与坐标归一化细节

虽然数据集里已经带了 txt,但你要做数据增强、重新划分训练验证集,或者想自己控制类别映射,还是得会转。下面这个脚本把 VOC 的 xml 批量转成 YOLO 的 txt,逻辑是读 xml 拿绝对坐标,除以图片宽高做归一化,再按class cx cy w h写出去。

import os import xml.etree.ElementTree as ET from PIL import Image classes = ["goose"] # 类别列表,顺序决定 class_id xml_dir = "goose_dataset/Annotations" img_dir = "goose_dataset/JPEGImages" out_dir = "goose_dataset/labels_from_xml" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue stem = os.path.splitext(xml_file)[0] img_path = os.path.join(img_dir, stem + ".jpg") if not os.path.exists(img_path): print("缺图,跳过:", stem) continue w, h = Image.open(img_path).size tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in classes: continue cls_id = classes.index(name) bbox = obj.find("bndbox") x1 = float(bbox.find("xmin").text) y1 = float(bbox.find("ymin").text) x2 = float(bbox.find("xmax").text) y2 = float(bbox.find("ymax").text) # 归一化并转中心点格式 cx = (x1 + x2) / 2.0 / w cy = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h # 边界裁剪,防止标注越界导致训练报错 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) bw = min(max(bw, 0.0), 1.0) bh = min(max(bh, 0.0), 1.0) lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines))

几个关键点值得说透。第一,classes列表的顺序就是 class_id,这份数据只有 goose 一个类,所以 id 恒为 0,但如果你后面要加「鸭」「鸡」做多类,顺序一旦定了就不能改,改了所有旧标签全错。第二,归一化用的是 PIL 读出来的真实宽高,不是 xml 里写的<size>,因为 xml 里的尺寸偶尔会因为标注工具版本问题不准,以原图为准最稳。第三,加了边界裁剪,min(max(...))把值卡在 0 到 1 之间。别小看这一步,标注时手抖把框拖出图外,归一化后会出现负数或大于 1 的值,YOLO 训练时直接给你抛个 assert 失败,排查半天。第四,保留 6 位小数够用,YOLO 官方也是这个精度,写太多位纯属浪费磁盘 IO。

3.2 data.yaml 怎么写与目录切分

YOLOv5/v8 靠一个data.yaml描述数据在哪、有几类、类名是什么。这份数据 370 张,量不大,建议按 8:2 切训练和验证,别单独再切测试集,否则验证集太小指标抖动厉害。切分脚本:

import os import random import shutil random.seed(42) # 固定种子,保证每次切分一致 img_dir = "goose_dataset/JPEGImages" lbl_dir = "goose_dataset/labels" base = "goose_yolo" for split in ["train", "val"]: os.makedirs(f"{base}/images/{split}", exist_ok=True) os.makedirs(f"{base}/labels/{split}", exist_ok=True) stems = [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")] random.shuffle(stems) cut = int(len(stems) * 0.8) train_stems, val_stems = stems[:cut], stems[cut:] for split, subset in [("train", train_stems), ("val", val_stems)]: for stem in subset: shutil.copy(f"{img_dir}/{stem}.jpg", f"{base}/images/{split}/{stem}.jpg") shutil.copy(f"{lbl_dir}/{stem}.txt", f"{base}/labels/{split}/{stem}.txt") print("train:", len(train_stems), "val:", len(val_stems))

random.seed(42)是血泪经验——不固定种子,每次跑切分结果不一样,你调参时以为模型变了,其实是数据换了,指标对比全是玄学。切完目录结构是goose_yolo/images/train、goose_yolo/labels/train这样成对出现,YOLO 默认会去 images 同级找 labels,路径别写错。

对应的data.yaml:

path: ./goose_yolo train: images/train val: images/val nc: 1 names: 0: goose

path是根目录,train和val是相对路径。nc是类别数,这里 1。names用字典或列表都行,字典更直观。写完拿 YOLO 官方校验命令过一遍,确认没有路径错误再开训。

3.3 接进 YOLOv8 训练与首轮参数建议

环境配好之后,训练命令就一行:

yolo detect train data=goose_yolo/data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16

参数逐个说:model=yolov8n.pt用 nano 版,370 张图用大模型纯属浪费,n 版足够跑出可用结果,想涨点再换 s。epochs=100是起步值,数据量小,100 轮通常能看到收敛,配合早停不会过训。imgsz=640是 YOLO 默认输入尺寸,鹅在图中占比如果偏小,可以提到 960 试试,但显存要够。batch=16在 8G 显存上比较稳,爆显存就降到 8。

训练起来后盯两个东西:cls_loss和mAP50。cls_loss 持续下降说明分类在学,mAP50 涨到 0.8 以上这份数据就算跑通了。如果 mAP 卡在 0.3 不动,先别怀疑模型,回头查标注——大概率是框太松或者漏标,鹅这种目标边界模糊,框松一点 IoU 就掉。

4. 避坑与排查:这份鹅数据集最容易翻车的五个地方

4.1 图片与标注文件名对不上导致静默丢样本

现象:训练日志里train的图片数比预期少,比如切了 296 张进去,日志显示只有 280。原因:YOLO 加载时会跳过找不到对应 txt 的图片,而且默认不报错,静默跳过。解决:训练前用 2.1 节的对齐脚本跑一遍,或者直接看labels/train和images/train的文件数是否相等。不等就补,别指望框架提醒你。

4.2 标注框越界引发训练中断

现象:训练跑几十步突然报AssertionError: normalized coordinates out of range或者类似越界提示。原因:xml 里某些框的坐标超出了图片边界,转 txt 时归一化后出现负数或大于 1。解决:用 3.1 节脚本里的边界裁剪逻辑,min(max(...))卡住。如果已经生成了 txt,写个脚本扫一遍所有 txt,把越界行找出来定位到具体图片,回 labelImg 修。

4.3 单类数据集误配 nc 导致维度不匹配

现象:模型加载时报size mismatch for model.24.m.0.weight之类的维度错误。原因:data.yaml里nc写成了别的数,或者names里塞了多个类但实际只有 goose。解决:这份数据nc必须是 1,names只留 goose。改完 yaml 重新跑,别去动模型权重。

4.4 图片尺寸差异大导致增强后目标变形

现象:训练时 mAP 波动大,同一批验证图有时准有时不准。原因:370 张图尺寸不统一,letterbox 填充后鹅的宽高比被改变,模型学到的形状特征不稳定。解决:训练前统计一下图片尺寸分布,如果长宽比差异超过 2:1,考虑统一 resize 到相近尺寸再训,或者开 mosaic 增强让模型适应形变。这份数据图片大小 1-500KB,尺寸大概率杂,值得先看一眼。

4.5 验证集切分随机性造成指标不可复现

现象:同样的代码跑两次,mAP 差好几个点。原因:切分时没固定随机种子,每次训练验证集不一样。解决:3.2 节脚本里的random.seed(42)必须保留,而且切分只做一次,把切好的目录固定下来,后续所有实验都用同一份,别每次训练重新切。

5. 把 370 张用出 3700 张的效果:增强策略与标注复核技巧

数据量小是这份数据集的天花板,370 张训个能用的检测器没问题,但想再往上提点,得在增强和标注质量上抠。YOLO 自带的增强参数里,mosaic和mixup对小数据集最管用,mosaic 把四张图拼一张,等于每轮看到的组合数翻几倍,mixup 做透明度叠加,能缓解过拟合。开法是在训练命令里加mosaic=1.0 mixup=0.1,mosaic 默认就是 1,mixup 从 0.1 起步,太高会让鹅和背景糊成一团。

但增强是双刃剑。鹅的形态特征——长脖子、扁嘴、体型——在 mosaic 拼接后可能被裁掉一半,模型学到的就是残缺特征。我的习惯是:前 50 轮开满增强让模型见世面,后 50 轮关掉 mosaic 和 mixup,用原图微调,让模型回归真实分布。这个「先增强后收敛」的节奏在多个小数据集上验证过,比全程开增强的 mAP 高 2 到 3 个点。

标注复核这块,370 张全人眼过一遍不现实,但可以用模型反查。拿训到一半的模型对训练集做推理,把预测框和标注框 IoU 低于 0.5 的图挑出来,这些就是「模型觉得不对」的样本,大概率标注有问题。脚本思路:

from ultralytics import YOLO import os model = YOLO("runs/detect/train/weights/best.pt") results = model.predict("goose_yolo/images/train", save_txt=True, conf=0.25) # 对比 save_txt 输出的预测与原始标注,IoU 低的挑出来人工看 # 具体比对逻辑略,核心是拿预测当第二双眼睛

conf=0.25是置信度阈值,调低能捞出更多可疑样本,调高只看高置信的。跑完把低 IoU 的图列个清单,重点看是不是漏标了小鹅、或者框只框了身子没框到头。鹅群密集时漏标很常见,补上这些漏标,比加一百张新图管用。

还有个技巧是「类别名统一」。这份数据类别是 goose,但如果你后续混入其他来源的鹅数据,别人可能标成Goose、goose_、鹅,合并时全乱。统一转小写、去空格、去下划线,在转换脚本的classes列表里做映射,别等到训练报未知类别才回头改。

最后说个习惯。我每次拿到新数据集,不管多干净,都强制走一遍「对齐检查 → 抽检画框 → 越界扫描 → 切分固定种子」这四步,跑完才开训。这四步加起来不到十分钟,但能省掉后面几小时的排查。这份鹅数据集整理得算规整,VOC 和 YOLO 双格式省了不少事,370 张的量适合快速验证想法,真要上生产还得按上面的路子扩。希望帮到你。

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

返回列表