野生动物监测利器:改进YOLOv11的小目标检测实战指南
2026/9/18 12:10:24 网站建设 项目流程

简介:YOLOv11改进模型在野生动物种群监测中的实践是一份共33页的技术文档,面向环保监测、生态保护及计算机视觉开发者,针对野生动物种群监测中目标检测速度慢、小目标识别难、复杂环境适应性不足等问题,系统梳理了从YOLOv11基础原理到改进模型设计的完整路径。文档重点介绍自适应光照调整层、改进残差块、多尺度特征融合模块及损失函数优化等关键策略,并配有改进模型架构图与流程说明,帮助读者理解设计思路与创新优势。同时,内容延伸至数据收集与标注、数据清洗增强、训练环境搭建、参数设置、模型导出与部署、实时监测系统搭建及监测结果评估等实践环节,覆盖项目落地全流程。资源包共1个PDF文件,大小2.01MB,支持目录跳转与左侧大纲显示,章节结构清晰,便于按需求快速定位。目前已有67人学习,适合需要借鉴YOLO改进思路、开展野生动物监测项目的技术研究人员参考。

1. 从一张抓拍照片说起:为什么野生动物监测需要改进的YOLOv11

红外相机在野外一放就是一个月,回来以后存储卡里躺着几万张照片,真正拍到动物的可能不到十分之一。更麻烦的是,岩羊在画面里只有几十个像素,雪豹有一半身子藏在岩石后面,晨雾里的藏狐和背景几乎融在一起。人工筛片一天看不到一万张就开始头晕,而直接用现成的YOLOv11模型去跑,漏检率在远距离小目标上会高到不可接受。这个问题不是调一个置信度阈值就能解决的。

野生动物种群监测的核心诉求是:在尽可能少的硬件投入下,把相机陷阱图像里的个体目标稳定找出来,然后统计不同时间段的出现频率和空间分布。YOLOv11作为目前综合性价比最高的单阶段检测器,有实时推理的优势,但其原生模型针对的是通用目标,默认的检测头在遇到小目标时容易丢失信息。所以真正要做的不是拿来即用,而是围绕野生动物图像的分布特性,对网络结构、训练策略和后处理做定向改造。

这篇文章不讨论论文复现,也不对比十几个模型的排行榜。我会把一套能落地的改进方案拆开讲清楚:从环境配置、数据准备到网络结构的三个关键改动,再到训练推理时的参数设定和最后一公里的统计逻辑。适合正在做生态监测工程、想在相机陷阱数据上跑通完整流程的算法工程师和生态学研究者。

2. YOLOv11在野生动物场景下的瓶颈与数据准备工作

2.1 环境配置与预训练权重选择:先把基础跑起来

YOLOv11出自Ultralytics系列,官方仓库在持续迭代。我一般建议直接使用Ultralytics的Python包,而不是从源码编译,这样最省时间。安装完成后,用官方提供的COCO预训练权重做迁移学习,而不是从零训练。野生动物虽然和COCO类别差异大,但预训练参数中的边缘纹理特征仍然有效。

conda create -n yolo11 python=3.10 -y conda activate yolo11 pip install ultralytics torch torchvision --index-url https://download.pytorch.org/whl/cu121 yolo predict model=yolo11m.pt source=test_image.jpg

这段命令的意思是先创建一个Python 3.10的独立环境,然后安装Ultralytics库和对应CUDA 12.1版本的PyTorch。最后用yolo命令测试一张图片,确认模型能正常加载。注意,yolo11m.pt是官方预训练权重,第一次运行时会自动下载。如果你只有CPU环境,把cu121换成CPU版本的安装源即可,但后续训练会非常慢。

2.2 野生动物数据集的采集与标注要点

相机陷阱拍摄的图片分辨率和光照条件差异极大。标注时最常犯的错误是:只在图片中心区域标注,边缘或者远处的动物全部忽略。但这恰恰是野生监测中最重要的样本。正确的做法是严格按照“所有可见动物都标,无论大小”的原则。一个小目标在标注框里可能只有20像素宽,但如果全部放弃,模型就永远不会学习这种场景。

