简介这是一份面向目标检测初学者与算法工程师的鸭子目标检测数据集适用于训练和验证单类别检测模型可支撑课程实验、模型对比与算法调优等场景。资源采用Pascal VOC与YOLO双格式标注jpg图片与对应的xml、txt标注文件一一对应方便直接接入YOLO系列或VOC风格训练流程。压缩包共约2000个文件以1999个xml标注文件和1个说明txt为主整体约261.38MB标注工具为labelImg采用矩形框方式对Duck类别进行标注共4341个标注框类别单一、标注集中适合快速验证检测效果。目前已有206人学习下载。读者可获得完整的图片与双格式标注数据省去自行采集与标注的成本直接用于模型训练、数据增强实验与精度评估也可作为单类别检测任务的入门练手素材。1. 鸭子数据集2700张VOCYOLO格式从拿到压缩包到跑通第一轮训练手里拿到一个标注好的鸭子数据集2700张图同时给了VOC和YOLO两套格式这件事本身就说明制作者踩过一遍格式转换的坑了。VOC是Pascal VOC那套XML标注YOLO是每张图配一个txt、每行类别 cx cy w h的归一化坐标。两套格式并存意味着你可以直接喂给YOLOv5/v8这类框架也可以拿XML去做数据清洗和可视化复核。这个数据集适合谁做养殖场鸭群计数、水域生态监测、农业无人机巡检的团队以及想找一个中等规模、类别单一但场景有变化的目标检测数据集来练手或做baseline的人。2700张不算大但足够把训练流程、增强策略、评估指标全部走一遍不会像COCO那样一跑就是几天。下面按我实际处理这类数据集的顺序把选型、转换、训练、排错讲清楚。2. 先搞清楚VOC和YOLO两套标注到底差在哪2.1 VOC的XML结构与YOLO的txt结构对照VOC格式每张图对应一个同名XML核心节点是object里面name是类别bndbox里是xmin/ymin/xmax/ymax四个绝对像素坐标。YOLO格式每张图对应一个同名txt每行五个值类别索引、中心点x、中心点y、宽、高后四个都是除以图像宽高后的归一化值范围0到1。这个差异决定了你不能直接把XML丢给YOLO训练脚本必须先转。维度VOC XMLYOLO txt坐标类型绝对像素归一化0-1表示方式左上角右下角中心点宽高类别表示字符串名称整数索引文件组织Annotations/ JPEGImages/images/ labels/多类别每object一个name每行第一个数字我一般会先确认一件事这个数据集的YOLO版本里类别索引和classes.txt或data.yaml里的顺序是否一致。很多数据集翻车就翻在这里索引错一位训练出来的模型把鸭子认成背景。2.2 为什么建议以YOLO格式为主、VOC做校验训练时用YOLO格式因为主流框架的dataloader直接读txt速度快、解析简单。VOC的XML留作校验用途当你怀疑某张图标注有问题打开XML能看到原始标注框的绝对坐标比归一化值直观得多。另外如果你要做数据增强后的标注同步XML改起来也比txt容易定位。常见做法是训练走YOLO抽查和可视化走VOC两边对不上就说明转换脚本有bug。2.3 目录结构怎么摆才不会被框架坑我一般会整理成下面这样避免路径问题duck_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── Annotations/ # VOC XML校验用 ├── JPEGImages/ # 原始图校验用 └── data.yaml注意images和labels下的子目录名必须完全一致YOLO系列框架默认用路径替换来找标签文件images/train/001.jpg对应labels/train/001.txt。少一层目录或者名字不一致训练时就会报“no labels found”。3. 把VOC转成YOLO转换脚本与四个边界坑3.1 转换脚本的核心逻辑下面这个脚本是我处理这类数据集时常用的读XML、写txt、同时生成classes.txt。关键点是坐标裁剪和归一化。import os import xml.etree.ElementTree as ET # 类别列表必须和数据集实际类别一致 classes [duck] def convert_voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) 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节点 img_name xml_file.replace(.xml, .jpg) img_path os.path.join(img_dir, img_name) from PIL import Image with Image.open(img_path) as im: w, h im.size 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) # 裁剪到图像范围内防止越界 xmin max(0, min(xmin, w)) xmax max(0, min(xmax, w)) ymin max(0, min(ymin, h)) ymax max(0, min(ymax, h)) if xmax xmin or ymax ymin: continue # 跳过无效框 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}) with open(os.path.join(out_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines)) convert_voc_to_yolo(Annotations, JPEGImages, labels_all)逻辑说明遍历XML对每个object取类别和框坐标用PIL读图片真实尺寸做归一化。参数上classes列表顺序决定类别索引必须和后续data.yaml一致坐标裁剪到[0, w]和[0, h]是防止标注越界导致归一化值超过1YOLO训练时虽然会做clamp但提前处理更干净。3.2 四个边界坑越界、空标注、类别名空格、图片尺寸不一致第一个坑是越界框。有些标注的xmax超过了图片宽度归一化后cx可能大于1训练时loss会异常。上面脚本里的min(xmax, w)就是处理这个。第二个坑是空标注。XML里没有object节点或者所有object都被过滤掉生成的txt是空文件。YOLO默认会把空txt当作负样本但如果你不希望这样需要在生成后检查并决定是否保留。第三个坑是类别名带空格或大小写不一致。比如duck和Duck会被当成两个类。转换前先统一strip和lower。第四个坑是图片尺寸不一致。VOC的XML里有个size节点但有些数据集这个节点写的是原始尺寸实际图片被resize过。所以上面脚本坚持用PIL读实际图片尺寸不用XML里的size。3.3 转换后怎么快速验证没转错转完不要直接开训先做两件事。一是统计每类框数量和VOC里的object总数对一下cat labels_all/*.txt | awk {print $1} | sort | uniq -c二是随机抽10张图用下面脚本把YOLO框画回图上肉眼确认框位置对import cv2 import os def draw_yolo(img_path, label_path, out_path): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as f: for line in f: cls, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(out_path, img)如果框明显偏移或者大小不对回去检查归一化时用的宽高是不是图片真实宽高。4. 用YOLOv8跑通鸭子检测训练配置、参数与显存控制4.1 data.yaml怎么写才不出错YOLOv8的data.yaml三个关键字段path、train、val以及names。我一般写成绝对路径避免相对路径解析歧义path: /data/duck_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: duck注意names的索引必须和转换脚本里的classes顺序一致。如果只有鸭子一类nc1。如果数据集里还有“其他鸟”之类按实际改。4.2 训练命令与关键参数含义yolo detect train \ data/data/duck_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0 \ projectruns/duck \ nameexp1参数说明modelyolov8n.pt用预训练权重2700张图从零训容易过拟合预训练能省很多事。imgsz640是常见输入尺寸如果鸭子目标很小可以提到960但显存翻倍。batch16在8G显存上跑640尺寸基本够不够就降到8。patience20是早停验证集20轮不提升就停省时间。lr00.01是初始学习率小数据集可以降到0.001更稳。4.3 显存不够时的三个降级方案显存不够报CUDA out of memory按顺序试第一降batch到8或4第二降imgsz到512或416第三换更小的模型比如yolov8n换成yolov8n-seg如果做分割或者直接用nano。不要一上来就上yolov8x2700张图nano足够跑出可用的baseline。另外ampTrue默认开启混合精度能省显存如果遇到BN崩溃可以关掉试试。4.4 训练过程中该盯哪些指标看runs/duck/exp1/results.csv重点看三个metrics/mAP50、metrics/mAP50-95、train/box_loss。mAP50在鸭子这种单类任务上通常能到0.9以上如果卡在0.5不动大概率是标注问题或者类别索引错了。box_loss不降反升检查学习率是不是太大。另外看val/box_loss和train/box_loss的差距差距大说明过拟合加增强或者减epoch。5. 训练翻车现场鸭子数据集常见的五类报错与排查5.1 报错“no labels found”但labels目录明明有文件现象训练启动即报找不到标签。原因images和labels的目录层级不匹配或者txt文件名和图片名不一致比如图片是.jpgtxt是.JPG.txt。解决用ls images/train | head和ls labels/train | head对比确保除了扩展名外完全一致。另外检查data.yaml里的train路径是相对于path的不要写成绝对路径又拼一次。5.2 训练loss正常但mAP一直是0现象box_loss在降但mAP50始终为0。原因类别索引错位比如data.yaml里names是0: duck但txt里写的是1。解决用cat labels/train/*.txt | awk {print $1} | sort -u看实际出现的类别索引和data.yaml对齐。如果出现1而names只有0要么改txt要么改names。5.3 BN崩溃训练中途报“Expected more than 1 value per channel”现象训练若干轮后突然报BN层错误。原因某个batch里某张图经过增强后只剩一个像素或者全黑导致BN统计量异常。解决检查增强参数特别是mosaic和mixup小数据集上mosaic概率调低到0.5以下。另外确认没有空txt被当成有效样本空txt对应的图如果被读入且没有框某些版本会出问题。5.4 验证集mAP远低于训练集现象训练集mAP0.95验证集0.6。原因训练集和验证集场景分布不一致比如训练集都是白天验证集有夜间。解决重新划分数据集按场景分层抽样确保验证集覆盖各种光照和角度。2700张图建议按8:1:1分验证集至少270张。5.5 推理时框重叠严重或漏检小鸭子现象推理图上同一个鸭子出多个框或者远处小鸭子完全没框。原因NMS的iou阈值默认0.7重叠框多可以降到0.5小目标漏检则要提高输入分辨率或者用imgsz960推理。解决推理时加conf0.25 iou0.5小目标场景把imgsz提到960再试。6. 把2700张用出27000张的效果增强、分层与推理调优6.1 小数据集的增强策略怎么配2700张对目标检测来说属于中等偏小增强是必须的。YOLOv8默认开了mosaic、随机翻转、HSV抖动。我一般会额外关注几个参数degrees旋转角度鸭子在水面可能各种朝向设10到15度translate平移0.1scale缩放0.5flipud上下翻转看场景如果鸭子不会倒过来就别开。mosaic概率mosaic0.8最后10轮关掉mosaic让模型适应真实分布这个技巧叫close mosaic能明显提点。6.2 按场景分层划分训练验证集如果数据集里包含不同场景池塘、草地、室内不要随机分。我一般先按文件名或目录粗分场景再在每个场景内按8:1:1抽。这样验证集mAP才能反映真实泛化能力。如果数据集没有场景标签可以用图片的亮度、色调做聚类粗略分层。6.3 推理阶段的conf和iou怎么调训练完导出模型后推理命令yolo detect predict \ modelruns/duck/exp1/weights/best.pt \ sourcetest_images/ \ conf0.25 \ iou0.5 \ imgsz960 \ saveTrueconf太低会出很多误检太高会漏检。鸭子数据集我一般从0.25起步看误检和漏检哪个更不可接受再调。iou用于NMS鸭子密集时降到0.4到0.5稀疏时0.6到0.7。imgsz推理时可以和训练不一致通常调大能提升小目标召回但速度下降。6.4 用混淆矩阵和PR曲线定位问题训练完runs/duck/exp1/下会有confusion_matrix.png和PR_curve.png。混淆矩阵看鸭子和背景的混淆情况如果背景被大量误判为鸭子说明负样本不够或者conf太低。PR曲线看不同conf下的precision和recall权衡选一个业务上可接受的平衡点。注意YOLO的混淆矩阵有时因为多batch统计导致总数不唯一看比例就行不用纠结绝对数。6.5 一个我常犯的错忘了固定随机种子最后说个血泪经验。有次调参对比两次训练结果差很多排查半天发现是随机种子没固定数据划分和增强每次都不一样。后来我习惯在训练命令里加seed42保证可复现。另外如果要做A/B对比除了seed还要确保deterministicTrue虽然会慢一点但结果可信。这个数据集不大固定种子多跑几轮比盲目调参有用得多。希望帮到你。本文还有配套的精品资源点击获取