☰
鸡状态检测实战:COCO标注转YOLOv8训练与避坑指南
2026/10/11 16:00:28 网站建设 项目流程

简介:鸡状态数据集包含3749张训练集图片,可识别正常鸡与异常鸡,平均正常识别准确率约91.8%,常用于家禽养殖场、监控系统中对鸡只状态进行自动判断与预警。资源面向计算机视觉初学者、农业智能化开发者及需要构建家禽检测模型的技术人员,支持当前主流的COCO JSON格式标注,便于直接接入YOLO、MMDetection等常用框架进行训练与评估。压缩包共2000个文件,其中1997张JPG图片为原始图像样本,3个JSON文件分别对应标注信息与数据集划分配置,整体大小451.26MB。目前已有410人学习下载。通过该资源可获得一套结构清晰、标注完整的状态分类数据集,覆盖多种拍摄角度与光照条件下的鸡只图像,有助于快速验证模型效果、进行数据增强实验以及理解基于COCO格式的目标检测与分类任务流程。

1. 鸡状态数据集:盯着监控画面找病鸡,先从这 3749 张图开始

做过家禽健康监控的人都知道,鸡舍里几百只鸡挤在一起,靠人眼盯屏幕找状态异常的个体,盯不了二十分钟就花眼。这个鸡状态数据集解决的正是这个问题:它提供了 3749 张训练集图片,图片里的鸡被标注为“正常”和“异常”两类,标注格式是 COCO json。你可以用它训练目标检测模型,部署到监控系统里自动判断鸡只是否有异常,数据集的基准识别率平均在 91.8% 左右。对做智慧养殖、农业 AI 的算法工程师和学生来说,这是少数能直接拿来跑检测任务的畜牧场景数据集,不用从零开始攒数据。它的价值不在 91.8% 这个数字本身,而在于给了你一套带统一标注规范的起点,后续换模型、调参数、加自己的数据,都有据可依。

2. 读懂 COCO json 标注:字段结构、类别映射与 91.8% 的来源

2.1 COCO json 的三个顶层字段

拿到这份数据集,先别急着训练,第一步是拆开 json 看结构。COCO 格式的标注文件本质上是一个字典,顶层只有三个关键的键:images、annotations、categories。images里记录每张图片的文件名、宽高和 id;annotations里是每个目标的标注框,包括框的左上角坐标bbox、目标类别category_id、以及一个表示分割或检测用的area字段;categories则定义类别编号到类别名的映射。

这份鸡状态数据集在类别定义上,常见做法是设两个类别:0对应 normal(正常鸡),1对应 abnormal(异常鸡)。annotations里的bbox是[x, y, width, height]的形式,注意不是中心点坐标,是左上角坐标加宽高。这个细节很多人第一次用会弄错——后续转成 YOLO 格式时需要做一次坐标变换,公式是center_x = x + width/2,center_y = y + height/2,再统一除以图片宽高做归一化。

2.2 类别体系与识别率指标从哪来

标题里提到的“平均正常识别率 91.8% 左右”,我需要先说明一下这个指标的常见口径。按照数据集通常的评测方式,它一般指在验证集上对“正常鸡”和“异常鸡”两个类别分别计算识别准确率后取平均,或者在置信度阈值取 0.5 时的整体分类准确率。不是 mAP,更不是某些模型在特定测试集上的超调结果。你要复现这个数字,必须自己划分训练集和验证集,并且用相同的类别权重和置信度阈值去评估。不同划分方式下,这个数字会有几个百分点的波动,黑匣子就在这里。

在实际训练中,你会遇到一个现实问题:正常鸡的样本量远多于异常鸡。假设 3749 张图里有 3000 张是正常鸡场景、700 张是异常鸡场景,模型天然会倾向把不确定的框判成正常类,因为这样总损失更低。所以 91.8% 这个基准,通常是在测试集类别均衡或者加权评测下得到的。你复现时不要只盯着整体准确率,要看每一类的 AP 和召回率。

2.3 用 Python 快速检查标注质量的实用脚本

拿到 COCO json 后,不要直接丢给训练框架,先跑一段检查脚本,确认标注的类别分布、框数量和格式是否正常。下面这个脚本是我每次拿到新数据集都会先跑的:

