mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)

这篇具有很好参考价值的文章主要介绍了mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在工程中,模型的运行速度与精度是同样重要的,本文中,我会运用不同的方法去优化比较模型的性能,希望能给大家带来一些实用的trick与经验。

有关键点检测相关经验的同学应该知道,关键点主流方法分为Heatmap-based与Regression-based。

其主要区别在于监督信息的不同,Heatmap-based方法监督模型学习的是高斯概率分布图,即把GroundTruth中每个点渲染成一张高斯热图,最后网络输出为K张特征图对应K个关键点,然后通过argmax来获取最大值点作为估计结果。这种方法由于需要渲染高斯热图,且由于热图中的最值点直接对应了结果,不可避免地需要维持一个相对高分辨率的热图(常见的是64x64,再小的话误差下界过大会造成严重的精度损失),因此也就自然而然导致了很大的计算量和内存开销。

Regression-based方法则非常简单粗暴,直接监督模型学习坐标值,计算坐标值的L1或L2 loss。由于不需要渲染高斯热图,也不需要维持高分辨率,网络输出的特征图可以很小(比如14x14甚至7x7),拿Resnet-50来举例的话,FLOPs是Heatmap-based方法的两万分之一,这对于计算力较弱的设备(比如手机)是相当友好的,在实际的项目中,也更多地是采用这种方法。

但是Regression在精度方面始终被Heatmap碾压,Heatmap全卷积的结构能够完整地保留位置信息,因此高斯热图的空间泛化能力更强。而回归方法因为最后需要将图片向量展开成一维向量,reshape过程中会对位置信息有所丢失。除此之外,Regression中的全连接网络需要将位置信息转化为坐标值,对于这种隐晦的信息转化过程,其非线性是极强的,因此不好训练和收敛。

为了更好的提高Regression的精度,我将对其做出一系列优化,并记录于此。
github地址

1.regression

我将以mobilenetv3作为所有实验的backbone,搭建MobileNetv3+Deeppose的Baseline。训练数据来自项目,config如下所示。

model = dict(
    type='TopDown',
    pretrained=None,
    backbone=dict(type='MobileNetV3'),
    neck=dict(type='GlobalAveragePooling'),
    keypoint_head=dict(
        type='DeepposeRegressionHead',
        in_channels=96,
        num_joints=channel_cfg['num_output_channels'],
        loss_keypoint=dict(type='SmoothL1Loss', use_target_weight=True)),
    train_cfg=dict(),
    test_cfg=dict(flip_test=True))

cpu端,模型速度是基于ncnn测试出来的,结论如下:

方法 input size AP50:95 acc_pse time
Deeppose 192*256 41.3% 65% 2.5ms

2.Heatmap

同样以mobilenetv3作为backbone,与Regression不同的是,为了获得尺寸为(48,64)的热图特征,我们需要在head添加3个deconv层,将backbone尺寸为(6,8)的特征图上采样至(48,64)。

model = dict(
    type='TopDown',
    backbone=dict(type='MobileNetV3'),
    keypoint_head=dict(
        type='TopdownHeatmapSimpleHead',
        in_channels=96,
        out_channels=channel_cfg['num_output_channels'],
        loss_keypoint=dict(type='JointsMSELoss', use_target_weight=True)),
    train_cfg=dict(),
    test_cfg=dict(
        flip_test=True,
        post_process='default',
        shift_heatmap=True,
        modulate_kernel=11))

cpu端,模型速度是基于ncnn测试出来的,结论如下:

方法 input size AP50:95 acc_pse time
Deeppose 192*256 41.3% 65% 2.5ms
Heatmap 192*256 67.5% 93% 60ms

由于head层结构不同,参数量变大,导致推理时间剧增。Heatmap全卷积的结构能够完整地保留位置信息,因此高斯热图的空间泛化能力更强。而回归方法因为最后需要将图片向量展开成一维向量,reshape过程中会对位置信息有所丢失。除此之外,Regression中的全连接网络需要将位置信息转化为坐标值,对于这种隐晦的信息转化过程,其非线性是极强的,因此不好训练和收敛。

