openpose姿态估计详解【学习笔记】

1、人体需要检测的关键点

在本项目中,需要检测人体18个关键点,除了下图所标注的17个关键点外,还有1个脖子关键点

在这里插入图片描述

2、Top-down方法

1、检测得到所有人的框;
2、对每一个框进行姿态估计输出结果;

Top-down方法的问题 :

  • 1、姿态估计做成啥样主要由人体检测所决定,能检测到效果估计也没问题
  • 2、但是如果俩人出现重叠,只检测到一个人,那肯定会丢失一个目标
  • 3、计算效率有点低,如果一张图像中存在很多人,那姿态估计得相当慢了

能不能设计一种方法不依赖于人体框而是直接进行预测呢?有的,接下来请看下面的方法,

3、Openpose制作标签和预测细节

3.1 姿态估计的步骤

姿态估计的2个步骤

  • 1、识别出图片中所有关键点,
  • 2、按顺序拼接同属一个人的所有关键点

如下面左图所示,我们识别人体18个关键点中的右肩关键点,就在图片中建立热度图(高斯),识别18个关键点就需要建立18个特征图
在这里插入图片描述

3.2 PAF(Part Affinity Fields)部分亲和场

PAF作用:将属于同一个人的不同关键点按顺序拼接

整体框架如下,1张图片经过CNN网络后分成2个网络,Parts Detection网络是预测关键点,Parts Association网络是预测方向,人体18个关键点总共需要19个不同的方向,而确定方向至少需要1个平面,即 x x x y y y 坐标,所以Parts Association需要预测19x2=38个特征图

在这里插入图片描述

3.3 制作PAF标签

Parts Association输出38张特征图,其中19张特征图预测 x x x 坐标,19张特征图预测 y y y 坐标,每一张特征图的像素点都需要输出坐标值。在PAF标签处理中,把2个关键点所包围的矩形当中的所有像素值的方向与2个关键点的方向都一致。

蓝色和红色分别是两个关键点, V V V是其向量, X j 1 , k X_{j_1,k} Xj1,k j 1 j_1 j1表示不同的关键点, k k k表示不同的人,

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

对于其中某个PAF特征图(19种连接方式中的1种),这就相当于得到PAF标签值了,包括所有人在该连接处的向量,
在这里插入图片描述

3.4 PAF权值计算

如何确定2个关键点之间的预测方向呢?

  • 比如下图中红色点表示左肩,左边的蓝点表示和左肩属于同一个人的左手肘,右边的蓝点表示另外一个人的左手肘,然后网络会输出每一个点的预测方向,然后比较每一个点的预测方向和ground truth的投影的之和,哪个预测方向的和最大哪个就是最佳的预测方向,
  • 如下图所示,绿色小箭头是网络的预测方向,可以看出左边的预测是最佳预测方向,

两个关键点j1与j2之间的权值计算方法
在这里插入图片描述
E = ∫ μ = 0 μ = 1 L c ( p ( μ ) ) ⋅ d j 2 − d j 1 ∣ ∣ d j 2 − d j 1 ∣ ∣ 2 E=\int_{\mu=0}^{\mu=1}L_c(p(\mu))\cdot\frac{d_{j2}-d_{j1}}{\mid\mid d_{j2}-d_{j1}\mid\mid_2} E=μ=0μ=1Lc(p(μ))∣∣dj2dj12dj2dj1
p ( μ ) = ( 1 − μ ) d j 1 + μ d j 2 p(\mu)=(1-\mu)d_{j1}+\mu d_{j2} p(μ)=(1μ)dj1+μdj2
d j 1 d_{j1} dj1 d j 2 d_{j2} dj2分别表示 j 1 j1 j1 j 2 j2 j2两点的坐标,求 j 1 j1 j1 j 2 j2 j2间各点的PAF在线段 j 1 j 2 j1j2 j1j2上投影的积分,其实就是线段上各点的PAF方向如果与线段的方向越接近权值就越大

