资讯详情 Mask-RCNN改造实现人体姿态估计:从热力图分支到关键点检测全流程
📅 2026/10/21 7:18:56
最近我把「深度学习一遍过」系列的笔记梳理到第27篇主题锁定在基于 Mask-RCNN 的人体姿态估计的设计与实现。很多人第一次听到这个组合会觉得奇怪Mask-RCNN 明明是做实例分割的跟人体关键点有什么关系其实只要把它的输出头改造一下在原来的目标框、类别、分割掩码之外增加一个关键点热力图分支整个模型就可以同时告诉你图片里有哪些人、人的轮廓在哪、每个关节的坐标在哪。这篇文章就是我完整跑通一遍之后的记录既讲网络结构怎么改也讲数据标注、训练评估、推理可视化的具体做法适合想从源码层面理解姿态估计、或者想在自有数据集上复现一个关键点检测模型的同学。1. 为什么选Mask-RCNN这套框架来做人体姿态估计1.1 自顶向下和自底向上两条姿态估计路线的区别人体姿态估计大致分成两类思路。一类是自顶向下也就是先做人检测把每一个人裁剪出来再对每个人独立预测关键点。另一类是自底向上先在全图中检测出所有关键点再把属于同一个人的关键点组装起来。自顶向下路线在多人和拥挤场景下的分组准确性更高因为检测框已经天然把人区分开了问题是速度会随着画面里人数增加而变慢。自底向上路线的推理速度基本与人数的关系不大但关键点之间的“装配”组合爆炸容易把两个人的手腕肘部接错。Mask-RCNN 天然适合自顶向下这套玩法。RPN 网络先产生一批人体候选框RoIAlign 把每个框内的特征对齐后后面的分支只对这一个框负责。这也是我在实际项目里优先选择它的原因检测、分割、姿态估计三个任务共享同一个特征提取骨架前后逻辑顺工程上也更好维护。路线代表思路优点难点自顶向下先检测再关键点多人分组准确精度上限高推理速度受人数影响自底向上先关键点后装配速度稳定适合实时关键点装配易出错Mask-RCNN检测分割关键点多任务统一结构紧凑模型重训练成本高1.2 Mask-RCNN的框架为什么能往关键点方向扩展Mask-RCNN 的核心不是“分割”这件事本身而是一套通用的两阶段检测流程骨干网络提特征RPN 提候选框RoIAlign 做特征对齐后续的网络头各自完成分类、回归和逐像素预测。逐像素预测这个能力非常关键。分割掩码的本质是在每个 RoI 内预测一张 HxW 的 mask而关键点热力图也是在一张 HxW 的图上做预测只是把“属于前景还是背景”变成了“这个位置属于第几个关键点”。从设计上看关键点分支和掩码分支几乎是同构的这就是改造的底气。我在实验里把关键点分支放在了掩码分支旁边两者共享 RoIAlign 后的特征但参数不共享。这样做的好处是如果训练数据里分割标注比较少、关键点标注比较多两者不会互相拖累损失反向传播时可以各自控制幅度。1.3 你真正需要的可能不是Mask-RCNN这句话我想放在前面说避免有人看完整个流程后才发现走错了路。如果项目里只有一个需求就是做人体 17 点姿态估计不需要实例分割也不需要同时跑分类检测那我更推荐直接使用轻量化的单人关键点模型或者以高分辨率保持为卖点的 HRNet 系列网络更简单、训练资源占用更少、推理速度更快。Mask-RCNN 的价值在于“多任务统一”。比如我要做一个行人分析的 Demo既需要框出人、抠出人、还需要知道人站着还是坐着这时候用一个 Mask-RCNN 改出来的模型一次推理就能拿到三大类输出后处理管线里不用拼接多个模型的坐标体系这是它最大的工程优势。所以选型前先列需求清单再决定要不要用这套重型框架。2. 网络改造的关键设计检测、分割之外再加一个关键点头2.1 共享骨架与多尺度ResNetFPN承担了什么Mask-RCNN 的骨干网络通常由 ResNet 和 FPN 组成。ResNet 负责把原始图像逐层抽象成高层次语义特征FPN 则把不同卷积层输出的多尺度特征融合起来形成金字塔特征层。FPN 在姿态估计里比在普通分类里更重要因为人的尺寸变化非常剧烈。近景的人可能占图片一半远景的人只有几十个像素RPN 需要在合适的特征层上匹配不同尺寸的人体框。我自己实验时对比过去掉 FPN 的结构小目标的漏检率明显上升姿态估计的 AP 直接掉了好几个点。RPN 在每一层金字塔特征上滑动生成锚框这些锚框会给出“它是不是前景”和“粗略的框回归偏移”。到了这一步模型其实已经完成了人的定位后面的关键点分支只需针对每个候选框精细化。2.2 RoIAlign像素级任务里最容易被低估的细节早年的 RoI Pooling 在做区域特征提取时会把浮点坐标直接取整比如一个 17.6x23.2 的区域被强行量化成 17x23这一两个像素的偏移对分类影响不大但对关键点来说可能是致命的。关键点坐标最终要映射回原图如果特征层面的坐标已经偏移了一两个像素关键点的误差就会被放大。RoIAlign 的做法是不取整把每个特征单元划分成若干采样点用双线性插值算出每个点的值再取平均。这个过程保留了空间位置精度让后面预测关键点热力图时能对齐到正确的空间位置。在我实测的对比中把 RoI Pooling 换回 RoIAlign 之后腕点和踝点这类小关节点的预测稳定性提高最明显。这就是为什么设计一个姿态估计网络时RoIAlign 不应该被视为可选组件。2.3 关键点头的结构设计与实现关键点分支的输入是每个候选框经过 RoIAlign 后得到的特征图输出是 K 张关键点热力图K 在 COCO 约定下是 17。常用做法是先用几个卷积层堆叠提取局部纹理再用反卷积把分辨率放大最后用 1x1 卷积输出 K 个通道每个通道对应一种关键点的响应图。我简化后的 PyTorch 风格代码大致长这样import torch.nn as nn class KeypointHead(nn.Module): def __init__(self, num_keypoints17): super().__init__() # 输入特征图经过 RoIAlign 后通常是 14x14 分辨率 self.deconv nn.ConvTranspose2d(256, 256, kernel_size4, stride2, padding1) self.predict nn.Conv2d(256, num_keypoints, kernel_size1) def forward(self, x): x self.deconv(x) # 14x14 - 28x28 x self.predict(x) # 28x28 - 17x28x28 return x实际项目里我会在反卷积之间再穿插一些带 ReLU 的 3x3 卷积层让特征表达更充分。热力图分辨率越大定位精度理论上越高但显存和计算量也会上升14x14 输入到 28x28 输出是一个比较平衡的设置。这里有一个设计关键不要用全连接层直接输出 17x2 的坐标。坐标回归看起来更直接但它把位置关系压成了一维向量丢失了空间结构而且对遮挡非常敏感。热力图方案天然能表示“这个位置有多少概率是关键点”还能保留多峰响应更符合 CNN 的平移等变性。2.4 多任务损失如何组合才不打架Mask-RCNN 改造后总损失由四部分组成分类损失、检测框回归损失、分割掩码损失、关键点损失。每一类损失的量纲和变化速度都不一样直接相加会导致某个分支或某几个分支学不好。我参考通用实践做了两个处理。第一关键点损失使用逐像素的二值交叉熵但只统计对应关键点为可见状态的位置并且除以可见关键点的数量做归一化避免不同样本间可见点数量不同导致loss规模不稳定。第二给关键点损失一个可调节的权重系数我常用 0.5 到 1.0。如果关键点任务简单权重调低一些让检测和分割的梯度不被带偏如果关键点任务是主要目标权重可以上调。在使用预训练权重时还会遇到一个坑预训练模型的权重里并没有关键点分支的对应参数。加载权重时要把关键点分支单独跳过并随机初始化否则 PyTorch 会报键值不匹配或者程序异常退出。这是我第一次加载权重时被卡了半小时的地方。3. 数据侧的准备让人知道“17个关键点在哪”3.1 COCO格式的关键点标注到底长什么样姿态估计最常用的标注格式是按 COCO 风格组织的 JSON。每一条人物实例的 annotation 除了包含 bbox 和 segmentation 之外还有一个 keypoints 数组长度是 17x3排列顺序是每个关键点的 x、y、可见性标志。可见性标志的语义值得注意。0 表示关键点没有标注训练时要完全忽略1 表示关键点被遮挡但位置仍然存在可以参与训练但置信度会偏低2 表示关键点可见且未被遮挡。我在第一轮训练时把所有 0 的点当成 1 处理结果腕点预测位置飘到画面外损失也迟迟降不下去后来仔细查了标注工具才意识到是对可见性理解错了。COCO 的 17 个关键点有固定顺序印象里最常用的顺序是鼻子、左眼、右眼、左耳、右耳、左肩、右肩、左肘、右肘、左腕、右腕、左髋、右髋、左膝、右膝、左踝、右踝。这里的“左”和“右”不是画面上的左右而是被拍摄者自己的左右标注和可视化时一定要统一视角不然左右手很容易对调。3.2 热力图真值怎么生成训练关键点分支时不是直接告诉模型“第 7 个点是 (x, y)”而是给模型一张高斯的“甜点区域”在真实关键点坐标附近用高斯核生成一个概率响应越靠近中心值越大越远越接近零。我常用的生成函数如下import numpy as np def gaussian_heatmap(shape, center, sigma2.0): h, w shape x0, y0 center ys, xs np.meshgrid(np.arange(h), np.arange(w), indexingij) dist2 (xs - x0) ** 2 (ys - y0) ** 2 return np.exp(-dist2 / (2 * sigma * sigma))高斯核的 sigma 要跟输出热力图的分辨率匹配。如果热力图是 28x28sigma 取 1 到 2 比较常见如果输出分辨率更低sigma 要适当放大否则目标区域太小正样本比例过低模型学不到东西。训练阶段每个 RoI 内的真值热力图要基于这个 RoI 的局部坐标生成而不是原图坐标。我在刚开始写 Dataset 时直接在原图上生成热力图再把整张图的热力图裁进 RoI结果训练时 loss 完全不对。正确做法是先把关键点坐标变换到 RoI 局部坐标系再生成目标热力图。3.3 数据增强里三个高价值的坑第一个坑是水平翻转。翻转后的关键点坐标 x 要变成“图片宽度减去 1 再减去当前 x”同时关键点顺序也要左右互换。比如左肩的关键点索引要换成右肩的索引。如果只翻转坐标不交换顺序模型会把左右肩搞混出现左肩热点跑到右边去的奇怪现象。第二个坑是缩放和旋转。关键点坐标要跟着图像施加同一个几何变换矩阵边界处还要处理新坐标超出图片范围的情况。我一般直接放弃那些关键点被裁出图像太多的样本而不是硬塞给模型这样能减少脏样本对训练的干扰。第三个坑是混合增强。业内常说的多图拼接或背景替换对目标检测很有效但对关键点任务要谨慎使用因为关键点在拼接边界附近可能被切割旧的关键点坐标不会因为新背景而变得有意义。我在做背景替换时只替换不含关键点区域的背景块并且过滤掉边界碰撞的样本实验效果才恢复正常。4. 训练评估中的指标与实测踩坑记录4.1 先看懂OKS再看AP姿态估计的主要评估指标是 AP但 AP 计算的底层是 OKS也就是目标关键点相似度。OKS 的直觉是预测点和真实点的距离越近越好但不同部位的容错范围不同。具体计算时每个关键点会有一个来自数据集统计的标准差 sigma两个点的距离除以这个标准差和平方做一个高斯衰减最终对所有可见关键点取平均。可以理解为有的关键点天生标注波动大比如髋部允许的误差就大像眼睛、手腕这种位置比较明确的点允许的误差就小距离稍偏就会扣分。实际训练时我通常关注两个数字AP0.5 和 AP0.75。前者看整体检测和定位是否基本正确后者看定位精度是否够高。如果 AP0.5 还行但 AP0.75 很低说明关键点普遍有一定偏差优先去提升热力图分辨率或优化坐标还原逻辑。4.2 损失不降时的排查链路训练时最烦的是损失掉不下去。我第一轮训练在 20 个 epoch 后关键点损失几乎不变先怀疑模型结构几天排查后发现是数据代码的问题。这里分享一条排查顺序第一先用单张图片过拟合。固定一批数据反复训练如果单张图都不能让 loss 降下来结构和数据流水线肯定有一个不对。第二检查损失中正样本的占比。关键点 loss 如果长时间是零很可能所有关键点可见性都被标记成了 0或者真值热力图坐标系没有转换。第三把 RoIAlign 输出的特征和热力图真值可视化出来叠到原图上看看是不是对得齐。第四再检查超参数尤其是学习率。关键点分支的梯度规模和预训练 backbone 不同学习率太大容易震荡太小则收敛极慢。我把训练过程记录过一张损失曲线最好的配置是先让检测分支和分割分支正常收敛再一点一点调大关键点损失的权重。从头就让四个损失全部生效模型会被不同量级的梯度拖住每个分支都学不好。4.3 多人重叠与遮挡场景的处理手段多人场景下Mask-RCNN 的主要麻烦是检测框重叠。两个路人很近时RPN 可能给出大量重复框NMS 阈值设得太高会把不同人的候选框合并掉设得太低又会让一个目标有多个输出。我实际测试下来NMS 阈值取 0.5 到 0.7 之间比较合理具体要看业务场景对召回率和精确率的取舍。另外可以在推理阶段加入一个最小框得分阈值只保留高分候选框减少低质量候选框对关键点热力图的干扰。遮挡严重的关节比如被桌子挡住的膝盖预测置信度通常会比较低后续可以把低置信度关键点直接标为不可见而不是强行给出一个位置。4.4 做几组消融实验验证设计思路消融实验不是科研人员的专利自己做项目时也值得跑几组对照。我建议至少做三组有无 FPN 的对比、RoIAlign 与旧池化方式的对比、热力图回归与直接坐标回归的对比。这三组对比分别检验的是多尺度特征对姿态估计的影响、特征对齐精度对关键点的影响、输出表示方式对模型表达能力的限制。我自己跑出来的结果基本符合预期去掉 FPN 后小目标 AP 掉得厉害换回传统池化后腕踝点误差增大把热力图换成坐标回归后有些关键点出现不可解释的漂移。这些数据不只是写报告用它还能解释为什么框架里某些模块不可删。5. 推理、可视化和把Demo跑通的完整链路5.1 从预测热力图到17个坐标点推理阶段模型输出的关键点不是坐标而是 17 张热力图。要把热力图变成点坐标第一步是判断该 RoI 内的热力图要不要上采样到原图分辨率。我的做法是把 28x28 的热力图通过双线性插值放大到原框尺寸然后在每张热力图上搜索最大响应的位置。最大响应位置对应的坐标就是关键点位置该位置的热力图响应值可以作为置信度。如果想更平滑一些可以在最大点附近做一个加权质心相当于 soft-argmax能减少量化抖动。这里特别容易踩坑的是坐标映射。热力图坐标是局部 RoI 内的最终输出到原图时要用 RoI 的原图坐标做偏移换算。我见过不止一次代码里漏掉这个偏移导致关键点全部堆在检测框的左上角。5.2 把人体的骨架连接画出来拿到 17 个关键点坐标后还需要按人体结构把它们连起来才能在图上形成骨架效果。COCO 风格里常用连接关系包括鼻子连左右眼左右眼连左右耳左右肩分别连左右肘左右肘连左右腕左右髋连各自膝盖膝盖再连脚踝两个肩和两个髋之间也有横线。实际绘制时我用两种颜色区分左右侧肢体比如左侧用暖色、右侧用冷色视觉上能直接看出左右是否被混淆。如果某个关键点置信度过低我选择不绘制该点也不绘制以它为端点的连线否则画出来会是一条歪斜的诡异线条影响人眼判断。下面给出连接关系的一部分示例完整使用时按 COCO 官方的 skeleton 配置来skeleton [ (0, 1), (0, 2), (1, 3), (2, 4), (5, 6), (5, 7), (7, 9), (6, 8), (8, 10), (5, 11), (6, 12), (11, 12), (11, 13), (13, 15), (12, 14), (14, 16) ]5.3 视频流处理的工程建议把单图推理接到视频流时我建议先做两件事一是给检测框做帧间匹配二是对关键点做时间平滑。否则每帧独立推理会出现同一个人的骨架左右晃动、跳来跳去的视觉效果。帧间匹配用最简单的方式就能起步计算当前帧检测框和上一帧已有轨迹的 IoU超过阈值的就认为是同一个人再更新轨迹中心。关键点平滑可以用一阶低通滤波让关节坐标的变化不要过于突兀。这样处理之后实时性稍微下降但观感好很多后续如果做动作识别也不会因为视频抖动产生误判。6. 复现过程中比较实用的配置与扩展思路6.1 显存不够和训练太慢怎么办Mask-RCNN 的显存占用确实偏高。12G 显存跑 640x480 输入、batch size 2加上关键点分支后已经有点紧张。我的处理办法有三个把输入分辨率降到 512x384把 RPN 的候选框数量从 512 降到 256使用梯度累积来模拟更大 batch。这三个组合通常能让训练跑起来精度损失基本可以接受。训练时间方面如果用通用开源预训练权重做初始化然后在自有小规模数据集上微调几张消费级显卡换来的时间大约是小半天到一天具体取决于数据量和 epoch 数。要是从零训练周期会成倍拉长没有充分理由不建议这么做。6.2 一套相对稳妥的训练配置参考基于我几次实验的经验这里给出一份可参考的配置。优化器选 SGD 加动量backbone 学习率较低新增分支的学习率可以高一些采用 warmup 策略先让学习率缓慢上升。初始学习率可以从 0.02 附近起步weight decay 取 0.0001momentum 取 0.9。如果使用小 batch可以通过梯度累积让有效 batch 控制在 8 到 16 之间。损失权重上关键点损失先给 0.5跑几十个 step 后观察各分支的 loss 量级再微调。有一个必须注意的细节每次调整学习率或权重之后都要重新做单图片过拟合验证确认改动没有破坏数据流和梯度传播。6.3 后续可以往哪些方向扩展这套模型跑通之后可以做很多衍生工作。把关键点序列按时间聚合接一个动作分类器就能做动作识别或者跌倒检测。把分割掩码和关键点结合还能做虚拟试穿、人体抠图这类互动应用。如果想把模型做的更轻可以尝试在骨干网络上换成移动端友好的结构同时用蒸馏的方式把关键点分支的预测能力迁移过去。我自己更推荐做慢动作分析方向因为视频中的时间上下文能显著提升对遮挡关节的预测能力这在单帧图像里几乎无解。把 Mask-RCNN 框架作为底座再接一个时序模块整体改造不复杂但体验提升非常明显。最后分享一个我在实战里反复验证过的体会姿态估计的数据质量往往比网络结构更决定效果。标注时左右弄反、可见性标志含糊、关键点顺序不一致这些数据问题会让再好的模型也学歪。先花时间统一标注规范再动手写训练代码后面能省下大量调参的时间。还有一个小技巧评估 OKS 时一定要带上标准化的关键点 sigma 表否则你会发现指标虚高模型实际部署后却远没有那效果。