
简介面向家禽养殖智能化与计算机视觉应用这份鸡状态数据集提供正常鸡与异常鸡的图像样本及标注可用于训练鸡只状态识别模型帮助监控家禽异常情况。数据采用COCO JSON格式标注便于接入主流目标检测与图像分类框架。压缩包共2000个文件包含1997张JPG鸡只图像和3个JSON标注文件整体大小约451.26MB图像覆盖不同养殖环境下的鸡只形态JSON文件按COCO格式记录类别与边界框等标注信息可直接用于模型训练、验证与测试。据页面统计已有410人浏览学习。对从事农业视觉、智慧养殖或目标检测研究的开发者而言可以省去数据采集和标注环节快速开展鸡只正常/异常识别实验并基于91.8%左右的平均正常识别率进一步调优适合作为算法验证或智慧养殖项目原型的基础数据集。1. 鸡状态数据集是什么养鸡场监控为何需要一套自己的标注集家禽养殖的规模化程度越高巡检压力越大。一个人管几万只鸡靠肉眼在监控墙上发现“某只鸡状态不对”基本是碰运气。鸡状态数据集要解决的就是把“看监控”这件事从人眼换成模型用3749张训练集图片让检测模型学会把画面里的每只鸡框出来并区分它是正常状态还是异常状态。这个数据集在异常检测场景里很典型——样本量不大、类别不平衡、单类目标多、背景脏和车辆行人检测完全是两回事。适合正在做农业视觉、养殖场智能化监控、边缘端巡检方案的从业者也适合想拿真实业务数据练手目标检测工程化的人。我用这个数据集跑过一轮完整流程从解析标注到训练再到监控逻辑下面按落地顺序把能复用的细节讲清楚。2. 读懂3749张训练集与COCO JSON标注数据格式与实际分布拿到一份数据集先别急着开训练脚本。我见过太多人直接拖进训练框架里跑到一半发现标注框数量对不上或者类别名写错导致训练集全废。第一步永远是“先把数据吃透”三个事必须做看目录结构、解析标注JSON、统计类别与样本分布。2.1 目录结构与标注文件的对应关系这套鸡状态数据集的常见组织方式和绝大多数COCO数据集一致训练前先确认目录里是不是这个结构chicken_status/ ├── images/ │ ├── train/ # 3749张训练图片 │ └── val/ # 验证集数量和划分比例以实际目录为准 ├── annotations/ │ ├── instances_train.json │ └── instances_val.json └── README.md拿到数据集后第一步不是改代码而是先在命令行确认图片和标注文件能对上。COCO格式的精髓在于所有信息都装进一个JSON文件里图片路径、宽高、目标类别、框坐标、分割掩码全在里面但图片文件本身是独立存放的JSON只存“引用”。我一般会先用一个快速脚本核验遍历images目录下的文件再遍历JSON里注册过的文件名找差集。# 快速核验图片与标注注册是否一致Linux/macOS ls images/train | sort /tmp/img_list.txt python3 -c import json with open(annotations/instances_train.json) as f: data json.load(f) names [img[file_name] for img in data[images]] print(\n.join(sorted(names))) | sort /tmp/ann_list.txt diff /tmp/img_list.txt /tmp/ann_list.txt echo OK图片与标注一一对应这段先用shell把文件名列表抽出来再让Python从JSON里把file_name字段导出成另一份列表diff命令比对两边的差异。逻辑很简单但能一次性揪出最坑的问题——标注文件里登记的图片根本不存在或者多出来一些没标注的图片。diff输出为空说明两边完全一致可以继续往下走。如果输出了差异行那行就是翻车的根源缺了图片意味着训练时读到空文件多出来的图片意味着这些样本永远不会参与训练。2.2 从COCO JSON里读出类别与分布两段够用的Python代码COCO JSON的结构是嵌套的最外层有五个键info、licenses、images、annotations、categories。实际训练时只关心后三个。images里的每个元素是图片元数据annotations里每条记录对应一个目标框categories则定义类别ID到名称的映射。多数初学的人在解析时会犯一个错直接用类别名去过滤标注但COCO内部用的是纯数字ID类别名只是个显示标签。先解决这个映射问题再统计分布。import json from collections import Counter with open(annotations/instances_train.json) as f: coco json.load(f) # 1. 构建类别ID - 类别名 的映射 cat_id2name {cat[id]: cat[name] for cat in coco[categories]} print(类别映射:, cat_id2name) # 2. 统计每个类别的目标框数量 ann_counter Counter() images_with_ann set() for ann in coco[annotations]: cat_name cat_id2name[ann[category_id]] ann_counter[cat_name] 1 images_with_ann.add(ann[image_id]) # 3. 统计每张图片的目标数分布判断单图多目标程度 img_target_count Counter() for img in coco[images]: img_id img[id] count sum(1 for ann in coco[annotations] if ann[image_id] img_id) img_target_count[count] 1 print(各类目标框数量:, dict(ann_counter)) print(目标数为0的图片数量:, img_target_count[0])这段代码干了三件事第一把categories列表转成字典用数字ID直接查类别名第二遍历所有annotations每次取category_id再映射成名字累加到计数器里第三反过来统计每张图带多少目标框images_with_ann和img_target_count[0]告诉我们有多少图片是完全没标注的。第二和第三两个指标直接决定训练策略——如果目标数为0的图片比例很高说明数据集里混入了大量背景负样本训练时要专门处理如果单张图平均目标数超过10那batch_size就得调小因为一张图的loss贡献会被放大。2.3 验证集划分COCO的val与自行划分的差别这个数据集给的是训练集3749张但验证集怎么来标准COCO数据集会自带instances_val.json但很多行业数据集只给训练集验证集要自己划。很多人图省事从训练集里随机抽20%当验证集这在数据量小的时候会让指标虚高——因为鸡舍场景里连拍的多张图高度相似随机切分会让模型“背题”。正确做法是按照“场景”切分同一个鸡舍、同一时段拍的图片必须分到同一边。import json, random random.seed(42) with open(annotations/instances_train.json) as f: data json.load(f) # 假设文件名格式为: pen1_20250110_093000.jpg前两段是场景标识 def scene_key(filename): parts filename.split(_) return f{parts[0]}_{parts[1]} # 按场景分桶 scene_buckets {} for img in data[images]: key scene_key(img[file_name]) scene_buckets.setdefault(key, []).append(img[id]) # 挑20%的场景做验证 scene_ids list(scene_buckets.keys()) val_scenes set(random.sample(scene_ids, kint(len(scene_ids) * 0.2))) val_img_ids set() for scene in val_scenes: val_img_ids.update(scene_buckets[scene]) print(f总场景数: {len(scene_ids)}验证场景: {len(val_scenes)}) print(f训练图片: {len(data[images]) - len(val_img_ids)}验证图片: {len(val_img_ids)})这段代码的价值在于引入scene_key这个概念把文件名里的场景特征提取出来保证同场景的图片不会被拆散。参数上random.seed(42)固定随机种子让每次运行划分结果一致方便复现和横向对比。random.sample的比例20%不是拍脑袋目标检测里验证集够用就行多了浪费训练数据。如果文件名规则和这个不一样只需修改scene_key函数里的切分逻辑。3. 用YOLOv8训练鸡状态检测模型从COCO转换到训练的最短路径COCO JSON本身是个通用标注协议但直接喂给YOLO训练是不可能的。YOLO系列要的是每个样本一个txt文件、一行一个目标、class x_center y_center width height的归一化格式。所以训练前必经的一步是转换格式这也是整个流程里最机械、最容易出错的活。3.1 为什么选YOLOv8而不是纯COCO API系模型COCO格式本身不限制模型但鸡状态检测是个典型的工业落地场景不是算法竞赛。选YOLOv8的理由不是因为它精度最高而是因为它三件事做得最顺第一数据集接口直接吃目录结构不用自己写DataLoader第二官方预训练权重丰富可以迁移学习第三部署导出到ONNX/TensorRT极其顺滑监控场景基本都要上边缘盒子。我一般不看榜单上的高精度模型因为COCO官方榜单的模型大多是实验室参数动辄几十亿参数放到鸡舍的嵌入式设备上推理延迟直接崩。YOLOv8的n和s版本在精度和速度之间平衡得最好。而且YOLOv8自带yolo命令行工具把标注转成YOLO格式后一条命令就能开训。3.2 COCO转YOLO尺寸换算与坐标归一化转换的核心就一个公式YOLO的框坐标是(x_center / img_w, y_center / img_h, box_w / img_w, box_h / img_h)而COCO给的是(x_min, y_min, box_w, box_h)是像素坐标。注意COCO的bbox字段里宽高是像素值中心点需要自己算。很多人直接拿x_min当中心点用这是第一个翻车点。import json import os def coco_to_yolo(coco_json_path, output_dir): 把COCO标注转为YOLO格式的txt文件 with open(coco_json_path) as f: coco json.load(f) # 先建映射image_id - file_name, 宽高 img_info {} for img in coco[images]: img_info[img[id]] img # 建类别映射COCO的category_id转为0开始的连续id cat_id_map {} for idx, cat in enumerate(coco[categories]): cat_id_map[cat[id]] idx os.makedirs(output_dir, exist_okTrue) for ann in coco[annotations]: img img_info[ann[image_id]] img_w, img_h img[width], img[height] # COCO bbox: [x_min, y_min, width, height] 是像素值 x_min, y_min, w, h ann[bbox] x_center x_min w / 2 y_center y_min h / 2 # 关键一步归一化到0~1区间 x_center_norm x_center / img_w y_center_norm y_center / img_h w_norm w / img_w h_norm h / img_h # 处理越界框裁剪到[0,1]范围 x_center_norm min(max(x_center_norm, 0.0), 1.0) y_center_norm min(max(y_center_norm, 0.0), 1.0) w_norm min(max(w_norm, 0.0), 1.0) h_norm min(max(h_norm, 0.0), 1.0) yolo_cls_id cat_id_map[ann[category_id]] line f{yolo_cls_id} {x_center_norm:.6f} {y_center_norm:.6f} {w_norm:.6f} {h_norm:.6f}\n # 输出文件名和图片同名只换扩展名 img_filename img[file_name] txt_filename os.path.splitext(img_filename)[0] .txt txt_path os.path.join(output_dir, txt_filename) with open(txt_path, a) as out: out.write(line) print(f转换完成标注文件已写到 {output_dir}) coco_to_yolo(annotations/instances_train.json, labels/train) coco_to_yolo(annotations/instances_val.json, labels/val)这个脚本核心逻辑就三步用ann[image_id]去img_info里找到对应的图片宽高然后把COCO的左上角坐标加半宽半高算出中心点最后除以宽高完成归一化。参数上最值得理解的是x_center x_min w / 2——COCO的bbox存左上角不是中心点不补这半截框会整体偏移。越界处理的意义在于监控画面边缘经常有半只鸡出画边框超出图片范围不裁剪会在训练时让anchor匹配产生严重的损失抖动。3.3 最小训练配置与模型结构选择格式转完后需要一个data.yaml把路径和类别告诉YOLOv8。类别顺序必须和转换脚本里cat_id_map生成的id一一对应这里错了模型会在训练时报类别数不匹配错得很快但很隐蔽。# chicken_status.yaml path: /path/to/chicken_status # 数据集根目录 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 # 类别必须与转换脚本里cat_id_map的索引一致 names: 0: normal_chicken 1: abnormal_chicken训练命令一行就够yolo detect train \ modelyolov8s.pt \ datachicken_status.yaml \ imgsz640 \ epochs100 \ batch16 \ patience15 \ projectchicken_exp \ nameexp1modelyolov8s.pt的意思是加载YOLOv8s的COCO预训练权重不是从头训练。从零训一个检测头需要的数据量在这个场景下不够用预训练权重做迁移学习前20轮就能看到明显收敛。patience15是早停参数验证集指标连续15轮不增长就停省时间。imgsz640是输入分辨率监控画面通常是1080p甚至更高但训练时统一缩放到640能显著提升batch吞吐推理时再按需调高到1280这就是训练推理分辨率解耦的常见做法。4. 监控场景里的模型评估91.8%的准确率是在什么条件下测出来的“平均正常分辨率在91.8%左右”——这里指的应该是正常状态这一类别的平均识别准确率也就是精确率和召回率的综合表现。在监控场景里这个数字到底可不可信取决于三个前提评估集是不是和训练集同源、阈值怎么设的、类别不均衡有没有被掩蔽掉。4.1 评估指标的取舍不能只看mAP还得看单类别的混淆矩阵目标检测的标准评估指标是mAP0.5和mAP0.5:0.95但对鸡状态监控这种场景单一mAP会骗人。比如数据集里正常鸡占90%、异常鸡占10%模型全部预测为正常mAP可能也有0.8以上因为多数框本来就是正常鸡。你必须把“正常”和“异常”分开看算各自的精确率和召回率。from ultralytics import YOLO model YOLO(chicken_exp/exp1/weights/best.pt) metrics model.val(datachicken_status.yaml, splitval) # 打印每个类别的指标明细 print(类别名:, metrics.names) for i, name in metrics.names.items(): print(f{name}: precision{metrics.box.p[i]:.3f}, recall{metrics.box.r[i]:.3f}, mAP50{metrics.box.map50[i]:.3f})这段代码用的是YOLOv8内置的val方法metrics.box.p是一个数组按类别索引取精确率r是召回率map50是IoU阈值为0.5时该类的平均精度。如果normal_chicken的召回率是0.95但abnormal_chicken的召回率只有0.6意味着异常鸡有40%被漏检——这在监控场景里是不可接受的因为漏检一只异常鸡可能意味着它已经生病到后期才被发现。91.8%这个整体数字掩盖了类别劣势所以一定要分开统计。4.2 置信度阈值与NMS参数影响着“报警”行为的实用参数模型输出的是每个框的置信度分数最终判定是正常还是异常由设定的置信度阈值决定。在监控场景里这个阈值不是拍脑袋定的要根据“漏检代价”和“误报代价”的比值来调。异常鸡漏掉可能损失一整栏鸡而误报一次只是派人去看一眼所以阈值应该向下调。# 推理时动态调整置信度阈值 yolo detect predict \ modelchicken_exp/exp1/weights/best.pt \ sourcechicken_exp/test_imgs/ \ conf0.25 \ iou0.5 \ saveTrueconf0.25表示只保留置信度大于0.25的框比默认的0.25更低也行但最低不建议低于0.1否则全是背景误检。iou0.5是NMS的IoU阈值鸡舍里鸡群比较挤框重叠严重这个值可以放宽到0.7把同一个目标的重复框更激进地合并掉。调这两个参数产生的效果比换更大的模型来得更直接。4.3 计算mAP时最容易产生的误差来源验证集污染我评估了一个模型发现mAP高达0.95高兴了没两天部署到现场就露馅。后来复盘发现验证集是从训练集里随机抽的导致同一个鸡舍同一时段的连续帧图片被分到了两个集合里模型等于“见过”验证数据指标虚高。解决办法就是2.3节里讲过的按场景分桶切分验证集宁可验证集小一点也要保证数据独立性。这里再补一个检查方法把训练集和验证集的file_name拉出来算一下文件名重复数应为0。import json def load_img_names(json_path): with open(json_path) as f: data json.load(f) return set(img[file_name] for img in data[images]) train_names load_img_names(annotations/instances_train.json) val_names load_img_names(annotations/instances_val.json) # 检查是否有重叠文件名 overlap train_names val_names if overlap: print(f警告{len(overlap)} 张图片同时出现在训练集和验证集) else: print(OK两个集合没有重复图片)这套逻辑虽然简单但每次跑实验前都应该过一遍。注意这里只管了文件名没有管内容相似的重采样帧所以在2.3节场景切分的基础上这个脚本是对验证集独立性的第二道保险。5. 鸡状态数据集避坑指南5个翻车现场与排查思路这个数据集用起来不算难但坑全埋在细节里。每个坑都对应一套“现象 → 原因 → 解决”的排查流程我按实际踩过的顺序写。5.1 训练时loss不降反升震荡剧烈现象训练前20轮loss像心电图完全没有收敛趋势。原因最常见是学习率太高。YOLOv8默认学习率是0.01但那是在batch为16的常规配置下。鸡状态数据集只有3749张batch设到32以上时梯度累计噪声变大0.01的学习率就偏激进了。解决把学习率降到0.001或者在命令里加cos_lrTrue让学习率余弦衰减。另一个有效手段是warmup_epochs5让前5轮先用小学习率把参数稳定下来再进入正式训练。5.2 所有预测框都偏向图片的左上角现象训练能收敛但推理时框的位置明显左偏。原因COCO转YOLO时坐标归一化错了。某个环节把x_min当成了x_center直接除以宽或者忘了加w/2。解决回到2.3节的转换脚本加一段自检代码把转换后txt里的坐标乘以图像宽高还原回像素坐标和原COCO的bbox比对误差超过2像素就是转换有问题。这个脚本我每次跑新数据集都会先做一轮校验再训练。5.3 正常鸡全检对了异常鸡一个都测不出现象验证集上normal类mAP 0.95abnormal类mAP 0.3模型相当于对异常状态“失明”。原因类别严重不平衡。异常鸡在饲养场景里本来就是少数如果原始数据只标注了少量异常框模型会倾向把所有目标都预测成正常鸡因为这样loss最小。解决不要先改模型先改数据。看一下2.2节统计的类别框数比例如果异常类不足正常类的20%需要做复制粘贴增强或者对整个异常类做mosaic增强。YOLOv8的augment参数里mosaic1.0默认开着但如果数据集里异常样本太少可以手动把异常类图片复制几份同时把mosaic的透明度调低防止背景杂乱。5.4 白天验证集效果不错晚上一测就全崩现象白天拍的视频检测正常一到夜间开补光灯误报率暴增。原因数据集里的图片几乎都是白天自然光拍的模型学到的特征依赖光照条件。夜间补光灯下的鸡颜色、阴影、对比度跟白天差很多。解决在数据集里补充夜间图片。如果没有现成的先用数据增强模拟——把训练图片随机调低亮度、增加高斯噪声、提高对比度hsv_v0.4亮度增强幅度和hsv_s0.5饱和度增强幅度是两个值得改的参数默认值分别是0.4和0.5但对夜间场景不够可以提到0.6和0.7。更彻底的做法是把白天图片用颜色变换强生成“夜晚感”图片但这是最后的手段最可靠的还是去现场补拍夜间照片。5.5 PyTorch Dataloader的num_workers设为8导致CPU被打满现象训练启动时CPU占用100%GPU利用率反而只有30%一个epoch比0.7倍速还慢。原因num_workers设得太高每个worker都在做图像解码缩放CPU成了瓶颈。3749张图的数据集规模不需要那么多子进程。解决把num_workers设为4或2prefetch_factor2减少预处理队列长度。在YOLO命令里没有直接暴露num_workers参数需要修改数据集的.yaml或者用trainer回调更简单的办法是去ultralytics源码里把默认值改掉但多数情况下2~4就够。6. 从单帧检测到连续监控帧间投票与误报过滤的落地技巧模型在单张图片上能跑通只是第一步。真正的监控场景是视频流每秒钟24帧如果对每一帧都做检测和报警画面里一个阴影一闪而过就会报警一次值班人员半小时后就免疫了。让模型真正适合监控必须加一层“时序逻辑”把单帧检测结果变成时间维度上的状态判断。6.1 用帧间投票消抖而不是对每一帧报警核心思路是异常状态是一个持续过程不是瞬间事件。一只鸡状态异常通常会持续数分钟以上所以可以设计一个滑动窗口在N帧里有M帧检测到异常框才算真正报警。我这里用一个Python伪代码来表达这个状态机逻辑class ChickenMonitor: def __init__(self, window_size30, trigger_ratio0.6): self.window_size window_size # 窗口大小30帧约为1秒 self.trigger_ratio trigger_ratio # 触发比例60% self.frame_buffer [] # 帧结果缓冲 self.alarm_active False # 当前是否在报警 def update(self, detections): 每一帧调用一次传YOLO检测结果 # 这一帧里有多少个异常目标 abnormal_count sum(1 for d in detections if d.cls 1 and d.conf 0.3) self.frame_buffer.append(abnormal_count) # 只保留最近window_size帧 if len(self.frame_buffer) self.window_size: self.frame_buffer.pop(0) # 统计窗口内有异常帧的占比 frames_with_abnormal sum(1 for c in self.frame_buffer if c 0) ratio frames_with_abnormal / len(self.frame_buffer) # 阈值判定超过60%帧有异常触发报警 if ratio self.trigger_ratio and not self.alarm_active: self.alarm_active True return True # 触发报警 elif ratio 0.3 and self.alarm_active: self.alarm_active False # 状态恢复正常 return False这段代码最关键的参数是window_size和trigger_ratio。window_size30在一秒约30帧的视频里相当于“观察窗口”是一秒如果这个窗口里有60%以上的帧都检测到异常框才判定为真异常。trigger_ratio太低会频繁误报太高会漏报短暂的真实异常。实际调参时先跑一段历史视频把误报帧数和真实报警数都打印出来人工调这两个值而不是靠感觉。另一个技巧是报警触发后进入alarm_active状态后要维持一段时间直到连续多帧都检测不到异常再解除否则一帧漏检就会打断报警值班人员看不到持续的信号。6.2 用ByteTrack保持鸡的ID实现“同一只鸡持续异常”的判断帧间投票只能统计“画面里有异常”但监控里更关注的是“哪只鸡持续异常”。要用模型输出做跟踪在帧间关联同一只鸡的检测框。YOLOv8的model.track()接口已经集成了ByteTrack用法是直接传视频路径yolo detect track \ modelchicken_exp/exp1/weights/best.pt \ sourcechicken_cam_01.mp4 \ conf0.3 \ iou0.6 \ persistTruepersistTrue是关键参数它让跟踪器在帧与帧之间保持ID的连续性。跟踪ID稳定后可以给每个ID维护一个“异常计数”当同一只鸡连续超过10帧被标为异常才在画面上标记它。这样比帧间投票更精确能锁定到具体个体。6.3 误报过滤的最后一关位置先验与场景规则即使加了帧间投票和跟踪仍然会有一种场景级的误报饲料槽反光、鸡舍墙上的污渍、偶尔飞过的飞虫。这些误报可以通过把模型输出和场景规则结合来过滤。比如异常鸡通常出现在地面活动区天花板区域出现“异常框”可以直接丢弃。最常见的规则有两种一是区域过滤标注好鸡舍里的活动区域多边形框中心点落在区域外就过滤二是尺寸过滤鸡的像素大小和摄像头安装高度强相关统计正常框的面积范围面积突然大好几倍或者小到离谱的目标框大概率是误报。# 尺寸过滤示例只保留面积在[500, 20000]像素之间的框 def filter_by_size(box, img_w, img_h, min_area500, max_area20000): x1, y1, x2, y2 box area (x2 - x1) * (y2 - y1) return min_area area max_area阈值min_area和max_area需要先跑一段正常视频统计出来我的经验是统计所有真实鸡框的面积取2%分位数和98%分位数用这两个值框住正常的范围。6.4 验证整个监控流程的指标不要只看模型要看报警准确率最终检验这套流程是否好用我只看两个指标每百次报警里真正需要人工处理的次数以及真正需要处理的异常被漏报的比例。模型mAP高不代表这两个数字好帧间投票和尺寸过滤对这两个指标的贡献常常比换一个更大的模型还明显。我有一次在项目里把误报率从一晚上30次压到2次靠的不是换模型而是调了window_size45和trigger_ratio0.7加上区域过滤。模型输出只是原料时序逻辑和场景规则才是让监控系统真正可用的关键。这套数据集的3749张图片训练出来的模型足够支撑起一个能用的监控前端但距离一个能上线的监控系统还差最后这层时序逻辑。希望这些经验帮你在做养鸡场状态监控的落地时少走几步弯路。本文还有配套的精品资源点击获取