第二十四周:OpenPose:使用部分亲和场的实时多人2D姿态估计

摘要

本篇博客介绍了一种实时多人2D姿态估计框架——OpenPose,其核心思想是通过自底向上的全局关联策略,解决传统方法在多人场景下面临的计算效率低与关键点误匹配问题。针对多人姿态中肢体拓扑关联的复杂性,提出部分亲和场(PAF)技术,以向量场代表关键点间的空间方向关系,结合双分支卷积网络同步输出关键点置信图与PAF场,通过路径积分与二分图匹配算法实现跨个体的精准聚类;针对小尺度关键点检测精度不足,设计多阶段级联网络,逐层细化预测结果以提升遮挡区域的鲁棒性。其优势在于开源易用性、多部位(身体/手部/面部)联合检测能力及遮挡场景下的稳定表现,但存在计算资源消耗高、密集人群误匹配等局限。未来研究可围绕轻量化模型部署、时序动作连续性建模及三维姿态扩展展开。

Abstract

This blog introduces OpenPose, a real-time multi-person 2D pose estimation framework. Its core innovation lies in a bottom-up global association strategy, which addresses the inefficiency and keypoint mismatch issues of traditional methods in multi-person scenarios. To tackle the complexity of limb topological association, the framework proposes Part Affinity Fields (PAF), a technique that represents spatial directional relationships between keypoints through vector fields. By integrating a dual-branch convolutional network, OpenPose simultaneously generates keypoint confidence maps and PAF fields. These outputs are then processed via path integration and bipartite graph matching algorithms to achieve precise cross-individual clustering. Furthermore, to enhance the accuracy of small-scale keypoint detection under occlusion, the framework employs a multi-stage cascaded network architecture that iteratively refines predictions. OpenPose demonstrates strengths in open-source accessibility, multi-part joint detection (body, hands, and face), and robust performance in occluded scenarios. However, limitations include high computational resource demands and mismatches in dense crowds. Future research directions may focus on lightweight model deployment, temporal motion continuity modeling, and extensions to 3D pose estimation.


文章信息

Title:OpenPose: Realtime Multi-Person 2D Pose Estimation Using Part Affinity Fields
Author: Cao, Z (Cao, Zhe) ; Hidalgo, G (Hidalgo, Gines) ; Simon, T (Simon, Tomas) ; Wei, SE (Wei, Shih-En); Sheikh, Y (Sheikh, Yaser)
Source:https://webofscience.clarivate.cn/wos/alldb/full-record/WOS:000597206900012


引言

人体的2D姿态估计,在定位人体的关键点或者部分关键点,主要集中在寻找个体的身体部位。预测多人的姿态面临着挑战:首先:每张图像中可能包含未知数量的人,且图像中的人可能以任意尺度出现在任何位置;第二:人与人之间的交互使空间推理变得复杂,如接触、遮挡等会使体关键点部分之间的关联变得困难;第三:运行时间复杂度随图像中人数的增加而增加,给实时性带来挑战。

姿态估计分为两种方法:自顶向下和自底向上
自顶向下:先使用目标检测器检测出图像中的人,然后对检测出的每个人分别进行姿态估计,是采用“检测→分割→关键点预测”的串行流程。这种方法的效果依赖目标检测的效果,如果图中出现重叠度较高的两个人,则可能会因非极大值抑制而遗漏其中一个人,后续的姿态估计也会受影响。且这种方法计算成本与人数成正比,实时性较差。
自底向上:先检测图像中所有可能的关键点,再通过分组算法将这些关键点关联到不同的个体,是通过“关键点检测→聚类分组”的并行流程。这种方法对遮挡和多人重叠场景更鲁棒,无需依赖检测框。但这种方法并不直接使用来自其他身体部位和其他人的全局上下文信息,在此论文提出之前,最终的图像解析需要耗费大量时间去做全局推理,实时性差。
openpose这篇论文就是采用自底向上的方法,文中提出了部分亲和场(Part Affinity Fields, PAF)方法,通过向量场建模肢体方向,解决多人场景下关键点的正确关联问题。

方法

