☰
家禽鸡小鸡检测数据集:2970张VOC+YOLO双格式实战训练指南
2026/9/26 12:26:04 网站建设 项目流程

简介:这是一份面向目标检测初学者与算法工程师的家禽鸡只检测数据集,聚焦母鸡、公鸡、小鸡等场景下的目标框标注任务,可用于训练与验证YOLO、Faster R-CNN等主流检测模型,也适合作为课程设计、毕业项目或小样本实验的数据基础。资源包共约2000个文件,以1999个VOC格式xml标注文件和1个说明txt为主,另含对应的jpg图片与YOLO格式txt标签,压缩包整体约50.18MB,VOC与YOLO双格式可直接对接不同训练框架,省去格式转换环节。全部标注由labelImg手工完成,统一类别为chicken,共6358个标注框,2971张图片与标注一一对应,标注质量较为可靠。目前已有462人学习下载,适合需要快速获取真实家禽检测数据、验证模型效果或开展迁移学习实验的读者使用。

1. 家禽鸡小鸡检测数据集:2970 张 VOC+YOLO 双格式到底能跑出什么

养殖场里数鸡这件事,听起来简单,做起来能把人逼疯。鸡舍光照忽明忽暗、小鸡扎堆重叠、笼养场景下目标密集到像素级粘连,人工盘点一批 5000 只的鸡群,误差经常在 3% 以上。这也是为什么最近一年「目标检测数据集」的检索量一直往上走——大家不是缺模型,是缺一份能直接开训、标注质量过关的鸡只检测数据。这份 2970 张 VOC+YOLO 双格式、手工标注的家禽鸡小鸡检测数据集,解决的正是「从零标注成本太高、公开数据又对不上自己场景」这个卡点。它适合三类人:想快速验证 YOLO 系列训练流程的算法新手、需要做养殖数量统计的工程落地者、以及拿它当标注规范参考的团队。下面我按「数据长什么样 → 怎么转怎么训 → 坑在哪 → 怎么调」的顺序,把这份数据集从拆包到跑出第一个 mAP 的路径讲透。

2. 拆开这份数据集:VOC 与 YOLO 双格式的目录结构与字段含义

拿到一个压缩包,第一件事不是急着解压训练,而是先搞清楚里面到底装了什么。这份数据集标注为 2970 张、VOC+YOLO 双格式、手工标注,意味着同一批图像配了两套标注文件。VOC 格式是 XML,YOLO 格式是每张图一个 txt。很多人拿到双格式数据直接只用 YOLO 那份,结果遇到类别对不上、坐标越界的问题才发现 VOC 那份才是原始标注。所以先把结构摸清楚,后面转换和校验才有依据。

2.1 VOC 格式的 XML 字段逐个拆解

VOC 的标注文件是标准 PASCAL VOC XML,一张图对应一个同名 .xml。核心字段就几个,但每个都有坑。下面是一份典型的鸡只标注 XML 结构:

<annotation> <folder>images</folder> <filename>chick_0001.jpg</filename> <size> <width>640</width> <!-- 图像宽,必须和实际像素一致 --> <height>480</height> <!-- 图像高 --> <depth>3</depth> <!-- 通道数,RGB 为 3 --> </size> <object> <name>chicken</name> <!-- 类别名,大小写敏感 --> <pose>Unspecified</pose> <truncated>0</truncated> <!-- 是否被截断,0/1 --> <difficult>0</difficult> <!-- 是否难样本,训练时可过滤 --> <bndbox> <xmin>112</xmin> <!-- 左上角 x,从 0 开始 --> <ymin>88</ymin> <xmax>305</xmax> <!-- 右下角 x --> <ymax>260</ymax> </bndbox> </object> </annotation>

这里要重点看三个地方。第一,size里的宽高必须和真实图像一致,手工标注时如果换了图没更新尺寸,坐标就会整体偏移。第二,name字段决定类别索引,如果数据集里同时有chicken和Chicken,转换后会变成两个类,这是血泪经验里最常见的翻车点。第三,difficult标记为 1 的样本,在计算 mAP 时通常会被忽略,但训练时是否保留要看你的策略——小鸡扎堆场景里,难样本恰恰是提升召回的关键,我一般会保留。