3.5 匹配方法
  • 每一个关键点先与其中一个关键点做匹配,而不是每一个关键点与几个关键点做匹配,因为每个关键点先与其中一个关键点做匹配的效果更好,
  • 如果同时考虑多种匹配,那太难了
  • 咱们固定好就是二分图,这样可以直接套匈牙利算法
    在这里插入图片描述

如下图,黑色线是ground truth,黄色小箭头是预测,预测方向和黑色线的投影和最大即为最佳预测方向,而绿色线就是错误匹配,
在这里插入图片描述

3.6 任务流程
  • (a):输入图片
  • (b):输入图片的后分为2个分支,一个分支是预测关键点,一个分支是预测关键点之间的方向,其中(b)图是预测关键点,有18个特征图
  • (c):预测关键点之间的方向,有19*2个特征图
  • (d):选出最佳预测方向
  • (e):整合预测的关键点和预测方向,
    在这里插入图片描述

4、CPM(Convolutional Pose Machines)模型

为OpenPose后面的工作奠定了基础,也可以当作基础框架,通过多个stage来不断优化关键点位置(stage1预测完全错误,2和3在纠正)

在这里插入图片描述

stage越多相当于层数越深,模型感受野越大,姿态估计需要更大的感受野,因为进行关键点检测需要更多关于人体的信息,所以感受野越大则人体的信息越多,关键点检测越能检测准确

在这里插入图片描述

使用多个stage级联的思想,每个stage都加损失函数,也就是中间过程也得做的好才行

在这里插入图片描述

5、Openpose所使用的卷积神经网络

  • 两个网络结构分别搞定:1、关键点预测;2、姿势的‘亲和力’向量(关键点方向)
  • 使用多个stage级联的思想,2个网络结构输出之后再进行特征融合,因为方向的预测离不开关键点的位置信息,关键点的位置信息也与方向有关

在这里插入图片描述

5.1 序列的作用

多个stage,相当于纠正的过程,不断调整预测结果,

在这里插入图片描述

5.2 整体框架

两个分支都要经过多个阶段,注意每个阶段后要把特征拼接一起

在这里插入图片描述

代码:

def forward(self, x):
     print(x.shape)
     saved_for_loss = []
     out1 = self.model0(x)  # 46*46的特征图
     print(out1.shape)
     out1_1 = self.model1_1(out1)  # PAF输出
     print(out1_1.shape)
     out1_2 = self.model1_2(out1)  # 关键点输出
     print(out1_2.shape)
     out2 = torch.cat([out1_1, out1_2, out1], 1)
     print(out2.shape)
     saved_for_loss.append(out1_1)
     saved_for_loss.append(out1_2)

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

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

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

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

     out6_1 = self.model6_1(out6)
     out6_2 = self.model6_2(out6)
     saved_for_loss.append(out6_1)
     saved_for_loss.append(out6_2)
     print(out6_1.shape)
     print(out6_2.shape)
     return (out6_1, out6_2), saved_for_loss
     

6、训练openpose所需要的数据集格式

key为“keypoints”的后面的内容就是标注的关键点数据,前面2列数据分别是x和y坐标,最后一列的0表示没有被标注,1表示遮挡,2表示没有被遮挡,在实际应用中选用1(标注但被遮挡)和2(标注未遮挡),

在这里插入图片描述

6.1 制作ground truth(重点)
  • 比如输入图片的像素是368*368,经过backbone之后的3次下采样特征图变为(368/8)*(368/8),即46*46,预测的结果为了和ground truth比较,所以ground truth的坐标也要除以8,
  • 首先制作关键点的ground truth,一共有(18+1)个特征图,18个关键点和1个背景,比如制作右肩的特征图,在右肩的中心点标注为1,然后逐渐递减0.9,0.8一直到0,如下图所示,
  • 需要注意如果发生不同的人的关键点的重合,采取相加的策略,然后再与1比较大小,大于1则为1,小于1则不改变,
  • 需要注意到,训练数据只标注了17个关键点,没有标注脖子关键点,所以还需要对左肩和右肩的关键点坐标进行处理得到脖子这个关键点坐标,
  • 同制作关键点的标签一样,关键点之间的方向需要制作19*2个特征图,如果不同的关键点方向重合,采取相加求平均值,比如一个躯干方向的x值为2.9,另一个躯干方向的x值为3.1,那么在这一个点上的x值为(2.9+3.1)/ 2 = 3,
    在这里插入图片描述

