☰
YOLO11双目标检测优化实战:RepVGG与OREPA提升精度和速度
2026/10/23 5:16:52 网站建设 项目流程

最近把一个目标检测项目从“能用”折腾到了“好用”,基底就是YOLO11。最开始直接跑官方权重,双目标识别和定位都能做,但一到遮挡、小目标、两类目标长得又像的场景,漏检和误检就开始冒头。后来我在主干里换了RepVGG风格的重参数化卷积,在颈部融入了OREPA模块,一套组合拳下来,mAP@0.5从0.852提到了0.914,推理速度反而还快了8%。这篇文章就是把整个改造和训练过程完整记录下来,包括踩过的坑、调参思路、导出部署时那些文档里不会明说的细节,给正在折腾YOLO系列做双目标识别定位的朋友一个可以直接抄作业的参考。

1. 项目背景与整体设计

1.1 双目标识别定位的任务拆解

双目标识别与定位,说白了就两件事:模型要能区分出画面里的目标属于哪一类,同时给出目标在图像中的位置框。这个“双目标”听起来比多目标简单,但实际做的时候你会发现,它只是类别数量少,并不会降低检测难度。

这次项目的实际场景是交通路口的行人与车辆检测,选这两类是因为它们天然存在几个让模型头疼的点:行人和车辆尺度差异极大,远处行人只有几十个像素;行人和非机动车、行人和路边障碍物经常互相遮挡;光照变化剧烈,早晨逆光、夜间路灯、雨天反光全都要扛。也就是说,虽然是二分类,但边界情况一点都不少。

我还做了一个比较极端的处理:只保留两个类别,但刻意在数据里加入了大量“难例”。比如把行人密集区域的图像整批放进训练集,让模型面对人群场景时学会克制误检;把车辆重叠的样本也放进去,逼模型把定位框回归得更准。这些都是实战中提升双目标检测鲁棒性的关键操作,只跑公开数据集是不会遇到这些痛点的。

1.2 为什么选YOLO11做基底

选择YOLO11作为基底,首先是看中它在工程上的成熟度。YOLO系列从v8开始,训练、验证、导出、部署这条链路已经被打磨得很顺,YOLO11进一步改进了C3k2模块和Head结构,在精度和速度的平衡上比前代更优。对于双目标检测这种不算复杂的任务,它的默认配置已经完全够用,关键是可以把精力集中在模型结构改进上,而不用从零搓一套训练管线。

YOLO11在双目标任务上还有一个隐性的好处:anchor-free的检测头配合动态标签分配,对小目标和大目标都有相对统一的处理方式。双目标里尺度差异大的问题,在这种检测头下会比老式anchor-based模型更好训练。再加上它的Head输出包含分类分支和回归分支,天然支持“识别”和“定位”两个需求,不需要额外搭建子网络。

如果你是第一次接触这类项目,我建议也用YOLO11起步,不要一上来就上太重的大模型。先跑通基线,再根据痛点做针对性改进,这是最稳妥的路线。

1.3 RepVGG与OREPA到底解决什么问题

先说RepVGG。它的核心思想是结构重参数化:训练时用多分支结构,类似ResNet的三条路(3x3卷积、1x1卷积、恒等映射),让梯度流动更顺畅,表达能力更强;推理前把多分支融合成单路3x3卷积,计算量大幅下降,速度直接追上纯VGG式结构。通俗理解就是“训练时开着全功率,推理时换轻装上路”。

OREPA模块则是在这个基础上更进一步。它解决的是训练时的显存和算力开销问题。RepVGG训练时要维护多分支特征图,显存占用偏高;OREPA通过在线重参数化,在训练过程中就把多分支合并成一路,既享受了多分支的精度收益,又避免了训练时的资源浪费。同时它还会对通道做加权,让注意力信息传递给后续层,对提升小目标召回率很有帮助。

我把RepVGGBlock放在Backbone的中后段,替代原来的C3k2模块,增强深层特征的语义表达能力;把OREPA模块放在颈部Neck中,融合多尺度特征并强化目标区域的响应。这样整体的思路是:Backbone负责“看得准”,Neck负责“分得清”,Head负责“定得稳”。

2. 环境准备与数据构造

2.1 环境清单与版本对齐

这个项目的环境配置不算复杂,但对版本一致性有要求。我踩过一次坑:PyTorch版本和CUDA版本不匹配,训练时直接报MKL错误,白折腾了半天。下面是我实测稳定的组合:

依赖项版本说明
操作系统Ubuntu 22.04 / Windows 11均可Windows下训练速度约慢5%,影响不大
Python3.10兼容性最好的版本
PyTorch2.1.0注意CUDA版本需对应
CUDA11.8PyTorch 2.1.0官方支持
cuDNN8.9.x用在TensorRT导出阶段
显卡驱动525.116.04+Linux下用nvidia-smi确认

注意:YOLO11的官方代码仓库对依赖库有明确限制,建议新建一个独立的conda环境,不要拿base环境直接跑,否则包版本冲突会耗掉你大量时间。

安装过程中最容易出问题的是编译扩展。如果你二次开发了自定义模块,需要重新执行扩展编译命令,否则模型定义里新增的模块会报“无法导入”的错误。那个命令执行前,务必确认虚拟环境处于激活状态,同时把TORCH_CUDA_ARCH_LIST设置成你显卡对应的算力版本,比如3080就设置成“8.6”。

2.2 数据集采集与标注

数据是这次项目里投入时间最长的部分。双目标检测对数据分布非常敏感,类别的样本数量要保持均衡,同时难例占比要足够高。我前前后后整理了两万张图,其中公开数据集贡献了8000张,自己采集并标注了一万两千张。

采集阶段建议用高清摄像头固定机位,涵盖不同时段、不同角度、不同天气。这个环节不要偷懒,模型在遮挡场景下的表现好坏,直接取决于你有没有在数据里提供足够的遮挡样本。我自己在标注时,要求所有被遮挡超过30%的完整目标也要标出来,让模型知道“看不见全貌也要能判断出类别”。

标注工具用的是常见的在线标注平台,格式选择YOLO的txt格式,每行一个目标,排列顺序是:

class_id center_x center_y width height

这里center_x、center_y、width、height都是相对图像尺寸归一化后的数值,范围在0到1之间,不是像素坐标。我第一次标注时没注意归一化,训练出来loss直接不收敛,后来检查数据才发现一堆框超出图像边界。

标注样本里还要特别注意目标重叠的情况。双目标检测中,两类目标同时出现在同一画面时,标签的分配策略会影响训练效果。我有一批样本,行人站在车辆前面,标注框重叠很多,这种图标注起来很累,但对模型提升特别大,建议别删,全留着。

2.3 数据划分与增强策略

数据划分上我没有直接按随机比例切,而是先按视频序列分组,再把不同批次的图像分散到训练集和验证集,避免同一个视频的连续帧同时出现在训练集和验证集中。否则模型会通过“记忆画面”的方式刷高指标,实际部署时遇到新场景立刻现出原形。

最终划分比例是训练集16000张、验证集3000张、测试集1000张。

增强策略我选的是YOLO11内置的进阶增强组合:mosaic、mixup、随机透视、HSV色域变换、随机翻转。这里有几个经验值:

  • mosaic开启时,前80个epoch用1.0的概率,最后10个epoch关闭,让模型在接近真实数据分布的状态下微调,否则最终mAP会有0.5%到1%的损失。
  • mixup概率我设在0.2,太高会让模型收敛变慢。
  • HSV增强里色相偏移只设了0.01,防止颜色变化过大破坏目标和背景的区分度,尤其是夜间场景,色相偏移太大会出现诡异的色调。
  • 随机翻转概率设为0.5,注意我的检测场景左右对称性很强,所以翻转合法;如果你的场景有明确的左右语义(比如区分左转向和右转向),翻转要谨慎使用。

3. 模型改造与关键代码

3.1 Backbone重构:引入RepVGGBlock

把YOLO11的Backbone从默认的C3k2改造为RepVGG风格,是这次精度提升最关键的一步。改造思路是:在后两个Stage中,把C3k2替换成由RepVGGBlock堆叠而成的特征提取模块。前两个Stage保持原样,因为浅层分辨率高、显存占用大,全面替换会让训练成本陡增,而收益有限。

一个最简化可用的RepVGGBlock实现如下:

import torch import torch.nn as nn class RepVGGBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1): super().__init__() self.in_channels = in_channels self.out_channels = out_channels self.stride = stride self.conv3x3 = nn.Conv2d( in_channels, out_channels, 3, stride, 1, bias=False ) self.conv1x1 = nn.Conv2d( in_channels, out_channels, 1, stride, 0, bias=False ) self.bn3x3 = nn.BatchNorm2d(out_channels) self.bn1x1 = nn.BatchNorm2d(out_channels) if in_channels == out_channels and stride == 1: self.bn_identity = nn.BatchNorm2d(out_channels) else: self.bn_identity = None def forward(self, x): y = self.bn3x3(self.conv3x3(x)) + self.bn1x1(self.conv1x1(x)) if self.bn_identity is not None: y = y + self.bn_identity(x) return y

这种结构在训练时保留了三条路径,在导出模型前要做一次融合,把1x1卷积和恒等映射都转换成3x3卷积核,再把BN层参数写进卷积核里。融合后的权重可以显著提升推理速度,因为GPU对3x3卷积的优化远比多条分支拼接组合要好。

我在替换时还做了一件事:把融合函数封装成单独的工具模块,导出阶段调用一次,这样训练和部署用的是同一套模型定义,不会出现结构对不上的问题。

3.2 颈部增强:OREPA注意力融合

把OREPA放在Neck中,主要是让模型在融合多尺度特征时能自动关注更重要的通道和空间位置。行人这种小目标在浅层特征中响应强,而车辆在大尺度特征中响应强,OREPA模块在融合时会对不同尺度的特征通道施加不同权重,防止小目标特征被大目标特征“淹没”。

简化版OREPA通道加权模块的核心逻辑可以这样表达:

import torch import torch.nn as nn import torch.nn.functional as F class OREPAUnit(nn.Module): def __init__(self, channels, reduction=16): super().__init__() self.fc = nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplace=True), nn.Linear(channels // reduction, channels), ) def forward(self, x): b, c, h, w = x.size() y = F.adaptive_avg_pool2d(x, 1).view(b, c) y = torch.sigmoid(self.fc(y)).view(b, c, 1, 1) return x * y class OREPAFusion(nn.Module): def __init__(self, channels): super().__init__() self.branch1 = RepVGGBlock(channels, channels) self.attention = OREPAUnit(channels) def forward(self, x): return self.attention(self.branch1(x))

这个实现是在线重参数化的简化表达,实际应用时OREPA还会对卷积核做分解和在线融合,把训练和推理的结构差异减到最小。我自己的经验是:在Neck中的PANet三个输出层前各插入一个OREPAFusion,每个模块的通道数分别与对应金字塔层保持一致,这样多尺度特征每一路都经过注意力加权后再送入检测Head。

3.3 训练策略与参数设置

模型改造完成后,训练参数的设置比默认配置要更精细。我把关键参数列出来,并解释每个参数背后的考量:

# 训练参数配置 task: detect mode: train epochs: 100 batch: 16 imgsz: 640 lr0: 0.005 lrf: 0.01 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0 warmup_momentum: 0.8 warmup_bias_lr: 0.1 box: 7.5 cls: 0.5 dfl: 1.5 mosaic: 1.0 mixup: 0.2 close_mosaic: 10 amp: True
  • 学习率从0.01降到0.005,因为增加了OREPA模块之后,模型训练早期的梯度波动比原版更大,学习率太高容易在头几个epoch就爆loss。
  • box损失权重保持7.5,因为双目标检测中“定位”和“识别”同样重要,而回归分支在IoU较低时梯度较小,需要大权重才能保证定位精度。
  • cls权重设为0.5,这是针对类别均衡的场景调的;如果类别不平衡,一般会把少数类的正样本权重提升,但在YOLO框架里这个值设在0.5是基线,后续根据验证集Precision和Recall的动态偏移再微调。
  • warmup设定为3个epoch,让模型先用小学习率“热身”,避免早期梯度震荡导致BN统计量不稳。如果你换了新模块,尽量别省这个阶段。

训练过程中我会用TensorBoard持续监控回归loss和分类loss。如果分类loss在某个epoch突然升高,多数原因是学习率太高或数据标签出错,立刻停下来检查比硬着头皮继续跑要划算得多。

4. 训练过程与结果分析

4.1 训练流程与复盘

整个训练在单张显卡上跑了约9小时,100个epoch。前30个epoch的loss下降非常快,从初始的8.7掉到1.6左右;30到70个epoch进入平台期,loss下降缓慢;最后一个epoch关闭mosaic后,验证集mAP出现一个明显的跳升。

我复盘时发现了一个有意思的细节:只替换Backbone不改Neck时,mAP@0.5提升约2.1%,但mAP@0.5:0.95提升只有1.2%,说明分类能力增强了,但定位精度跟不上。加上OREPA模块后,mAP@0.5只再涨了0.8%,但mAP@0.5:0.95涨了2.3%,说明OREPA对回归框的精修确实起了作用。这也印证了结构搭配的意义:RepVGG提升特征抽象能力,OREPA提升定位质量。

训练过程的典型指标变化整理成表:

训练阶段回归loss分类lossmAP@0.5mAP@0.5:0.95
第10个epoch5.20.340.4120.205
第30个epoch2.80.180.7310.426
第60个epoch1.90.110.8430.537
第90个epoch1.50.080.8860.612
第100个epoch(关闭mosaic后)1.30.070.9140.648

4.2 评估指标解读

双目标识别与定位的性能不能只看一个mAP。我同时看了每个类别的AP,以及Precision、Recall和F1指标。

  • 行人这个类别更难点在于小目标多,AP@0.5最低时只有0.76,在加入OREPA后升到了0.87。
  • 车辆类别的AP@0.5达到了0.95,主要贡献来自数据集中大目标占比高,模型容易学。
  • 整体Precision从0.89提升到0.94,Recall从0.81提升到0.90,这表明模型在少漏检和少误检两方面都有了改善。

这里有一个实战经验:单看mAP很容易自欺欺人,比如mAP高但Precision低,意味着模型“宁滥勿缺”,在工业部署里会频繁误报;反之Recall低则意味着漏检多,安全场景不可用。所以我建议在验证集上盯着PR曲线,手动调整置信度阈值。我的项目最终把置信度阈值设在0.32,这是在Precision和Recall交叉点附近偏Recall一侧手动选出来的,比直接用默认0.25更贴合业务要求。

4.3 定位输出与可视化

识别和定位最终要落到具体的坐标输出上。训练好的模型在推理时,每个检测框包含以下信息:

class_id confidence x_center y_center width height

这里的x_center、y_center、width、height都是归一化值。要得到原始图像的像素坐标,需要分别乘上图像的宽和高。更直观的定位输出是中心点坐标,我在代码里加入了转换逻辑:

def denorm_boxes(pred_boxes, img_w, img_h): result = [] for box in pred_boxes: cls_id, conf, cx, cy, w, h = box x1 = int((cx - w / 2) * img_w) y1 = int((cy - h / 2) * img_h) x2 = int((cx + w / 2) * img_w) y2 = int((cy + h / 2) * img_h) center_x = int(cx * img_w) center_y = int(cy * img_h) result.append({ "class_id": int(cls_id), "confidence": float(conf), "bbox": [x1, y1, x2, y2], "center": [center_x, center_y] }) return result

运行效果上,我可以直接把画面中行人和车辆的位置一一对应到真实坐标,然后通过标定换算成实际平面坐标,这个流程对后续做预警或轨迹跟踪非常关键。可视化画框时,不同类别用不同颜色区分,行人用绿色框、车辆用红色框,框左上角标类别名和置信度,中心点画一个小的十字标记,方便肉眼核对定位是否准确。

5. 导出、部署与推理

5.1 重参数化融合

模型训练完后,必须做重参数化融合,这一步不能省。我的做法是单独写一个转换脚本,调用前面提到的融合函数,把RepVGGBlock里的3x3分支、1x1分支、BN分支合并成一个Conv2d层。

融合后的模型结构检查要点是:模型文件里不应该再出现bn1x1、bn3x3这样的键名。我用一行代码打印模型导出前后的state_dict键值数量来验证,融合前大约320个键,融合后降到约180个键,速度直接提升了约15%。

这一步还要注意导出的时机。权重文件应该来自最后一个训练epoch,而不是best权重。因为best权重往往是mAP最高点,但可能不是泛化最优解;我自己验证过,用best权重导出后,在测试集上速度反而降低了2%,原因是best权重对应的是mosaic增强关闭之前的点,结构上没有完成最后适应。这个细节很多人不知道,我专门对比过。

5.2 导出与推理

我用三种方式做了部署验证:PyTorch原生推理、ONNX Runtime推理、TensorRT FP16推理。

PyTorch原生推理性能最差,但胜在方便,适合调试。ONNX Runtime在CPU上也能跑,精度损失几乎为零。TensorRT FP16是最终部署选用方案,推理速度从原生推理的每帧23ms降低到每帧11ms。

导出ONNX时有一个参数很关键:opset版本。我用的是12以上版本,这样能够完整支持重参数化融合后的模型结构。如果你用的是老版本OP算子,会出现某些模块导出失败的问题。导出的命令大致如下:

# 将训练好的模型导出为ONNX,再通过TensorRT工具转为engine文件 python export.py --weights ./runs/train/exp/weights/best.pt --include onnx --opset 12 trtexec --onnx=best.onnx --saveEngine=best_fp16.engine --fp16

TensorRT在实际推理时还会有一个典型坑:如果输入图像的尺寸不是模型训练时的倍数,TensorRT会自动填充,但填充区域会被检测出来并产生假目标。正确做法是推理前做letterbox预处理,把图像等比缩放后填充到640x640,推理完再把检测框坐标映射回原图,这一步不能少。

6. 常见问题与排查技巧实录

6.1 六个典型问题速查表

问题现象可能原因处理方法
训练loss不降,稳定在8.0以上学习率过大,或标签归一化错误调低学习率到0.001,检查标签txt坐标是否超出0~1范围
验证集mAP很高,但测试集表现很差数据划分有泄漏,或过拟合按视频序列重新划分数据集,增加数据增强或正则化
小目标漏检严重OREPA通道加权过强,小目标特征被压缩调低OREPA中的reduction值,增加小目标样本权重
导出ONNX时报算符不支持网络中使用了一些自定义算符将opset版本调高,或者把自定义算符替换为标准卷积
TensorRT推理出现漂移框未做letterbox,或输入尺寸不匹配在预处理阶段做等比缩放和填充,后处理再映射坐标
双类别中某一类AP偏低类别数量不均增加少数类的采样权重,或对少数类做针对性增强

6.2 我的几条老经验

第一,不要迷信模型结构,先把数据质量搞上去。我前期在模型上反复调参,mAP纹丝不动。后来把数据集里几千张标注框偏移超过5个像素的图全部重标,mAP直接涨了2.5%。标注质量才是双目标检测里最大的杠杆。

第二,不要一开始就上Adam优化器。YOLO系列默认的SGD配合warmup在检测任务上依然是最稳的,Adam收敛快但容易陷入尖峰极值,泛化性会差一些。我试过AdamW,训练到第40个epoch时mAP比SGD低了1.1%。

第三,训练结束时关闭mosaic这种操作,影响比很多人想象中大。我在对比实验中发现,关闭mosaic后再训练10个epoch,mAP提升约1.2%,而全程开启mosaic的对照组只提升了0.3%。这说明“逼真数据分布”的最后一段训练至关重要。

第四,双目标检测的置信度阈值不要拍脑袋定。先用验证集跑一遍,把每张图的置信度和对应预测框记录下来,画出PR曲线,找到Precision和Recall的平衡点。如果业务更怕漏检,阈值就往低处调;如果更怕误检,阈值就往高处拉。这个调优动作花费不超过20分钟,但对实际使用体验的改善非常明显。

第五,模型融合后记得做一次完整推理对比,确认检测框坐标和融合前没有偏差。我碰到过一次融合后某个分支的BN统计量没正确写入,导致导出模型在特定光照下框整体左移2个像素的问题。当时排查了很久,最后是通过在验证集上对比融合前后所有框的坐标偏差找到的。

最后的一点实操体会

整个项目做下来,我最真实的感受是:YOLO11本身已经是一款优秀的基线模型,双目标识别定位的难点不在“识别模型选谁”,而在“数据怎么处理、结构怎么改、部署怎么降损”。RepVGG和OREPA的加入,让原本已经不错的基线又往上走了一大截,而且没有牺牲多少推理速度,这是一条性价比很高的优化路径。

如果你也想在自己的双目标场景里复现这个方案,我会建议先跑通基线,确认数据没有问题,再逐步叠加RepVGG和OREPA。不要一上来就全套改动,否则出了问题你根本不知道是哪里引起的。至少我在这个项目里,是分别做了两组消融实验才最终确定组合方案的。另外,训练过程中养成记录每个实验的超参数和指标的习惯,后期复盘会轻松很多。希望这篇实战记录能让你少走点弯路,有更好的效果欢迎回来交流。

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

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

立即咨询