在这里插入图片描述
上图展示了整体的工作流程。整个网络以大小为 w × \times × h 的彩色图像作为输入,输出图像中每个人的关键点及连接的信息。
在这里插入图片描述

  1. 将图像输入卷积神经网络提取特征
  2. 利用第一步得到的特征图同时预测关键点热度图和PAF信息
  3. 将关键点进行二分匹配,选择有最大得分的连接方式进行连接
  4. 组合所有连接得到图像中所有人的全身姿势

预测身体关键点位置的网络输出为一组2D置信图S,人体有J个关键点,每个关键点对应一个置信图,则每组有J个置信图,即 S = ( S 1 , S 2 , S 3 . . . , S J ) S=(S_1,S_2,S_3...,S_J) S=(S1,S2,S3...,SJ) S j ∈ R w × h , j ∈ { 1 , 2... , J } S_j \in R^{w\times h},j \in {\{1,2...,J\}} SjRw×h,j{1,2...,J}
预测PAF信息的网络输出一组2D矢量场L,编码各个部位之间的关联度。每个肢体是一种连接,需要一个矢量场,2D矢量分为 x 和 y 方向,所以C个关键点的连接方式(C个肢体)有C个向量场, L = ( L 1 , L 2 . . . , L C ) L = (L_1,L_2...,L_C) L=(L1,L2...,LC) L c ∈ R w × h × 2 , c ∈ { 1 , 2... , C } L_c \in R^{w \times h \times 2},c \in {\{1,2...,C\}} LcRw×h×2,c{1,2...,C}

同时进行检测和关联

在这里插入图片描述
如上图所示,上部分分支网络(Branch 1)用来预测置信图,下部分分支网络(Branch 2)用来预测亲和场 PAF。每一个分支都是一个迭代的预测结构。这种连续重复的阶段优化了预测结果,每个阶段都会有中继监督。
图像首先通过卷积网络进行分析(如使用VGG-19的前10层来初始化并且微调),生成了一组特征图F,作为stage1的输入。在第一阶段,网络产生一组关键点置信度图 S 1 = ρ 1 ( F ) S^1=\rho ^1(F) S1=ρ1(F)和一组PAF: L 1 = ϕ 1 ( F ) L^1=\phi ^1(F) L1=ϕ1(F),其中 ρ 1 \rho^1 ρ1 ϕ 1 \phi ^1 ϕ1是stage 1的预测网络,在后续的每个阶段中,前一个阶段的两个分支预测与原始图像特征F进行拼接(concat),作为后一阶段的输入,用来产生更好的预测结果。
在这里插入图片描述
其中, ρ t \rho ^t ρt ϕ t \phi ^t ϕt是stage t的预测网络。
在这里插入图片描述
上图展示了置信度图和PAF在不同阶段的优化结果。为了指导迭代预测置信度图和PAF,在每个阶段后都使用损失函数。在预测结果与真实标签之间使用L2损失,并对损失函数进行空间加权,来解决一些数据并不是由所有人的标签的问题。stage t 的两个分支的损失函数描述如下:
在这里插入图片描述
其中, S j ∗ S^*_j Sj是真实关键点置信图, L c ∗ L^*_c Lc是真实亲和向量场。 W W W是二值掩码,当图像位置p没有标注时, W ( p ) = 0 W(p)=0 W(p)=0。这个掩码用于避免在训练时惩罚(降低)真实积极的预测。在每个stage后的中继监督通过周期性的补充梯度,解决了梯度消失的问题。总体损失为:
在这里插入图片描述

关键部位检测的置信图

为了评估训练阶段的 f s f_s fs ,用标注的2D关键点生成真实置信图 S ∗ S^* S。每个置信图是特定身体部位在任意像素位置的置信度的2D表示。每张置信图包含图像中每个人k的可见特定关键部位j的峰。
对于图像中包含多人的情况,为每个人生成置信图 S j , k ∗ S^*_{j,k} Sj,k, x j , k ∈ R 2 x_{j,k}\in R^2 xj,kR2是图中第k个人的第j个关键点的真实位置,则在位置 p ∈ R 2 p\in R^2 pR2处:
在这里插入图片描述
这其实就是一个高斯运算,其中, σ \sigma σ控制峰值的范围。
网络预测的置信图是通过max运算符汇总的各个置信度图:
在这里插入图片描述
即不管图像中有多少人,对于每种关键点都只生成一张置信图,取最大值操作只是在置信图中保留了最高置信度的位置,而不会改变其他关键点的检测结果。
在这里插入图片描述
测试时,预测置信度图,并通过执行非最大抑制来获得候选关键点。