2.2 YOLO 格式的归一化坐标与类别索引

YOLO 格式每张图一个 txt,每行一个目标,格式是class_id x_center y_center width height,全部归一化到 0~1。同样一张图,对应的 YOLO 标注长这样:

0 0.325781 0.362500 0.301563 0.358333 0 0.612500 0.541667 0.218750 0.291667

第一列0是类别索引,对应一个classes.txt或data.yaml里的类别列表。后面四个值是中心点坐标和宽高,都是相对图像宽高的比例。这里最容易出问题的是归一化基准:如果 XML 里写的宽高是 640×480,但实际图像是 1280×960,转换出来的坐标会全部缩小一半,框会偏到左上角。所以转换前必须用脚本校验一遍图像真实尺寸和 XML 声明尺寸是否一致。

2.3 双格式并存时的目录组织与校验清单

一份规范的双格式数据集,目录通常长这样:

dataset/ ├── JPEGImages/ # 所有原图,2970 张 ├── Annotations/ # VOC XML,与图同名 ├── labels/ # YOLO txt,与图同名 ├── classes.txt # 类别列表,一行一个 └── data.yaml # YOLO 训练配置

拿到手先跑一遍校验,确认三件事:图像数量、XML 数量、txt 数量是否都是 2970;每张图是否都有对应的 XML 和 txt;类别名是否统一。下面这段脚本就是干这个的:

import os from pathlib import Path img_dir = Path("dataset/JPEGImages") xml_dir = Path("dataset/Annotations") txt_dir = Path("dataset/labels") imgs = {p.stem for p in img_dir.glob("*.jpg")} xmls = {p.stem for p in xml_dir.glob("*.xml")} txts = {p.stem for p in txt_dir.glob("*.txt")} print("图像数:", len(imgs)) print("XML数:", len(xmls)) print("txt数:", len(txts)) print("缺XML的图:", imgs - xmls) # 有图没标注 print("缺txt的图:", imgs - txts) # 有图没YOLO标注 print("多余XML:", xmls - imgs) # 标注没有对应图

这段脚本的逻辑很直接:用文件名主干做集合运算,差集就是缺失项。参数上唯一要注意的是扩展名,有些数据集图像是 .png 或 .JPG 大写,glob 模式要跟着改。跑完如果三个数量都是 2970 且差集为空,说明数据完整性没问题,可以进入下一步。如果差集非空,先别急着训练,缺标注的图要么补标要么剔除,否则训练时会被当成纯背景,拉低召回。

3. 从 VOC 转 YOLO:转换脚本、坐标计算与三个边界坑

双格式数据集虽然省了标注,但实际训练时大家基本都用 YOLO 格式,因为 ultralytics 系的训练框架直接吃 txt。所以哪怕包里已经带了 YOLO 格式,我也建议自己跑一遍转换脚本——一是验证两套标注是否真的一致,二是转换过程本身就是一次数据体检。这一章把转换的坐标计算讲清楚,再把三个最容易翻车的边界情况列出来。

3.1 坐标转换的数学逻辑与脚本实现

VOC 给的是绝对坐标的左上角和右下角,YOLO 要的是归一化的中心点和宽高。转换公式不复杂,但每一步都要除以图像宽高:

  • x_center = (xmin + xmax) / 2 / width
  • y_center = (ymin + ymax) / 2 / height
  • w = (xmax - xmin) / width
  • h = (ymax - ymin) / height

下面是一个完整的转换脚本,读 XML、写 txt,同时做越界裁剪:

import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image classes = ["chicken"] # 类别列表,顺序决定 class_id class_to_id = {c: i for i, c in enumerate(classes)} xml_dir = Path("dataset/Annotations") img_dir = Path("dataset/JPEGImages") out_dir = Path("dataset/labels") out_dir.mkdir(exist_ok=True) for xml_path in xml_dir.glob("*.xml"): tree = ET.parse(xml_path) root = tree.getroot() # 用真实图像尺寸,而不是XML里声明的尺寸 img_path = img_dir / (xml_path.stem + ".jpg") with Image.open(img_path) as im: W, H = im.size lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in class_to_id: continue # 跳过未知类别 bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 裁剪到图像范围内,防止越界 xmin, xmax = max(0, xmin), min(W, xmax) ymin, ymax = max(0, ymin), min(H, ymax) if xmax <= xmin or ymax <= ymin: continue # 无效框直接丢弃 xc = (xmin + xmax) / 2 / W yc = (ymin + ymax) / 2 / H w = (xmax - xmin) / W h = (ymax - ymin) / H lines.append(f"{class_to_id[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") (out_dir / (xml_path.stem + ".txt")).write_text("\n".join(lines))