import json from collections import Counter with open('annotations/train.json', 'r', encoding='utf-8') as f: coco = json.load(f) cat_id_to_name = {c['id']: c['name'] for c in coco['categories']} print('类别映射:', cat_id_to_name) img_ids = [img['id'] for img in coco['images']] ann_count = Counter() img_with_ann = set() for ann in coco['annotations']: ann_count[cat_id_to_name[ann['category_id']]] += 1 img_with_ann.add(ann['image_id']) print('标注框总数:', len(coco['annotations'])) print('各类别标注数量:', dict(ann_count)) print('有标注的图片数:', len(img_with_ann)) print('总图片数:', len(img_ids)) # 检查是否有无效框 invalid = 0 for ann in coco['annotations']: x, y, w, h = ann['bbox'] if w <= 0 or h <= 0 or x < 0 or y < 0: invalid += 1 print('非法框数量:', invalid)

这段脚本的作用是三层:第一层看类别映射是否符合预期;第二层统计每个类别的标注数量,判断是否存在严重的类别不均衡;第三层检查bbox是否有坐标小于 0 或宽高为 0 的脏数据。

我一般会重点看第二层的输出——如果两个类别的标注数量差距超过 5 倍,训练前就要安排类别权重或者采样策略,不然后面训练阶段再发现就晚了。另外json模块在 3749 张图这种量级下读取速度很快,但如果以后数据量到几万张,建议换成orjson,读取耗时能缩短一半以上,对大项目是实打实的收益。标注工具方面,COCO json 格式可以直接被 cvat、doccano 这些开源标注平台导入导出,后续补充新数据时不用重新发明轮子。

3. 用 YOLOv8 训练鸡状态检测器:COCO 转 YOLO、数据划分与训练命令

3.1 COCO json 转 YOLO txt 转换脚本

虽然 ultralytics 训练框架内部也支持 COCO 格式,但把数据统一转成 YOLO txt 格式是更稳妥的做法——因为后续换模型、做可视化、跑老代码时,YOLO 格式的兼容性最好,不需要每次重新解析 json。转换脚本的重点是坐标变换和归一化:

import json import os def coco_to_yolo(json_path, output_dir, img_dir): with open(json_path, 'r', encoding='utf-8') as f: coco = json.load(f) os.makedirs(output_dir, exist_ok=True) img_id_to_name = {img['id']: img['file_name'] for img in coco['images']} img_id_to_size = {img['id']: (img['width'], img['height']) for img in coco['images']} anns_by_img = {} for ann in coco['annotations']: image_id = ann['image_id'] anns_by_img.setdefault(image_id, []).append(ann) for image_id, anns in anns_by_img.items(): file_name = img_id_to_name[image_id] img_w, img_h = img_id_to_size[image_id] txt_path = os.path.join(output_dir, os.path.splitext(file_name)[0] + '.txt') with open(txt_path, 'w') as f: for ann in anns: cat_id = ann['category_id'] - 1 # COCO类别id从1开始,YOLO从0开始 x, y, w, h = ann['bbox'] cx = (x + w / 2) / img_w cy = (y + h / 2) / img_h w_norm = w / img_w h_norm = h / img_h # 归一化后的中心点cx和宽度w_norm f.write(f'{cat_id} {cx:.6f} {cy:.6f} {w_norm:.6f} {h_norm:.6f}\n') coco_to_yolo('annotations/train.json', 'labels/train', 'images/train')

这段代码的关键逻辑有两点。第一,类别 id 减 1:COCO json 里 categories 的 id 从 1 开始,而 YOLO 格式要求类别索引从 0 开始,很多人在这一步栽跟头,转换完训练出来的类别会整体偏移一个位置。第二,坐标从左上角 x,y 转成中心点 cx,cy 并除以图宽高做归一化,保证 txt 文件里所有数值都在 0 到 1 之间,这是 YOLO 训练框架的硬性要求。

转换完成后,去任意一个 txt 文件里看一眼格式,每行应该是类别id cx cy w h五个数字,用空格分隔。如果发现某一行有负数或大于 1 的数,回去查对应图片的标注,多半是 json 里的bbox越界了,属于标注源头的问题。

3.2 数据目录与 data.yaml 配置

ultralytics 训练时不直接读 json,而是读数据集的 yaml 配置文件。你需要把数据整理成下面这样的目录结构:

chicken_status/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

images和labels下每个子目录一一对应,同一张图的图片路径和标签路径的文件名必须完全一致,只是扩展名不同。划分比例上,我一般按 8:1:1 分训练集、验证集、测试集,3749 张图大概对应 2999 张训练、375 张验证、375 张测试。划分时建议用 sklearn 的train_test_split设置固定的random_state,保证结果可复现。