3.RLE

Regression只关心离散概率分布的均值(只预测坐标值,一个均值可以对应无数种分布),丢失了 μ \mu μ周围分布的信息,相较于heatmap显示地将GT分布(人为设置方差 σ \sigma σ)标注成高斯热图并作为学习目标,RLE隐性的极大似然损失可以帮助regression确定概率分布均值与方差,构造真实误差概率分布,从而更好的回归坐标。

model = dict(
    type='TopDown',
    backbone=dict(type='MobileNetV3'),
    neck=dict(type='GlobalAveragePooling'),
    keypoint_head=dict(
        type='DeepposeRegressionHead',
        in_channels=96,
        num_joints=channel_cfg['num_output_channels'],
        loss_keypoint=dict(
            type='RLELoss',
            use_target_weight=True,
            size_average=True,
            residual=True),
        out_sigma=True),
    train_cfg=dict(),
    test_cfg=dict(flip_test=True, regression_flip_shift=True))

mmpose已经实现了RLE loss,我们只需要在config上添加loss_keypoint=RLELoss就能够运行。

方法 input size AP50:95 acc_pse time
Deeppose 192*256 41.3% 65% 2.5ms
Heatmap 192*256 67.5% 93% 60ms
RLE 192*256 67.3% 90% 2.5ms

从上表中,我们可以发现,当引入RLE损失后,AP提升至67.3%与heatmap相近,同时推理时间仍然保持2.5ms。RLE详细讲解请参考。

4.Integral Pose Regression

我们知道Heatmap推理时,是通过argmax来获取特征图中得分最高的索引,但argmax本身不可导。为了解决这个问题,IPR采用了Soft-Argmax方式解码,先用Softmax对概率热图进行归一化,然后用求期望的方式得到预测坐标。我们在deeppose上引入IPR机制,将最后的fc换成conv层,保留backbone最后层的特征尺寸,并对该特征Softmax,利用期望获得预测坐标。这样做的一大好处是,能够将更多的监督信息引入训练中。

我在mmpose上写了IPRhead代码

@HEADS.register_module()
class IntegralPoseRegressionHead(nn.Module):
    def __init__(self,
                 in_channels,
                 num_joints,
                 feat_size,
                 loss_keypoint=None,
                 out_sigma=False,
                 debias=False,
                 train_cfg=None,
                 test_cfg=None):
        super().__init__()

        self.in_channels = in_channels
        self.num_joints = num_joints

        self.loss = build_loss(loss_keypoint)

        self.train_cfg = {} if train_cfg is None else train_cfg
        self.test_cfg = {} if test_cfg is None else test_cfg

        self.out_sigma = out_sigma
        self.conv = build_conv_layer(
                            dict(type='Conv2d'),
                            in_channels=in_channels,
                            out_channels=num_joints,
                            kernel_size=1,
                            stride=1,
                            padding=0)

        self.size = feat_size
        self.wx = torch.arange(0.0, 1.0 * self.size, 1).view([1, self.size]).repeat([self.size, 1]) / self.size
        self.wy = torch.arange(0.0, 1.0 * self.size, 1).view([self.size, 1]).repeat([1, self.size]) / self.size
        self.wx = nn.Parameter(self.wx, requires_grad=False)
        self.wy = nn.Parameter(self.wy, requires_grad=False)

        if out_sigma:
            self.gap = nn.AdaptiveAvgPool2d((1, 1))
            self.fc = nn.Linear(self.in_channels, self.num_joints * 2)
        if debias:
            self.softmax_fc = nn.Linear(64, 64)

    def forward(self, x):
        """Forward function."""
        if isinstance(x, (list, tuple)):
            assert len(x) == 1, ('DeepPoseRegressionHead only supports '
                                 'single-level feature.')
            x = x[0]

        featmap = self.conv(x)
        s = list(featmap.size())
        featmap = featmap.view([s[0], s[1], s[2] * s[3]])
        featmap = F.softmax(16 * featmap, dim=2)
        featmap = featmap.view([s[0], s[1], s[2], s[3]])
        scoremap_x = featmap.mul(self.wx)
        scoremap_x = scoremap_x.view([s[0], s[1], s[2] * s[3]])
        soft_argmax_x = torch.sum(scoremap_x, dim=2, keepdim=True)
        scoremap_y = featmap.mul(self.wy)
        scoremap_y = scoremap_y.view([s[0], s[1], s[2] * s[3]])
        soft_argmax_y = torch.sum(scoremap_y, dim=2, keepdim=True)
        output = torch.cat([soft_argmax_x, soft_argmax_y], dim=-1)
        if self.out_sigma:
            x = self.gap(x).reshape(x.size(0), -1)
            pred_sigma = self.fc(x)
            pred_sigma = pred_sigma.reshape(pred_sigma.size(0), self.num_joints, 2)
            output = torch.cat([output, pred_sigma], dim=-1)

        return output, featmap