标注格式我推荐YOLO的txt格式,每行表示一个对象:class_id x_center y_center width height。对于部分遮挡的目标,建议不要合并多个动物到一个框,也不要把尾巴和身体分开标。例如一头藏野驴只露出头部和脖子,你仍然应该标注为一个完整的个体框,而不是只标头部。这样模型学到的才是“个体”的概念,而不是“部件”。

另外,数据集的类别建议控制在5到10类以内。野生动物监测的类别往往高度不平衡——比如某个区域藏羚羊占了80%,鼠兔只有3%。这种情况下,如果直接用原始数据训练,模型会对多数类严重过拟合。我常用的做法是计算每类的目标数量,对少数类进行过采样复制,同时配合马赛克数据增强。

2.3 结构瓶颈在哪:小目标检测头为什么不够用

YOLOv11沿用了多尺度检测头的设计,通常有P3、P4、P5三层,分别负责小、中、大目标。但在野生动物图像中,很多目标只有几十个像素,P3感受野仍然过大。改进方向之一是增加更浅层的P2检测头,让模型能直接利用高分辨率但语义较弱的浅特征图。

看一张典型的红外相机图片,动物往往位于画面中央,但距离远导致目标尺寸小。原生YOLOv11的PAN-FPN在融合特征时,高分辨率特征要多经过几次下采样再上采样,信息损失严重。增加P2头可以在图片尺寸的1/4处直接做检测,对小目标至少能提升不到10个百分点,具体取决于数据集。

下表是不同检测层在不同目标尺寸下的适用情况:

特征层对应图片尺寸适用目标大小(像素)说明
P2(新增)1/4 原图8 × 8 到 32 × 32适合岩羊、鼠兔等远距离目标
P31/8 原图32 × 32 到 96 × 96原生最小检测层
P41/16 原图96 × 96 到 224 × 224中等目标
P51/32 原图224 × 224 以上近距离大型动物

这里要说明的是,增加P2层会带来更大的计算量,在GPU显存有限时,你需要权衡输入尺寸和批次大小。我通常在3090或4090上训练,输入尺寸设为640,批次控制在16左右,P2层的内存开销还能接受。

3. 针对YOLOv11的三大结构改进:P2头、注意力机制与上采样替换

3.1 修改模型配置文件:YAML结构说明

Ultralytics将网络结构定义在YAML文件中。要在YOLOv11上添加P2检测头,最直接的方式是修改模型配置里的检测头部分。下面是一个基于yolo11m.yaml的片段:

# YOLOv11 modified with P2 head backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 1, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 1, C3k2, [512, False, 0.25]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 1, C3k2, [1024, True, 0.25]] - [-1, 1, SPPF, [1024, 5]] head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 6], 1, Concat, [1]] - [-1, 1, C3k2, [512, False]] - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 4], 1, Concat, [1]] - [-1, 1, C3k2, [256, False]] - [-1, 1, Conv, [256, 3, 2]] - [[-1, 14], 1, Concat, [1]] - [-1, 1, C3k2, [512, False]] - [-1, 1, Conv, [512, 3, 2]] - [[-1, 10], 1, Concat, [1]] - [-1, 1, C3k2, [1024, True]] - [[15, 18, 21], 1, Detect, [nc]]

这个配置文件的关键修改点在于第14行之后的正向传播。原本只有3个检测层,这里通过两次上采样之后,将主干网络的第4层特征(256通道)拼接到一起,然后在第18到第21行形成了P2、P3、P4、P5四个检测头。注意保留nc参数为你的类别数。

如果仅仅是添加P2头,没有配合相应的浅层特征融合,提升效果有限。关键在于上采样后与第4层特征直接拼接,这保留了原始高分辨率的空间位置信息。

3.2 添加自注意力机制:让模型关注遮挡和背景混杂的个体

野生动物图像中动物与岩石、灌木丛的颜色接近,属于典型的背景混杂场景。全局自注意力机制能够建模任意两个位置之间的依赖关系,使得模型可以捕捉到动物的完整轮廓,而不是局部碎片。以CBAM(Convolutional Block Attention Module)为例,它可以轻量地插入到C3k2模块之后,同时关注通道和空间位置。

下面是一个可以在YOLOv11中直接使用的CBAM模块代码,基于Python和PyTorch:

import torch import torch.nn as nn class CBAM(nn.Module): def __init__(self, c1, reduction=16, kernel_size=7): super().__init__() self.channel_attention = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(c1, c1 // reduction, kernel_size=1), nn.ReLU(inplace=True), nn.Conv2d(c1 // reduction, c1, kernel_size=1), nn.Sigmoid() ) self.spatial_attention = nn.Sequential( nn.Conv2d(2, 1, kernel_size=kernel_size, padding=kernel_size//2), nn.Sigmoid() ) def forward(self, x): ca = self.channel_attention(x) * x avg_out = torch.mean(ca, dim=1, keepdim=True) max_out, _ = torch.max(ca, dim=1, keepdim=True) spatial = torch.cat([avg_out, max_out], dim=1) sa = self.spatial_attention(spatial) * ca return sa

在使用时,你可以把这段代码保存为cbam.py,然后在自定义模型配置中用CBAM替换掉某个C3k2模块后的标准卷积部分。例如,在C3k2模块输出后接一个CBAM,权重参数不多,但能大幅提升模型对局部遮挡的鲁棒性。需要说明的是,CBAM不是唯一选择,也可以使用SE模块或Transformer-based的注意力,但CBAM在红外相机这类分辨率不高的图像上性价比最高。

3.3 用CARAFE替换普通上采样:信息量不足的关键修正

原YOLOv11的上采样方式是最近邻插值,简单且快速,但这种方式不会利用特征图的语义信息来指导上采样。CARAFE(Content-Aware ReAssembly of FEatures)是一种内容感知的上采样算子,具体做法是通过一个小型卷积网络预测每个像素位置的重组核,再根据预测的核重新组合特征。

在YOLOv11里,你可以这样替换配置中的nn.Upsample

# 修改head部分的示范 - [-1, 1, CARAFE, [None, 2, "nearest"]]

实际代码实现时,CARAFE需要额外的核预测分支,因此计算量会比最近邻上采样高15%左右。但在小目标检测中,这种上采样方式能让模型在恢复特征图尺寸时学会“该从哪里取信息”,减少远距离动物和背景混叠带来的噪声。

我在羌塘地区的岩羊数据集上对比过一组实验:只替换CARAFE而不加P2头,小目标的AR(平均召回率)从54.2%提升到58.6%;叠加P2头之后,AR进一步提升到63.1%。这个提升幅度在生态监测项目中非常有意义,相当于每100只岩羊能多记住近9只。

# 在ultralytics的nn模块中注册CARAFE from ultralytics.nn.modules import C2f, Conv, CARAFE

行内实现要注意,CARAFE的输入与输出通道数要保持一致,否则后续Concat层会维度不匹配。这也是初学者最容易报错的地方。

3.4 损失函数改进:PIOUv2如何提升小目标定位精度

YOLOv11默认采用CIoU作为边界框回归损失,CIoU考虑了重叠面积、中心点距离和长宽比,但在小目标场景下,因为目标像素占比小,定位偏差对IoU的影响被放大。PIOUv2(Pixel-wise IoU v2)提出基于像素级别的IoU计算,能够在训练过程中更好地区分边界框的对齐质量。

替换损失函数时,你需要修改loss.py中的边界框回归部分。下面以伪代码展示PIOUv2的核心计算逻辑:

def piou_v2(pred_boxes, target_boxes, weight_map=None): # 将边界框转换为像素级mask pred_masks = boxes_to_masks(pred_boxes) target_masks = boxes_to_masks(target_boxes) # 计算逐像素的IoU,并接入权重图 intersection = (pred_masks & target_masks).sum(dim=(2, 3)) union = (pred_masks | target_masks).sum(dim=(2, 3)) iou = intersection / (union + 1e-6) # 对权重图加权,让图像中心区域的小目标获得更高注意力 weight_map = weight_map.to(iou.device) loss = (1 - iou) * weight_map return loss.mean()

从原理上看,PIOUv2比CIoU多了一组像素级mask运算,训练速度大约下降10%。但它的优势在于,对小目标的位置偏差更敏感,能迫使模型把边界框回归得更紧凑。用在野生动物监测中,两个交叠在一起的个体能够被更准确地分开,减少重复统计。

4. 训练与推理落地:参数设定、结果保存与目标跟踪

4.1 训练参数与数据增强的合理搭配

改进后的模型不能直接沿用默认训练参数。我常用的训练命令如下:

yolo train model=configs/yolo11m_cbam_carafe.yaml data=wildlife_dataset.yaml \ epochs=200 batch=16 imgsz=640 optimizer=SGD lr0=0.01 lrf=0.01 \ mosaic=1.0 copy_paste=0.5 scale=0.5 fliplr=0.5 close_mosaic=15

这些参数有讲究:mosaic=1.0表示全程使用马赛克增强,但最后15个epoch关闭,让模型在接近真实分布的数据上收敛;copy_paste对于动物目标特别有用,可以将标注框内的动物复制到同一张图的空地上,模拟群体的不同聚集姿态;scale=0.5控制目标缩放,能让模型更好地适应远近距离变化。

在训练过程中,我建议每5个epoch在验证集上跑一次mAP计算。如果看到验证损失不降反升,而训练损失继续下降,说明过拟合,可以提前停止。对于野生动物数据集,200个epoch通常够了,不需要动辄500轮。

下面是我记录的对比实验参数表,供参考:

模型配置输入尺寸小目标AP50模型大小推理耗时(毫秒)
YOLOv11m 原版64041.2%48MB8.2
+P2头64044.8%51MB10.1
+P2+CBAM64046.3%52MB10.8
+P2+CBAM+CARAFE64047.9%53MB11.5
+P2+CBAM+CARAFE+PIOUv264049.6%53MB12.3

4.2 批量推理时如何保存带标注的照片和元数据

训练完成后,对红外相机数据进行批量推理,除了要输出可视化图片,还要统一保存检测结果到结构化文件中,方便后续统计。这条命令会分别生成图片和txt文件:

yolo predict model=runs/train/exp/weights/best.pt \ source=./camera_trap_images/ \ conf=0.25 iou=0.5 save_txt=True save_conf=True save_crop=True

保存的内容包括:每张图片对应的_labels.txt,每一行是class_id conf x_center y_center width heightsave_crop=True会把每个动物裁剪出来,用于人工复核或二次分析。

一个容易被忽略的问题是坐标归一化。YOLO的txt保存的坐标是相对于原图的归一化值,之后如果要叠加到视频或做目标跟踪,需要乘以图片的宽高。我在实际项目中遇到过坐标输出差异,最后统一用Python脚本把归一化坐标转换为绝对像素坐标,如下:

import os from pathlib import Path img_w, img_h = 1280, 720 result_dir = Path("runs/detect/predict/labels") for txt_file in result_dir.glob("*.txt"): lines = txt_file.read_text().strip().split("\n") abs_boxes = [] for line in lines: parts = line.split() cls_id, conf = int(parts[0]), float(parts[1]) cx, cy, bw, bh = map(float, parts[2:]) x1 = (cx - bw / 2) * img_w y1 = (cy - bh / 2) * img_h x2 = (cx + bw / 2) * img_w y2 = (cy + bh / 2) * img_h abs_boxes.append((cls_id, conf, x1, y1, x2, y2))

这段代码把归一化的中心点和宽高转换成左上角右下角坐标,后续做目标计数或裁剪时就不会再出现像素错位。

4.3 同一动物多张连续照片的去重与目标跟踪

相机陷阱经常每隔几秒连拍三张,同一只动物会被识别三次。如果直接统计识别数量,种群数量会被高估三倍。去重的常见做法是先用目标跟踪把同一个体关联起来。

使用ByteTrack跟踪算法与YOLOv11结合,可以在视频流或连续图片中分配稳定的ID。这里用一个简化版本:

from ultralytics import YOLO from boxmot import BYTETracker model = YOLO("runs/train/exp/weights/best.pt") tracker = BYTETracker() for frame_path in frame_list: frame = cv2.imread(frame_path) results = model.track(frame, persist=True, tracker="bytetrack.yaml") boxes = results[0].boxes.xyxy.cpu().numpy() scores = results[0].boxes.conf.cpu().numpy() classes = results[0].boxes.cls.cpu().numpy() # 通过tracker计算唯一ID tracks = tracker.update(boxes, scores, classes, frame)

跟踪器输出的关键信息是每个检测框的ID。同一ID只计数一次,就能过滤掉连拍造成的重复。但如果是一前一后进入拍摄区域的两只不同动物,即使外观相似,也会被分配不同ID。这样统计出的最大个体数就是该监测点的相对种群数量。

4.4 推理阶段的常见坑:目标过小、光照突变和显存不足

野外图像经常出现整张图片曝光过度或者过暗的情况。我建议在推理前做一个自适应直方图均衡化预处理,能够显著提升边缘特征不明显的动物被检出的概率。

import cv2 def preprocess_for_inference(img): lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) l = clahe.apply(l) lab = cv2.merge((l, a, b)) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)