PAF

通过预测置信图可以得到预测的候选关键点,如何将它们组合成未知数量的人的全身姿势是一个难题。
在这里插入图片描述
需要检测出相邻关键点之间联系的置信测量,即 这些关键点是否属于同一个人。
一种可能的方法是,检测肢体上每对部位之间的附加中点,并检查其在候选部位检测之间的发生率,如上图 b所示。但当图像中的人比较聚集时,这些中点很可能支持错误的关联(如上图 b中的绿色线所示)。导致这种错误的原因:1.这种方法只编码了每个肢体的位置,没编码方向。2.这种方法将肢体的支撑区域减少到一个点。
为了解决上述问题,论文提出了部位亲和场的方法,它编码了肢体支撑区域的位置和方向信息,如上图 c所示。对于位于特定肢体区域的每个像素,部分亲和场编码了一个部位到另一个部位的方向,并且每种肢体都有相应的亲和场来连接它关联的两个身体部位。
在这里插入图片描述
以上图中的肢体为例, X j 1 , k X_{j1,k} Xj1,k X j 2 , k X_{j2,k} Xj2,k是图像中人 k 的肢体 c 对应的两个关键点 j 1 j1 j1 j 2 j2 j2的位置。如果点p位于这个肢体上,则点p的PAF标签为从 j 1 j1 j1 j 2 j2 j2的单位向量,如果p不在这个肢体上,则该点处的亲和场值为零向量。公式表示如下:
在这里插入图片描述
其中 v 是单位向量,因为不能连接方式不能与大小有关(图像中的肢体的尺度可能不同),仅与方向有关。
在肢体上的点p定义为在一定距离阈值之内的点集,满足:
在这里插入图片描述
其中 σ l \sigma _l σl是肢体宽度。
肢体长度为:
在这里插入图片描述
部分亲和场是图像中所有人的肢体c的平均值:
在这里插入图片描述
其中 n c ( P ) n_c(P) nc(P)是所有k个人在位置p处的非零向量的个数(即不同人的肢体重叠处的像素平均值)
在预测时,通过沿着连接候选部位的线段计算对应部位亲和场的积分来测量候选部位之间的关联,对于两个候选关键点 d j 1 d_{j1} dj1 d j 2 d_{j2} dj2
在这里插入图片描述
其中 p ( u ) = ( 1 − u ) d j 1 + u d j 2 p(u)=(1-u)d_{j1}+ud_{j2} p(u)=(1u)dj1+udj2。实际中,通过取样u的等间距值,求和对应L值来计算积分。

使用PAF进行多人解析

上一小节提到的部分亲和场是计算两个关键点间联系的得分的,并没有给出解决多个候选部位之间如何连接以形成肢体的问题。
对于每个关键点,可以有多个候选点,这是由于图片中存在多人或者错误预测情况,如下图中 (b)。这些候选关键点定义了一个有可能为肢体的集合。用在PAF上线积分计算的结果来给每个候选肢体打分。找出最优划分的问题,这是一个k维匹配问题,即一个NP-Hard问题。
在这里插入图片描述
论文中提出贪婪松弛,可以不断产生更准确的匹配。论文中猜测这是因为由于PAF网络更大的感受野,成对关联的得分隐式的编码了全局上下文。
首先获取图中多人的人体检测的候选关键点 D j D_j Dj D j = { d j m : f o r j ∈ { 1 , 2... J } , m ∈ { 1 , 2... N j } } D_j=\{d^m_j :for j\in {\{1,2...J\}},m\in {\{1,2...N_j\}}\} Dj={djm:forj{1,2...J},m{1,2...Nj}},其中 N j N_j Nj是关键点 j j j的候选数量, d j m ∈ R 2 d^m_j \in R^2 djmR2是关键点 j j j的第 m m m个检测框位置。
需要找出正确的每对的肢体连接,图的节点是检测的身体关键点的候选点 D j 1 D_{j1} Dj1 D j 2 D_{j2} Dj2,它的边是检测点之间所有可能的成对连接方式。每一个边的权重由公式求 E EE 来确定,即关键点的亲和的总和。二分图的匹配是通过两边没有共享一个节点的方式,来选出边的子集。我们的目标是在边中找到一个有最大权重的匹配,
在这里插入图片描述
其中, E c E_c Ec是c类肢体的的匹配的总体权重, Z c Z_c Zc是 c类肢体的 Z的子集, E m n E_{mn} Emn是关键点 d j 1 m d^m_{j1} dj1m d j 2 m d^m_{j2} dj2m之间的亲和度。
当需要找到多个人完整的姿态时,确定 Z 是一个 k 维的匹配问题。
首先,选择一个最小边数来获取人体姿势的一块棵生成树的骨架,而不是使用完整的图;然后,进一步的将这个匹配问题分解成二分图匹配的集合,同时分别确定相邻树节点的匹配。这两个“松弛”被简单的分解为:
在这里插入图片描述
对于所有肢体连接的候选,组合这些共享同一个关键点的连接,来得到多人完整的姿势。