我们引入IPR后实际输出的特征与Heatmap方法输出的特征形式类似,Heatmap方法有人造的概率分布即高斯热图,而在deeppose中引入IPR则是将期望作为坐标,并通过坐标GT直接监督的,因此,只要期望接近GT,loss就会降低。这就带来一个问题,我们通过期望获得的预测坐标无法对概率分布进行约束。
mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
如上图所示,上下两个分布的期望都是mean,但是分布却是完全不同。RLE已经论证一个合理的概率分布是至关重要的,为了提高模型性能,对概率分布加以监督是必要的。DSNT提出了利用JS散度将模型的概率分布向自制的高斯分布靠拢,这里有一个问题,高斯分布的方差只能通过经验值设定,无法针对每个样本自适应的给出,同时高斯分布也未必是最优选择。

@LOSSES.register_module()
class RLE_DSNTLoss(nn.Module):
    """RLE_DSNTLoss loss.
    """
    def __init__(self,
                 use_target_weight=False,
                 size_average=True,
                 residual=True,
                 q_dis='laplace',
                 sigma=2.0):
        super().__init__()
        self.dsnt_loss = DSNTLoss(sigma=sigma, use_target_weight=use_target_weight)
        self.rle_loss = RLELoss(use_target_weight=use_target_weight,
                                size_average=size_average,
                                residual=residual,
                                q_dis=q_dis)
        self.use_target_weight = use_target_weight

    def forward(self, output, heatmap, target, target_weight=None):

        assert target_weight is not None
        loss1 = self.dsnt_loss(heatmap, target, target_weight)
        loss2 = self.rle_loss(output, target, target_weight)

        loss = loss1 + loss2 # 这里权重可以调参

        return loss

@LOSSES.register_module()
class DSNTLoss(nn.Module):
    def __init__(self,
                 sigma,
                 use_target_weight=False,
                 size_average=True,
                 ):
        super(DSNTLoss, self).__init__()
        self.use_target_weight = use_target_weight
        self.sigma = sigma
        self.size_average = size_average
    
    def forward(self, heatmap, target, target_weight=None):
        """Forward function.

        Note:
            - batch_size: N
            - num_keypoints: K
            - dimension of keypoints: D (D=2 or D=3)

        Args:
            output (torch.Tensor[N, K, D*2]): Output regression,
                    including coords and sigmas.
            target (torch.Tensor[N, K, D]): Target regression.
            target_weight (torch.Tensor[N, K, D]):
                Weights across different joint types.
        """
        loss = dsntnn.js_reg_losses(heatmap, target, self.sigma)

        if self.size_average:
            loss /= len(loss)

        return loss.sum()