data.yaml的内容如下:

path: /path/to/chicken_status train: images/train val: images/val test: images/test nc: 2 names: 0: normal 1: abnormal

这里的path建议写绝对路径,因为 ultralytics 在训练时经常会因为相对路径解析出错而报找不到数据集。nc是类别数,names的映射必须和转换脚本里生成的类别 id 完全一致。如果abnormal是类别 1,但 yaml 里写反了,模型学到的语义就会错位。

3.3 启动训练:关键参数与迁移学习

数据准备好后,训练命令如下:

yolo detect train \ model=yolov8s.pt \ data=/path/to/chicken_status/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ patience=20 \ lr0=0.01 \ augment=True

参数说明:model=yolov8s.pt表示加载 COCO 预训练权重,这是迁移学习的标准做法。虽然数据集本身是家禽场景,但 COCO 预训练权重里已经学会了边缘、纹理等通用视觉特征,用这个起点训练会快很多,也比从头训练收敛得更稳定。imgsz=640是输入分辨率,鸡在监控画面里通常只占几十到一百像素,建议不低于 640;如果显存吃紧可以用 512,但小目标漏检率会明显上升。batch=16看显存调,12G 显存的卡跑 yolov8s 用 16 没问题。patience=20是早停参数,验证集指标连续 20 轮不提升就自动停。

训练完成后的输出在runs/detect/train/目录下,重点关注weights/best.pt和last.pt,best.pt是验证集上表现最好的权重,部署时用这个。训练日志里的P(精确率)、R(召回率)、mAP50是判断模型好坏的核心指标。如果在 100 轮内mAP50能达到 0.9 左右,那基本就复现了标题里 91.8% 的水平甚至更高;如果只有 0.7 左右,先别急着调参,回顾一下数据划分和标注质量问题,问题多半不在模型参数上。

4. 验证与调参:把 91.8% 跑成自己可复现的指标

4.1 在验证集上计算 mAP 与混淆矩阵

训练完模型,要用验证集独立评测,别用训练时的输出自欺欺人。ultralytics 提供了现成的验证命令:

yolo detect val \ model=runs/detect/train/weights/best.pt \ data=/path/to/chicken_status/data.yaml \ conf=0.25 \ iou=0.5 \ imgsz=640

conf=0.25表示只保留置信度大于 0.25 的检测框,iou=0.5是计算 mAP 时判定预测框是否匹配真实框的 IoU 阈值。输出会显示mAP50、mAP50-95以及每个类别的 AP。我一般先看每个类别的 AP,如果正常鸡 AP 高但异常鸡 AP 低,说明模型对异常特征的区分度不够,不是整体精度的问题。

如果想更直观地看到模型把什么认成了什么,让 ultralytics 生成混淆矩阵图:

yolo detect val \ model=runs/detect/train/weights/best.pt \ data=/path/to/chicken_status/data.yaml \ save_conf=True \ save_txt=True

生成的confusion_matrix.png会显示模型把真实正常鸡预测成了什么、把真实异常鸡预测成了什么。关键要看的格子是:真实异常鸡被预测为正常鸡的比例(漏检率)和背景被预测为异常鸡的框数(误报率)。这两个数字直接决定了这套系统在实际监控中可不可用。漏检率高于 10% 的话,“监控家禽鸡的状态是否有异常”这个目标基本落空,异常鸡没被报警比误报更致命,因为人工复核可以跳过误报,但漏检等于彻底漏掉。

4.2 置信度阈值怎么影响正常/异常两个类

conf阈值调到多少,在鸡状态这个任务上不是随便定的。我在项目里的经验是:正常类检测框的置信度普遍在 0.7 到 0.95 之间,异常类由于表现形态多样(炸毛、蜷缩、站立不稳都可能算异常),置信度会被拉低到 0.4 到 0.7。如果你把全局阈值设成 0.5,确实能过滤掉大量低质量检测,但同时会把不少真异常鸡的框一起过滤掉。所以最常见的处理方法是:推理时把conf设成 0.25,然后在后处理逻辑里对不同类别做差异化决策——正常类要置信度超过 0.7 才认为可信,异常类只要超过 0.3 就触发一个待确认告警,由人工复核。这样整体准确率仍然能维持在 91.8% 附近,但漏检率比用一个统一阈值低得多。

4.3 从 COCO 预训练权重迁移到鸡状态任务