关键代码

以下是参考pytorch中openpose的搭建:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Donny You(youansheng@gmail.com)


import torch
import torch.nn as nn

from model.pose.loss.loss import BASE_LOSS_DICT
from lib.model.module_helper import ModuleHelper


# 定义 OpenPose 网络类,继承自 nn.Module
class OpenPose(nn.Module):
    def __init__(self, configer):
        # 调用父类的构造函数
        super(OpenPose, self).__init__()
        # 保存配置信息
        self.configer = configer
        # 根据配置信息获取骨干网络
        self.backbone = ModuleHelper.get_backbone(
            backbone=self.configer.get('network.backbone'),
            pretrained=self.configer.get('network.pretrained')
        )
        # 初始化 PoseModel 模型,输入通道数为骨干网络的特征数
        self.pose_model = PoseModel(configer, self.backbone.get_num_features())
        # 获取有效的损失字典
        self.valid_loss_dict = configer.get('loss', 'loss_weights', configer.get('loss.loss_type'))

    def forward(self, data_dict):
        # 将输入图像数据通过骨干网络
        x = self.backbone(data_dict['img'])
        # 将骨干网络的输出通过 PoseModel 模型,得到 paf_out 和 heatmap_out
        paf_out, heatmap_out = self.pose_model(x)
        # 构建输出字典,包含最后一层的 paf 和 heatmap 输出
        out_dict = dict(paf=paf_out[-1], heatmap=heatmap_out[-1])
        # 如果是测试阶段,直接返回输出字典
        if self.configer.get('phase') == 'test':
            return out_dict

        # 初始化损失字典
        loss_dict = dict()
        # 遍历 paf_out 的每个输出
        for i in range(len(paf_out)):
            # 如果对应的 paf_loss 在有效损失字典中
            if 'paf_loss{}'.format(i) in self.valid_loss_dict:
                # 计算 paf 损失,包括参数、损失类型和权重
                loss_dict['paf_loss{}'.format(i)] = dict(
                    params=[paf_out[i]*data_dict['maskmap'], data_dict['vecmap']*data_dict['maskmap']],
                    type=torch.cuda.LongTensor([BASE_LOSS_DICT['mse_loss']]),
                    weight=torch.cuda.FloatTensor([self.valid_loss_dict['paf_loss{}'.format(i)]])
                )

        # 遍历 heatmap_out 的每个输出
        for i in range(len(heatmap_out)):
            # 如果对应的 heatmap_loss 在有效损失字典中
            if 'heatmap_loss{}'.format(i) in self.valid_loss_dict:
                # 计算 heatmap 损失,包括参数、损失类型和权重
                loss_dict['heatmap_loss{}'.format(i)] = dict(
                    params=[heatmap_out[i]*data_dict['maskmap'], data_dict['heatmap']*data_dict['maskmap']],
                    type=torch.cuda.LongTensor([BASE_LOSS_DICT['mse_loss']]),
                    weight=torch.cuda.FloatTensor([self.valid_loss_dict['heatmap_loss{}'.format(i)]])
                )

        return out_dict, loss_dict