从下表可以看出,引入IPR+DSNT后模型性能提升。

方法 input size AP50:95 acc_pse time
Deeppose 192*256 41.3% 65% 2.5ms
Heatmap 192*256 67.5% 93% 60ms
RLE 192*256 67.3% 90% 2.5ms
RLE+IPR+DSNT 256*256 70.2% 95% 3.5ms

5.Removing the Bias of Integral Pose Regression

我们引入IPR后,可以使用Softmax来计算期望获得坐标值,但是,利用Softmax计算期望会引入误差。因为Softmax有一个特性让每一项值都非零。对于一个本身非常尖锐的分布,Softmax会将其软化,变成一个渐变的分布。这个性质导致的结果是,最后计算得到的期望值会不准确。只有响应值足够大,分布足够尖锐的时候,期望值才接近Argmax结果,一旦响应值小,分布平缓,期望值会趋近于中心位置。 这种影响会随着特征尺寸的变大而更剧烈。

Removing the Bias of Integral Pose Regression提出debias方法消除Softmax软化产生的影响。具体而言,假设响应值是符合高斯分布的,我们可以根据响应最大值点两倍的宽度,把特征图划分成四个区域:
mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
我们知道一旦经过Softmax,原本都是0值的2、3、4象限区域瞬间就会被长长的尾巴填满,而对于第1象限区域,由于响应值正处于区域的中央,因此不论响应值大小,该区域的估计期望值都会是准确的。

让我们回到Softmax公式:
mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
为了简洁,我们先把分母部分用C来表示:

mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
由于假设2、3、4区域的响应值都为0,因而分子部分计算出来为1,划分区域后的Softmax结果可以表示成:

mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
然后继续按照Soft-Argmax的计算公式带入,期望值的计算可以表示为:

mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)

即:第一区域的期望值,加上另外三个区域的期望值。已知2,3,4趋于 H ~ ( P ) = 1 / c \tilde{H}(P)=1/c H~(P)=1/c,因此这三个区域的期望值可以把1/c提出来,只剩下

mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)

而这里的求和,在几何意义上等价于该区域的中心点坐标乘以该区域的面积,我给一个简单的演示,对于[n, m]区间:

mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)

因而对于整块特征图的期望值,又可以看成四个区域中心点坐标的加权和:

mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)

由于四个区域的中心点存在对称性,假设第一区域中心点坐标为 J 1 = ( x 0 , y 0 ) J_1=(x_0,y_0) J1=(x0,y0),那么剩下三个区域中心点坐标为 J 2 = ( x 0 , y 0 + w / 2 ) , J 3 = ( x 0 + h / 2 , y 0 ) , J 4 = ( x 0 + h / 2 , y 0 + w / 2 ) J_2=(x_0,y_0+w/2),J_3=(x_0+h/2,y_0), J_4=(x_0+h/2,y_0+w/2) J2=(x0,y0+w/2),J3=(x0+h/2,y0),J4=(x0+h/2,y0+w/2)

对应上面我们得出的1/c乘以中心点坐标乘以面积,就得到了每个加权值:
mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)

带入上面的加权和公式(6),整张特征图的期望值可以表示为:
mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
由于已知四个区域权重相加为1,所以有 w 1 = 1 − w 2 − w 3 − w 4 w_1=1-w_2-w_3-w_4 w1=1w2w3w4,因此整张特征图期望值化简成如下形式:
mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
由于 J r J^r Jr值可以很容易通过对整张图计算Soft-Argmax得到,因此对公式(9)移项就能得到准确的第一区域中心点坐标:
mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)
这一步就相当于将原本多余的长尾从期望值中减去了,对该公式我们还可以进一步分析,整张图的期望估计值相当于第一区域期望值的一个偏移。