标题说这份数据集支持 COCO json 格式标注,它的价值也体现在这里——可以直接对接那些在 COCO 上预训练过的模型权重,做迁移学习。上面用的yolov8s.pt就是 ultralytics 团队在 COCO 上预训练好的权重。除了 YOLOv8,如果你更习惯用 DETR 系列(比如 DEIM),这些模型也提供 COCO 预训练权重,YOLO 格式的数据同样可以喂进去,只是训练命令要对齐它的数据接口。我建议第一次跑通用 YOLOv8s 或 YOLOv8m,这两个模型在 3749 张图的数据量下训练耗时可控(单卡约 40 到 90 分钟),性能余量又足够支撑后续调参。用更大模型如 YOLOv8x,在这个数据量下很容易过拟合——训练损失漂亮,但验证集准确率反而变差。如果你发现mAP50高但mAP50-95低得离谱,那就是典型的过拟合征兆,先别加数据增强,直接换回小模型验证。

5. 避坑与排查:标注噪声、类别失衡和监控视角的三类典型翻车

5.1 数据划分不当:同一只鸡同时进了训练集和验证集

现象:训练时 mAP 一路涨到 0.95 以上,但一到实际视频里检测就频繁漏检,表现和验证指标完全不匹配。

原因:这是数据集划分的经典问题。如果划分时只是随机打乱图片,同一只状态异常的鸡可能出现在训练集的一张图里,也出现在验证集的另一张图里(因为连拍或视频抽帧会重复出现个体),模型相当于已经“见过”过验证集的目标,评测指标虚高。这在监控场景的数据里特别常见,因为视频连续帧之间高度相关。

解决:划分数据前先看图片文件名。如果文件名带有时间戳或摄像头编号(比如cam01_1640995200_001.jpg),就按摄像头编号分组,同一个摄像头拍摄的图片只能全部进训练集或全部进验证集,保证验证集和训练集里的鸡没有重叠个体。做法是先把独立的摄像头或拍摄批次作为分组单元,用sklearn.model_selection.GroupShuffleSplit按组划分,而不是随机打散。

5.2 异常样本太少:类别不均衡把召回拉到脚踝

现象:整体准确率看着有 90% 以上,但仔细看类别指标,正常鸡的 AP 是 0.95,异常鸡的 AP 只有 0.55,异常类的召回率(真实异常鸡被检测出来的比例)极低。

原因:标注分布天然不均衡。3749 张图里,正常鸡占大头,因为鸡舍里大部分时间大部分鸡都是正常的,异常状态本身是小概率事件。模型在训练时见到正常样本太多,对“背景”和“正常鸡”的边界学得很好,但异常样本的特征空间只被少量标注覆盖。

解决:常见做法有三个,优先级从高到低。第一是调损失权重,在 YOLOv8 里给异常类设置更高的cls权重;第二是离线复制异常类的标注框,把异常目标的图片在训练时多做几次增强后重复采样,本质是过采样;第三是专门针对异常类做 mosaic 增强,把它和正常样本拼在同一张图里。我实测下来最有效的是第二和第三的组合,异常类的 AP 能从 0.55 拉到 0.75 以上,代价是正常类的精确率会掉一两个点,整体平衡后仍然值得。

5.3 标注框不贴边:框的质量决定了 AP 的上限

现象:训练过程中损失值能降但降不到低位,验证集的 mAP 始终卡在 0.75 附近上不去。画出来看,预测框经常比真实目标大一圈或者偏移半个鸡身。

原因:数据集的标注框质量参差不齐。鸡在密集场景下互相遮挡,标注员很难精准框出每一只鸡的边缘,有些框把隔壁鸡的翅膀也包进去了,有些只框了鸡身漏了头颈。检测模型的回归头天然会把预测框向“标注框的平均尺寸”收敛,标注框如果普遍偏大,预测框就跟着偏大。

解决:用脚本统计所有标注框的宽高比分布,画直方图。正常情况下鸡的宽高比应该在 0.7 到 1.5 之间,如果出现大量宽高比大于 2 或小于 0.3 的框,多半是标注噪声。处理方案是先用cvat打开标注重新抽查一部分图片,确认框的贴合程度;如果不想全部重新标,一个折中的办法是在训练时把box_loss的权重调高,让模型更关注回归精度。我个人经验是,这类“差一点点”的标注噪声对 mAP 的拖累非常明显,找标注员把最差的那部分图重标一遍,收益往往比折腾模型结构更大。

5.4 COCO json 读取慢:解析方式决定训练前处理效率

