免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

YOLOv8牲畜体温监测实战:从目标检测到边缘部署

YOLOv8牲畜体温监测实战:从目标检测到边缘部署 简介一套面向毕业设计或课程设计的YOLOv8智慧农场牲畜体温异常监测系统源码、完整数据集、可视化页面与部署教程齐全下载后按README操作即可运行。系统基于YOLOv8实现牲畜目标检测与体温异常识别可直接用于计算机视觉相关专业的毕设、课设及初期项目演示。内容涵盖模型训练、验证与推理全流程可输出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、标签分布图与验证集预测结果方便答辩展示。包体共97个文件以Python脚本为主含70个py同时提供模型权重pt、运行配置xml、演示视频mp4及说明文档压缩包仅24.21MB目录结构清晰。目前已有28人学习下载代码经测试通过另含可视化界面与异常视频示例拿来即用也便于二次扩展。1. 用YOLOv8给牲畜量体温先解决的不是模型而是目标定义牲畜体温异常监测在智慧农场里一直是个“看着刚需、落地别扭”的场景。传统的耳标式体温计要逐个佩戴、定期读取应激反应大红外测温仪得人拿着靠近效率低且数据难以归档。用YOLOv8做这件事本质上是把“测体温”这个物理动作换成“检测温度映射”的视觉任务先用目标检测模型框出牲畜的关键体表区域再结合热成像或温度传感器数据做异常判断。这套路线的价值在于它把接触式测温变成了非接触式的连续监测而且可以并行处理多头牲畜。但这里有个反直觉的结论YOLOv8本身并不输出温度值它只负责“找到目标在哪”。真正测体温的是热成像相机或点温传感器YOLOv8做的是把“目标检测框”和“温度数据”做空间对齐。很多人拿到这套系统后第一反应是调模型、改权重实际上第一步应该想清楚“你检测的到底是什么部位”——是整头猪、牛的脸部还是耳根、眼窝这些接近核心体温的区域。检测目标定义错了后续温度映射全是错的。这套方案适合两类人一是做毕设或课程设计的学生需要一个完整可演示的落地点二是农场信息化的一线工程师想评估视觉测温的可行性。前者要的是“能跑通、有界面、有数据”后者要的是“准确率、误报率、部署成本”。本文按“数据集构建 → 模型训练 → 系统集成 → 部署优化 → 问题排错”这条线讲直接把参数和代码给到能复制粘贴的粒度。2. 数据集构建与预处理YOLOv8训练自己的数据集时最容易忽略的3个细节2.1 体表温度检测的目标框标注规范YOLOv8训练自己的数据集第一步不是标注而是定标注规范。牲畜体温监测的目标框和常规目标检测不一样它不追求框住整个动物而是框住“体温代理区域”。以猪为例耳根部基部的皮肤温度与核心体温相关性最高眼窝区域次之牛的方案则倾向于眼眶和鼻镜。标注时如果框了全身YOLOv8会学到“整头猪的轮廓”但这个轮廓边缘的温度和核心体温差好几度映射出来就是废数据。标注规范建议如下单类别即可类别名统一叫heat_spot或按部位拆成ear_base/eye_socket。框的边界需要收紧不要包含太多背景。标注工具常见的做法是用 LabelImg 或 X-AnyLabeling后者支持自动化预标注可以先用一个初步模型生成框再人工修正能省不少时间。数据量上单类目标每类至少 8001500 张图像分辨率建议不低于 640x640因为温度映射需要框内的像素足够清晰否则一个框里混入了背景像素取平均温度时会拉低数值。# 标注格式转换把LabelImg的VOC XML转为YOLOv8的txt格式 import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, class_names, img_width, img_height): tree ET.parse(xml_file) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # YOLO格式中心点x, 中心点y, 宽, 高均归一化 x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines class_names [ear_base] # 按你的标注规范修改 # 使用示例读取图片尺寸、转换XML、写入txt # 注意实际使用时需要先获取每张图片的宽高不能硬编码这段代码的关键在于归一化坐标计算。YOLOv8的标签格式是class_id x_center y_center width height坐标全部除以图像宽高映射到 01 区间。转换时有个容易踩的坑XML里的xmin和xmax是相对于原图的像素坐标如果你做了图像缩放或裁剪必须先记录缩放后的实际宽高用缩放后的值做归一化否则训练时标签错位mAP 直接掉十几个点。2.2 热成像与可见光图像的对齐策略如果数据集包含热成像图对齐问题就绕不开。热成像相机的分辨率通常远低于可见光常见 640x512 或 384x288而可见光可能是 1920x1080。两种方案一是直接用热成像图做检测模型输入分辨率降下来二是可见光检测、热成像数据做空间映射。前者简单但检测精度受限后者需要标定。常见做法是用 OpenCV 的仿射变换做粗略对齐前提是两路相机固定安装视场角重叠。标定流程是在场景中放几个高温点源比如加热电阻同时采集可见光和热成像帧手动点选对应的点对用cv2.getPerspectiveTransform求得单应矩阵之后运行时用同一矩阵做投影。import cv2 import numpy as np # 手动选点可见光坐标系下的点和热成像坐标系下的点至少4对 src_points np.array([[100, 100], [500, 100], [500, 400], [100, 400]], dtypenp.float32) dst_points np.array([[50, 60], [250, 60], [250, 200], [50, 200]], dtypenp.float32) # 计算单应矩阵 H cv2.getPerspectiveTransform(src_points, dst_points) # 运行时把可见光检测框映射到热成像坐标系 def map_bbox_to_thermal(bbox_vis, H): x1, y1, x2, y2 bbox_vis corners np.array([[x1, y1], [x2, y1], [x2, y2], [x1, y2]], dtypenp.float32).reshape(-1, 1, 2) transformed cv2.perspectiveTransform(corners, H).reshape(-1, 2) tx1, ty1 transformed.min(axis0) tx2, ty2 transformed.max(axis0) return int(tx1), int(ty1), int(tx2), int(ty2)这里的逻辑是先用固定点对求单应矩阵H再用矩阵把可见光框的四个角投影到热成像坐标取投影后的最小外接矩形。要特别注意的是cv2.getPerspectiveTransform要求输入是 4 个点对且这 4 个点不能有三点共线的情况。点到面的映射只有在小视差、固定安装的前提下才可靠如果两路相机安装位置差异大基线过长近处和远处的物体对齐误差会不一致这时就得考虑用标定板做立体校正复杂度上一个台阶。2.3 数据增强与数据集划分的坑YOLOv8自带增强参数但默认配置是给通用目标检测用的用在牲畜体温场景需要调整。重点看hsv_h、hsv_s、hsv_v这三个颜色扰动参数。热成像图是伪彩色的灰度值代表温度如果盲目标定色彩扰动过大模型会把“颜色变化”当成特征学习而实际部署时不同环境温度下伪彩色映射是会变的。建议把 HSV 扰动全部调低到 0.1 以下尤其是在热成像数据上。另一个坑是数据集划分。牲畜图像采集通常是“同一场景连续拍摄”帧间相似度极高如果随机划分训练集和验证集相似帧会泄漏到训练集里验证集指标虚高。正确做法是按视频片段或按个体划分同一头猪的所有帧要么全在训练集要么全在验证集。# dataset.yaml 的参考配置 path: ./datasets/livestock_temp train: images/train val: images/val names: 0: ear_base划分完成后建议额外检查一下每个类别在训练集和验证集中的数量分布避免验证集某个类别只有几十张。温度异常样本本来就稀少如果验证集中异常样本太少评估结果会完全被正常样本主导模型的“召回异常”能力根本看不见。3. YOLOv8网络结构选型与训练C2f模块对体温特征提取的影响3.1 选择n/s/m/l/x哪个版本GPU显存和帧率怎么权衡YOLOv8有五个版本从 nnano到 xextra-large在牲畜体温监测场景里选择逻辑和通用检测略有不同。体温监测不需要识别“精细类别”本质上是一个单类别检测任务模型容量不是瓶颈推理速度和部署便利性才是。在 GTX 1660 Ti 这类显卡上n 版本可以跑到 60 FPS 以上s 版本 40 FPS 左右m 版本就掉到 20 FPS 以下了。农场监控场景通常 10 FPS 就够用但如果你还跑温度映射和告警逻辑CPU 侧有额外开销建议先跑 s 版本。选择版本还要看“检测目标尺寸”和输入分辨率的关系。YOLOv8的默认输入是 640x640如果你的监控画面里牲畜单只占画面比例很小比如栏位全景可以考虑把输入分辨率提到 960 或 1280但这样推理时间会翻倍。更合理的做法是先降分辨率跑检测锁定到牲畜后用热成像 ROI 区域单独做温度分析而不是在大图上检测小目标。# s 版本的简化配置这里不是完整yaml只是关键参数实际使用时用yolov8s.yaml # model: yolov8s.yaml 中的 scale 参数可以调整深度和宽度YOLOv8的模型配置文件里有两个关键参数depth_multiple和width_multiple。n 版本是 0.33/0.25s 版本是 0.33/0.50m 版本是 0.67/0.75。因为体温监测是单类任务你可以手动把 s 版本的width_multiple降到 0.35通道数减半参数明显变少精度损失很小。这种“定制”能让你在 GPU 显存吃紧的机器上跑得更流畅。3.2 C2f结构对体温区域特征提取的意义YOLOv8的核心骨干是 C2fCross Stage Partial with 2 convolutions and n bottleneck blocks它是在 C3 基础上加了一个额外的分支拼接让梯度流更丰富。对本场景来说C2f的意义在于它擅长捕捉“局部纹理变化”——而体温异常区域发红、炎症、局部高温恰好表现为特定的纹理和边缘模式。C2f模块的计算过程是输入先过一个卷积分成两个分支一个走 Bottleneck 堆叠另一分支直接连接最后在通道维度拼接再卷积输出。这种结构增大了感受野但不增加太多计算量对小目标如耳根区域的检测更友好。训练时有个细节温度异常的牲畜往往伴随“不愿动”的行为即姿势变化小但身体姿态可能与正常个体有细微差异。C2f对姿态差异的鲁棒性不错但如果你的训练集里数据都来自固定角度实际部署时换了视角掉点会很厉害。3.3 训练指令与参数从命令行到断点续训训练命令本身不复杂复杂的是参数选择。下面是完整的训练指令建议直接复制yolo detect train \ modelyolov8s.pt \ datadataset.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience20 \ cos_lrTrue \ lrf0.01 \ warmup_epochs3 \ workers4 \ optimizerAdamW \ lr00.001 \ valTrue \ plotsTrue \ project./runs \ namelivestock_temp关键参数说明modelyolov8s.pt是预训练权重路径使用COCO预训练权重做迁移学习收敛速度远快于从零训练batch16根据显存调整GTX 1660 Ti 6GB 显存下 16 是安全的如果 OOM 就降到 8patience20是早停耐心值即验证集 mAP 连续 20 轮不提升就自动停止cos_lrTrue配合lrf0.01让学习率从 0.001 余弦衰减到 0.00001训练后期更稳optimizerAdamW比默认的 SGD 在单类小数据集上收敛更快但最终精度可能略低如果追求极致精度切回 SGD 且lr00.01训练中断不要慌YOLOv8会自动保存last.pt恢复训练用yolo detect train \ model./runs/livestock_temp/weights/last.pt \ resumeTrueresumeTrue会自动读取之前的训练状态包括优化器参数、学习率调度器和轮次不需要手动指定其他参数。恢复训练最怕的是改动数据集路径或图像尺寸导致数据流不一致所以恢复前检查一下args.yaml是否和之前一致。4. 可视化界面与推理部署把YOLOv8检测结果落到温度映射和告警4.1 PyQt5界面架构视频流、检测线程、温度读取的三线程模型可视化界面是整个系统能“展示”的关键。常见做法是 PyQt5 做界面难点在于不能把视频解码、模型推理、界面刷新放在同一个线程里否则界面会卡死。推荐三线程模型主线程跑QApplication事件循环视频采集线程负责读帧和YOLOv8推理温度线程负责在检测框的映射区域提取温度值。import sys import cv2 import numpy as np from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QApplication from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import QTimer, QThread, pyqtSignal from ultralytics import YOLO class DetectionThread(QThread): frame_ready pyqtSignal(np.ndarray) temp_alert pyqtSignal(str, float) def __init__(self): super().__init__() self.model YOLO(./runs/livestock_temp/weights/best.pt) self.running True def run(self): cap cv2.VideoCapture(0) # 或rtsp流地址 while self.running: ret, frame cap.read() if not ret: continue results self.model(frame, conf0.5, imgsz640, verboseFalse) annotated results[0].plot() for box in results[0].boxes.data: x1, y1, x2, y2, conf, cls box.tolist() # 模拟从热成像传感器读取该区域温度 temp_value read_temp_from_thermal(x1, y1, x2, y2) if temp_value 39.5: # 猪的正常体温上限约39.3℃ self.temp_alert.emit(f检测到高温个体位置({int(x1)},{int(y1)}), temp_value) self.frame_ready.emit(annotated) cap.release() def stop(self): self.running False self.wait()这个线程类的核心是把模型推理结果通过信号传递给界面。frame_ready信号携带标注后的帧temp_alert信号在温度超过阈值时触发。逻辑说明results[0].boxes.data是 [x1, y1, x2, y2, confidence, class] 的 tensor遍历时逐个判断read_temp_from_thermal是你对接温度传感器的接口函数输入是检测框坐标输出该区域的温度值——这个函数内部实现取决于你的硬件方案。界面侧接收信号和刷新标签class MainWindow(QMainWindow): def __init__(self): super().__init__() self.label QLabel(self) self.btn_start QPushButton(开始监测, self) layout QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn_start) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.det_thread DetectionThread() self.det_thread.frame_ready.connect(self.update_frame) self.det_thread.temp_alert.connect(self.show_alert) self.btn_start.clicked.connect(self.det_thread.start) def update_frame(self, frame): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape bytes_per_line ch * w qimg QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg)) def show_alert(self, msg, temp): self.statusBar().showMessage(f{msg}体温{temp:.2f}℃, 5000)注意update_frame里的QImage构造参数rgb.data需要确保 numpy 数组在创建 QImage 期间不被垃圾回收所以通常把 frame 保存为类成员变量。另外QImage.Format_RGB888要求数据结构是H x W x 3的连续内存如果frame经过裁剪或拼接需要用np.ascontiguousarray()处理一下否则显示出来是花的。4.2 温度阈值与告警联动不要让YOLOv8的误检直接触发告警告警逻辑是这个系统最容易“狼来了”的地方。YOLOv8的检测框偶发抖动相邻帧的框位置波动几个像素会导致温度读数抖动如果阈值卡得太死误报频繁。建议采用“连续帧确认”机制同一 ID可以用IoU匹配在连续 5 帧中温度都超阈值才触发告警。from collections import deque class TempMonitor: def __init__(self, max_frames5): self.history {} # track_id - deque of temp readings self.max_frames max_frames def update(self, track_id, temp_value, threshold39.5): if track_id not in self.history: self.history[track_id] deque(maxlenself.max_frames) self.history[track_id].append(temp_value) if len(self.history[track_id]) self.max_frames: # 取中位数而非平均值更抗瞬时抖动 median_temp sorted(self.history[track_id])[len(self.history[track_id]) // 2] if median_temp threshold: return True return False这个类的逻辑解释了“为什么不直接比较”的原因取中位数主要是为了过滤脉冲噪声队列长度等于确认帧数。注意这里用track_id而不是直接用检测框坐标是因为连续帧中同一个动物框的位置会漂移必须依赖目标跟踪如ByteTrack来维持ID稳定性。如果只是逐帧独立检测同一头猪在前后帧会被当成不同目标确认机制失去意义。4.3 数据集与训练结果的关联验证训练完的模型不能盲目用于测温。建议抽 100 张测试图人工标注“检测框数量”和“检测到的高温个体数量”与模型输出做对照。常见偏差是模型把两个紧挨着的猪检测成一个大框导致温度平均值被拉到中间值掩盖了个体差异。这种情况下的修正手段是调整 NMS 的 IoU 阈值默认是 0.45如果经常出“多合一”下调到 0.3 试试。from ultralytics import YOLO model YOLO(./runs/livestock_temp/weights/best.pt) results model(frame, conf0.5, iou0.3) # 调低iou阈值减少框合并iou参数控制 NMS 时认为两个框重叠多少算同一目标。数值越小越不容易合并相邻目标但也会导致同一个目标被重复框出多次需要根据实际画面密度调。除此之外conf阈值也要配合调置信度设太高漏检严重设太低背景误检混入又会触发温度误报。一般流程是先用conf0.25 iou0.6做探索性观察然后根据验证集 Precision-Recall 曲线选工作点。5. 模型优化与边缘部署从RK3588到TensorRT的量化实践5.1 模型剪枝与蒸馏让YOLOv8在ARM平台上跑起来园区级智慧农场通常不可能给每路摄像头配一台带独显的工控机边缘盒子如 RK3588、Jetson Orin Nano才是主流。在 RK3588 上部署 YOLOv8首要动作是剪枝。YOLOv8本身没有内置剪枝工具但可以用 Ultralytics 的model.fuse()做卷积和 BN 的合并减少一部分推理开销。from ultralytics import YOLO model YOLO(./runs/livestock_temp/weights/best.pt) model.fuse() # 卷积层和BN层融合 model.export(formatonnx, imgsz640, simplifyTrue)fuse()把 Conv2d 和 BatchNorm2d 合并成单一卷积在推理时省去 BN 层计算。export(formatonnx, simplifyTrue)导出计算图简化后的 ONNX这是转 RKNN 或 TensorRT 的前提。注意导出时imgsz必须和训练时一致或更大不能更小否则模型输出的特征图尺寸不匹配。RK3588 部署栈通常是 ONNX → RKNN需要用到 RKNN-Toolkit2在 PC 上完成模型转换生成.rknn文件后拷贝到板子推理。量化是重点RK3588 的 NPU 强项是 INT8 量化推理但量化后精度可能掉 1%5%。先做 PTQ训练后量化用 500 张左右代表性图像做校准如果精度掉得不可接受再考虑 QAT量化感知训练。有个实操技巧量化前先统计模型输出的激活值范围如果异常点特别大先把输入图像做归一化到 0~1数值分布更平缓量化损失更小。5.2 TensorRT加速与批处理推理如果你用的是 NVIDIA Jetson 平台TensorRT 是绕不开的加速方案。YOLOv8官方提供了导出 TensorRT 引擎的方法yolo export model./runs/livestock_temp/weights/best.pt formatengine device0 halfTrueformatengine直接生成 TensorRT 的序列化引擎文件halfTrue启用 FP16 精度推理速度可以翻倍而精度损失极小。生成时有个坑device0必须在目标平台上执行TensorRT 引擎是针对特定 GPU 架构编译的在 PC 上生成的 engine 拷贝到 Jetson 上跑不起来。TensorRT 推理时批量处理多个视频流的做法值得考虑import tensorrt as trt import pycuda.driver as cuda # 加载engine并分配显存 with open(best.engine, rb) as f: engine_data f.read() runtime trt.Runtime(trt.Logger(trt.Logger.WARNING)) engine runtime.deserialize_cuda_engine(engine_data) context engine.create_execution_context() # 绑定输入输出缓冲 h_input cuda.pagelocked_empty(trt.volume(engine.get_binding_shape(0)), dtypenp.float32) h_output cuda.pagelocked_empty(trt.volume(engine.get_binding_shape(1)), dtypenp.float32) d_input cuda.mem_alloc(h_input.nbytes) d_output cuda.mem_alloc(h_output.nbytes)TensorRT 的pagelocked_empty是锁页内存DMA 传输更快避免 CPU/GPU 显存拷贝时操作系统换页。这里批处理设置需要注意导出的引擎如果max_batch_size是 1那一次只能推一帧图像要发挥 TensorRT 的批处理优势需要在导出时指定动态形状。对于农场单路摄像头batch1 足够了多路摄像头场景才考虑动态 batch。5.3 省成本方案直接用API调用还是全本地推理这是部署阶段绕不开的决策。如果农场已经有稳定的局域网并且摄像头数量在 4 路以内全本地推理是合理的一次性硬件成本约 20004000 元无长期费用。如果摄像头分散在不同区域、没有机房条件用无线传输到中心服务器推理也可以但要考虑带宽单路 1080p 热成像视频流约 4~8 Mbps4 路就是 32 Mbps对农场的弱网环境可能不够。对比下来nano 模型全本地部署最稳妥。推理逻辑写好后用 systemd 托管开机自启断线重连[Unit] DescriptionYOLOv8 Livestock Temp Monitor Afternetwork.target [Service] Typesimple WorkingDirectory/opt/livestock ExecStart/usr/bin/python3 /opt/livestock/main.py Restartalways RestartSec5 [Install] WantedBymulti-user.targetRestartalways和RestartSec5保证进程崩溃后 5 秒自动拉起。配合journalctl -u livestock查看日志。部署不是写代码的终点运维做得好系统才能持续跑。6. 测温校准与异常数据回流最后一公里是数据闭环模型训完、界面跑通、部署完成这个项目看起来就结束了。但对牲畜体温监测这个场景真正的工程量在后面环境温度变化会影响体表温度风速、太阳辐射、猪只密度都会让同一头猪的耳根温度浮动 0.51℃。部署后第一周应该做的是校准而不是信任模型的输出。校准方法是采集一批“正常个体的体表温度 它们的直肠温度”配对数据直肠温度是金标准。然后用线性回归拟合core_temp a * surface_temp b这个回归系数每个农场都不同因为圈舍环境差异太大。可以做一个自动校准脚本隔一段时间更新系数。但要注意体表受环境影响大的部位耳朵边缘、背部中央模型的稳定性较差这其实暴露了检测部位选择的另一个维度尽量选择体表覆盖恒定的区域。眼窝比耳根更稳定因为眼眶结构暴露程度受皮毛竖立状态影响小。我在实际项目中会同时输出两个部位的温度差作为环境干扰的参考信号差值的稳定性通常比绝对值好。异常检测模型也要持续迭代。部署后每天把“模型判为正常但后来确认发烧”的样本收集起来把检测图像和热成像数据回流到训练集每周增量训练一次。YOLOv8支持增量训练yolo detect train \ model./runs/livestock_temp/weights/best.pt \ datadataset_augmented.yaml \ epochs30 \ lr00.0003 \ freeze10freeze10冻结前10层主干参数只微调检测头避免灾难性遗忘。lr00.0003是正常训练学习率的 1/3防止大步伐破坏已有权重。增量训练是模型精度提升的关键但要注意验证集里的分布偏差。当最终指标的精度和误检率稳定时模型才算真正完成系统才算真正闭环——测温的精度提升是一个持续的过程不是一次训练能了结的。本文还有配套的精品资源点击获取
返回列表