# 定义 PoseModel 类,继承自 nn.Module
class PoseModel(nn.Module):
    def __init__(self, configer, in_channels):
        # 调用父类的构造函数
        super(PoseModel, self).__init__()
        # 保存配置信息和输入通道数
        self.configer = configer
        self.in_channels = in_channels
        # 获取模型字典
        model_dict = self._get_model_dict(self.configer, in_channels)
        # 从模型字典中获取各个模块
        self.model0 = model_dict['block_0']
        self.model1_1 = model_dict['block1_1']
        self.model2_1 = model_dict['block2_1']
        self.model3_1 = model_dict['block3_1']
        self.model4_1 = model_dict['block4_1']
        self.model5_1 = model_dict['block5_1']
        self.model6_1 = model_dict['block6_1']

        self.model1_2 = model_dict['block1_2']
        self.model2_2 = model_dict['block2_2']
        self.model3_2 = model_dict['block3_2']
        self.model4_2 = model_dict['block4_2']
        self.model5_2 = model_dict['block5_2']
        self.model6_2 = model_dict['block6_2']

        # 初始化卷积层的权重和偏置
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                m.weight.data.normal_(0, 0.01)
                if m.bias is not None:
                    m.bias.data.zero_()

    @staticmethod
    def _make_layers(layer_dict):
        # 初始化层列表
        layers = []

        # 遍历除最后一层外的所有层
        for i in range(len(layer_dict) - 1):
            layer = layer_dict[i]
            for k in layer:
                v = layer[k]
                # 如果是池化层
                if 'pool' in k:
                    layers += [nn.MaxPool2d(kernel_size=v[0], stride=v[1], padding=v[2])]
                else:
                    # 构建卷积层和 ReLU 激活函数
                    conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4])
                    layers += [conv2d, nn.ReLU(inplace=True)]

        # 处理最后一层
        layer = list(layer_dict[-1].keys())
        k = layer[0]
        v = layer_dict[-1][k]

        conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4])
        layers += [conv2d]

        return nn.Sequential(*layers)

    @staticmethod
    def _get_model_dict(configer, in_channels):
        # 获取 paf 和 heatmap 的输出通道数
        paf_out = configer.get('network', 'paf_out')
        heatmap_out = configer.get('network', 'heatmap_out')
        # 初始化块字典
        blocks = {}

        # 定义 block_0
        block_0 = [{'conv4_3_CPM': [in_channels, 256, 3, 1, 1]}, {'conv4_4_CPM': [256, 128, 3, 1, 1]}]

        # 定义 block1_1 和 block1_2
        blocks['block1_1'] = [{'conv5_1_CPM_L1': [128, 128, 3, 1, 1]}, {'conv5_2_CPM_L1': [128, 128, 3, 1, 1]},
                              {'conv5_3_CPM_L1': [128, 128, 3, 1, 1]}, {'conv5_4_CPM_L1': [128, 512, 1, 1, 0]},
                              {'conv5_5_CPM_L1': [512, paf_out, 1, 1, 0]}]

        blocks['block1_2'] = [{'conv5_1_CPM_L2': [128, 128, 3, 1, 1]}, {'conv5_2_CPM_L2': [128, 128, 3, 1, 1]},
                              {'conv5_3_CPM_L2': [128, 128, 3, 1, 1]}, {'conv5_4_CPM_L2': [128, 512, 1, 1, 0]},
                              {'conv5_5_CPM_L2': [512, heatmap_out, 1, 1, 0]}]

        # 定义 block2 到 block6
        for i in range(2, 7):
            blocks['block%d_1' % i] = [{'Mconv1_stage%d_L1' % i: [128 + paf_out + heatmap_out, 128, 7, 1, 3]},
                                       {'Mconv2_stage%d_L1' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv3_stage%d_L1' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv4_stage%d_L1' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv5_stage%d_L1' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv6_stage%d_L1' % i: [128, 128, 1, 1, 0]},
                                       {'Mconv7_stage%d_L1' % i: [128, paf_out, 1, 1, 0]}]
            blocks['block%d_2' % i] = [{'Mconv1_stage%d_L2' % i: [128 + paf_out + heatmap_out, 128, 7, 1, 3]},
                                       {'Mconv2_stage%d_L2' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv3_stage%d_L2' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv4_stage%d_L2' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv5_stage%d_L2' % i: [128, 128, 7, 1, 3]},
                                       {'Mconv6_stage%d_L2' % i: [128, 128, 1, 1, 0]},
                                       {'Mconv7_stage%d_L2' % i: [128, heatmap_out, 1, 1, 0]}]

        layers = []
        for block in block_0:
            for key in block:
                v = block[key]
                if 'pool' in key:
                    layers += [nn.MaxPool2d(kernel_size=v[0], stride=v[1], padding=v[2])]
                else:
                    conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4])
                    layers += [conv2d, nn.ReLU(inplace=True)]

        models = {
            'block_0': nn.Sequential(*layers)
        }

        # 构建其他模块
        for k in blocks:
            v = blocks[k]
            models[k] = PoseModel._make_layers(v)

        return models

    def forward(self, x):
        # 通过 model0 进行前向传播
        out1 = self.model0(x)

        out1_1 = self.model1_1(out1)
        out1_2 = self.model1_2(out1)
        # 拼接输出
        out2 = torch.cat([out1_1, out1_2, out1], 1)

        out2_1 = self.model2_1(out2)
        out2_2 = self.model2_2(out2)
        out3 = torch.cat([out2_1, out2_2, out1], 1)

        out3_1 = self.model3_1(out3)
        out3_2 = self.model3_2(out3)
        out4 = torch.cat([out3_1, out3_2, out1], 1)

        out4_1 = self.model4_1(out4)
        out4_2 = self.model4_2(out4)
        out5 = torch.cat([out4_1, out4_2, out1], 1)

        out5_1 = self.model5_1(out5)
        out5_2 = self.model5_2(out5)
        out6 = torch.cat([out5_1, out5_2, out1], 1)

        out6_1 = self.model6_1(out6)
        out6_2 = self.model6_2(out6)

        # 收集 paf 和 heatmap 的输出
        paf_out = [out1_1, out2_1, out3_1, out4_1, out5_1, out6_1]
        heatmap_out = [out1_2, out2_2, out3_2, out4_2, out5_2, out6_2]
        return paf_out, heatmap_out


