简介:这是一份面向目标检测初学者与算法工程师的鸭子识别数据集,采用Pascal VOC与YOLO双格式标注,可直接用于训练和验证单类别检测模型,省去自行采集与标注的成本。压缩包共1892个文件,包含630张jpg原图、630个VOC格式xml标注文件、630个yolo格式txt标签文件,另有少量说明文本,整体约199.6MB,图片与标注一一对应,方便在YOLO、Faster R-CNN等框架间快速切换。标注由labelImg完成,类别仅Duck一类,共1149个矩形框,平均每张图约1.8个目标,适合做小样本单类检测的入门实验或迁移学习预训练。目前已有217人学习下载,可作为课程设计、毕业项目或算法验证的现成数据基础,帮助读者把精力集中在模型调参与效果对比上。
1. 630 张鸭子数据集:小样本 VOC+YOLO 双格式到底能干什么
手上只有几百张图,还想训一个能用的检测模型,这是很多做垂直场景的人绕不开的处境。这个鸭子数据集就是典型样本:630 张图、1 个类别(duck)、同时给了 VOC 格式和 YOLO 格式。它解决的不是「通用大模型」问题,而是让你在半天内跑通一条从标注文件到可推理权重的完整链路,验证自己的训练脚本、增强策略和部署流程有没有问题。适合两类人:一是刚入门目标检测、想找一个类别干净、标注完整的小数据集练手的人;二是手里有真实业务图但还没标完,想先用一个结构一致的小集把 pipeline 调通的人。630 张不算多,但 1 类别意味着类别不平衡、背景干扰这些麻烦事基本没有,你能把注意力放在格式转换、训练参数和验证指标上,而不是清洗脏数据。
2. VOC 与 YOLO 双格式:先搞懂两套标注的坐标系差异
2.1 VOC 的 XML 结构与字段含义
VOC 格式每张图对应一个同名 XML,核心信息在<object>节点里。一个典型的鸭子标注长这样:
<annotation> <folder>duck</folder> <filename>duck_001.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>duck</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>112</xmin> <ymin>86</ymin> <xmax>398</xmax> <ymax>402</ymax> </bndbox> </object> </annotation>xmin/ymin/xmax/ymax是绝对像素坐标,原点在左上角。difficult字段在评估时决定是否跳过该目标,truncated表示目标是否被截断。很多转换脚本翻车就翻在没读size里的宽高,直接拿坐标归一化,结果框全飘了。VOC 的好处是可读性强、工具链老牌(labelImg 默认就存这个),坏处是文件多、解析慢,630 张就是 630 个 XML。
2.2 YOLO 的 txt 归一化坐标与类别索引
YOLO 格式每张图对应一个同名 txt,每行一个目标,格式是:
0 0.3984375 0.5083333 0.446875 0.6583333五个字段依次是class_id center_x center_y width height,全部是相对图像宽高的归一化值,范围 0~1。上面这行对应 VOC 里那个框:中心 x = (112+398)/2/640 ≈ 0.398,中心 y = (86+402)/2/480 ≈ 0.508,宽 = (398-112)/640 ≈ 0.447,高 = (402-86)/480 ≈ 0.658。注意 YOLO 没有difficult概念,类别从 0 开始编号,1 类别时永远是 0。还有一个隐藏坑:YOLO 要求每张图都有对应 txt,负样本(没有目标的图)要留一个空 txt,否则训练时该图会被当成损坏数据跳过。
2.3 两套格式的对应关系与转换时机
| 维度 | VOC | YOLO |
|---|---|---|
| 坐标 | 绝对像素 | 归一化 0~1 |
| 存储 | 每图一个 XML | 每图一个 txt |
| 类别 | 字符串 name | 整数 id |
| 负样本 | 无 object 节点 | 空 txt |
| 常用工具 | labelImg、mmdetection | ultralytics、darknet |
选哪套取决于你的训练框架。用 ultralytics 系(YOLOv5/v8/v11)直接吃 YOLO 格式;用 mmdetection、Detectron2 走 VOC 或 COCO。这个数据集两套都给,省掉了自己转的麻烦,但你要清楚它们不是简单改后缀,坐标语义完全不同。常见做法是:训练用 YOLO 格式,评估和可视化用 VOC 格式回查原始框,两边对不上时优先信 VOC,因为它是绝对坐标,肉眼可验。
3. 从 630 张图到可训练集:目录组织与最小训练命令
3.1 目录结构怎么摆才不报错
ultralytics 对目录结构有硬性约定,摆错了会直接报No labels found。推荐这样组织:
duck_dataset/ ├── images/ │ ├── train/ # 约 500 张 │ └── val/ # 约 130 张 ├── labels/ │ ├── train/ # 与 train 图片同名的 txt │ └── val/ └── duck.yaml图片和标签必须同名不同后缀,且 images 和 labels 的目录层级要镜像对应。630 张按 8:2 切,训练 504 张、验证 126 张,1 类别下这个量级验证集够看趋势,但别指望指标很稳。切分时注意同一只鸭子的连拍图不要跨 train/val,否则验证指标虚高,这是小数据集最隐蔽的泄漏。
3.2 duck.yaml 的四个必填字段
path: /home/user/duck_dataset train: images/train val: images/val nc: 1 names: 0: duckpath是数据集根目录,train/val是相对 path 的子路径。nc是类别数,1 类别就写 1,写错会直接维度不匹配。names是 id 到类名的映射,顺序必须和 txt 里的 class_id 一致。很多人从别处抄 yaml 忘了改nc,训练能启动但 loss 一直不降,回头查半天才发现类别数对不上。
3.3 一条命令跑通首次训练
yolo detect train \ data=duck_dataset/duck.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs/duck \ name=exp1model=yolov8n.pt用预训练权重,小数据集必须靠迁移学习,从零训 630 张基本学不出东西。imgsz=640是输入分辨率,和标注时的图像尺寸不必一致,框架会自己缩放。batch=16在 8G 显存上跑 640 分辨率比较稳,显存不够就降到 8 并开amp=True。epochs=100对 1 类别小集通常够收敛,看results.csv里 mAP50 连续 20 轮不涨就可以停。训练完权重在runs/duck/exp1/weights/best.pt。
3.4 验证与推理:确认模型真的学到了鸭子
yolo detect val model=runs/duck/exp1/weights/best.pt data=duck_dataset/duck.yaml yolo detect predict model=runs/duck/exp1/weights/best.pt source=test_imgs/ save=Trueval会输出 mAP50、mAP50-95、precision、recall。1 类别小集上 mAP50 到 0.85 以上算正常,低于 0.6 先查标注有没有框错、类别 id 有没有写乱。predict的save=True会把画框结果存到runs/detect/predict,肉眼过一遍比看数字更靠谱。如果框位置整体偏移,八成是训练时图像被 letterbox 填充而你没意识到,推理时又用了原图比例。
4. 小样本训练的避坑清单:630 张最容易翻车的五个地方
4.1 现象:loss 正常下降但 mAP 始终为 0
原因通常是验证集标签路径没对上,框架读到了图但没读到对应 txt,把所有预测都当误检。解决:检查labels/val下 txt 数量和images/val下图片数量是否一致,文件名(不含后缀)是否逐一对应。用ls images/val | wc -l和ls labels/val | wc -l对比,数字不等就是这里的问题。
4.2 现象:训练报Label class X is out of bounds
原因:txt 里的 class_id 超过了nc-1。1 类别时合法 id 只有 0,如果转换脚本从 1 开始编号就会报这个。解决:全局搜一遍 txt 第一列,确认最大值是 0。批量修正可以用:
find labels -name "*.txt" -exec sed -i 's/^1 /0 /' {} \;执行前先备份,sed 改错不可逆。
4.3 现象:框位置整体偏移或缩放
原因:VOC 转 YOLO 时用了错误的宽高做归一化,比如拿size里的值去归一化另一张图的坐标,或者图像被预处理过(裁剪、旋转)但标注没同步。解决:随机抽 5 张图,用脚本把 YOLO 坐标反算回像素,和 VOC 的 xmin/ymin 对比,误差超过 2 像素就要查转换逻辑。归一化公式是(x - xmin) / width,别把 xmax 当宽用。
4.4 现象:验证指标高得离谱,实际推理一塌糊涂
原因:train/val 切分时同一场景或连拍图泄漏,模型在验证集上等于见过答案。解决:按拍摄批次或时间切分,而不是随机切。630 张如果来自几十段视频抽帧,按视频段切,保证验证集里的鸭子和训练集不是同一批。这个坑在小数据集上比过拟合更常见。
4.5 现象:显存溢出或训练中途被杀
原因:batch或imgsz设太大,或者workers开太多导致内存爆。解决:先把batch降到 8、imgsz降到 416 跑通,再逐步往上加。workers=4在多数机器上够用,设 8 以上反而可能因为数据加载竞争变慢。用nvidia-smi盯显存,留 1G 余量给系统。
5. 把 630 张用到极致:增强策略与指标验证的进阶技巧
小数据集的瓶颈从来不是模型结构,而是数据多样性。630 张如果都是相似角度、相似光照,模型学到的就是「这只鸭子在这个背景下长这样」,换个场景立刻崩。我一般会先做一轮离线增强,把训练集扩到 3~5 倍,再开在线增强。离线增强用 albumentations 做随机裁剪、亮度对比度扰动、轻微旋转和水平翻转,注意翻转后要同步改标注坐标,水平翻转的 x 坐标变换是new_x = 1 - x,中心点和宽不变,但 xmin/xmax 要互换。
import albumentations as A import cv2, os transform = A.Compose([ A.HorizontalFlip(p=0.5), A.RandomBrightnessContrast(p=0.4), A.Rotate(limit=10, p=0.3), A.RandomCrop(width=512, height=512, p=0.3), ], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels'])) img = cv2.imread('duck_001.jpg') with open('duck_001.txt') as f: boxes = [list(map(float, line.split())) for line in f] bboxes = [b[1:] for b in boxes] labels = [int(b[0]) for b in boxes] out = transform(image=img, bboxes=bboxes, class_labels=labels) # out['bboxes'] 已是增强后的归一化坐标,直接写回 txtformat='yolo'告诉 albumentations 输入是归一化坐标,它会自动处理翻转和裁剪后的坐标变换。label_fields保证类别 id 跟着框走。离线增强后训练集从 504 张扩到 2000 张左右,再跑同样的训练命令,mAP50 通常能涨 5~10 个点。在线增强则靠 ultralytics 自带的hsv_h/hsv_s/hsv_v/degrees/translate/scale/fliplr参数,默认值对鸭子这种目标偏保守,可以把scale调到 0.5、translate调到 0.2,让模型见更多位置变化。
验证阶段别只看 mAP。1 类别小集上,recall 比 precision 更值得盯,漏检一只鸭子比多框一个背景更影响业务。如果 recall 低,先降置信度阈值看曲线,再查是不是小目标太多、imgsz不够。630 张里如果有大量远距离小鸭子,把imgsz提到 960 比换模型更有效。最后留 20 张完全不参与训练和验证的图做盲测,跑一遍predict肉眼看,这是唯一能暴露「指标好看但实际不能用」的后悔药。我自己的习惯是每次改完增强或参数,先跑 10 轮看 loss 曲线形状,形状不对就别浪费 100 轮的电。希望帮到你。
本文还有配套的精品资源,点击获取