逻辑上关键的一步是用Image.open读真实尺寸,而不是信 XML 里的size。参数上,classes列表的顺序就是最终 class_id,必须和data.yaml里的names完全一致,否则训练出来的模型会把鸡识别成别的类。坐标保留 6 位小数足够,YOLO 内部也是按浮点处理。

3.2 边界坑一:坐标越界与零面积框

手工标注时鼠标一抖,xmax 可能超过图像宽度,或者 xmin 等于 xmax 形成零面积框。这种框在训练时会导致 loss 计算异常,表现为 loss 突然变 NaN 或者 mAP 一直上不去。上面的脚本用max(0, xmin)和min(W, xmax)做了裁剪,并且用xmax <= xmin判断丢弃无效框。但要注意,裁剪后如果框变得极小(比如宽高只剩 1 像素),也建议丢弃,因为这种框对训练只有干扰。我一般会加一个最小面积阈值,比如宽高都小于 4 像素就跳过。

3.3 边界坑二:类别名大小写与空格

XML 里的name字段如果写成chicken(尾部带空格)或者Chicken,直接拿去匹配class_to_id会匹配失败,导致整张图的标注被跳过。更隐蔽的是,有些标注工具会写入chicken\n,strip()能解决大部分问题,但大小写必须统一。转换前先跑一遍类别统计:

from collections import Counter import xml.etree.ElementTree as ET from pathlib import Path counter = Counter() for xml_path in Path("dataset/Annotations").glob("*.xml"): root = ET.parse(xml_path).getroot() for obj in root.findall("object"): counter[obj.find("name").text] += 1 print(counter)

如果输出里出现多个变体,先统一再转换。这一步花两分钟,能省掉后面几小时的排查。

3.4 边界坑三:图像与标注文件名不一致

有些数据集图像叫chick_0001.jpg,XML 叫chick_0001.xml,但 YOLO txt 可能叫chick_0001.txt,这没问题。坑在于图像扩展名不统一,比如一部分是 .jpg 一部分是 .png,而脚本里写死了.jpg,就会导致Image.open报文件不存在。解决办法是用glob同时匹配多种扩展名,或者先统计一遍扩展名分布。另外,文件名里的空格和中文也是隐患,训练框架读取路径时可能出错,建议转换前统一重命名为纯英文数字下划线。

4. 用这份数据集训练 YOLO:环境配置、data.yaml 与首轮训练参数

数据校验和转换做完,接下来就是把它喂给 YOLO。这一章按「环境 → 配置 → 启动 → 看结果」的顺序走,参数给具体值,命令能直接抄。需要说明的是,YOLO 版本迭代很快,下面用的是 ultralytics 系的通用流程,具体版本差异在参数名上可能有微调,但核心逻辑一致。

4.1 环境配置:conda 建环境与依赖安装

我一般用 conda 隔离环境,避免和系统里的其他包打架。Python 版本选 3.9 或 3.10 都行,太新反而有些依赖轮子不全。

conda create -n chick_det python=3.10 -y conda activate chick_det # 安装 PyTorch,按自己的 CUDA 版本选,下面以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics pip install ultralytics # 验证 yolo checks

yolo checks会打印环境信息,重点看 CUDA 是否可用、PyTorch 版本是否匹配。如果显示 CPU only,说明 CUDA 没装上,训练会慢到无法接受。参数上,--index-url后面的地址要和本机 CUDA 版本对应,装错了会报CUDA error: no kernel image is available。

4.2 data.yaml 的五个必填字段

YOLO 训练靠一个 yaml 文件告诉它数据在哪、有几类。这份鸡只数据集只有一类,yaml 长这样:

path: /home/user/dataset # 数据集根目录,绝对路径最稳 train: JPEGImages # 训练图像目录,相对 path val: JPEGImages # 验证目录,数据少时可复用 names: 0: chicken # 类别索引和名称,必须和转换脚本一致

五个字段里,path用绝对路径能避免「找不到文件」的玄学问题;train和val是相对path的子目录;names的索引必须从 0 开始且和 txt 里的 class_id 对应。如果只有一份数据没有单独验证集,可以按 8:2 切分,或者直接用同一份做 val,但 mAP 会偏乐观,心里要有数。

4.3 首轮训练命令与关键参数取值

启动训练的命令很短,但参数决定成败:

yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/chick \ name=exp1

逐个说参数。model=yolov8n.pt是预训练权重,n 是最小模型,适合先跑通流程;如果显存够、精度要求高,可以换 s 或 m。imgsz=640是输入尺寸,鸡只目标普遍偏小,640 是平衡速度和精度的起点,如果小鸡在图中占比很小,可以提到 960 甚至 1280,但显存占用会翻倍。batch=16按显存调,8G 显存跑 640 大概能到 16,爆显存就减半。lr0=0.01是初始学习率,预训练模型微调时这个值比较稳,从零训练才需要更小的值。patience=20是早停,20 轮 mAP 不涨就停,省时间。

4.4 训练日志怎么看:loss 曲线与 mAP 的合理区间

训练启动后,控制台会打印每轮的 box_loss、cls_loss 和 mAP50。判断训练是否正常,看三点:box_loss 和 cls_loss 应该在前 10 轮快速下降然后趋于平缓,如果一直震荡或者不降,多半是学习率太大或标注有问题;mAP50 在 50 轮左右应该能到 0.8 以上,鸡只这种单类目标检测任务,标注质量好的话 0.9 也不难;如果 mAP 卡在 0.3 以下,先回去查标注,别急着调模型。训练完的结果在runs/chick/exp1/下,weights/best.pt是验证集上最好的权重,拿它做推理。

5. 避坑与排查:这份数据集训练时最容易翻车的五个地方

数据、转换、训练流程都走通了,但实际跑起来还是会遇到各种问题。这一章把我在类似数据集上踩过的坑列出来,每条按「现象 → 原因 → 解决」写,遇到对应情况直接对号入座。

5.1 现象:训练 loss 正常但 mAP 始终为 0

原因通常是类别索引错位。YOLO 的 txt 里 class_id 是 0,但 data.yaml 的 names 如果写成1: chicken,模型学到的类和验证时对不上,mAP 直接归零。另一种可能是验证集路径写错,模型在空目录上验证。

解决:先确认 txt 里第一列的最大值,再确认 data.yaml 的 names 索引范围,两者必须一致。然后手动检查val路径下是否有图像,路径是相对path拼接的,拼错了不会报错但会静默失败。

5.2 现象:显存溢出,训练中途 OOM

原因一般是imgsz或batch设太大,或者图像本身分辨率远超imgsz,预处理时占用额外显存。鸡舍图像如果是 4K 原图,直接喂进去很容易爆。

解决:先把batch减半试,还爆就降imgsz。另外可以在训练前统一把图像缩放到接近imgsz的尺寸,减少预处理开销。如果显存实在小,用yolov8n加batch=4也能跑,只是慢。

5.3 现象:mAP 虚高但实际推理漏检严重

原因是训练集和验证集重叠。如果train和val指向同一个目录,模型在验证时见到的都是训练过的图,mAP 会虚高到 0.95 以上,但换一批新图就原形毕露。

解决:老老实实切分训练集和验证集,按 8:2 或 9:1,确保验证集的图没在训练里出现过。切分时用随机种子固定,方便复现。

5.4 现象:密集小鸡场景下框大量重叠、NMS 后只剩几个

原因是小鸡扎堆时目标框高度重叠,NMS 的 IoU 阈值默认 0.7,会把相邻的鸡当成同一个目标抑制掉。

解决:推理时调低 NMS 的 IoU 阈值,比如设到 0.5 甚至 0.4,让重叠框保留更多。命令里加iou=0.5。但阈值太低会引入重复框,需要在验证集上试几个值找平衡。另外训练时可以用close_mosaic参数在最后几轮关闭马赛克增强,让模型更适应密集场景。