if __name__ == "__main__":
    print(OpenPose(1))

其中,OpenPose 类是整个 OpenPose 网络模型的封装,主要负责整合骨干网络、姿态模型以及损失计算,PoseModel 类是 OpenPose 网络中具体的姿态估计模型,负责构建和执行从特征图到 PAF 和热图输出的计算过程。

实验结果

在这里插入图片描述
与其他方法比较,openpose有最快的速度和相对好的mAP。
在这里插入图片描述
同时,也展现了其运行时间随着图像中人数的增加的变化很小,意味着openpose在多人姿态估计上的表现优异。

创新与不足

OpenPose采用了自底向上的方法来评估人体姿态。文中提出部分亲和场,通过向量场建模肢体方向,解决多人场景下关键点的正确关联问题。PAF为每个肢体定义了一个方向向量场,不仅指示关键点之间的空间关系,还编码了肢体方向信息。相比传统的中点匹配或几何约束方法,PAF能有效处理肢体重叠、遮挡和复杂背景,显著提升多人姿态估计的鲁棒性。传统方法(如Mask R-CNN)处理时间随人数线性增长,而OpenPose在多人场景下仍保持高效。通过多阶段(Stage)迭代逐步细化预测结果。每个阶段将前一阶段的置信图、PAF场与原始特征拼接,通过中间监督(Intermediate Supervision)补充梯度,缓解梯度消失问题,确保预测精度逐级提升。
但在面对遮挡与极端姿势时效果不佳,对严重遮挡、快速动作或非常规姿势(如舞蹈动作)的检测鲁棒性不足,易导致关键点误关联或漏检。且OpenPose对复杂手部姿态(如手指弯曲)和脚部细节(如鞋型)的检测常出现模糊或错误。

总结

