ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

YOLOv11改进与野生动物监测:小目标检测、注意力机制与TensorRT部署实战

YOLOv11改进与野生动物监测:小目标检测、注意力机制与TensorRT部署实战 简介《环保监测创新-YOLOv11改进模型在野生动物种群监测中的实践》是一份面向环保监测、生态保护与计算机视觉领域研究者的技术文档重点解决复杂野外环境中野生动物目标检测效率低、小目标识别难等实际问题。文档共33页完整覆盖YOLOv11模型基础、改进思路、数据预处理、训练优化、部署实践与监测结果分析等环节包含自适应光照调整层、改进残差块、多尺度特征融合和损失函数优化等具体设计细节适合用于技术选型参考或项目方案梳理。资源压缩包为2.01MB包含1个PDF文件支持目录章节跳转与大纲快速定位阅读体验较友好。目前已有68人学习下载适合环保监测从业者、生态研究机构技术团队以及对目标检测算法改进感兴趣的开发者作为专题参考资料。1. 环保监测里的 YOLOv11 改进模型先搞清楚自己面对的是什么数据红外触发相机拍回来的几十万张照片绝大多数是空镜头、草动、树影少数里面有一只鹿、一群野猪或者一只大猫。你要做的不是把它们全部看一遍而是让一个目标检测模型替你把有动物的那一小部分捞出来并对每个物种计数、记录时间戳和位置。野生动物种群监测这个场景和常规安防、工业质检的最大区别在于目标小、遮挡重、类间相似、环境光照从一个极端到另一个极端。YOLOv11 在公开数据集上的指标再漂亮落到这种数据上不改进一样会被打成筛子。本文要讲的就是一条完整落地方案从 YOLOv11 的缺陷分析出发讲小目标检测头怎么加、注意力模块怎么选、红外数据怎么处理、模型怎么在边缘设备上跑起来以及我在实际做这个方向时踩过的几个大坑。适合正在做生态监测、动物行为分析、农田鸟害预警等方向手里有相机陷阱或无人机航拍数据的人看。2. 适用于野生动物监测的 YOLOv11 结构改前先找准四个薄弱点2.1 为什么不直接拿预训练权重用你的数据不在它见过的分布里YOLOv11 的 COCO 预训练权重覆盖的都是常规视角、常规光照、常规物体比例。而野生动物监测的典型输入是 1200 万像素的相机陷阱原图动物在画面里往往只占几百到几千像素夜间模式又是红外 LED 补光的灰度图雾天、雨天、逆光更是家常便饭。用原版模型直接推理小目标漏检率非常高而且会把树干上的苔藓、枯叶堆误报成动物。所以我的第一步永远是冻结主干只在数据上微调 结构调整不是直接拿去量化部署。YOLOv11 的检测头已经从 YOLOv5/YOLOv8 的解耦头延续下来分类和回归分支分开这对小目标有一定的容忍度但颈部特征融合还是偏向中层特征对 32 倍下采样后只剩十几个像素的目标基本无能为力。对这类场景我有两个必改的位置。第一是 Neck 部分YOLOv11 默认只输出 P3、P4、P5 三个尺度分别对应 8 倍、16 倍、32 倍下采样我通常会增加一个 P2 输出4 倍下采样来保留小目标的细节信息。第二是主干最后一个阶段的 C2PSA 模块这个模块本身是为了提升感受野设计的但在小目标上容易把特征抹平我的做法是保留它但把后续融合权重调低防止大感受野吞掉小目标的边界信息。2.2 小目标检测头怎么加P2 层的结构与代价修改 YOLOv11 的网络结构文件核心是把原本三尺度输出改成四尺度。找到模型配置文件里的head部分在[-1, 6]这类融合节点后面追加一个来自主干第二层 stride 4 的捷径连接。# 以 ultralytics/cfg/models/11/yolo11.yaml 为基础改出的关键片段 head: - [-1, 1, Conv, [256, 1, 1]] # 从 C2PSA 出来的特征先降通道 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, C3k2, [256, False, 0.25]] # 4 是主干第 4 层的索引得到 P4 - [-1, 1, Conv, [128, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 2], 1, C3k2, [128, False, 0.25]] # 2 是主干第 2 层得到 P3 - [-1, 1, Conv, [64, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 1], 1, C3k2, [64, False, 0.25]] # 1 是主干第 1 层索引得到 P2这段配置的逻辑是从主干第二层拉出 4 倍下采样的浅层特征经过上采样后与更深层特征拼接再交给 C3k2 做融合。加了 P2 之后模型参数量会增加大约 15% 到 20%训练显存占用也会明显上涨如果你的显卡只有 8GB 显存batch size 可能要从 16 降到 8这是最直接的代价。另一个替代方案是把输入分辨率从 640 提高到 1280但这会让推理速度下降近四倍在 Jetson 这类边缘设备上几乎不可接受。所以我个人更推荐加 P2 而不是一味提高分辨率效果接近但推理开销小得多。2.3 注意力模块的选择加在 Neck 还是加在 Backbone看了不少热门的 YOLOv11 改进方案注意力模块的加法和位置各有说法有加在 Backbone 最后一个阶段的有加在 Neck 上采样之前的。我在野生动物数据上的实测结论是加在 Neck 的上采样之前收益最稳定因为那里正好是语义信息最丰富、空间信息开始丢失的位置CACoordinate Attention或者 CBAM 都能在这里把是什么和在哪里重新对齐。# 以 CA 注意力为例写成一个可插入的模块 import torch import torch.nn as nn class CoordAtt(nn.Module): def __init__(self, inp, oup, reduction32): super().__init__() self.pool_h nn.AdaptiveAvgPool2d((None, 1)) self.pool_w nn.AdaptiveAvgPool2d((1, None)) mip max(8, inp // reduction) self.conv1 nn.Conv2d(inp, mip, kernel_size1, stride1, padding0) self.bn1 nn.BatchNorm2d(mip) self.act nn.Hardswish() self.conv_h nn.Conv2d(mip, oup, kernel_size1, stride1, padding0) self.conv_w nn.Conv2d(mip, oup, kernel_size1, stride1, padding0) def forward(self, x): identity x b, c, h, w x.size() x_h self.pool_h(x).permute(0, 1, 3, 2) # b,c,1,w - b,c,w,1 x_w self.pool_w(x).permute(0, 1, 3, 2) # b,c,h,1 - b,c,1,h # 以下为完整的坐标注意力前向流程实际接入时在 yaml 的 C3k2 之后调用 y torch.cat([x_h, x_w], dim2) y self.act(self.bn1(self.conv1(y))) x_h, x_w torch.split(y, [h, w], dim2) x_h self.conv_h(x_h.permute(0, 1, 3, 2)).sigmoid() x_w self.conv_w(x_w.permute(0, 1, 3, 2)).sigmoid() return identity * x_h * x_wCA 注意力的特点是分别对高度和宽度方向做全局池化然后把两个方向的位置信息编码进注意力权重非常适合野生动物这种目标可能在画面任意角落的分布。不过要强调一点注意力模块不是越多越好我在一个项目里试过同时叠加 CBAM CA SEmAP 反而掉了 1.2 个点原因是梯度在多层注意力之间传播时被过度抑制训练收敛变慢了。选型建议如果目标以地面兽类为主CA 效果最好如果目标主要是飞鸟天空背景简单、目标轮廓清晰SE 或者什么都不加差别不大如果是水下或浓密植被里的目标CBAM 的空间注意力部分会更有帮助。2.4 损失函数与标签分配小目标最怕的其实是正样本不够YOLOv11 默认使用 TaskAlignedAssigner 做标签分配这玩意对大小目标一视同仁导致小目标的正样本数量天然偏少。我调整的第一件事是放宽小目标的匹配阈值让更多锚点参与小目标的回归预测。在 ultralytics 的配置里主要调tolerance参数和anchor_match_threshold具体数值不能一概而论我一般是从默认值往下调 10% 到 15%然后观察小目标类别的 recall 变化。损失函数方面CIoU 在目标框只有十几像素的时候宽高比惩罚项的梯度容易不稳定我换成 WIoUWise-IoU后小目标的 AP 涨了大约 3 个点。WIoU 的核心思路是对高质量锚点给更高权重对低质量锚点做梯度抑制恰好契合小目标样本少、正样本质量参差不齐的痛点。如果你不想改损失函数本身一个折中方案是在训练时把box_loss的权重从默认的 7.5 提到 10同时把cls_loss从 0.5 降到 0.3让模型更关注框的位置精度而不是分类置信度。3. 野外相机数据的训练流程从标注到装进模型3.1 标注规范小目标怎么标才不会把模型带偏标注野生动物数据有个常见的争议一个目标只有 10 像素要不要标我的原则是只要人能凭单帧判断出物种就标标的时候尽量贴近轮廓不要把动物周围的草和地面框进去。模糊到无法确认物种的目标单独建一个 unknown_animal 类不要硬塞进相近物种里否则模型会被逼着在豹猫和家猫之间强行二选一产生一堆高置信度的错误预测。如果目标是夜间红外灰度图标注前先做一个批量处理把单通道灰度图复制成三通道或者训练时在数据加载里做np.repeat让模型输入保持三通道。这里有一个很多新手翻车的点红外相机的输出往往带有时间戳和水印这些像素会被模型当成背景纹理学进去我在预处理里固定裁掉图片四角的元数据区域只保留中间净图区域参与标注和训练。3.2 数据增强的坑不能把动物增强没了YOLOv11 自带的 mosaic、mixup 增强对通用目标效果不错但在动物监测里要慎用。Mosaic 会把四张图拼在一起如果四张图里只有一张含有动物那张图里的动物可能被缩放得只剩几个像素模型学到的就是小目标越来越难认。我的实际做法是mosaic 概率从默认的 1.0 降到 0.5mixup 直接关掉把省下来的增强预算给随机亮度对比度调整和随机旋转。下面是训练命令里增强相关参数的设置yolo train \ modelyolo11n.pt \ datawildlife.yaml \ imgsz640 \ epochs150 \ batch16 \ mosaic0.5 \ mixup0.0 \ hsv_h0.02 \ hsv_s0.5 \ hsv_v0.4 \ degrees10 \ fliplr0.5 \ scale0.3参数说明mosaic0.5表示只有一半的训练批次做 mosaic 拼接mixup0.0关闭混合增强hsv_h/s/v控制色相、饱和度、明度的随机扰动范围红外灰度图主要靠hsv_v模拟不同补光强度degrees10做小角度旋转因为动物在画面里的姿态本身不固定scale0.3限制随机缩放幅度防止小目标被缩到不可见。 这个配置是我跑了几轮对比之后得到的最稳组合在目标类别超过 10 类时建议把mosaic再降到 0.3。3.3 类别不均衡处理不能只靠 class_weight野生动物数据天然不均衡兔子出现的频率可能是豹猫的 100 倍。YOLOv11 的损失函数默认是按类别独立计算的所以模型会把猜成兔子当成一个低风险错误导致豹猫类别的召回率惨不忍睹。我的做法是先用一次短训练30 epoch拿到各类别的 AP 曲线把 AP 低的类别挑出来单独做过采样而不是给 loss 加权重。具体做法是在data.yaml里为每个类别文件建一个清单对样本少的类别重复列出路径让加载器看到它们的频率提高 5 到 10 倍# wildlife.yaml 中的数据路径写法 train: - /data/deer/images - /data/rabbit/images - /data/leopard_cat/images # 这个目录在数据准备阶段被复制了 8 份 - /data/bird/images还有一种更稳的做法对稀少类别做离线过采样增强把每张原始图预先做 5 个版本的随机裁剪和颜色扰动存成新的训练图片。这样做不会改变模型训练时的 batch 构成但等价于把该类别的有效样本量放大了 5 倍。我两个方法都用过离线增强在样本量极少少于 200 张时效果更明显。3.4 训练过程中的关键监控指标训练野生动物模型我最关注三个指标按优先级排列小类别的 recall、误报率precision、类别间的混淆度。mAP 在这个场景里意义不大因为空镜头占了数据的大头模型只要把空镜头全判成空mAP 也能很好看但它完全没有实用价值。在训练日志里我用一段小脚本打印每个类别的混淆矩阵# 验证结束后从 ultralytics 的 validator 结果中提取混淆矩阵 import glob import pandas as pd results_files glob.glob(runs/detect/train*/confusion_matrix.png) for f in results_files: print(f混淆矩阵已生成: {f}) # 正确读取方式ultralytics 也会保存 confusion_matrix 的归一化数值 # 用 pandas 读取 runs/detect/train*/confusion_matrix.csv 自行分析 df pd.read_csv(runs/detect/train2/confusion_matrix.csv, index_col0) rare_classes [leopard_cat, civettictis] for cls in rare_classes: # 检查该类别有多少样本被误分成兔子/鹿等常见类 misclassified df.loc[cls].sort_values(ascendingFalse)[1:4] print(f{cls} 最容易混淆为: {misclassified.index.tolist()})这个脚本的用途是定位哪两个类别让模型犯迷糊。比如我曾经发现黄麂和赤麂两个物种的混淆度高达 60%这说明标注阶段本身就没有把两者的特征差异教给模型——需要考虑合并类或者补充更多两个物种同框的样本而不是继续调参。这个洞察比反复刷 mAP 有用得多。4. 模型推理与部署把改进模型放到相机旁边跑起来4.1 用 Jetson Nano 部署 YOLOv11 改进模型环境配置与 TensorRT 导出野生动物监测设备往往部署在野外没有稳定的云连接所有推理都得在边缘完成。Jetson Nano 是我用得最多的设备虽然算力只有 472 GFLOPS但胜在功耗低、能用太阳能板供电。部署流程分三步换 PyTorch 版本、导出 TensorRT 引擎、写推理脚本。# Jetson Nano (JetPack 4.6) 上的环境配置要点 sudo apt-get install libopenblas-dev libopenmpi-dev pip3 install --no-cache-dir torch1.13.0 JetPack版本对应的torch版本 pip3 install ultralytics8.3.0 # 导出 TensorRT 引擎务必关闭动态输入以提升效率 yolo export \ modelbest.pt \ formatengine \ imgsz640 \ halfTrue \ workspace2 \ device0导出命令里halfTrue表示开启 FP16 精度workspace2限制 TensorRT 构建时最大显存占用为 2GB避免在 Nano 上导出时因为显存不足被 OOM 杀掉。TensorRT 引擎导出成功后会得到一个.engine文件加载时如果目标设备换了需要重新导出不能直接拷贝到另一台 Jetson 上用。这里有个非常重要的经验TensorRT 对结构中的自定义注意力模块支持参差不齐。CA 注意力这类模块在导出时经常报算子不支持我踩过这个坑之后现在的做法是先把整个模型重导出为 ONNX再用polygraphy工具逐层检查哪些算子被 TensorRT 拒绝了对不支持的算子要么改写实现、要么退回用 ONNX Runtime 跑。如果你不想折腾最保险的做法是训练时只用原生支持的算子Conv、Concat、Sigmoid、Multiply 这些注意力模块尽量用这些基础算子组合实现。4.2 推理结果保存不只是输出一张画框的图生态监测项目里推理结果的保存比检测框本身重要十倍因为后续的种群统计和个体识别全都依赖这些输出。我用表格说清楚三种保存格式各自的价值保存格式内容用途裁剪后的目标小图每个检测框裁剪出 JPEG人工复核、建立物种图像库标注文件YOLO txt类别、归一化坐标、置信度再次训练时的伪标签、模型迭代CSV 日志时间戳、相机 ID、坐标、置信度种群数量统计、活动节律分析# 推理脚本中同时保存三种结果 from ultralytics import YOLO import csv import cv2 model YOLO(best.engine) # 加载 TensorRT 引擎 results model.predict( sourcecamera_trap_images/, imgsz640, conf0.35, iou0.45, verboseFalse, save_txtTrue, # 保存 YOLO 格式的标签文件 save_cropTrue # 保存裁剪后的目标小图 ) log_rows [] for r in results: for box in r.boxes: cls_id int(box.cls[0].item()) conf float(box.conf[0].item()) x1, y1, x2, y2 [float(v) for v in box.xyxy[0].tolist()] image_path r.path timestamp image_path.split(/)[-1].replace(.jpg, ) log_rows.append([timestamp, image_path, cls_id, round(conf, 4), round(x1, 2), round(y1, 2), round(x2, 2), round(y2, 2)]) with open(inference_log.csv, w, newline) as f: writer csv.writer(f) writer.writerow([timestamp, image_path, class_id, confidence, x1, y1, x2, y2]) writer.writerows(log_rows)这段脚本的关键点有两个第一是save_cropTrue会让 ultralytics 自动为每个检测框裁剪一张小图我强烈建议开启因为后续做个体识别比如通过斑纹区分不同个体直接拿这些裁剪图做输入比在大图上反复截取快得多。第二是 CSV 日志里的时间戳直接从文件名提取所以相机时间设置要统一否则后续节律分析全是错的——这个事看着小实操中坑过我好几次。4.3 目标跟踪与计数群体场景下的严重遮挡问题当多个动物同框出现比如一群野猪目标检测会把它们当成分散的独立个体但群体移动时互相遮挡检测框会频繁丢失。我的做法是在 YOLOv11 检测结果之上接一个 ByteTrack 跟踪器利用检测框之间的 IoU 和外观特征做关联维持每个个体的 ID 稳定。# 用 ultralytics 内置的跟踪接口做检测跟踪 from ultralytics import YOLO model YOLO(best.engine) results model.track( sourcecamera_trap_videos/, imgsz640, conf0.35, iou0.45, trackerbytetrack.yaml, # 也可以换 botsort.yaml persistTrue, saveTrue )persistTrue是关键参数它告诉跟踪器在视频帧之间保持 ID 记忆不会因为某一帧检测失败就重置所有 ID。ByteTrack 对比 DeepSORT 的优势是不需要额外的 ReID 模型在 Jetson Nano 上少了一个模型推理开销帧率能提升 30% 左右。我做种群统计时取的是视频片段内出现过的最大 ID 数而不是某一帧的检测框数量这样能最大程度规避遮挡导致的漏计。5. 野生动物监测中 YOLOv11 改进模型的避坑指南5 条踩坑记录5.1 红外灰度图做三通道复制后模型反而变差现象把红外灰度图复制成三通道喂给预训练模型验证集 mAP 比预期低了 5 个点。原因预训练权重是在彩色自然图像上学的灰度图的三通道完全一致模型内部对通道差异的假设被打破浅层卷积核产生大量冗余响应。解决在数据加载器里对灰度图随机做通道置换R、G、B 位置随机互换或者直接把模型第一层 Conv 的输入通道改成 1加载预训练权重时只取三通道权重的平均值。我用的是后者配合重新训练 50 个 epoch效果比三通道复制提升明显。5.2 加了 P2 层之后训练损失下降但验证集 mAP 没变化现象P2 层加上之后训练损失漂亮地下降验证 mAP 纹丝不动。原因P2 层带来的小目标特征确实更丰富了但标签分配策略没有跟着改小目标的正样本匹配数量依旧不够模型学到了更多的背景特征而不是目标特征。解决同步修改标签分配参数。把TaskAlignedAssigner里的topk从默认的 13 调大到 20让更多浅层锚点参与小目标匹配。调完这个参数小目标类别的 recall 通常能涨 3 到 5 个点。5.3 TensorRT 导出时自定义注意力模块报算子错误现象yolo export formatengine报错op not registered: aten::adaptive_avg_pool2d之类的错误。原因CA 注意力模块使用了 PyTorch 的AdaptiveAvgPool2d这个算子在不同尺寸输入下生成动态 shapeTensorRT 不喜欢这种不确定性。解决把AdaptiveAvgPool2d替换为固定 kernel size 和 stride 的平均池化。如果输入尺寸始终是 640nn.AvgPool2d(kernel_size(8, 8), stride(8, 8))的效果是一样的且是 TensorRT 原生支持的算子。5.4 同一物种在白天和夜间的检测率差距极端现象白天拍摄的同类动物检测 mAP 达到 0.85夜间红外模式下只有 0.4。原因红外图的纹理信息极少模型主要依赖轮廓和形状而预训练权重里的形状特征和红外模式不完全匹配。解决把白天和夜间图片按 7:3 的比例混合训练而不是单独训两个模型。我一开始分开训维护成本翻倍效果也没更好。混合训练时把hsv_v扰动范围从 0.4 扩大到 0.6模拟更多补光强度变化。5.5 推理日志里出现大量置信度高于 0.8 的误报现象模型把树干上的菌斑、石头上的地衣认成动物置信度还高得离谱。原因训练数据里负样本空镜头不够模型没有见过足够的看起来像动物但其实是背景的样本。我这里说的负样本不是纯空图而是那些包含树桩、岩石、水洼等容易混淆物的图。解决收集 3000 到 5000 张无动物但有挑战性背景的图片单独建一个background目录加入训练集模型学到的是背景也参与分类误报率能下降一半以上。如果项目上线后出现了新场景的误报持续把这些误报样例加入训练集做迭代模型会越用越准。6. 进阶验证改进有效性的关键方法——用同一批数据跑 A/B 测试改进模型的验证不能只看最后的 mAP要落到这个改动在真实相机陷阱数据上到底带来了多少有效检出。我的习惯是准备一份几百张的测试集里面包含白天、夜间、雨天、逆光、不同季节五个子集每个子集都有标注。每次做完一个改进加 P2、换注意力、调损失函数就把旧模型和新模型分别在这份测试集上跑一遍逐个子集对比 recall 和 F1 值。# 快速对比两个模型的逐子集 recall import pandas as pd from ultralytics import YOLO test_images { day: test_data/day/*.jpg, night: test_data/night/*.jpg, rain: test_data/rain/*.jpg, backlight: test_data/backlight/*.jpg, winter: test_data/winter/*.jpg, } old_model YOLO(yolo11n.pt) new_model YOLO(best_improved.engine) for name, pattern in test_images.items(): r_old old_model.predict(sourcepattern, conf0.35) r_new new_model.predict(sourcepattern, conf0.35) # 统计每张图的检出数量和标注真值做对比 print(f{name}: old_recall{compute_recall(r_old)}, new_recall{compute_recall(r_new)})如果新模型只在白天子集上有提升、夜间子集反而退步那这个改进方向就要重新想而不是整体 mAP 看起来涨了就完事。我有一次加了一个对大目标特别友好的模块整体 mAP 涨了 2 个点结果分完子集发现夜间小目标全部退化幸好做了拆分验证不然模型部署到野外夜间场景就是灾难。最后一个建议是给改进模型留一份后悔药每训练完一个版本把权重文件、训练配置 yaml、数据增强参数和测试集预测结果打包成一个带日期的目录按model_name_YYYYMMDD命名。模型迭代越多越需要知道哪个版本是在什么配置下产出的这比任何实验记录都省心。这个习惯帮我避免过至少三次改了一通结果变差了但不知道哪里改的的翻车事故。希望帮到你。本文还有配套的精品资源点击获取
返回列表