1. 为什么多人姿态估计值得死磕
多人姿态估计这个方向,从学术圈到工业界,热度一直没降过。原因很简单:只要涉及到“理解人在做什么”,就绕不开先找到人的关节在哪。健身App要判断深蹲姿势对不对,安防场景要分析人群异常行为,体育转播要实时标注运动员骨骼线,甚至零售场景要统计顾客在货架前的停留姿态——这些应用的底层都依赖同一个能力:从一张图里同时定位多个人,并把每个人身上的关键点连成骨架。
但多人姿态估计的难度比单人姿态估计高出一个量级。单人场景下,你只需要处理一个人,网络可以集中全部注意力去回归关键点。多人场景下,人数未知、尺度不一、遮挡严重、人与人之间还会互相干扰。早期的主流做法是“自顶向下”:先用一个人体检测器把所有人框出来,再对每个框单独做单人姿态估计。这个思路逻辑清晰,但有个致命问题——检测框的质量直接决定姿态估计的上限,而且人数越多,耗时线性增长,实时性很难保证。
另一条路是“自底向上”:先检测出图中所有的关键点,再通过某种分组策略把属于同一个人的关键点聚到一起。这条路的好处是推理时间和人数基本无关,一次前向传播就能搞定所有人。但难点也很明显:关键点分组极其容易出错,尤其是当两个人靠得很近、肢体交叠的时候,网络很容易把A的左手腕分到B身上。
2018年前后,自底向上的方法开始真正具备和自顶向下掰手腕的能力,其中一个标志性的工作就是Cascaded Pyramid Network(级联金字塔网络,简称CPN)。这篇论文的核心贡献在于:它没有去卷分组策略,而是把精力集中在“怎么把难检测的关键点找出来”这件事上。这个思路在当时非常务实——因为分组算法再聪明,如果关键点本身就没检测到,后面全是白搭。
CPN的设计哲学可以用一句话概括:先粗后精,先易后难。它用一个特征金字塔网络(FPN)作为骨干,先把不同尺度的特征融合起来,然后通过两个级联的阶段——GlobalNet和RefineNet——逐步把难点的关键点从背景和遮挡中“抠”出来。这个结构在当年的COCO关键点挑战赛上拿了冠军,而且推理效率相当可观。
我读这篇论文的时候,最大的感受是:它没有堆砌花哨的模块,而是把“多尺度”和“难例挖掘”这两个经典思路在姿态估计任务上做到了极致。对于想入门多人姿态估计的开发者来说,CPN是一个非常好的切入点——它的结构清晰、代码实现相对友好、效果在当时也是SOTA级别。更重要的是,它提出的很多设计理念,比如级联精修、在线难例挖掘,在后来的很多工作中都被反复借鉴。
这篇文章我会从论文的核心设计出发,把CPN的每个模块拆开讲透,包括它为什么这么设计、参数怎么定、训练时有哪些坑、实际复现时要注意什么。如果你正在做姿态估计相关的项目,或者想找一个结构清晰的自底向上方案作为baseline,这篇笔记应该能帮你省下不少读论文和调代码的时间。
2. CPN的整体架构与设计思路拆解
2.1 自底向上范式的核心矛盾
在深入CPN的细节之前,有必要先把自底向上范式的核心矛盾讲清楚。自底向上的多人姿态估计通常分两步走:第一步是关键点检测,网络输出所有关键点的热力图(heatmap);第二步是关键点分组,把热力图上检测到的关键点分配给不同的人。
这两步里,第一步是基础。如果关键点检测本身漏了、偏了,分组算法再精妙也救不回来。而关键点检测的难点在于:不同关键点的检测难度差异巨大。眼睛、鼻子、耳朵这些头部关键点,通常纹理清晰、对比度高,网络很容易定位;但手腕、脚踝、膝盖这些关节,经常被衣物遮挡、被其他肢体压住、或者因为运动模糊而变得模糊不清。更麻烦的是,这些“难关键点”恰恰是姿态估计中最具判别力的部位——你判断一个人是抬手还是垂手,看的是手腕;判断是站立还是行走,看的是脚踝。
CPN的作者显然抓住了这个矛盾。他们没有去设计更复杂的分组算法,而是把全部精力放在“如何让网络学会检测难关键点”上。这个选择在当时是很有勇气的,因为分组问题看起来更“显眼”,很多工作都在卷分组。但CPN用结果证明了:把关键点检测做到极致,分组用简单的贪心匹配就能拿到很好的效果。
2.2 特征金字塔:多尺度信息的融合
CPN的骨干网络是一个特征金字塔结构。特征金字塔的核心思想是:不同尺度的特征图包含不同层次的信息。浅层特征图分辨率高,包含丰富的空间细节,适合定位小目标和精确边缘;深层特征图分辨率低,但语义信息强,适合判断“这是什么”。对于姿态估计来说,既需要精确的空间位置,又需要足够的语义理解来区分不同关键点,所以多尺度融合是必然选择。
CPN的特征金字塔具体是怎么做的呢?它以一个ResNet作为基础网络,取最后几个stage的特征图,然后通过自顶向下的路径和横向连接把它们融合起来。自顶向下的路径通过上采样把深层特征图放大,横向连接则把浅层特征图通过1x1卷积调整通道数后与上采样结果相加。这样得到的融合特征图,既有深层的语义信息,又有浅层的空间细节。
这里有个细节值得注意:CPN在融合时用的是加法而不是拼接。加法操作参数量更少,计算效率更高,而且在实际实验中效果并不比拼接差。这个选择体现了CPN的一个设计原则:在保证效果的前提下,尽量降低计算复杂度。毕竟姿态估计是一个对实时性有要求的任务,模型太重很难落地。
2.3 GlobalNet与RefineNet的分工逻辑
CPN最核心的设计是它的级联结构:GlobalNet和RefineNet。这两个网络不是简单的串联,而是有明确的分工。
GlobalNet的任务是“粗定位”。它接收特征金字塔的输出,对每个关键点生成一个热力图。这个阶段的目标是尽可能把所有关键点都找出来,哪怕位置不够精确。GlobalNet的设计比较常规:几层卷积加上采样,输出K个通道的热力图(K是关键点数量)。它的损失函数是标准的均方误差(MSE),对所有关键点一视同仁。
RefineNet的任务是“精修”。它接收GlobalNet的输出和特征金字塔的融合特征,专门针对那些GlobalNet没检测好的难关键点进行修正。RefineNet的关键创新在于它的损失函数:它不是对所有关键点一视同仁,而是根据每个关键点的检测难度动态调整权重。具体来说,它计算每个关键点在当前batch中的损失值,然后只对损失最大的那部分关键点进行反向传播。这就是在线难例挖掘(Online Hard Keypoints Mining)的思想。
这个分工逻辑非常符合直觉:先让网络学会走,再让网络学会跑。GlobalNet负责把大部分简单关键点搞定,RefineNet负责攻坚剩下的硬骨头。而且由于RefineNet只关注难关键点,它的网络可以设计得更轻量,不会显著增加整体计算量。
2.4 为什么级联比单阶段更有效
你可能会问:为什么不直接用一个网络端到端训练,非要搞两个阶段?这个问题我在读论文时也想过。后来在复现过程中才慢慢理解:单阶段网络在训练时,简单关键点的损失会很快降下去,难关键点的损失虽然大,但在总损失中的占比不高,梯度被简单关键点“稀释”了。网络会倾向于优先优化简单关键点,因为这样总损失下降得更快。结果就是难关键点一直学不好。
级联结构通过显式地分离两个阶段,让RefineNet可以专注于难关键点。而且RefineNet的输入包含了GlobalNet的输出,相当于站在GlobalNet的肩膀上做精修,学习难度大大降低。这就像考试时先做简单题再做难题,而不是所有题一起做——先做简单题能快速建立信心,再做难题时心态和策略都会更好。
另外,级联结构还有一个好处:它允许两个阶段使用不同的损失函数和训练策略。GlobalNet可以用较大的学习率快速收敛,RefineNet则可以用较小的学习率精细调整。这种灵活性在单阶段网络中很难实现。
3. 核心模块的细节解析与实操要点
3.1 特征金字塔的具体实现与参数选择
CPN的特征金字塔实现有几个关键参数需要仔细选择。首先是基础网络的选择。论文里用的是ResNet-50和ResNet-101,这两个网络在ImageNet上预训练过,特征提取能力有保障。实际复现时,如果你追求轻量化,可以用ResNet-18或MobileNet替换,但要注意浅层网络的语义信息可能不够强,需要适当增加融合层的数量。
其次是融合的层数。CPN取了ResNet的C2、C3、C4、C5四个stage的输出进行融合。C2分辨率最高但语义最弱,C5语义最强但分辨率最低。融合时从C5开始自顶向下,逐层与C4、C3、C2相加。这个过程中,上采样用的是最近邻插值,横向连接用的是1x1卷积。1x1卷积的作用是调整通道数,让不同stage的特征图通道数一致,这样才能做加法。
这里有个实操细节:1x1卷积的通道数设置。论文里没有明确说,但根据我的经验,通常设置为256比较合适。太小会导致信息瓶颈,太大则增加计算量且容易过拟合。另外,上采样后的特征图尺寸要和横向连接的特征图完全一致,否则加法会报错。在实际写代码时,建议用F.interpolate的size参数显式指定目标尺寸,而不是用scale_factor,这样可以避免尺寸不匹配的问题。
还有一个容易被忽略的点:融合后的特征图是否需要再加一层卷积?论文里的做法是加了一个3x3卷积来平滑融合后的特征。这个卷积的作用是消除上采样带来的棋盘效应,同时增强特征的表达能力。我在复现时试过去掉这个卷积,结果mAP掉了大概1.5个点,说明这个平滑操作还是很有必要的。
3.2 GlobalNet的网络结构与训练策略
GlobalNet的结构相对简单:输入是特征金字塔的输出,经过几层卷积和上采样,最终输出K个通道的热力图。具体来说,它包含一个头部网络和几个上采样模块。头部网络通常是两个3x3卷积加ReLU,然后通过1x1卷积输出K个通道。上采样模块则是简单的双线性插值或转置卷积。
训练GlobalNet时,损失函数是标准的MSE损失。这里有个关键点:热力图的生成方式。CPN用的是高斯热力图,即以每个关键点为中心,生成一个二维高斯分布。高斯核的大小(sigma)是一个超参数,通常设置为2到4之间。sigma太小会导致热力图过于尖锐,网络难以学习;sigma太大会导致热力图过于平滑,定位精度下降。我的经验是:对于COCO数据集,sigma=3是一个比较稳妥的选择。
学习率方面,GlobalNet可以用较大的初始学习率,比如1e-3,然后随着训练轮数增加逐步衰减。优化器用Adam或SGD都可以,但SGD需要更仔细地调参。Batch size建议至少设置为32,因为热力图回归任务对batch size比较敏感,太小的batch size会导致梯度噪声过大。
还有一个实操要点:GlobalNet的输出分辨率。CPN最终输出的热力图分辨率是输入图像的1/4。这个分辨率是精度和计算量的折中。如果你需要更高的定位精度,可以输出1/2分辨率的热力图,但计算量会显著增加。在实际项目中,1/4分辨率通常已经足够,因为后续还可以通过亚像素精修来提升精度。
3.3 RefineNet的难例挖掘机制与实现细节
RefineNet是CPN的灵魂模块,它的核心是在线难例挖掘。具体实现方式是:在每个batch的前向传播后,计算每个关键点的损失值,然后对所有关键点的损失进行排序,只取损失最大的前M个关键点进行反向传播。M是一个超参数,论文里设置为8。
这个机制的关键在于“在线”两个字。它不是预先定义哪些关键点是难的,而是根据网络当前的状态动态判断。训练初期,所有关键点的损失都很大,难例挖掘相当于选了损失最大的几个;训练后期,简单关键点的损失降下去了,难例挖掘就会聚焦在那些真正难的关键点上。这种动态调整的策略比静态加权更有效。
实现时有一个坑需要注意:难例挖掘是在关键点维度上进行的,而不是在样本维度上。也就是说,对于每个batch,我们计算所有样本所有关键点的损失,然后取损失最大的M个关键点。这M个关键点可能来自不同的样本,也可能来自同一个样本的不同部位。这种设计的好处是充分利用了batch内的信息,不会因为某个样本特别难就过度关注它。
另一个细节是:RefineNet的输入是GlobalNet的输出加上特征金字塔的融合特征。这两个特征在通道维度上拼接,然后经过几层卷积输出精修后的热力图。拼接操作要求两个特征图的空间尺寸一致,所以GlobalNet的输出需要上采样到和特征金字塔输出相同的分辨率。
训练RefineNet时,学习率通常比GlobalNet小一个数量级,比如1e-4。这是因为RefineNet是在GlobalNet的基础上做精修,不需要太大的学习率来改变网络参数。另外,RefineNet的训练轮数通常比GlobalNet少,因为它的任务更聚焦,收敛更快。
3.4 关键点分组策略的工程实现
虽然CPN的重点不在分组,但分组策略的选择会直接影响最终效果。论文里用的是贪心匹配:先选一个置信度最高的关键点作为种子,然后找离它最近的其他关键点,如果距离小于某个阈值,就认为它们属于同一个人。这个阈值通常设置为关键点之间平均距离的某个比例。
贪心匹配的优点是简单快速,缺点是容易在人群密集场景下出错。改进的思路有几种:一是用部位间的几何约束来过滤错误匹配,比如左手腕和左肘的距离不应该超过某个范围;二是用图神经网络来学习关键点之间的关联;三是用多尺度的分组策略,先分大部位再分小部位。
在实际工程中,我建议先用贪心匹配跑通流程,然后根据具体场景的bad case来针对性优化。如果场景中人群密集度不高,贪心匹配通常够用;如果人群密集,可以考虑引入几何约束或学习-based的分组方法。另外,分组时的距离度量也很关键,欧氏距离是最简单的,但在尺度变化大的场景下,用归一化后的距离或者马氏距离效果更好。
4. 完整训练流程与核心环节实现
4.1 数据准备与增强策略
训练CPN的第一步是准备数据。COCO数据集是姿态估计的标准 benchmark,包含超过20万张图像和25万个标注的人体实例。每张图像中的人数不定,关键点标注包括17个部位:鼻子、左右眼、左右耳、左右肩、左右肘、左右腕、左右髋、左右膝、左右踝。
数据增强是提升模型泛化能力的关键。CPN论文里用了随机旋转、随机缩放、随机翻转三种增强方式。随机旋转的角度范围是-30度到30度,随机缩放的尺度范围是0.7到1.3,随机翻转则是水平翻转。这三种增强方式组合使用,可以显著提升模型对不同姿态和尺度的适应能力。
这里有个实操细节:旋转和缩放后,关键点坐标需要同步变换。变换公式是:先减去图像中心点,然后乘以缩放因子,再乘以旋转矩阵,最后加回图像中心点。这个变换过程容易出错,建议写一个专门的函数来处理,并且用可视化工具检查变换后的关键点是否还在正确位置。
另外,CPN在训练时还用了随机遮挡增强。具体做法是在图像上随机画一些黑色矩形,模拟遮挡场景。这个增强方式对提升模型在遮挡情况下的鲁棒性很有帮助。遮挡矩形的数量和大小需要控制,太多太大反而会破坏有效信息。我的经验是:每张图随机画1到3个矩形,每个矩形的面积不超过图像面积的10%。
4.2 损失函数的设计与权重调整
CPN的损失函数分为两部分:GlobalNet的损失和RefineNet的损失。GlobalNet用的是标准MSE损失,对所有关键点一视同仁。RefineNet用的是难例挖掘后的MSE损失,只对损失最大的M个关键点计算梯度。
MSE损失的公式是:L = (1/N) * Σ(y_pred - y_gt)^2,其中N是关键点总数。这个损失函数对大的误差惩罚更重,符合姿态估计的需求——我们希望网络优先修正那些偏差大的关键点。
难例挖掘的实现方式是:先计算所有关键点的损失,然后按损失值降序排序,取前M个关键点的损失求平均作为最终损失。M的取值需要根据关键点总数来定。COCO有17个关键点,M=8大约是一半。如果关键点更多,M可以适当增大。
还有一个细节:GlobalNet和RefineNet的损失权重。论文里没有明确说,但根据我的实验,两者权重设为1:1效果就不错。如果RefineNet的权重太大,训练初期会不稳定;如果太小,RefineNet的作用就不明显。建议在训练初期用1:0.5,后期逐步调整到1:1。
4.3 训练超参数与收敛判断
CPN的训练超参数设置如下:初始学习率1e-3,每30个epoch衰减一次,衰减因子0.1。优化器用Adam,beta1=0.9,beta2=0.999。Batch size设置为32,训练总轮数大约200个epoch。
收敛判断主要看验证集上的mAP(mean Average Precision)。COCO的mAP计算方式是:对每个关键点,计算预测点和真实点之间的OKS(Object Keypoint Similarity),然后取不同OKS阈值下的AP平均值。OKS的计算考虑了关键点的距离和人体尺度,比简单的欧氏距离更合理。
训练过程中,如果验证集mAP连续10个epoch没有提升,就可以考虑早停。另外,训练损失和验证损失的变化趋势也要关注。如果训练损失持续下降但验证损失开始上升,说明过拟合了,需要增加正则化或减少模型复杂度。
4.4 推理流程与后处理
推理阶段,CPN的流程是:输入一张图像,经过特征金字塔和GlobalNet得到初始热力图,再经过RefineNet得到精修后的热力图。然后对热力图做非极大值抑制(NMS),找出每个关键点的峰值位置。最后用贪心匹配把关键点分组到不同的人。
NMS的作用是消除热力图上的冗余响应。具体做法是:对每个关键点的热力图,用一个固定大小的窗口滑动,如果窗口中心的响应值是窗口内的最大值,就保留这个点,否则抑制。窗口大小通常设置为3到5个像素。
贪心匹配的阈值设置很关键。阈值太小会导致同一个人被拆成多个,阈值太大会导致不同人被合并。我的经验是:阈值设置为人体尺度(比如肩膀到髋部的距离)的0.5倍比较合适。这个比例可以根据具体场景调整。
后处理还有一个可选步骤:亚像素精修。热力图的峰值位置是整数像素,但真实关键点可能在亚像素位置。通过拟合峰值周围的热力图分布,可以估计出亚像素级别的偏移量。这个步骤对提升定位精度有帮助,但会增加计算量。如果对精度要求不高,可以跳过。
5. 常见问题与排查技巧实录
5.1 训练不收敛或收敛缓慢
训练不收敛是复现CPN时最常见的问题。可能的原因有几种:学习率太大导致梯度爆炸,学习率太小导致收敛缓慢,数据增强太强导致训练难度过大,或者损失函数实现有误。
排查思路是:先把学习率调小一个数量级试试,如果loss开始下降,说明之前学习率太大了。如果调小后loss还是不降,检查数据增强的强度,可以先把增强关掉,用原始数据训练,看是否能收敛。如果原始数据能收敛,说明增强太强了,需要降低增强强度。如果原始数据也不收敛,检查损失函数的实现,特别是热力图的生成和MSE的计算是否正确。
还有一个容易被忽略的点:Batch Normalization的使用。CPN在特征金字塔的每个卷积层后都加了BN,但如果batch size太小,BN的统计量会不准确,导致训练不稳定。如果batch size小于16,建议用Group Normalization替代BN。
5.2 难关键点检测效果差
难关键点(手腕、脚踝等)检测效果差是姿态估计的经典难题。如果发现这些关键点的AP明显低于其他关键点,可以从几个方面优化。
首先是数据层面:检查训练集中这些关键点的标注是否准确。COCO数据集中,手腕和脚踝的标注有时会因为遮挡而缺失或不准。如果标注质量差,网络很难学好。可以考虑用一些半自动的方法清洗标注,或者用额外的数据集补充。
其次是网络层面:增加RefineNet的容量。RefineNet是专门处理难关键点的,如果它的容量不够,难关键点就修不好。可以增加RefineNet的卷积层数或通道数,但要注意不要过度增加计算量。
还有一个技巧:在损失函数中给难关键点更高的权重。虽然CPN用了难例挖掘,但挖掘的M值是固定的。如果某些关键点特别难,可以手动给它们加一个权重系数,让它们在损失中占比更大。
5.3 分组错误导致姿态混乱
分组错误的表现是:同一个人的关键点被分到了不同的人身上,或者不同人的关键点被合并到了同一个人身上。这种错误在人群密集场景下尤其常见。
排查分组错误时,先可视化中间结果:把关键点检测的热力图和分组结果画出来,看看是检测错了还是分组错了。如果关键点检测本身没问题,那就是分组算法的问题。
改进分组的方法有几种:一是调整贪心匹配的阈值,这个需要根据具体场景调;二是引入几何约束,比如左右对称性、肢体长度比例等;三是用学习-based的分组方法,比如用图神经网络学习关键点之间的关联。我的经验是:先用几何约束过滤掉明显错误的匹配,再用贪心匹配处理剩下的,效果比纯贪心匹配好很多。
5.4 推理速度不达预期
CPN的推理速度主要受限于特征金字塔和两个阶段的级联。如果速度不达预期,可以从几个方面优化。
首先是模型剪枝:去掉一些冗余的卷积层,或者减少通道数。CPN的特征金字塔有四个融合层,如果输入分辨率不高,可以只融合三个层,速度会快不少。
其次是量化:把FP32的模型量化成FP16或INT8,速度可以提升2到4倍,精度损失通常在1个点以内。量化时要注意校准数据的代表性,否则精度损失会更大。
还有一个技巧:把GlobalNet和RefineNet并行化。虽然它们是级联的,但RefineNet的输入只依赖GlobalNet的输出,不依赖GlobalNet的中间层。所以可以在GlobalNet输出后立即启动RefineNet,同时GlobalNet继续处理下一个batch,实现流水线并行。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 训练loss不下降 | 学习率太大或太小 | 尝试不同学习率 | 调整到1e-4到1e-3之间 |
| 验证mAP远低于训练mAP | 过拟合 | 检查数据增强和正则化 | 增加增强强度,加Dropout |
| 难关键点AP低 | RefineNet容量不足 | 可视化难关键点热力图 | 增加RefineNet层数或通道数 |
| 分组错误多 | 贪心匹配阈值不当 | 可视化分组结果 | 调整阈值,加几何约束 |
| 推理速度慢 | 模型太大或未量化 | 测各模块耗时 | 剪枝、量化、流水线并行 |
| 热力图峰值不清晰 | sigma设置不当 | 可视化热力图 | 调整sigma到2-4之间 |
| 小尺度人体检测差 | 特征金字塔融合不足 | 检查浅层特征 | 增加C2层的融合权重 |
| 遮挡场景效果差 | 训练数据遮挡不足 | 检查训练集 | 增加随机遮挡增强 |
6. 从CPN延伸出的工程经验
6.1 级联思想在其他任务中的应用
CPN的级联思想不仅适用于姿态估计,在很多其他任务中也能借鉴。比如人脸关键点检测,同样存在难易关键点差异大的问题,可以用类似的级联结构:先粗定位所有关键点,再针对难关键点精修。再比如目标检测,可以先粗筛候选框,再对难样本精修边界框。甚至在一些回归任务中,也可以先预测一个粗略值,再级联一个网络预测残差。
级联的核心价值在于:它把一个大问题拆成了两个小问题,每个小问题都更容易解决。而且两个阶段可以有不同的侧重点,第一阶段追求召回率,第二阶段追求精确率。这种分而治之的思路在工程上非常实用。
6.2 难例挖掘的通用实现模板
难例挖掘是一个通用技巧,不限于姿态估计。它的核心思想是:让网络把注意力集中在当前还学不好的样本上。实现模板如下:
def hard_example_mining(loss, k): # loss: [batch_size, num_keypoints] # k: 取损失最大的前k个 batch_size, num_keypoints = loss.shape loss_flat = loss.view(-1) # 展平 topk_loss, topk_indices = torch.topk(loss_flat, k) return topk_loss.mean()这个模板可以直接用在任何需要难例挖掘的任务中。需要注意的是,k的取值要根据任务难度来定。任务越难,k可以越大;任务越简单,k可以越小。另外,难例挖掘在训练初期效果不明显,因为所有样本都难;训练后期效果显著,因为简单样本已经学好了。
6.3 姿态估计项目的落地建议
如果你正在做一个姿态估计的落地项目,有几个建议可以参考。首先是模型选择:如果对精度要求高且算力充足,CPN是一个很好的baseline;如果对速度要求高,可以考虑轻量化的自底向上方法,或者用自顶向下但优化检测器。其次是数据:姿态估计对数据质量非常敏感,标注不准会直接导致模型学偏。如果自有数据标注质量不高,可以考虑用预训练模型做半自动标注,再人工修正。
最后是后处理:分组策略和NMS参数需要根据具体场景调优。建议在验证集上做网格搜索,找到最优参数组合。另外,如果场景中人数变化大,可以考虑动态调整分组阈值,人数多时阈值小一点,人数少时阈值大一点。
6.4 我踩过的几个坑
第一个坑是热力图的sigma设置。我一开始用了sigma=1,结果热力图太尖锐,网络很难学到有效的梯度,训练loss一直震荡。后来改成sigma=3,训练就稳定了。这个参数对训练稳定性影响很大,建议从3开始试。
第二个坑是难例挖掘的M值。我一开始设M=17,相当于没有挖掘,效果和普通MSE一样。后来改成M=8,效果明显提升。但M也不能太小,太小会导致梯度噪声过大。我的经验是M取关键点总数的一半左右比较合适。
第三个坑是分组阈值。我一开始用固定阈值,结果在人群密集场景下错误率很高。后来改成基于人体尺度的自适应阈值,错误率降了不少。这个改动虽然简单,但效果立竿见影。
第四个坑是数据增强的强度。我一开始用了很强的旋转和缩放,结果模型在验证集上表现很差。后来降低了增强强度,模型泛化能力反而更好了。数据增强不是越强越好,要根据任务和数据集的特点来定。
6.5 后续可以尝试的改进方向
CPN虽然效果不错,但也有一些可以改进的地方。一个方向是引入注意力机制,让网络更关注难关键点区域。另一个方向是用更高效的特征融合方式,比如用NAS搜索最优的融合结构。还有一个方向是把分组和检测联合训练,让分组信息反过来指导关键点检测。
另外,随着Transformer在视觉领域的兴起,用Transformer替代CNN做姿态估计也是一个热门方向。Transformer的自注意力机制天然适合建模关键点之间的关联,在分组任务上可能有优势。不过Transformer的计算量通常更大,需要仔细权衡精度和速度。
我个人比较看好的一个方向是:把CPN的级联思想和Transformer的全局建模能力结合起来。用CNN做特征提取和粗定位,用Transformer做难关键点精修和分组。这样既能保持效率,又能利用全局信息。当然,这只是一个想法,具体效果还需要实验验证。