代码:

def putGaussianMaps(center, accumulate_confid_map, sigma, grid_y, grid_x, stride):
    start = stride / 2.0 - 0.5  # 3.5
    y_range = [i for i in range(int(grid_y))]
    x_range = [i for i in range(int(grid_x))]
    xx, yy = np.meshgrid(x_range, y_range)
    xx = xx * stride + start
    yy = yy * stride + start
    d2 = (xx - center[0]) ** 2 + (yy - center[1]) ** 2
    exponent = d2 / 2.0 / sigma / sigma
    mask = exponent <= 4.6052
    cofid_map = np.exp(-exponent)
    cofid_map = np.multiply(mask, cofid_map)
    accumulate_confid_map += cofid_map  # 多个点会叠加的
    accumulate_confid_map[accumulate_confid_map > 1.0] = 1.0

    return accumulate_confid_map


def putVecMaps(centerA, centerB, accumulate_vec_map, count, grid_y, grid_x, stride):
    centerA = centerA.astype(float)
    centerB = centerB.astype(float)

    thre = 1  # limb width
    centerB = centerB / stride  # 映射到特征图中
    centerA = centerA / stride

    limb_vec = centerB - centerA
    norm = np.linalg.norm(limb_vec)  # 求范数
    if norm == 0.0:
        # print 'limb is too short, ignore it...'
        return accumulate_vec_map, count
    limb_vec_unit = limb_vec / norm  # 单位向量
    # print 'limb unit vector: {}'.format(limb_vec_unit)

    # To make sure not beyond the border of this two points
    min_x = max(int(round(min(centerA[0], centerB[0]) - thre)), 0)  # 得到所有可能区域
    max_x = min(int(round(max(centerA[0], centerB[0]) + thre)), grid_x)
    min_y = max(int(round(min(centerA[1], centerB[1]) - thre)), 0)
    max_y = min(int(round(max(centerA[1], centerB[1]) + thre)), grid_y)

    range_x = list(range(int(min_x), int(max_x), 1))
    range_y = list(range(int(min_y), int(max_y), 1))
    xx, yy = np.meshgrid(range_x, range_y)
    ba_x = xx - centerA[0]  # the vector from (x,y) to centerA 根据位置判断是否在该区域上(分别得到X和Y方向的)
    ba_y = yy - centerA[1]
    # 下面2行代码判断特征图上的点是不是在关键点之间的躯干上
    limb_width = np.abs(ba_x * limb_vec_unit[1] - ba_y * limb_vec_unit[0])  # 向量叉乘根据阈值选择赋值区域,任何向量与单位向量的叉乘即为四边形的面积
    mask = limb_width < thre  # mask is 2D # 小于阈值的表示在该区域上

    vec_map = np.copy(accumulate_vec_map) * 0.0  # 本次计算

    vec_map[yy, xx] = np.repeat(mask[:, :, np.newaxis], 2, axis=2)
    vec_map[yy, xx] *= limb_vec_unit[np.newaxis, np.newaxis, :]  # 在该区域上的都用对应的方向向量表示(根据mask结果表示是否在)

    mask = np.logical_or.reduce(
        (np.abs(vec_map[:, :, 0]) > 0, np.abs(vec_map[:, :, 1]) > 0))  # 在特征图中(46*46)中 哪些区域是该躯干所在区域

    accumulate_vec_map = np.multiply(
        accumulate_vec_map, count[:, :, np.newaxis])  # 每次返回的accumulate_vec_map都是平均值,现在还原成实际值
    accumulate_vec_map += vec_map  # 加上当前关键点位置形成的向量
    count[mask is True] += 1  # 该区域计算次数都+1

    mask = count == 0

    count[mask is True] = 1  # 没有被计算过的地方就等于自身(因为一会要除法)

    accumulate_vec_map = np.divide(accumulate_vec_map, count[:, :, np.newaxis])  # 算平均向量
    count[mask is True] = 0  # 还原回去

    return accumulate_vec_map, count