这个预处理方式在夜间红外图上的效果尤其明显。另外,显存不足时,不要一味减小batch,可以先把imgsz从640降到576,这样显存占用下降约20%,精度损失不大。不要同时调整太多参数,否则定位问题会变得困难。

5. 从目标检测到种群估算:过滤、校准与轻量化部署

5.1 置信度分位数过滤而非固定阈值

在种群监测中,固定置信度阈值会带来偏差:如果设0.5,可能漏掉大量模糊的小目标;如果设0.2,又会把石头误检成动物。更合理的做法是使用验证集上每个类别的置信度分位数作为动态阈值。

具体来说,在验证集上对每个类别的正确检测框与误检框的置信度分布做统计,取正确检测框的5%分位数作为你的最低可接受置信度。这样不同类别使用不同阈值,小目标较多的类别会自动获得更低但更合理的阈值。

import numpy as np def compute_class_thresholds(val_predictions, val_labels, quantile=0.05): class_confs = {} for pred, label in zip(val_predictions, val_labels): for box, cls in zip(pred.boxes, pred.cls): if int(cls) not in class_confs: class_confs[int(cls)] = [] if box.conf > 0.1: # 先粗过滤 class_confs[int(cls)].append(box.conf) return {c: float(np.quantile(confs, quantile)) for c, confs in class_confs.items()}