5.5 现象:转换后的 txt 全是空文件

原因是 XML 里的类别名和脚本里的classes列表对不上,所有目标都被continue跳过了。或者 XML 的object节点路径写错,findall返回空。

解决:先跑 3.3 节的类别统计脚本,确认 XML 里的实际类别名;再检查 XML 结构,有些标注工具的根节点不是annotation,findall("object")会找不到。打印一个 XML 的前几行看看结构,比猜快得多。

6. 把 2970 张用到极致:数据增强、类别平衡与推理阈值调优

数据量 2970 张,单类目标,说多不多说少不少。如果只是跑一遍默认训练,mAP 可能停在 0.85 左右;但把增强策略和推理参数调一调,同样的数据能再挤出几个点。这一章讲三个具体技巧,都是我在实际项目里验证过有效的。

6.1 针对小鸡密集场景的增强参数

YOLO 默认的增强里,马赛克(mosaic)对小目标检测帮助很大,它把四张图拼成一张,变相增加了小目标的出现频率。但鸡只场景有个特殊点:小鸡颜色和背景(垫料、笼具)接近,颜色抖动(hsv_v)太强反而会让模型学偏。我一般会把默认增强改成这样:

yolo detect train \ data=dataset/data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=960 \ batch=8 \ hsv_h=0.015 hsv_s=0.5 hsv_v=0.3 \ degrees=5 translate=0.1 scale=0.5 \ mosaic=1.0 close_mosaic=15 \ project=runs/chick name=exp2

hsv_v=0.3比默认的 0.4 略低,减少亮度扰动;scale=0.5允许图像缩放,模拟不同拍摄距离;close_mosaic=15在最后 15 轮关闭马赛克,让模型在真实分布上收尾。imgsz=960是因为小鸡在原图里占比小,提高输入分辨率能显著提升小目标召回,代价是训练变慢,但 2970 张的数据量扛得住。

6.2 用验证集反推最佳置信度阈值

训练完拿到 best.pt,别直接用默认 conf=0.25 推理。鸡只检测的置信度阈值对结果影响很大:阈值高了漏检,低了误检。正确做法是在验证集上扫一遍阈值,找 F1 最高的点。下面这段脚本干这个:

from ultralytics import YOLO model = YOLO("runs/chick/exp2/weights/best.pt") results = model.val(data="dataset/data.yaml", conf=0.001, iou=0.5) # 从验证结果里提取不同置信度下的 P/R/F1 # ultralytics 的 val 会输出曲线数据,也可用 predict 手动扫 for conf in [0.1, 0.2, 0.3, 0.4, 0.5]: r = model.val(data="dataset/data.yaml", conf=conf, iou=0.5) print(f"conf={conf} mAP50={r.box.map50:.4f} mAP50-95={r.box.map:.4f}")

跑完看哪个 conf 的 mAP50-95 最高,就用那个值做部署。注意conf=0.001那次是为了拿完整的 P-R 曲线,实际部署用扫出来的最优点。这个步骤花几分钟,比拍脑袋设 0.25 靠谱得多。

6.3 类别不平衡与难样本的处理

这份数据集只有一类,不存在多类不平衡,但存在「易样本 vs 难样本」的不平衡。大部分鸡只清晰可辨,少数扎堆、遮挡、模糊的样本才是决定模型上限的关键。如果发现召回上不去,可以把difficult=1的样本单独拎出来,在训练时给更高权重,或者干脆复制一份加入训练集。另一个技巧是调box和cls的损失权重,YOLO 默认box=7.5 cls=0.5,如果定位不准就加大 box,如果分类混淆(虽然单类不太会)就加大 cls。这些参数在训练命令里加box=8.0 cls=0.6即可。

最后说个我自己的习惯:每次拿到新数据集,先花半小时把校验脚本、转换脚本、类别统计跑一遍,再花十分钟用最小模型跑 10 个 epoch 看 loss 是否正常下降。这四十分钟能挡掉后面 80% 的玄学问题。数据集这东西,质量比数量重要,2970 张标注干净的鸡只图,比三万张糊成一团的强得多。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询