简介:这份鸭子目标检测数据集面向计算机视觉入门者、算法工程师及需要扩充训练样本的研究人员,用于鸭子识别与检测模型的训练与验证。资源同时提供VOC与YOLO两种标注格式,可直接对接主流检测框架,省去格式转换环节。压缩包共1039个文件,包含346张jpg图片、346个xml标注文件与347个txt标注文件,整体约25.28MB,图片大小在1至500KB之间,解压后按图片、xml、txt三个文件夹分类存放,结构清晰便于直接读取。所有图片均由labelImg人工标注,类别统一为duck,标注过程遵循准确框选目标边界、尽量覆盖全部目标、完成后进行一致性检查的原则,标注质量较为可靠。目前已有130人学习下载,适合用于课程实验、模型微调或小样本检测任务,也可作为标注规范参考。
1. 鸭子数据集 VOC 和 yolo 格式目标标注 348 张:一份能直接喂给检测器的现成料
手上攒了一批鸭子场景的图,想跑个目标检测看看效果,结果卡在标注这一步——要么找不到合适的公开数据,要么下下来的格式对不上自己的训练脚本。这份鸭子数据集就是冲着这个痛点来的:348 张 jpg 图片,每张都配了 VOC 的 xml 和 yolo 的 txt 两套标注,类别只有一个 duck,用 labelImg 标完的。换句话说,你拿到手不用自己画框,解压就能直接进训练流程。它适合两类人:一是刚接触目标检测、想找个干净小数据集跑通全流程的新手;二是需要快速验证某个检测模型在单类别小目标上表现的从业者。图片体积控制在 1-500KB,压缩包无密码,三个文件夹分得清清楚楚,图片、xml、txt 各归各的。下面我按「这数据长什么样 → 怎么接进训练 → 哪里容易翻车」的顺序拆一遍,都是我自己跑过一遍的路径。
2. 拆开压缩包先看结构:VOC 与 yolo 两套标注到底差在哪
2.1 三个文件夹的物理布局与命名规律
解压之后你会看到三个平级目录,常见命名是JPEGImages、Annotations、labels,也可能直接叫images、xml、txt,取决于整理者的习惯。图片文件名是duck_104.jpg、duck_217.jpg这种带序号的格式,序号不连续是正常的——原始图集里剔掉了一些质量不行的,剩下的重新编号。xml 和 txt 的文件名主干跟图片一一对应,比如duck_104.xml和duck_104.txt。这里有个容易忽略的点:348 张图对应 348 个 xml 加 348 个 txt,数量必须对齐,如果你解压后发现某一类少了几个,大概率是压缩包传输时丢了文件,重新下一遍比手动补标注省事得多。
图片格式统一 jpg,体积 1-500KB 这个区间说明分辨率不会太夸张,常见在 640×480 到 1280×720 之间。这个尺寸对训练挺友好——不用做大幅降采样,显存压力小,单卡跑起来不费劲。类别只有一个duck,意味着这是个单类检测任务,省掉了多类平衡的麻烦,但也意味着模型学到的特征比较单一,别指望拿它直接迁移到多类场景。
2.2 VOC 的 xml 与 yolo 的 txt:同一份标注的两种表达
VOC 格式的 xml 是「绝对坐标 + 图像元信息」的组合。打开一个 xml,你会看到<size>里记着宽高和通道数,<object>里是类别名和<bndbox>的 xmin、ymin、xmax、ymax,全是像素值。这种格式的好处是自解释——不看图片也能知道框在哪、图多大。yolo 的 txt 则是「归一化中心点 + 宽高」,每行类别索引 cx cy w h,五个值全在 0 到 1 之间。它不存图像尺寸,所以 txt 必须跟图片配对使用,单独一个 txt 文件是没法还原框位置的。
两套格式的换算关系不复杂,但手算容易错。常见做法是用脚本批量转,而不是一个个改。下面这段是我常用的 VOC 转 yolo 的转换逻辑,直接对着你的 xml 目录跑就行:
import os import xml.etree.ElementTree as ET # 类别映射,单类就写一个 classes = ["duck"] def convert_voc_to_yolo(xml_dir, txt_dir, img_w, img_h): os.makedirs(txt_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() # 优先从 xml 里读尺寸,读不到再用传入的默认值 size = root.find("size") w = int(size.find("width").text) if size is not None else img_w h = int(size.find("height").text) if size is not None else img_h lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in classes: continue cls_id = classes.index(cls_name) bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 归一化:中心点 + 宽高 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") txt_name = os.path.splitext(xml_file)[0] + ".txt" with open(os.path.join(txt_dir, txt_name), "w") as f: f.write("\n".join(lines)) convert_voc_to_yolo("Annotations", "labels", 640, 480)逻辑说明:先解析 xml 拿到图像宽高,再遍历每个 object 取框的绝对坐标,最后按 yolo 的规则做归一化。参数上,classes列表的顺序决定了 txt 里类别索引的值,单类就是 0;img_w和img_h是兜底值,只在 xml 里缺 size 字段时生效。跑完检查一下 txt 行数,每行五个值、没有空行、坐标都在 0 到 1 之间,基本就没问题。如果你手上已经是 txt,想反推回 xml 做可视化,把公式倒过来乘宽高就行,但要注意反推需要知道原图尺寸,所以图片和 txt 必须成对保存。
3. 把 348 张鸭子图接进 yolo 训练:目录划分与配置文件
3.1 训练集验证集怎么切,切完目录长什么样
348 张不算多,切分比例我一般用 8:2,也就是训练 278 张、验证 70 张。别用 7:3,验证集太小评估波动大;也别切 9:1,验证集不到 35 张,mAP 抖得你怀疑人生。切分的时候要保证图片和标注同步移动,图片进了images/train,对应的 txt 就得进labels/train,漏一个就是训练时报「找不到标签」的经典错误。
切完的目录结构建议长这样:
duck_dataset/ ├── images/ │ ├── train/ # 278 张 jpg │ └── val/ # 70 张 jpg └── labels/ ├── train/ # 278 个 txt └── val/ # 70 个 txt注意 yolo 系列(v5 之后)默认按「图片路径里把images替换成labels」去找标签,所以目录名别乱改,images和labels这两层必须对应上。如果你用的是 v8 或更新版本,它支持在 data 配置里显式指定 train 和 val 的路径,但底层还是按这个替换规则找 txt,所以结构保持一致最省心。
3.2 data.yaml 的字段含义与常见填错点
yolo 训练靠一个 yaml 文件告诉它去哪找数据、有几个类。针对这份鸭子数据集,配置大概是这样:
path: /home/user/duck_dataset # 数据集根目录,绝对路径最稳 train: images/train # 相对 path 的训练图目录 val: images/val # 相对 path 的验证图目录 nc: 1 # 类别数,鸭子只有一类 names: ["duck"] # 类别名,顺序要和 txt 里的索引对上path写绝对路径能避免「工作目录一变就找不到文件」的玄学问题。nc和names必须一致,nc: 1配names: ["duck"],如果你写成nc: 2但 names 只有一个,训练启动时就会报索引越界。train和val是相对path的路径,别写成绝对路径,否则换台机器就崩。这份数据只有 duck 一类,所以names里就一个字符串,txt 里每行的第一个值全是 0,对应duck。
3.3 启动训练的命令与关键参数
配置写好后,用 yolo 官方命令行启动就行。以 v8 为例:
yolo detect train \ data=duck_dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ project=runs/duck \ name=exp1model=yolov8n.pt用的是 nano 版预训练权重,348 张图用大模型容易过拟合,nano 或 s 版足够。epochs=100对小数据集是常见起点,配合早停(默认 patience=50)基本不会白跑。imgsz=640是 yolo 的默认输入尺寸,你的原图如果小于这个值会被放大,大于则缩小,统一到 640 后 batch 能开大一点。batch=16在 8GB 显存上跑 nano 版没问题,显存不够就降到 8。project和name决定权重和日志存哪,跑完去runs/duck/exp1/weights/拿best.pt。
训练启动后重点看几个输出:train/box_loss和val/box_loss是否同步下降,如果 train 降 val 不降就是过拟合,减 epoch 或加数据增强;metrics/mAP50在单类任务上通常能到 0.9 以上,如果卡在 0.5 左右,回去查标注框有没有画偏或者漏标。
4. 标注质量与格式转换里的避坑清单
4.1 现象:训练报「Label class X is not in the class list」
原因:txt 里的类别索引超出了names的长度。这份数据只有 duck 一类,正常 txt 每行开头都是 0,但如果转换脚本里classes列表写多了,或者手动改过某个 txt,就可能出现 1、2 这样的索引。解决:批量扫一遍所有 txt,把每行第一个值取出来看最大值,超过nc-1的就是脏数据,改回 0 或者删掉那行。
4.2 现象:验证集 mAP 正常但推理时框全偏了
原因:VOC 转 yolo 时图像宽高取错了。xml 里的<size>如果跟实际图片尺寸不一致(比如标注时用的缩略图),归一化坐标就会整体偏移。解决:转换前用 PIL 或 OpenCV 读一遍每张图的真实宽高,跟 xml 里的 size 对比,不一致的以图片实际尺寸为准重新算。我一般会在转换脚本里加一句断言,宽高对不上就打印文件名,跑完扫一眼日志。
4.3 现象:同一张图里鸭子挨得近,只标出来一个框
原因:标注时漏标了重叠目标。这份数据的标注遵循里明确写了「尽量标注所有目标」,但人工操作难免有遗漏,尤其是鸭子扎堆的场景。解决:训练前抽 20 张图用 labelImg 或可视化脚本过一遍,重点看密集区域。发现漏标就补上,补完重新生成对应的 txt。别指望模型自己学会区分挨着的两只鸭子,漏标的那只会被当成背景,训完模型对密集场景的召回率会明显偏低。
4.4 现象:解压后图片能打开但 xml 解析报错
原因:xml 文件在传输或压缩过程中损坏,常见的是结尾标签缺失或编码异常。解决:用xmllint --noout *.xml批量校验,报错的文件单独打开看,能修就补全标签,修不了就找到对应图片重新标。348 个文件里坏一两个不罕见,别硬着头皮往训练里塞,解析失败会直接中断训练流程。
4.5 现象:训练 loss 正常但推理时一个框都不出
原因:txt 文件里的坐标被写成了绝对像素值,没有归一化。yolo 期望的是 0 到 1 之间的小数,如果你拿到的是别人转了一半的 txt,里面可能是0 320 240 100 80这种。解决:检查 txt 每行后四个值,只要有一个大于 1,就说明没归一化,用图片宽高除一遍重新写。这个坑在混合使用不同来源的标注文件时特别常见。
5. 用这份数据做迁移与验证:从单类检测到实际场景的衔接
348 张单类鸭子图,直接拿去上线肯定不够,但它的价值在于快速验证和迁移起点。我一般会拿它做两件事:一是跑通某个新模型的训练链路,确认数据加载、增强、损失计算都没问题,再换自己的业务数据;二是当预训练数据用,虽然类别单一,但鸭子这种有羽毛纹理、姿态多变的目标,学到的浅层特征对同类细粒度检测有迁移价值。
验证训练结果是否靠谱,别只看 mAP 一个数。把best.pt拉出来在验证集上跑一遍推理,用下面的脚本把预测框和真实框画到同一张图上对比:
from ultralytics import YOLO import cv2 model = YOLO("runs/duck/exp1/weights/best.pt") results = model.predict("duck_dataset/images/val", save=True, conf=0.25) # 抽一张看预测框数量与真实标注的差异 img_path = "duck_dataset/images/val/duck_104.jpg" result = model(img_path)[0] print(f"预测框数量: {len(result.boxes)}") # 真实框数量从对应 txt 里数行数 with open(img_path.replace("images", "labels").replace(".jpg", ".txt")) as f: print(f"真实框数量: {len(f.readlines())}")conf=0.25是推理置信度阈值,调低能召回更多框但误检也会涨,单类场景我一般从 0.25 起调。预测框数量和真实框数量差太多,说明模型要么漏检要么过检,回去看训练日志里的val/box_loss和metrics/mAP50-95。如果 mAP50 高但 mAP50-95 低,说明框的位置不够准,多半是标注框边界不够贴合,这份数据的标注遵循里强调了「准确框选边界」,但人工标注的贴合度参差不齐,必要时可以拿预测结果反推哪些图的标注需要修。
还有一个实用技巧:把验证集里预测错的图挑出来,按错误类型分类——漏检、误检、框偏。漏检多的图大概率是目标太小或太密,误检多的图可能是背景里有类似鸭子的物体。这份数据背景相对干净,误检通常不多,主要问题会集中在密集场景的漏检上。针对漏检,可以在训练时把imgsz提到 800 或 960,让小目标占更多像素;或者开 mosaic 增强,让模型多见一些目标堆叠的样本。
从那以后我每次拿到新数据集,都强制先跑一遍「结构检查 → 格式转换 → 抽样可视化 → 小轮次试训」这四步,确认数据本身没问题再上正式训练。这份鸭子数据集结构规整、双格式齐全,省掉了最耗时的标注环节,拿来练手或者做基线都合适。希望帮到你。
本文还有配套的精品资源,点击获取