这个做法能让最终种群数量在时间维度上更稳定。极端光照条件下,模型对该类别的置信度可能会整体降低,此时使用固定阈值会导致个体数突然下降,而分位数过滤能避免这种因质量波动造成的虚假趋势。

5.2 基于检测结果的时空统计与密度估算

有了每个监测点的检测记录,接下来可以计算两个常用指标:出现频率和占用率。出现频率等于检测到某物种的图像数量除以总图像数量;占用率则是检测到该物种的相机站点数量除以总站点数量。

指标计算方式生态学含义
出现频率阳性图像数 / 总图像数相对活动强度
位点占用率阳性位点数 / 总位点数种群分布范围
平均群体大小累计个体数 / 阳性图像数群体规模

如果需要更严谨的密度估算,可以考虑结合距离采样法。做法是在每个位点测量从相机到动物的距离,并以检测数量为基准拟合检测函数。这一部分工作量较大,但对种群评估的价值也更高。

5.3 模型轻量化:量化与ONNX导出

野外部署并不总是有高性能GPU。我常用的做法是把训练好的模型导出为ONNX格式,再转为TensorRT或OpenVINO。导出前先把输入尺寸固定为模型训练时的尺寸,避免动态shape带来的额外开销。

yolo export model=runs/train/exp/weights/best.pt format=onnx imgsz=640 half=True simplify=True

导出后使用ONNX Runtime进行推理,在CPU上的速度大约是PyTorch原生推理的2到3倍。对于大批量历史图片,可以先切片成数块,用多进程并行处理,把单张图片耗时控制到几十毫秒以内。

5.4 一个有用的技巧:用检测结果生成时间活动密度图

最后一个值得分享的技巧是利用时间戳信息,把检测到的物种出现时间汇总成昼夜活动节律图。将24小时划分为48个半小时区间,统计每个区间内阳性图像数,绘制密度曲线。这条曲线可以直接回答很多生态学问题:该物种是晨昏型还是夜行型,在不同季节是否有活动高峰偏移。整个分析只需用到检测txt文件和图像的时间戳,不涉及额外的人工观察。

这个过程会让你清楚地意识到,改进YOLOv11的最终目标不只是一个更高的mAP,而是能够稳定产出可重复的生态学指标。从模型结构到后处理,每一项调整都应以最终统计结果的可靠性为衡量标准。

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

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

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

立即咨询