@HEADS.register_module()
class IntegralPoseRegressionHead(nn.Module):
    def __init__(self,
                 in_channels,
                 num_joints,
                 feat_size,
                 loss_keypoint=None,
                 out_sigma=False,
                 debias=False,
                 train_cfg=None,
                 test_cfg=None):
        super().__init__()

        self.in_channels = in_channels
        self.num_joints = num_joints

        self.loss = build_loss(loss_keypoint)

        self.train_cfg = {} if train_cfg is None else train_cfg
        self.test_cfg = {} if test_cfg is None else test_cfg

        self.out_sigma = out_sigma
        self.debias = debias

        self.conv = build_conv_layer(
                            dict(type='Conv2d'),
                            in_channels=in_channels,
                            out_channels=num_joints,
                            kernel_size=1,
                            stride=1,
                            padding=0)

        self.size = feat_size
        self.wx = torch.arange(0.0, 1.0 * self.size, 1).view([1, self.size]).repeat([self.size, 1]) / self.size
        self.wy = torch.arange(0.0, 1.0 * self.size, 1).view([self.size, 1]).repeat([1, self.size]) / self.size
        self.wx = nn.Parameter(self.wx, requires_grad=False)
        self.wy = nn.Parameter(self.wy, requires_grad=False)

        if out_sigma:
            self.gap = nn.AdaptiveAvgPool2d((1, 1))
            self.fc = nn.Linear(self.in_channels, self.num_joints * 2)
        if debias:
            self.softmax_fc = nn.Linear(64, 64)

    def forward(self, x):
        """Forward function."""
        if isinstance(x, (list, tuple)):
            assert len(x) == 1, ('DeepPoseRegressionHead only supports '
                                 'single-level feature.')
            x = x[0]

        featmap = self.conv(x)
        s = list(featmap.size())
        featmap = featmap.view([s[0], s[1], s[2] * s[3]])
        if self.debias:
            mlp_x_norm = torch.norm(self.softmax_fc.weight, dim=-1)
            norm_feat = torch.norm(featmap, dim=-1, keepdim=True)
            featmap = self.softmax_fc(featmap)
            featmap /= norm_feat
            featmap /= mlp_x_norm.reshape(1, 1, -1)
            
        featmap = F.softmax(16 * featmap, dim=2)
        featmap = featmap.view([s[0], s[1], s[2], s[3]])
        scoremap_x = featmap.mul(self.wx)
        scoremap_x = scoremap_x.view([s[0], s[1], s[2] * s[3]])
        soft_argmax_x = torch.sum(scoremap_x, dim=2, keepdim=True)
        scoremap_y = featmap.mul(self.wy)
        scoremap_y = scoremap_y.view([s[0], s[1], s[2] * s[3]])
        soft_argmax_y = torch.sum(scoremap_y, dim=2, keepdim=True)
        # output = torch.cat([soft_argmax_x, soft_argmax_y], dim=-1)
            
        if self.debias:
            C = featmap.reshape(s[0], s[1], s[2] * s[3]).exp().sum(dim=2).unsqueeze(dim=2)
            soft_argmax_x = C / (C - 1) * (soft_argmax_x - 1 / (2 * C))
            soft_argmax_y = C / (C - 1) * (soft_argmax_y - 1 / (2 * C))
            
        output = torch.cat([soft_argmax_x, soft_argmax_y], dim=-1)
        if self.out_sigma:
            x = self.gap(x).reshape(x.size(0), -1)
            pred_sigma = self.fc(x)
            pred_sigma = pred_sigma.reshape(pred_sigma.size(0), self.num_joints, 2)
            output = torch.cat([output, pred_sigma], dim=-1)

        return output, featmap
方法 input size AP50:95 acc_pse time
Deeppose 192*256 41.3% 65% 2.5ms
Heatmap 192*256 67.5% 93% 60ms
RLE 192*256 67.3% 90% 2.5ms
RLE+IPR+DSNT 256*256 70.2% 95% 3.5ms
RLE+IPR+DSNT+debias 256*256 71% 95% 3.5ms

