
简介这份PDF文档总结了一种基于深度学习的病鸡识别系统开发方案面向智慧养殖、计算机视觉方向的研究者与开发者。针对笼养鸡场病鸡难以实时监测的痛点文档详细阐述了使用工业相机从鸡栏侧上方采集图像、使用LabelImg标注整鸡及鸡头鸡身区域、并构建包含病鸡与健康鸡数据集的过程。技术层面覆盖SSD单阶段目标检测网络用于定位鸡体及局部区域再融合多个区域特征并采用CNN网络完成病鸡分类识别同时介绍了数据扩增方法与模型在服务器端的应用部署。文档基于实际现场实验展示了整鸡精确定位和病鸡实时识别的效果可作为深度学习和数据分析类课程论文、课题研究或实际项目开发的参考文献与专业指导。资源包共1个pdf文件压缩包大小约592KB已有366人浏览学习。这份文档浓缩了从场景分析、数据采集标注、模型设计训练到现场部署验证的完整科研链路既包含目标检测与图像分类的关键算法选型也覆盖多区域特征融合等实现细节。文档内容结构清晰包括数据采集与分析、模型设计与训练、实验结果等模块适合高校学生、研究人员及养殖行业技术者快速建立病鸡识别系统认知并借鉴其数据制作、模型调优与部署思路。1. 病鸡识别系统开发最难的不是模型而是病鸡这个类怎么定义给养殖场做智能巡检系统这几年我最深的感受是病鸡识别难不在模型而在病鸡本身不是一个稳定的类别。同样是发病有的鸡垂头闭眼有的离群呆立有的羽毛蓬松个体差异比品种差异还大。基于深度学习的病鸡识别系统开发真正要做的第一件事是把兽医的经验换算成图像特征再顺着采集、标注、训练、推理、告警这条路把它落到鸡舍的摄像头流上。这篇笔记就是这条完整链路适合打算自己动手做 AI 养殖项目的工程师也给准备给养殖场交付小规模智能巡检方案的团队一个参考。2. 病鸡图像数据怎么攒把兽医经验转成可计算特征任何深度学习检测系统数据质量决定模型上限。病鸡识别尤其明显因为病不是独立物体而是鸡的体态、行为、羽毛状态在某一时刻的叠加。我在做这个项目时第一步不是架设摄像头而是拉上驻场兽医一起定出一份什么样的鸡算病鸡的标注规范。这个规范如果不提前定好后面标注返工的成本比训练翻车还高。2.1 采集设备与拍摄参数你能拍到什么模型就只能学什么层叠式鸡笼里病鸡在画面里往往只占几十个像素。如果摄像头分辨率只有 640一只缩在笼角的病鸡在图像里就是一坨浅色噪点什么模型都救不回来。所以采集这一环的要求是宁可图片文件大一点也要保证鸡的躯干在图中至少占 100×100 像素。常见做法是装 1080P 以上的固定摄像头利用鸡舍顶部支架斜向下 30 度视角拍摄。这个角度能把一排笼位收入画面也能看到鸡的头部、颈部和翅膀状态。手机拍摄补充细节样本时保持横屏、关闭美颜和滤镜滤镜会改变羽色纹理傍晚自然光下拍出来的病鸡样本到了人工照明场景下就失灵。拍摄时段要刻意覆盖四种光照白天自然光、白天人工照明、夜间红外灯、清晨半暗环境。很多项目后来翻车都是因为训练集里只有白天照片部署到鸡舍后一开红外灯漏检率直接翻倍。采集档位分辨率帧率用途固定机位全景1080P15fps巡检抓帧、行为观察人工补拍特写1200 万像素静态单张扩充病鸡细节样本夜间红外1080P IR10fps夜间巡舍、凌晨发病监测2.2 标注规范病鸡的三个真阳性特征与两类噪声样本病鸡最常见的视觉特征有三类第一是精神萎靡表现为垂头、闭眼、长时间趴在角落不动第二是体态异常表现为羽毛蓬松逆立、颈缩、翅膀下垂第三是肩颈部或泄殖腔周围羽毛被粪便粘连。基于深度学习的病鸡识别系统开发在做标注时不需要把每种病分那么细只需要先定义两个检测类别healthy 和 sick。如果还要监测病程恶化再加一个 dead但 dead 和重度 sick 在单帧图像里很难分建议留给后面的时序模型处理不要一上来就分得太碎。标注工具常见是 LabelImg 或 Labelme输出格式建议直接准备成 YOLO 需要的 txt。这条环节最容易踩的坑是只标病鸡而把健康鸡全部空着。检测模型会把所有没标注的区域当成背景于是健康鸡在模型眼里变成了未知异常。后面训练时你会看到置信度 0.9 的误报刷屏。正确做法是每张图把所有清晰可见的鸡都框出来标签按规范和健康状况填哪怕训练时并不需要单独输出 healthy 类别。2.3 把 labelme 标注转成 YOLO 格式转换脚本及小目标过滤Labelme 导出的是 JSON 格式的多边形或矩形坐标不能直接喂给 YOLO 训练。这里需要一段转换脚本同时做两件事把多边形换成外接矩形过滤掉面积小于阈值的标注。小目标过滤很关键一张全景图里只有 8×8 像素的病鸡框人眼都难确认训练时只会给模型引入噪声。import json from pathlib import Path def labelme_to_yolo(json_path: Path, out_dir: Path, min_side: int 32) - None: labelme 的 JSON 标注转 YOLO 训练 txt min_side: 短边小于该像素值的标注直接丢弃 with open(json_path, encodingutf-8) as f: data json.load(f) img_w, img_h data[imageWidth], data[imageHeight] class_map {healthy: 0, sick: 1} # 按实际标注类名调整 lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue pts shape[points] xs [p[0] for p in pts] ys [p[1] for p in pts] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) w, h x_max - x_min, y_max - y_min if w min_side or h min_side: print(f丢弃过小目标 {label} {w:.1f}x{h:.1f}) continue x_center (x_min x_max) / 2 / img_w y_center (y_min y_max) / 2 / img_h bw w / img_w bh h / img_h lines.append(f{class_map[label]} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) out_path out_dir / (json_path.stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) # 遍历 json 目录执行 # for j in Path(labels_json).glob(*.json): # labelme_to_yolo(j, Path(labels_yolo))这段脚本的核心逻辑是从 JSON 里取每个标注的外接矩形再做相对图片宽高的归一化。min_side 建议先设 32因为主流训练图 640 下32 像素约等于 5% 的图片宽度再小基本就是噪声。class_map 要和你后续在 data.yaml 里声明的类别顺序完全一致否则模型学习了错乱的类别映射推理结果会变成看不懂的数字。2.4 数据增强与类别均衡一张病鸡样本怎么顶三张用养殖场里病鸡是少数一个万羽鸡舍健康的鸡占绝大多数。如果训练集里 sick 标签远少于 healthy模型会学会偏向预测健康。常见做法是先把病鸡样本做离线增强水平翻转、亮度对比度扰动、局部裁剪放大。这里有个血泪经验不要做垂直翻转和任意角度旋转。鸡不会倒立也不会横着站角度超过 30 度后数出来的样本已经不像鸡了增强反而制造出错误先验。import albumentations as A import cv2 # 针对病鸡小目标设计的增强组合 transform A.Compose([ A.HorizontalFlip(p0.5), # 左右对称不引入反生理姿态 A.RandomBrightnessContrast(p0.8), # 模拟不同时段进光量变化 A.CLAHE(clip_limit2.0, p0.3), # 凸显羽毛与冠色纹理 A.RandomSizedBBoxSafeCrop(height640, width640, p0.5), ], bbox_paramsA.BboxParams(formatyolo, min_visibility0.3)) img cv2.imread(sick_023.jpg) boxes [[0.5, 0.6, 0.07, 0.09]] # yolo格式的 bbox transformed transform(imageimg, bboxesboxes)这段增强里最关键的是 RandomSizedBBoxSafeCrop它会在保证标注框完整可见的前提下随机裁剪放大等价于把远处的小病鸡拉近到特写尺寸。min_visibility0.3 的含义是允许框被裁剪掉一部分但不能超过 70%否则模型学到的是半个鸡头。这类增强能缓解小目标问题但它不属于制造新信息真实病鸡样本还是要靠多拍不同鸡舍、不同发病日龄的照片来补。3. 病鸡识别的模型选型与训练从 YOLOv8 基线到小目标优化数据准备妥当后进入模型环节。小型养殖巡检项目里深度学习模型选型的核心矛盾是检测精度和边缘推理速度的权衡。YOLOv8 是当前最容易跑通的基线但我不会直接用它默认参数开训。病鸡检测面临的共性问题有三个小目标、类别不平衡、光照域变化这些问题需要在训练阶段就处理而不是寄希望于推理时调阈值。3.1 模型选型为什么检测模型比分类模型更适合病鸡识别分类模型只能判断整张图有没有病鸡但鸡舍画面里几乎每一帧都有鸡养殖员需要知道的是第几架第几层的鸡有问题。目标检测模型天然输出边框坐标既能定位也能通过类别区分病鸡与健康鸡。如果换做关键点检测模型比如 YOLOv8-pose还能进一步捕捉鸡头部角度、脖子弯曲程度但标注成本更高初版系统不建议直接上。在检测模型内部我一般先试 YOLOv8s。对比过 n、s、m 三档s 和 n 在病鸡这种小目标任务上的差距很小而 m 在 Jetson 边缘设备上推理速度吃紧。病鸡识别的关键特征是局部羽色纹理变化和姿态这类信息不需要深层大参数量去编码所以更小的模型反而泛化更好。用 COCO 预训练权重做迁移是值得的底层边缘、纹理卷积都能复用因为鸡舍环境太单调从零训练收敛会慢得多。3.2 用 ultralytics 跑通第一轮训练最小可复现命令训练配置写在 data.yaml 里包含训练集和验证集路径、类别数量、类别名称。命令本身不用写 Python 脚本ultralytics 命令行可以直接启动。但有几个参数需要按任务调整不能照抄默认值。yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs150 \ imgsz1024 \ batch16 \ patience20 \ device0这段命令里imgsz 从默认的 640 提到 1024 是我强烈建议的改动。鸡舍全景画面里的病鸡通常只有几十像素640 输入下病鸡特征在小感受野内就不够区分了1024 可以把可用像素提升到原来的两倍多。代价是训练显存增加如果你的显卡是 8G 以下batch 可能要降到 8或者退回 imgsz800。patience20 是早停耐心值意思是验证集指标连续 20 个 epoch 不提升就停止。这个值我习惯设大一点小目标任务的损失曲线波动明显patience 太小容易在收敛途中被误杀。训练中主要盯验证集的召回率而不是精确率因为病鸡识别宁可多框几个疑似目标也不能让病鸡漏掉。如果训练完发现召回率很高但精确率低得离谱那大概率是第 2 章里说的健康鸡漏标注问题不是模型问题。3.3 训练翻车时的三个排查切口看 loss、看 P-R 曲线、看预测图第一轮训练翻车是常态。当 loss 曲线不降反而震荡走高先看学习率——常见做法是把初始学习率调低到 0.0001 再试YOLOv8 默认的调度策略对部分小数据集偏激进。第二验证集的 mAP 好看但打开预测图发现病鸡框全打在金属笼杆上这是过拟合了笼子纹理。解决方法不是改模型而是回到数据里检查训练集是不是只在某一个鸡舍拍的背景纹理太单一。第三P-R 曲线在低召回区间掉得厉害说明模型对病鸡特征不够敏感此时优先切块训练而不是盲目加大模型效果会明显。3.4 切块训练与预测拼接把大图喂给小模型的小目标解法即使 imgsz 拉到 1280一张整舍画面里的病鸡依然可能只有 40 像素。此时常见做法是把原始大图切成若干 960×960 的 patch 分别训练与推理。切块后病鸡相对尺寸翻倍检测难度显著下降。训练时如果你的原始图是 2448×2048需要在预处理脚本里切好并同步修改标注坐标推理时要把各 patch 的结果坐标还原回原图再做一次跨 patch 的 NMS否则同一只鸡在两个 patch 边界会出现两个框。# 推理侧切块与坐标还原的简化实现 def slide_inference(image, model, patch960, overlap64, conf0.25): h, w image.shape[:2] dets [] for y0 in range(0, h, patch - overlap): for x0 in range(0, w, patch - overlap): y1, x1 min(y0 patch, h), min(x0 patch, w) crop image[y0:y1, x0:x1] result model(crop, confconf, verboseFalse)[0] for box in result.boxes: x1b, y1b, x2b, y2b box.xyxy[0].tolist() dets.append([ x1b x0, y1b y0, x2b x0, y2b y0, float(box.conf[0]), int(box.cls[0]) ]) # 对 dets 做标准 NMS合并 patch 边界重复框 return nms(dets)这个函数里 patch960 与 overlap64 是常用组合。overlap 的价值是避免病鸡正好被切在 patch 边缘导致两个 patch 里各只露出一半身体宽高小于最小检出尺寸而漏检。overlap 越大越安全但推理耗时线性增加。64 像素在 960 patch 下约等于 6.7% 的冗余实测能覆盖绝大多数边缘裁切漏检情况。切块推理的耗时需要自己测在 CPU 上跑会比较吃力如果只有 CPU建议把 patch 降到 640并放弃实时检测改做定时巡检抓帧。4. 把模型包成系统推理接口、RTSP 巡检与告警闭环训练收敛只是第一步基于深度学习的病鸡识别系统开发真正交付的是服务。模型如果只是躺在训练脚本里对养殖场没有价值。这一章把单模型扩展成小系统包含三个部件HTTP 推理接口、定时拉取摄像头的巡检脚本、告警与状态记录。部署方式按常见的项目落地路径来先在局域网内跑通再考虑外网访问。4.1 用 FastAPI 把 YOLO 封装成可并发推理的 HTTP 接口养殖场地形复杂摄像头往往和算法服务器不在一台机器上。常见做法是把模型封装成独立服务巡检端只做图像采集和请求转发。FastAPI 比 Flask 更适合这类场景它是异步框架多路视频同时请求时不会轻易阻塞。# app.py 病鸡识别推理服务 import io from fastapi import FastAPI, File, UploadFile import numpy as np from PIL import Image from ultralytics import YOLO app FastAPI(titlechicken-health-api) model YOLO(best.pt) # 训练导出的权重 app.post(/predict) async def predict(file: UploadFile File(...)): data await file.read() img np.array(Image.open(io.BytesIO(data)).convert(RGB)) # 病鸡类置信度阈值 0.3健康类 0.6这里是初筛宁漏勿错交给后级过滤 result model(img, conf0.3, verboseFalse)[0] dets [] for box in result.boxes: dets.append({ class: int(box.cls[0]), conf: float(box.conf[0]), bbox: [round(float(x), 2) for x in box.xyxy[0].tolist()] }) return {count: len(dets), detections: dets} if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)代码里最值得注意的点是 model 对象在服务启动时加载一次而不是每次请求都重新加载这是一个常见的性能坑。推理函数设了 conf0.3 的低阈值这不是最终告警阈值只是让接口做全量初筛。最终是否告警由后面的时序状态机决定这样既不会漏检也不会因为低置信度框而疯狂推送。上传的图片用 PIL 转成 RGB是因为 YOLO 训练时用 RGB 输入如果用 OpenCV 的 BGR 直接送进去颜色通道顺序错乱特征几乎全废。4.2 多路 RTSP 巡检从摄像头拉流到定时抓帧鸡舍无法做到每一秒都推理一是算力不允许二是持续报告没有意义。常见方案是定时巡检比如白天每 30 分钟抓一次画面夜里每 1 小时抓一次。巡检脚本只需要抓单帧不需要持续解码视频流用 OpenCV 的 VideoCapture 读一帧后立刻释放连接既省内存也防止上报的画面过时。# patrol.py 定时巡检并调用推理接口 import cv2 import requests import time RTSP_URL rtsp://user:pass192.168.1.64:554/ch1 API_URL http://127.0.0.1:8000/predict def check_frame(): cap cv2.VideoCapture(RTSP_URL) ok, frame cap.read() cap.release() if not ok: print(拉流失败检查摄像头地址) return _, encoded cv2.imencode(.jpg, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) resp requests.post(API_URL, files{file: encoded.tobytes()}, timeout10) dets resp.json()[detections] sick [d for d in dets if d[class] 1] if sick: print(f当前画面检出疑似病鸡 {len(sick)} 只) notify(sick) # 通知函数见 4.3 if __name__ __main__: while True: check_frame() time.sleep(1800) # 30 分钟一次这个巡检脚本是极简版本实际项目里建议用 schedule 库或 Supervisor 守护避免脚本意外退出后无人拉活。编码 JPEG 时质量参数设 90 是一个平衡质量太低会压缩掉病鸡羽毛的纹理细节太高则每帧图片体积过大局域网内虽然不慢但多路并发时请求排队会明显。巡检间隔参数的设定不能拍脑袋如果鸡舍是密闭式白羽鸡养殖发病传播快间隔建议缩短到 10 到 15 分钟如果是普通散养舍30 分钟就够。4.3 告警与落库把像素坐标映射成养殖员能看懂的笼位模型输出的是图像坐标养殖员不关心坐标数字只想知道哪一栋哪一列哪一层。常见做法是做一张静态映射表把每个摄像头画面按像素区域划分到对应的笼架编号。这个划分需要现场测量一个粗略的方法是采集一张标准画面后在图上标记每个笼位边框的像素范围存成 JSON推理时用检测框中心点查表即可。还要处理一个问题单次巡检检出不一定真的是病鸡鸡可能只是低头喝水被误判。常见做法是连续确认。我在项目里维护一个内存字典记录笼位 目标类别的出现次数只有连续 3 次巡检都命中同一个笼位才发告警。这样能过滤掉大量瞬时抖动误报。告警通道可以直接用企业微信机器人 webhook比自建 IM 省事也方便现场养殖员在手机端查看。# 连续命中后的告警确认状态机 from collections import defaultdict hits defaultdict(int) CONFIRM_TIMES 3 # 同一笼位连续巡检命中次数 def confirm_sick(pen_id: str) - bool: hits[pen_id] 1 return hits[pen_id] CONFIRM_TIMES def reset_pen(pen_id: str) - None: hits.pop(pen_id, None)在巡检脚本里每次检出病鸡先通过 bbox 中心映射出 pen_id再调用 confirm_sick。返回 True 才发送告警。这里有一个小坑bbox 中心点每次会抖动几个像素导致 pen_id 在不同笼位的边界处跳变连续计数永远到不了 3。解决方法是把 bbox 中心量化到笼位网格的格子内再取整例如把像素坐标除以网格宽度后取整作为稳定键。这段状态机代码虽然小却是整套系统被养殖员持续信任的关键也是网上很多病鸡识别源码包没写透的部署环节。5. 病鸡识别落地避坑清单反光、红外、漏标与告警疲劳这一章集中写我在这个方向上踩过的坑每一条都是现场问得最多的问题。它们不属于算法问题不调参数也救不回来只能按工程手段处理。把这些避坑经验写全是可以让后来者少走一个月弯路的内容。5.1 场景因素金属笼反光与夜间红外色调偏移现象白天训练和验证 mAP 都很高部署到鸡舍后漏检率骤增尤其是靠近窗户的一排笼位病鸡框频繁抖动甚至丢失。原因金属笼具在直射光下产生镜面反光反光区域的亮白纹理把鸡的羽色和轮廓洗掉了。饲料粉尘还会在笼架表面上形成灰白色膜模型在训练集里没见过这种局部过曝 脏污的组合检测失败很正常。解决安装摄像头时让镜头轴线与光照方向成一定夹角避免正对窗户和强光源。固定支架略向下倾斜 15 度使金属笼反光在画面里退居到背景边缘。数据层面加入随机亮度和对比度增强并把白天不同朝向的拍摄样本混合进训练集。现象夜间开启红外灯后模型把所有画面都预测为背景有时干脆崩溃输出空列表。原因红外图像本质是灰度域虽然有伪彩色渲染但色彩通道分布与白天图像差距极大。训练集若全是可见光图像模型学到的是可见光色彩特征面对色相完全不同的红外画面相当于域切换。解决把红外画面单独作为一类训练数据混合进训练集时保持比例不低于 20%。如果摄像头支持双光切换就在切换瞬间打一个时间戳让巡检脚本按模式使用不同的置信度阈值。这类问题不是换一个大模型能解决的属于典型的域偏移只能靠数据覆盖。5.2 数据因素漏标健康鸡导致模型躺平误报现象训练时只标注病鸡健康鸡完全没有标签。训练后模型对画面里所有鸡都输出病鸡置信度集中在 0.7 到 0.9看起来非常自信。原因检测模型把没有标注的区域默认为背景负样本。健康鸡在画面里占据大面积却未标注模型无法把它们归入背景于是它们成了未知前景在计算 loss 时被当作病假阳性不断修正。这是我在做病鸡识别系统时收获最大的一次翻车问题不在网络结构而在标注方针。解决回到标注阶段把每张图所有可见鸡都按真实健康状况打上标签。即使业务上只关心 sick 类别healthy 也必须存在。如果健康鸡和病鸡比例悬殊训练时会倾向预测 healthy此时可以对病鸡类别做上采样或引入 loss 权重但最常见的还是先把 healthy 框标全再谈类别均衡。5.3 部署因素巡检周期与告警频率之间的失衡现象系统部署第一周每天推送几十条发现病鸡告警养殖员看了一天就关掉了通知系统形同虚设。原因单帧检测本质存在随机误报同一位置附近的目标可能在连续巡检中反复出现如果每次巡检都告警告警疲劳会迅速杀死一个本身有效的系统。这和我之前说的连续确认状态机是同一件事。解决把抓帧频率和告警频率拆开。抓帧可以 10 分钟一次图片推理后只更新内存状态连续 3 次或滑动窗口内命中率达到 50% 以上才真正发告警。同时要保留重置机制比如某笼位连续 24 小时没有新命中就清掉计数避免历史命中导致永续告警。这样改完后告警量从每天 50 条降到每周 3 到 5 条而这 3 到 5 条基本都能对上实际病鸡养殖员又重新开始看通知。6. 从单帧检测到行为序列给病鸡识别系统加上时间上下文模型本身只能回答这帧里有没有疑似病鸡但病鸡的判定天然需要时间上下文。一只健康鸡低头喝水单帧看起来可能和垂头病鸡没有差别而真正的病鸡会连续多帧保持异常姿态。给系统加时间上下文是精度已经到瓶颈时期成本最低的提升方式也是我从几个失败项目里保留下来最有效的手段。具体做法是维护一个滑动窗口。在巡检抓帧频率下记录同一笼位每一次推理的检出类别和置信度窗口长度设 10 次巡检窗口内检出病鸡的次数占比达到 50% 就触发告警。一个简化实现如下from collections import deque class SickWindow: def __init__(self, win_size10, alarm_ratio0.5): self.history deque(maxlenwin_size) self.alarm_ratio alarm_ratio def push(self, is_sick: bool) - bool: self.history.append(1 if is_sick else 0) ratio sum(self.history) / len(self.history) return ratio self.alarm_ratio这个窗口的 win_size 和 alarm_ratio 是互相关联的。巡检间隔 10 分钟时win_size10 相当于一个 100 分钟的观察期适合检测慢性鸡病如果是急性传染病可以把巡检间隔压到 5 分钟并设 win_size645 分钟内就能确认异常。这些参数需要结合养殖密度、值班人力来确定我先给出一组能用的初始值再按现场告警数据反向调。最后说验证方法。我评估病鸡识别系统最常用的指标不是 mAP而是 P-R 曲线和每百次巡检误报率。上线前要做的第一件事是把验证集按时间段拆成早上、正午、傍晚、夜间四组分别测召回率。往往白天数据很漂亮傍晚和夜间掉得厉害。这时候你会明白病鸡识别系统开发最不变量的是光照域变化不是模型结构。我自己的习惯是每调完一组参数先拿夜间红外照片过一遍再决定是否部署这个习惯帮我拦下了三次本该翻车的交付。希望帮到你。本文还有配套的精品资源点击获取