def get_ground_truth(self, anns):

    grid_y = int(self.input_y / self.stride)
    grid_x = int(self.input_x / self.stride)
    channels_heat = (self.HEATMAP_COUNT + 1)  # 多出一个背景
    channels_paf = 2 * len(self.LIMB_IDS)  # 2*19,38个特征图表示关键点之间的连接方向
    heatmaps = np.zeros((int(grid_y), int(grid_x), channels_heat))
    pafs = np.zeros((int(grid_y), int(grid_x), channels_paf))

    keypoints = []
    for ann in anns:
        single_keypoints = np.array(ann['keypoints']).reshape(17, 3)
        single_keypoints = self.add_neck(single_keypoints)
        keypoints.append(single_keypoints)
    keypoints = np.array(keypoints)
    keypoints = self.remove_illegal_joint(keypoints)

    # confidence maps for body parts
    for i in range(self.HEATMAP_COUNT):
        joints = [jo[i] for jo in keypoints]  # 每一种关节点
        for joint in joints:  # 遍历每一个点
            if joint[2] > 0.5:  # 1是标注被遮挡 2是标注且没被遮挡
                center = joint[:2]  # 点坐标
                gaussian_map = heatmaps[:, :, i]
                heatmaps[:, :, i] = putGaussianMaps(
                    center, gaussian_map,
                    7.0, grid_y, grid_x, self.stride)
    # pafs
    for i, (k1, k2) in enumerate(self.LIMB_IDS):
        # limb
        count = np.zeros((int(grid_y), int(grid_x)), dtype=np.uint32)  # 表示该位置是否被计算了多次(计算的数量)
        for joint in keypoints:
            if joint[k1, 2] > 0.5 and joint[k2, 2] > 0.5:
                centerA = joint[k1, :2]
                centerB = joint[k2, :2]
                vec_map = pafs[:, :, 2 * i:2 * (i + 1)]  # 每一个躯干位置,选择x和y两个方向

                pafs[:, :, 2 * i:2 * (i + 1)], count = putVecMaps(
                    centerA=centerA,
                    centerB=centerB,
                    accumulate_vec_map=vec_map,
                    count=count, grid_y=grid_y, grid_x=grid_x, stride=self.stride
                )

    # background
    heatmaps[:, :, -1] = np.maximum(
        1 - np.max(heatmaps[:, :, :self.HEATMAP_COUNT], axis=2),
        0.
    )
    return heatmaps, pafs

参考:
1、哔站

  • 6
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我没有找到关于YOLO算法姿态估计的详细讲解的引用内容。但是我可以为您提供一些关于YOLO算法的基本解释。 YOLO(You Only Look Once)算法是一种实时目标检测算法,它的主要优点是速度快。YOLO算法通过将图像分为一系列网格,每个网格预测一定数量的边界框和类别概率。然后,根据预测的边界框和类别概率来确定图像中的物体。 YOLO算法的改进版本YOLOv2和YOLO9000解决了YOLOv1在召回率和定位精度方面的不足。这些改进包括使用更好的特征提取方法、多尺度训练和更精细的边界框预测。 尽管YOLO算法具有快速且实时的优势,但与其他目标检测算法相比,其准确性可能略低。然而,YOLO算法在实时监测系统中的平均精度要高于其他实时监测系统。此外,YOLO算法具有良好的迁移能力,可以应用于其他领域,例如艺术品目标检测。 如果您需要更详细的姿态估计算法的讲解,我建议您参考相关的科学论文或专业书籍。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [yolo算法讲解ppt](https://download.csdn.net/download/zpf529/10631820)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [YOLO系列算法精讲:从yolov1至yolov8的进阶之路(2万字超全整理)](https://blog.csdn.net/wjinjie/article/details/107509243)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值