OpenPose作为首个实现实时多人全身姿态估计的自底向上的方法,其工作流程围绕全局特征解析→关键点关联→拓扑合成展开:首先通过VGG-19骨干网络提取图像特征,并采用多阶段级联的双分支网络同步预测关键点置信图(Confidence Maps)和部分亲和场(PAF),其中置信图定位所有人体关节点的概率分布,PAF则以向量场形式编码相邻关键点的空间关联性;随后对置信图的峰值进行非极大值抑制提取候选关键点,并基于PAF对每对候选点沿连接路径积分计算亲和度得分,利用二分图匹配算法(如匈牙利算法)将离散关键点聚类为完整的个体姿态;最后结合预定义的肢体拓扑规则验证连接合理性,输出涵盖身体、手部及面部的多粒度姿态数据。其核心价值在于兼顾实时性与多尺度检测能力,既能实现高效推理,又能联合输出身体、手部及面部的精细化运动特征。然而,该框架仍受限于计算资源消耗较高、复杂遮挡下关键点分组易出错等缺陷,未来研究可围绕轻量化模型压缩、多模态时序信息融合以及抗遮挡关联算法展开优化。

<think>好的,用户需要查找OpenPose相关的论文,尤其是研究论文。首先,我应该回顾一下系统级指令的要求,确保回答符合格式。用户的问题集中在OpenPose的论文上,所以需要找到相关的引用信息。 查看提供的引用,引用[2]提到了OpenPose实时2D姿势估计论文,并附有GIF示例,说明用户已经部分构建了项目。引用[3]讨论了OpenPose的自底向上方法,对比其他团队的方法,指出其效率优势。这些引用已经提供了部分论文信息,但需要更具体的引用来源。 根据用户提供的引用,引用[2]中提到的论文可能是OpenPose的关键论文。通常,这类论文会发布在arXiv上,所以需要确认论文的标题和作者。根据记忆,OpenPose的原始论文是“Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields”,由Cao等于2017年发表在CVPR上,但arXiv版本可能更早。用户提供的引用[1]链接是arXiv的1704.04861,但该链接实际上属于MobileNet的论文,这可能是一个错误。因此,需要纠正这一点。 用户可能在引用中混淆了不同的论文。正确的OpenPose论文arXiv编号应为1611.08050,但根据用户提供的引用[2],没有给出具体的arXiv链接。因此,可能需要建议用户直接查找Cao等在2017年的论文,或者访问OpenPose的官方网站获取正确引用。 此外,用户可能需要知道如何访问论文,比如通过arXiv、CVPR会议论文集,或者通过Google Scholar搜索标题。同时,用户可能对论文的核心贡献和方法感兴趣,如PAFs(Part Affinity Fields)和自底向上方法,这些在引用[2]和[3]中都有涉及。 最后,生成相关问题时,应考虑用户可能的后续需求,如技术细节、应用景或与其他方法的比较。确保问题覆盖这些方面,帮助用户深入理解OpenPose。</think>### OpenPose研究论文 OpenPose的原始研究论文为《Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields》,由Zhe Cao等于2017年提出,首次实现了实时2D姿态估计。其核心创新是通过**部件亲和(Part Affinity Fields, PAFs)**将检测到的身体关键点关联成完整的骨骼姿态[^2],同时采用**自底向上的方法**避免了全局推理的计算开销,显著提升了效率(每张图像仅需数毫秒)[^3]。 #### 论文关键点: 1. **自底向上流程** - 先检测所有体关键点(如关节),再通过PAFs关联关键点形成个体,解决了景中重叠和遮挡问题。 2. **部件亲和(PAFs)** - 使用向量编码肢体方向和位置信息,公式为: $$ \text{PAF}(p) = \begin{cases} \frac{\boldsymbol{v}}{||\boldsymbol{v}||_2} & \text{若点}p在肢体内} \\ 0 & \text{否则} \end{cases} $$ 其中$\boldsymbol{v}$为肢体方向向量[^2]。 3. **阶段CNN架构** - 第一阶段预测关键点热图,后续阶段迭代优化PAFs和关键点。 #### 获取论文: - **arXiv版本**:https://arxiv.org/abs/1611.08050 - **CVPR 2017版本**:https://openaccess.thecvf.com/content_cvpr_2017/html/Cao_Realtime_Multi-Person_2D_CVPR_2017_paper.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值