简介:一套面向养殖场肉鸡识别场景的YOLO目标检测数据集,适合目标检测初学者、农业智能化算法工程师及养殖项目开发者直接使用。数据集中包含大量标注好的鸡只位置,采用Pascal VOC格式的xml与jpg图片一一对应,可供yolov5、yolov7、yolov8等主流算法直接训练,无需额外转换格式。资源共1001个文件,包括500个xml标注文件、500张jpg图像以及1个txt说明文件,压缩包大小为883.41MB;图片与标注同名匹配、目录结构清晰,便于快速划分训练集和验证集,也能较好覆盖真实养殖场景下的检测需求,且涵盖不同光照、角度和饲养密度下的鸡只姿态,有助于提升模型泛化能力。当前已有708人学习或下载,可用于鸡只计数、行为监测、养殖管理系统等方向,节省自行采集与标注的时间成本。配套博文还提供了数据集构成与模型检测效果说明,帮助读者更好评估和部署。
1. 养殖场肉鸡目标检测数据集:它解决的并不是“能认出鸡”的问题
如果你手里有一批养殖场的监控视频,想做一个自动数鸡、鸡群健康监测或者出栏统计的系统,那么这份YOLO养殖场肉鸡目标检测数据集就是你最需要的那瓶“场景燃料”。和COCO这类通用数据集不同,它拍的是真实鸡舍里的肉鸡,俯视视角为主,鸡群密集、互相遮挡,还有低照度和地面反光的干扰;在这些条件下,通用预训练模型往往只能框出最显眼的几只,漏检率高到没法在生产环境里用。这份数据集适合正在做农业AI监控、畜牧数字化项目,或者想在目标检测里练出场景适配能力的开发者。它不负责教你YOLO是什么,它负责让模型在“鸡叠着鸡”的画面里把每一只鸡的位置和数量交给你。
2. 为什么养殖场检测难:密集、遮挡、低照度与YOLO格式的底层逻辑
2.1 养殖场场景的三个检测难点,以及它们如何冲击通用模型
肉鸡养殖场的目标检测和常规的车辆、行人检测有本质区别,至少有三个维度是通用模型扛不住的。第一是密集,平养中后期的鸡舍里,每平方米能站十来只鸡,鸡与鸡之间几乎没有间隙,标注框大量重叠。第二是遮挡,俯视摄像头往下拍,上层的鸡挡住下层的鸡,很多目标只能露出头或者半个背脊。第三是低照度和低对比度,鸡舍为了模拟夜间休息会开暗光灯,白羽肉鸡在水泥地面的反光下轮廓很淡,尤其雏鸡阶段,绒毛和地面颜色几乎融在一起。
这三个条件放在一起,对目标检测模型产生的直接冲击是:特征提取阶段,模型很难找到稳定的局部纹理;在密集区域,NMS(非极大值抑制)会把靠得很近的几只鸡合并成一个框;在低对比度区域,浅层网络学到的边缘特征会被噪声淹没。这也是为什么很多人拿通用权重直接做迁移学习,loss能降,但可视化结果里漏检一片。用这份数据集训练,本质上不是让模型学会“鸡长什么样”,而是让它学会“在这种特定光线、特定视角、特定密度下,鸡的边界在哪里”。
2.2 YOLO标注格式解构:数据集的真实面貌和检查标注质量的方法
下载这份数据集后,解压出来通常就是标准的YOLO格式:一个images目录放着图片,一个labels目录放着同名txt标注文件。每个txt文件里的每一行对应一个目标,格式是固定的五列:类别id、归一化中心点x、归一化中心点y、归一化宽度w、归一化高度h。归一化指的是全部除以图片宽高,所以坐标值都在0到1之间。这和VOC的像素级xml标注不同,YOLO格式里你拿不到“左上角坐标”,只有相对位置,训练时模型读取的是相对值,推理时再按输入尺寸换算回像素。
拿到数据集后我建议先抽查标注质量,不要直接开训。常见问题是:有人把像素坐标直接除以短边,长边方向的坐标就会大于1,模型训练时loss曲线非常奇怪;还有人把类别id从1开始而不是从0开始,模型会多出一个永远学不会的类别。检查方法很简单,写个脚本遍历所有标注文本,先看坐标范围,再看空标签分布:
import os import glob label_dir = "labels" bad_files = [] empty_files = [] out_of_range_files = [] for txt_path in glob.glob(os.path.join(label_dir, "*.txt")): lines = [line.strip() for line in open(txt_path) if line.strip()] if not lines: empty_files.append(txt_path) continue for line in lines: parts = line.split() if len(parts) != 5: bad_files.append(txt_path) break cls_id, cx, cy, w, h = parts if not (0 <= float(cx) <= 1 and 0 <= float(cy) <= 1): out_of_range_files.append(txt_path) break print("标注格式异常:", len(bad_files)) print("空标签文件:", len(empty_files)) print("坐标越界文件:", len(out_of_range_files))这段代码的核心逻辑是快速暴露三类问题:行数不是五列、坐标超过0到1的范围、以及没有任何标注的图片。空标签文件不一定有问题,如果它对应的是纯背景图,那这是合理的负样本,后面训练时也有用。但如果大量图片都为空,说明采集时漏标严重,需要人工回补,否则模型会把“无标注区域”默认当作背景,漏检率会很高。我一般会在训练前把这三类统计结果打印出来,确认异常文件占比低于1%再动手。
2.3 训练前先看清目标尺寸分布,别等训练完才发现小目标问题
养殖场场景里有一个长期被忽略的问题:肉鸡在图片上通常很小。如果监控摄像头装得高、视角覆盖整个鸡舍,一只成年肉鸡在1080P画面里往往只有60~80像素宽,雏鸡可能只有30像素。而YOLO的默认输入尺寸是640×640,下采样到最后一层特征图时,这些小目标只对应几个像素,特征几乎被抹平。所以训练前必须统计一遍标注框的尺寸分布,用数据决定imgsz怎么设置。
import os import glob from PIL import Image import numpy as np label_dir = "labels" image_dir = "images" areas = [] for txt_path in glob.glob(os.path.join(label_dir, "*.txt")): img_path = os.path.join(image_dir, os.path.splitext(os.path.basename(txt_path))[0] + ".jpg") if not os.path.exists(img_path): continue with Image.open(img_path) as im: img_w, img_h = im.size with open(txt_path) as f: for line in f: parts = line.split() if len(parts) != 5: continue _, cx, cy, w, h = parts box_w = float(w) * img_w box_h = float(h) * img_h areas.append(box_w * box_h) areas = np.array(areas) print("标注框总数:", len(areas)) print("面积中位数:", np.median(areas)) print("小于32x32的框占比:", np.mean(areas < 32 * 32))这个脚本把归一化坐标还原回像素面积,重点看两个数字:面积中位数和小于32×32的框占比。如果小于32×32的占比超过三成,说明这份数据集里小目标数量很大,训练时把imgsz从640提到960甚至1280是更合理的选择。代价是显存占用成倍上涨,显存有限的显卡需要同步降低batch。这一步看起来是预处理,实际上决定了后续评估的基线,值得认真跑一遍而不是凭感觉。
3. 把数据集跑起来:划分、YAML配置与YOLOv8训练全流程
3.1 关键一步:按采集时间分组划分训练集和验证集,而不是随机划分
监控视频类数据集有一个隐藏陷阱:相邻帧之间高度相似。如果按照普通图片数据集的做法随机切分,同一段视频的连续几十帧会同时出现在训练集和验证集里,模型在训练时已经见过验证集里几乎相同的画面,验证指标虚高。等到部署时遇到新画面,精度立刻打回原形。这也是很多养殖场检测项目“训练时mAP很高,一上线就漏检”的首要原因。
比较稳妥的划分方式是按采集时间段分组。假设采集数据时按日期或鸡舍编号建了目录,每个目录里是一批连续帧,划分时应该按目录切分,而不是按单张图片切分。下面这个脚本就是按目录名分组,把整组文件夹划入训练集或验证集:
import os import random import shutil src_images = "images" src_labels = "labels" dst_root = "dataset" # 按目录名分组,目录名通常是采集时间和鸡舍编号 groups = {} for img_name in os.listdir(src_images): group_name = os.path.dirname(os.path.relpath(os.path.join(src_images, img_name), src_images)) groups.setdefault(group_name, []).append(img_name) random.seed(42) group_names = list(groups.keys()) random.shuffle(group_names) val_ratio = 0.15 val_count = max(1, int(len(group_names) * val_ratio)) for idx, group_name in enumerate(group_names): split = "val" if idx < val_count else "train" os.makedirs(os.path.join(dst_root, split, "images"), exist_ok=True) os.makedirs(os.path.join(dst_root, split, "labels"), exist_ok=True) for img_name in groups[group_name]: label_name = os.path.splitext(img_name)[0] + ".txt" src_img = os.path.join(src_images, group_name, img_name) src_lbl = os.path.join(src_labels, group_name, label_name) if not os.path.exists(src_lbl): continue shutil.copy(src_img, os.path.join(dst_root, split, "images", img_name)) shutil.copy(src_lbl, os.path.join(dst_root, split, "labels", label_name))这个脚本把整个分组划入同一边,保证验证集和训练集不会出现来自同一时间段的高度相似帧。代码里的val_ratio取0.15,如果总样本量小,可以提高到0.2;如果样本量很大,0.1也够用。分组的关键是group_name的提取方式,我这里用相对路径模拟目录分组,实际项目中如果文件名本身就带时间戳,也可以按文件名前缀提取时间段。
3.2 编写YAML配置并启动训练:参数怎么选,每个参数管什么
完成划分后,在数据集根目录写一个data.yaml。YOLO训练时通过这个文件定位图片和标注,同时声明类别数。这份数据集大概率是单类别肉鸡,我从通用写法调整为单类示例:
# dataset/data.yaml path: /your/absolute/path/to/dataset # 修改为你的实际路径 train: train/images val: val/images nc: 1 names: 0: broilerpath字段在YOLOv8里必须写成绝对路径,或者相对当前工作目录的路径,写错会直接报错找不到训练图片。train和val字段是相对path的子路径。nc为1,names列表只包含一个类别名。如果解压后发现labels里的类别id有0也有1,那说明标注信息里其实有两个类,需要先检查一下每一条标注第一列的最大值,适当调整nc,否则训练会报索引越界。
启动训练我用YOLOv8官方CLI,下面是一套适合养殖场场景的基础配置:
yolo detect train \ data=dataset/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=960 \ batch=8 \ device=0 \ patience=30 \ project=runs \ name=broiler_v1这里几个参数的取舍逻辑我展开说一下。模型选yolov8s而不是nano,是因为密集小目标场景下nano的参数量容易欠拟合,s尺寸是精度和速度的平衡点;如果显卡只有6GB显存,可以退回yolov8n并接受一部分精度损失。imgsz选960是基于前面第2章的小目标面积统计,如果统计结果中小框占比不高,用640能省将近一半的训练时间。batch设8是因为960输入下显存占用大约是640的2.25倍,默认16大概率爆显存。patience设为30表示连续30个epoch验证集指标不上升就提前停止,避免无效的长时间训练。device=0表示用第一张显卡。
3.3 训练日志、权重选择与中断恢复:别让训练白跑
训练启动后,输出目录里会实时生成results.csv和weights目录,每轮epoch的loss、精度、召回率都记录在案。我习惯每隔十来个epoch看一眼results.png里的曲线图,重点看验证集mAP50的走向。正常情况是前20轮快速上升,后面趋于平缓;如果验证集loss在某个节点突然反弹,多半是学习率过高或者数据增强过强,而不是模型结构问题。
训练结束后,weights目录下会有best.pt和last.pt两个权重。best.pt是在验证集上表现最好的那一轮权重,last.pt是最后一轮权重。部署时默认选best.pt,但如果你计划继续训练或者做进一步微调,last.pt更适合作为起点。如果训练中途因为断电或者显存溢出中断了,可以直接续跑,不需要从头再来:
yolo detect train resume model=runs/broiler_v1/weights/last.ptresume功能会自动读取上一次训练的状态和超参数,继续从断点处开始。这里有一个我踩过的坑:训练初期就中断然后resume,如果原训练命令里的imgsz和batch参数影响了优化器状态,恢复后的曲线会有一截异常抖动。所以训练开始前尽量确认环境稳定,resume是后悔药,不是常规操作。
4. 评估不只盯mAP:按养殖场场景对齐验证指标与Bad Case定位
4.1 从results.csv里读出的不是分数,而是问题
很多人在训练结束后只看控制台打印的mAP50就下结论,这在养殖场场景里不够。mAP50表达的是“预测框和真实框的IoU超过0.5的检测质量”,它允许框有一定偏移,这对鸡群计数场景确实够用。但如果只盯着mAP50,会掩盖小目标漏检和密集区域合并这两个问题。评估养殖场肉鸡检测模型,我建议同时看五个维度:
| 指标 | 关注的维度 | 在养殖场场景的解读 |
|---|---|---|
| precision | 查准率 | 框出来的目标里有多少确实是鸡,误检率在这里体现 |
| recall | 查全率 | 真正的鸡有多少被框出来,漏检率在这里体现 |
| mAP50 | 常规定位精度 | 落点是否够准,能吃但不算严苛 |
| mAP50-95 | 严格定位精度 | 框的贴合程度,密集场景里会显著偏低 |
| 小目标AP | 小尺寸目标表现 | 雏鸡和远处鸡只的可信度,比mAP50更说明问题 |
用下面脚本直接读取results.csv,把关键指标打印出来:
import csv import matplotlib.pyplot as plt rows = list(csv.reader(open("runs/broiler_v1/results.csv"))) header = rows[0] data = rows[1:] epochs = [int(r[0]) for r in data] # 假设列索引:1=train/box_loss, 6=val/box_loss, 8=metrics/mAP50(B) train_loss = [float(r[1]) for r in data] val_map50 = [float(r[8]) for r in data] plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.plot(epochs, train_loss, label="train box loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.subplot(1, 2, 2) plt.plot(epochs, val_map50, label="val mAP50") plt.xlabel("epoch") plt.ylabel("mAP50") plt.legend() plt.tight_layout() plt.savefig("my_metrics.png")scripts执行后重点看两个信息:验证集mAP50是否还在涨,以及train box loss和验证集mAP50之间有没有背离。如果train loss一直降但mAP50停滞,说明过拟合,需要增加数据增强或减少epoch。如果mAP50已经到0.9以上,但后面第4.2节的视频抽帧实验里漏检仍然明显,那大概率是划分阶段出了问题,验证集太“简单”,需要回到第3.1节重新划分。
4.2 视频抽帧做一次“仿真部署”:指标欺骗不了真实画面
训练集和验证集都是静态图片,而养殖场实际使用场景是监控视频。视频和图片之间有一个关键差异:相邻帧的目标位置变化小,但光照可能连续变化,镜头可能轻微抖动。我最常做的一项验证是,从未参与训练的一段视频里抽100帧出来,跑一次批量推理,保存结果图,然后人工翻看:
yolo detect predict \ model=runs/broiler_v1/weights/best.pt \ source=test_frames/ \ imgsz=960 \ conf=0.25 \ save=Trueconf设为0.25,意思是置信度低于0.25的预测框会被删除。养殖场场景里如果漏检太严重,可以往下调到0.15,但这会带来更多误检。我一般用0.25跑一遍,重点观察三类画面:鸡群密度最高的一帧、逆光导致对比度很低的一帧、以及远处鸡只较小的一帧。如果这三类画面里漏检都很明显,说明模型没学好这个场景,而不是置信度阈值的问题。
4.3 Bad Case定位:三步找到模型“看不清”的具体原因
定位bad case有三个步骤。第一步,跑验证集并保存预测的json结果,命令里加save_json=True,预测框会输出为label格式文件,和真实标注一一对应。第二步,写一个脚本把预测框和真实框同时画在图上,预测框标红,真实框标绿,重叠区域变成半透明,人工扫一遍就能发现哪些位置漏检、哪些位置误检。第三步,把置信度阈值降到0.05再跑一遍,把所有低置信度预测框也画出来,用于判断“模型其实检测到了,只是不敢确认”还是“完全没检测到”。
这两者的处理策略完全不同。如果大量目标都能被低置信度框覆盖,说明模型特征提取没问题,只是分类置信度校准不好,部署时可以把conf调低,或者用更多数据把这个场景的置信度分布拉高。如果低置信度也完全没有框,说明那些区域的感受野或者特征表达缺失,必须提高输入分辨率或者补数据。这一步是整个评估流程里最有价值的动作,能直接决定下一步是该调阈值还是该补训练。
5. 避坑记录:训练肉鸡检测模型最容易翻车的五个细节
5.1 三个直接影响结果的参数翻车
第一条,imgsz保持默认640不动,小目标漏检严重。现象是训练结束mAP50不低,但验证集视频里远处的鸡几乎全漏,尤其是雏鸡阶段。原因很简单:前面统计过小于32×32的框占比超过三成,这些目标在640输入下对应到最后一层特征图只剩几个像素,网络根本没有足够信息去判断边界。解决方法是把imgsz提到960或1280,同时batch降下来。用960后显存占用如果超了,就把batch从16降到8,或者开启梯度累积。第二条,随机划分训练验证集,验证指标虚高。现象是训练过程一切正常,mAP50到了0.93,但一换新视频段就崩。原因是监控视频连续帧高度相似,随机划分让同一段视频同时出现在训练和验证里,模型只是在“回忆”。解决方法是按采集时间段分组划分,这部分逻辑我写在第3.1节,代码直接可用。第三条,YOLOv8默认数据增强太强,验证集上精度波动大。现象是每轮的验证mAP50忽高忽低,训练集loss降得很快但泛化不行。原因在于默认参数里的HSV增强会随机改变色调饱和度,而养殖场画面本身是低饱和度的暗光环境,增强把颜色弄成了模型不认识的风格。解决方法是关闭或者调低hsv增强,我常用的做法是把hsv_h从默认0.015降到0.005,hsv_s从0.7降到0.3,fliplr从0.5降到0.2,甚至不开水平翻转,因为鸡舍左右两侧的光线不对称。
5.2 两个“玄学”问题,其实都有明确原因
第四条,模型把食槽、运料车、走道识别成鸡。现象是单张画面里出现五六个误检框,位置全在鸡舍的设备边缘。原因是训练集图片里所有帧都包含这些设备,模型学会了“设备边缘的纹理特征和鸡的边缘相似”,但没有足够多不含鸡的负样本让它知道“这些场景不是鸡”。解决方式是在训练集里加入几十张不包含任何肉鸡的鸡舍背景图,labels目录里放空的txt文件,让模型把这部分区域学习为背景。如果数据集本身没有这类图,可以自己从视频里截取设备特写帧补进去,加入后误检率通常能降一半以上。第五条,推理时两只挨得很近的鸡被合并成一个框。现象是NMS之后保留的框数量比实际鸡数少,尤其密度高的画面。原因是这两个目标的框IoU超过NMS阈值,置信度低的那个被直接抑制掉。解决方法是调高NMS的IoU阈值,训练阶段用nms_iou控制推理时的合并程度,从默认0.5提高到0.65左右;同时把conf阈值适当从0.25提到0.3,让低置信度的预测框不参与后续合并。双管齐下,密集场景的精度通常会明显变好。
6. 进阶技巧:小目标切片推理与数据增强的实战组合
6.1 为肉鸡场景定制的数据增强参数
如果已经按前面的流程训练了一版,还想继续提升小目标召回率,优先考虑两件事:数据增强策略和大图切片推理。数据增强方面,我在默认参数基础上做了几处针对养殖场场景的调整。把mosaic保持开启,它能把四张图拼成一张,模拟更密集的鸡群;把hsv增强调弱,保留原有暗光色彩分布;scale增强开到0.3,模拟不同摄像头高度下的目标大小变化;translate增强开到0.1,模拟镜头小幅抖动。训练命令里加一段augment参数组合:
yolo detect train \ data=dataset/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=960 \ batch=8 \ hsv_h=0.005 \ hsv_s=0.3 \ hsv_v=0.2 \ fliplr=0.2 \ scale=0.3 \ translate=0.1 \ mosaic=0.8这一套参数的核心思路是:保留养殖场真实的光线特征,通过几何变换模拟不同机位,不通过剧烈颜色变换制造模型不认识的样本。mosaic调配到0.8而不是1.0,是为了保留一部分正常构图的样本,避免模型过度依赖拼接纹理。
6.2 大图切片推理:解决“图很大目标很小”的终极方案
监控画面的分辨率经常是1920×1080甚至更高,YOLO推理时会把整张图缩放成960或640,等于把原始信息压缩了一半以上。切片推理的思路是先把原始大图切成重叠的小块,对每一块单独做检测,再把结果合并回原图坐标系。切片边长一般取512或640,重叠率取0.2左右,既能保证目标不会被切成两半,又不会产生太多重复检测。用SAHI这类现成工具做切片推理,命令很直接:
sahi predict \ --model_type yolov8 \ --model_path runs/broiler_v1/weights/best.pt \ --source test_video.mp4 \ --slice_width 512 \ --slice_height 512 \ --overlap_width_ratio 0.2 \ --overlap_height_ratio 0.2 \ --postprocess_class_threshold 0.25 \ --postprocess_nms_threshold 0.6切片推理的代价是推理耗时成倍增加,但它把小目标的实际像素放大到了模型能看清的范围。在鸡舍监控这种对实时性要求不极端、更看重精度的场景里,用切片推理换回的小目标召回率提升非常可观。如果部署设备算力有限,可以只对画面中鸡群密集的局部区域做切片,其他区域用整图推理。
我在处理养殖场检测项目时养成的一个习惯是,拿到任何新数据,第一步先做小目标面积统计和按时间分组划分,然后才让训练跑起来。这个流程看起来多花一小时,但能省掉后面调参和返工的好几天。希望你也在自己的数据集上先跑一遍这个流程,再决定怎么训练。希望帮到你。
本文还有配套的精品资源,点击获取