现象:每次跑训练前,光加载 json 就要几十秒,改个参数重新训练又要再等一次,来回折腾很烦人。

原因:json.load在解析大文件时是纯 Python 逐字符扫描,3749 张图如果每张图有几十个标注框,json 文件体量会到几十 MB,解析耗时自然慢,而且这个过程在每次启动训练时都要重复执行。

解决:用上面第三节里的脚本,把 COCO json 一次性转成 YOLO txt 格式。txt 文件是纯文本,每张图一个文件,训练框架读取时只加载当前 batch 用到的标签文件,省掉了全量解析的开销。如果你还是想保留 json 作为统一的数据格式,可以用orjson替代标准json模块,它是 Rust 实现的解析器,读取速度大概能提升一倍。另外注意,在文件数量较大的场景,用lmdb存储图像和标签能进一步加速数据加载,但在这个数据量级下没有必要。

5.5 监控视频部署误报多:训练图片和真实视角不一致

现象:在验证集上准确率 90% 以上,部署到鸡舍监控视频里,每隔几分钟就冒出一次异常告警,点开一看是正常的鸡在理毛或者低头啄食。

原因:训练集中的图片大概率是白天光照良好、近距离或中等距离拍摄的。但真实监控视频是固定的俯视角,鸡群密度更高、个体更小、运动模糊和灯光变化更常见。模型在训练分布内表现优秀,到了分布外就翻车,这是检测任务里最常见的鲁棒性陷阱。

解决:做数据增强之外,还有一个更有效的办法:直接从目标监控摄像头抽帧,挑几百张覆盖不同光线、不同拥挤程度和不同姿态的图,用你训练好的模型先自动检测生成预标注,再用 cvat 人工复核修正,合入训练集重新训练一轮。这一轮微调之后模型对目标场景的适应力会有肉眼可见的提升。我自己的习惯是部署后两周内持续收集误报样本,累积到 200 张左右就增量训练一次,迭代三次后漏检率和误报率能降到可接受水平。

6. 进阶:从单图检测到视频帧采样预警

训练好单图检测模型只是第一步,真正放到监控场景里,需要把它改造成输出“告警事件”而不是输出“一个个框”。我一般用滑窗帧采样的方法:从视频流里每 10 帧取 1 帧做检测,统计一个 30 帧的窗口内异常类别出现的总次数,只有超过阈值才触发告警。这样能滤掉单帧误检的噪声,同时避免每帧都跑推理带来的算力浪费。

import cv2 from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') cap = cv2.VideoCapture('chicken_house_01.mp4') fps = cap.get(cv2.CAP_PROP_FPS) frame_id = 0 abnormal_hits = [] while True: ret, frame = cap.read() if not ret: break if frame_id % 10 == 0: results = model(frame, conf=0.3, imgsz=640) abnormal_count = 0 for box in results[0].boxes: if int(box.cls[0]) == 1 and box.conf[0] > 0.3: abnormal_count += 1 abnormal_hits.append(abnormal_count) if len(abnormal_hits) > 30: abnormal_hits.pop(0) # 滑窗内异常鸡的检出次数达到5次以上才报警 if sum(h > 0 for h in abnormal_hits) >= 5: print(f'第 {frame_id // fps:.1f} 秒: 检测到鸡状态异常') frame_id += 1 cap.release()

这段代码的逻辑是:每 10 帧检测一次,把当前帧的异常鸡框数记录到队列里,队列长度 30(约 5 秒的视频时间);如果 30 次检测中有超过 5 次出现了异常鸡,就输出一条告警信息。两个核心参数需要按你的场景调:frame_id % 10对应采样间隔,鸡舍画面变化慢可以放宽到 15 或 20 帧采样一次,降低算力开销;告警阈值 5 次,对应约 1 秒内有两次以上检测到异常,偏低会增加误报,偏高会漏掉短暂异常。

这套流程走下来,我个人的教训是:这个数据集真正卡人的地方永远不在模型结构,而在标注一致性和场景迁移。同样的“异常鸡”,标注员在图片上看是异常,到了视频里换个角度可能就不像异常了。你需要在项目开始前就把“什么程度算异常”的定义用图文写成标注规范,让所有参与标注和审核的人对齐口径。另外 91.8% 的基准识别率是一个不错的起点,但生产环境要求通常更高,把它当作基线而不是目标,持续用线上误报和漏检数据做迭代,才是把这个数据集价值用尽的正确姿势。希望这些经验能帮你少走几步弯路。

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

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

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

立即咨询