非常感谢知乎作者镜子文章给予的指导,在这里借鉴了很多,有兴趣的朋友可以查看知乎地址。文章来源地址https://www.toymoban.com/news/detail-479064.html

到了这里,关于mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 计算机视觉:利用RANSAC算法优化关键点匹配

    随机抽样一致算法(RANdom SAmple Consensus,RANSAC),采用迭代的方式从一组包含离群的被观测数据中估算出数学模型的参数。RANSAC算法被广泛应用在计算机视觉领域和数学领域,例如直线拟合、平面拟合、计算图像或点云间的变换矩阵、计算基础矩阵等方面。     RANSAC算法假

    2024年02月06日
    浏览(56)
  • 基于骨骼关键点的动作识别(OpenMMlab学习笔记,附PYSKL相关代码演示)

    骨骼动作识别 是 视频理解 领域的一项任务 1.1 视频数据的多种模态 RGB:使用最广,包含信息最多,从RGB可以得到Flow、Skeleton。但是处理需要较大的计算量 Flow:光流,主要包含运动信息,处理方式与RGB相同,一般用3D卷积 Audio:使用不多 Skeleton :骨骼关键点序列数据,即人

    2024年02月03日
    浏览(41)
  • 从零开始的目标检测和关键点检测(三):训练一个Glue的RTMPose模型

    从零开始的目标检测和关键点检测(一):用labelme标注数据集 从零开始的目标检测和关键点检测(二):训练一个Glue的RTMDet模型 1、数据集类型即coco格式的数据集,在dataset_info声明classes、keypoint_info(关键点)、skeleton_info(骨架信息)。 2、训练参数 3、模型定义、数据预处理、

    2024年02月06日
    浏览(41)
  • 从零开始的目标检测和关键点检测(二):训练一个Glue的RTMDet模型

    从零开始的目标检测和关键点检测(一):用labelme标注数据集 从零开始的目标检测和关键点检测(三):训练一个Glue的RTMPose模型 在 [1]用labelme标注自己的数据集 中已经标注好数据集(关键点和检测框),通过labelme2coco脚本将所有的labelme json文件集成为两个coco格式的json文件,

    2024年02月06日
    浏览(37)
  • 描述点云关键点提取ISS3D、Harris3D、NARF、SIFT3D算法原理

    ISS3D(Intrinsic Shape Signatures 3D ):ISS3D算法是一种基于曲率变化的点云关键点提取算法。它通过计算每个点与其近邻点的曲率变化,得到该点的稳定性和自适应尺度,从而提取稳定性和尺度合适的关键点。 Harris3D :Harris3D算法是一种基于协方差矩阵的点云关键点提取算法。它通

    2024年01月25日
    浏览(42)
  • 2D人脸关键点转3D人脸关键点的映射~头部姿态笔记

    对通过相机参数计算图像上的二维坐标到三维坐标的映射进行简单探讨。         学习的话直接看他们的就好,我仅是拾人牙慧,拿GPT写给自己看的,图也是直接搬运的别人画的,以下链接有很完善的理论研究和代码提供。 https://medium.com/@susanne.thierfelder/head-pose-estimation

    2024年02月04日
    浏览(50)
  • 关键点数据增强

    1.关键点平移数据增强 2.关键点旋转数据增强 3.关键点可视化 4.json2txt(用YOLOV8进行关键点训练) 5.划分训练集和验证集

    2024年02月09日
    浏览(41)
  • opencv-人脸关键点定位

    2024年02月12日
    浏览(53)
  • Mediapipe人脸关键点检测

    MediaPipe是由google制作的开源的、跨平台的机器学习框架,可以将一些模型部署到不同的平台和设备上使用的同时,也能保住检测速度。 从图中可以发现,能在Python上实现的功能包括人脸检测(Face Detection)、人脸关键点(Face Mesh),手部关键点(Hands)等。利用C++能实现更丰富

    2024年02月02日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包