《Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields》论文笔记

《Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields》论文笔记


原论文: Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields
源码:ZheC/Realtime_Multi-Person_Pose_Estimation
   CMU-Perceptual-Computing-Lab/openpose

文章亮点

  • 多人实时人体检测
  • PCM(Part Confidence Maps)+PAF(Part Affinity Fields)
  • Bipartite Matching

人体姿态估计

人体姿态估计的挑战:1.每张图片包含不同数量的人,并且每个人会有不同的姿态;2.图片中肢体遮挡、接触等使得姿态估计比较困难;3.估计的时间随着图片中人数的增长而增加使得实时监测很有挑战。

人体姿态估计两种主流的方式是:top-down approach和bottom-up approach。top-down是在检测到图片中的人之后,对人的姿态进行估计。bottom-up是指先检测关节点再判断关节点属于哪个人。

该论文提出的模型是一种bottom-up的估计方法。通过Convolutional pose machines的方式得到关节点的heatmap来判断关节点(body part),通过PAF的CNN结构(也是采用Convolutional pose machines的网络结构)得到2D的向量集合来编码肢体(limb)的位置和方向。此时判断关节点属于哪个人可以转化为经典的二分图解决方法。通过这种方式解决了之前bottom-up在判断关节点属于哪个人时的NP困难问题,实现了实时估计,具体流程见图1。
figure1
图1 (a)的图片输入模型,生成(b)的PCM热量图和(c)的PAF向量图,通过二分图匹配来确定多人下关节点肢体匹配问题,最终输出得到(e)。

网络结构

输入一张 wh 的图片(已通过CNN提取特征),通过前向传播预测关节点位置的2D置信图S(PCM)和这些关节点亲和度向量集L(PAF)。集合 S=(S1,S2,...,SJ) 指有 J 个关节点对应的位置置信图,S的维度为wh,集合 L=(L1,L2,...LC) 指有 C 个关节点亲和度的向量图,L的维度为 wh2 ,网络结构见图2。
figure2
图2 双层多阶段的CNN架构。每个阶段的第一层预测置信图 St ,第二层预测PAFs Lt 。每个阶段输出之后,输入F和该阶段的两层输出堆叠起来作为下一阶段的输入。

St=ρt(F)t=1
Lt=ϕt(F)t=1
St=ρt(F,St1,Lt1)t2
Lt=ϕt(F,St1,Lt1)t2

ρt , ϕt 为阶段t下CNN计算。
损失函数的计算如下:
ftS=j=1JpW(p)||Stj(p)Sj||22
ftL=j=1JpW(p)||Ltj(p)Lj||22

Sj j 关节点的置信图的groundtruth,Lj是PAF向量的groundtruth, W 是一个二元掩码,当p像素没有被标注时, W(p)=0 (由于某些数据集并没有标注所有的人)。最终的损失函数为:
f=t=1T(ftS+ftL)
每个阶段对损失函数都有贡献可以防止梯度消失的产生。

论文外阅读源码时一些模型细节的补充:
  • F为输入图像经过10层VGG-19后通过两层 3×3 的卷积层(Convolutional pose machines结构),这两层卷积层不改变图像的大小只降低通道数,从512通道降低为128通道。
  • PCM的输出通道为19,PAF的输出通道为38,即 J=19,C=19 ,stage中均不改变图像的大小。
  • stage为6

数据集PCM label处理

计算公式如下:

Sj,k(p)=exp(||pxj,k||22σ2)
Sj,k(p) 为第 k 个人,第j个body part的置信度, xj,k 为第 k 个人,第j个body part的位置groundtruth。即,PCM满足高斯分布,峰值为body part的位置groundtruth。当某个像素点存在多个人的body part置信度时,取最大值而非平均值。这是因为平均值会磨平峰值,而选择最大值会使得像素点靠近峰值时依旧很准确,如图3所示。
figure3
图3

数据集PAF label处理

PAF是每个肢体(limb)的2D向量场:对于位于limb上的像素点,该点的2D向量表示该limb连接两个body part的亲和力大小和方向。
对于单个limb,假设 xj1,k , xj2,k 为第 k 个人两个body part j1, j2 的位置groundtruth。如果 p 在limb上,则Lc,k(p)=v,v=(xj2,kxj1,k)/||(xj2,kxj1,k)||2;如果 p 不在limb上,Lc,k(p)为0向量。
按照如下范围判断像素 p 是否位于limb上:

0v(pxj1,k)||(xj2,kxj1,k)||2()

0v(pxj1,k)σl)
σl 为设置的阈值。
当一个图像上有多人时,取平均值:
Lc(p)=1nc(p)kLc,k(p)
nc(p) p 像素处非零向量的数目。

预测结果的算法

假设只有两个预测的候选位置点dj1, dj2 ,定义这两个点关联的置信度 E 计算公式如下:

E=u=1u=0Lc(p(u))dj1dj2||dj1dj2||2
其中
p(u)=(1u)dj1+udj2
实际测试中,往往是选择两点间等间隔分布的像素点进行求和计算来取代积分计算。
多人估计时,首先通过非极大值抑制算法选择出body part的候选位置,源码如下:

import scipy
print heatmap_avg.shape

#plt.imshow(heatmap_avg[:,:,2])
from scipy.ndimage.filters import gaussian_filter
all_peaks = []
peak_counter = 0

for part in range(19-1):
    x_list = []
    y_list = []
    map_ori = heatmap_avg[:,:,part]
    map = gaussian_filter(map_ori, sigma=3)

    map_left = np.zeros(map.shape)
    map_left[1:,:] = map[:-1,:]
    map_right = np.zeros(map.shape)
    map_right[:-1,:] = map[1:,:]
    map_up = np.zeros(map.shape)
    map_up[:,1:] = map[:,:-1]
    map_down = np.zeros(map.shape)
    map_down[:,:-1] = map[:,1:]

    peaks_binary = np.logical_and.reduce((map>=map_left, map>=map_right, map>=map_up, map>=map_down,
    map > param['thre1']))
    peaks = zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0]) # note reverse
    peaks_with_score = [x + (map_ori[x[1],x[0]],) for x in peaks]
    id = range(peak_counter, peak_counter + len(peaks))
    peaks_with_score_and_id = [peaks_with_score[i] + (id[i],) for i in range(len(id))]

    all_peaks.append(peaks_with_score_and_id)
    peak_counter += len(peaks)

此时一个body part会存在多个候选位置 DJ={dmj:forj(1...J),m(1...Nj)} , Nj 为body party j 候选位置。关于候选位置的关联问题可以转化为二分图问题:
对于第c个limb

maxEc=maxmDj1nDj2Emnzmnj1j2

s.t.

mDj1,nDj2zmnj1j21
mDj2,nDj1zmnj1j21

zmnj1j2{0,1} 表示候选位置 dmj1 dmj2 是否连接, Emn 为候选位置连接的置信度。
文章中最终选择匈牙利算法来解决此二分图问题,源码如下:

# find connection in the specified sequence, center 29 is in the position 15
limbSeq = [[2,3], [2,6], [3,4], [4,5], [6,7], [7,8], [2,9], [9,10], \
           [10,11], [2,12], [12,13], [13,14], [2,1], [1,15], [15,17], \
           [1,16], [16,18], [3,17], [6,18]]
# the middle joints heatmap correpondence
mapIdx = [[31,32], [39,40], [33,34], [35,36], [41,42], [43,44], [19,20], [21,22], \
          [23,24], [25,26], [27,28], [29,30], [47,48], [49,50], [53,54], [51,52], \
          [55,56], [37,38], [45,46]]

connection_all = []
special_k = []
mid_num = 10

for k in range(len(mapIdx)):
    score_mid = paf_avg[:,:,[x-19 for x in mapIdx[k]]]
    candA = all_peaks[limbSeq[k][0]-1]
    candB = all_peaks[limbSeq[k][1]-1]
    nA = len(candA)
    nB = len(candB)
    indexA, indexB = limbSeq[k]
    if(nA != 0 and nB != 0):
        connection_candidate = []
        for i in range(nA):
            for j in range(nB):
                vec = np.subtract(candB[j][:2], candA[i][:2])
                norm = math.sqrt(vec[0]*vec[0] + vec[1]*vec[1])
                vec = np.divide(vec, norm)

                startend = zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), \
                               np.linspace(candA[i][1], candB[j][1], num=mid_num))

                vec_x = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])),\
                0] for I in range(len(startend))])
                vec_y = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])),\
                1] for I in range(len(startend))])

                score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1])
                score_with_dist_prior = sum(score_midpts)/len(score_midpts) + 
                min(0.5*oriImg.shape[0]/norm-1, 0)
                criterion1 = len(np.nonzero(score_midpts > param['thre2'])[0]) > 0.8 *
                len(score_midpts)
                criterion2 = score_with_dist_prior > 0
                if criterion1 and criterion2:
                    connection_candidate.append([i, j, score_with_dist_prior,
                    score_with_dist_prior+candA[i][2]+candB[j][2]])

        connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True)
        connection = np.zeros((0,5))
        for c in range(len(connection_candidate)):
            i,j,s = connection_candidate[c][0:3]
            if(i not in connection[:,3] and j not in connection[:,4]):
                connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]])
                if(len(connection) >= min(nA, nB)):
                    break

        connection_all.append(connection)
    else:
        special_k.append(k)
        connection_all.append([])
# last number in each row is the total parts number of that person
# the second last number in each row is the score of the overall configuration
subset = -1 * np.ones((0, 20))
candidate = np.array([item for sublist in all_peaks for item in sublist])

for k in range(len(mapIdx)):
    if k not in special_k:
        partAs = connection_all[k][:,0]
        partBs = connection_all[k][:,1]
        indexA, indexB = np.array(limbSeq[k]) - 1

        for i in range(len(connection_all[k])): #= 1:size(temp,1)
            found = 0
            subset_idx = [-1, -1]
            for j in range(len(subset)): #1:size(subset,1):
                if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]:
                    subset_idx[found] = j
                    found += 1

            if found == 1:
                j = subset_idx[0]
                if(subset[j][indexB] != partBs[i]):
                    subset[j][indexB] = partBs[i]
                    subset[j][-1] += 1
                    subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]
            elif found == 2: # if found 2 and disjoint, merge them
                j1, j2 = subset_idx
                print "found = 2"
                membership = ((subset[j1]>=0).astype(int) + (subset[j2]>=0).astype(int))[:-2]
                if len(np.nonzero(membership == 2)[0]) == 0: #merge
                    subset[j1][:-2] += (subset[j2][:-2] + 1)
                    subset[j1][-2:] += subset[j2][-2:]
                    subset[j1][-2] += connection_all[k][i][2]
                    subset = np.delete(subset, j2, 0)
                else: # as like found == 1
                    subset[j1][indexB] = partBs[i]
                    subset[j1][-1] += 1
                    subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]

            # if find no partA in the subset, create a new subset
            elif not found and k < 17:
                row = -1 * np.ones(20)
                row[indexA] = partAs[i]
                row[indexB] = partBs[i]
                row[-1] = 2
                row[-2] = sum(candidate[connection_all[k][i,:2].astype(int), 2]) +
                connection_all[k][i][2]
                subset = np.vstack([subset, row])

模型在MPII上的表现

table1
表1 分别为在测试子集和完整测试集上不同模型结果的对比

figure4
图4 在不同的PCKh阈值下mAP的变化曲线。
PCKh-0.5阈值下,使用PAFs,其mAP比one-midpoint的高2.9%,比two-midpoints的方法高2.3%。这是因为PAFs同时利用了位置和方向这两个信息,在人体有交叉的图像中表现更好。通过对图像未标记的的部分进行掩码,提高了2.3%mAP,因为它避免了训练时对正确的预测进行损失惩罚。PAFs算法可以得到与使用GT连接相似的mAP结果(分别为79.4%和81.6%)

模型在COCO上的表现

figure5
图5 在COCO数据集上AP成绩和运行时间
图5(d)数据来源为:原始的图片为 1080×1920 ,resize成 368×654 ,GPU型号为NVIDIA GeForce GTX-1080 GPU。最终结果可以发现top-down方法运行时间会随着图片人数的增多显著提高,而使用bottom-up其运行时间相对很缓慢。

相关阅读

人体姿态估计综述by桃木子

### 回答1: 实时多人二维姿态估计使用的是部件关联场技术。该技术利用深度学习网络对图像中的人体关键点进行检测和定位,并通过学习人体部位之间的关联性来提高姿态估计的准确性。在实时性方面,该技术利用高效的网络结构和并行计算技术,能够在处理多人图像时保持较高的处理速度和较低的延迟。 ### 回答2: 实时的多人2D姿态估计是指在照片或视频中同时检测多个人的姿态并实时反馈结果。这个任务主要是依赖计算机视觉领域的人体关键点检测技术。而Part Affinity Fields(PAF)是现在最常用的一种检测方法。 PAF可以理解为是人体姿态中的“骨架”,它在这里指的是需要将骨骼节点间的关系一同考虑进去来提高精度的设计。每个PAF都对应着一对关联的节点,例如手臂这一关节对应的PAF就是肩膀和手腕两个关键点中间的向量场。PAF能够将关键点之间的联系编码为一个向量场,并将它们的图像位置和方向作为通道特征,这样就可以通过深度卷积网络获得节点的连接信息。 对于多人2D姿态估计任务,PAF可以自动推断出人体的椭圆形状,使得不同人的节点互相不干扰,能够高效地分离不同人体之间的关键点信息,保证检测精度。 总体而言,实时的多人2D姿态估计技术是计算机视觉研究领域中一个非常重要的方向。通过Part Affinity Fields技术,可以实现对人体姿态的快速准确检测和分析,并具有广泛的应用前景,比如拍摄跳舞类视频、体育比赛等。未来,该领域还将会不断提高研究和开发技术,提高其在实际场景中的使用效果,为人们的生产和生活提供更多更好的便利。 ### 回答3: 在计算机视觉领域,人体姿态估计一直是一个十分重要的研究方向。现在,研究者们正在致力于开发实时多人二维姿态估计方法,本文将介绍一种方法——part affinity fields。 Part affinity fields是指身体部位之间存在的空间关系矩阵。多人姿态估计就是先将图像中的每一个像素与人体相关的身体部件联系起来,然后再利用network output将这些点连接起来形成人体姿态。part affinity fields的基本思想是采用CNN对每一个像素做预测,以定位人体骨架上的每一个连接点。 主要步骤: 1.生成部件置信图:对于输入的图像,通过CNN估计每个像素是否为其中每个身体部位的一部分,这个图叫做部件置信图(Part Confidence Maps),可以通过训练数据集来检测出身体部位的位置。 2.生成连接部件映射图:对于预测出来的部件置信图,我们可以通过预测到的部件之间的关系来学习生成连接映射图(Part Affinity Fields),即学习两个不同身体部件之间的关系(connectivity),这个关系是一个形状为“c&times;2&times;h&times;w”的4维张量。在测试阶段,对于输入图像中的每个像素,都会有其对应到一个连接部件映射图的位置。 3.生成姿态结果:最后,我们将生成的部件置信图和连接部件映射图进行联合,把已确定的部件通过连接映射图装配起来并组合成人体的姿态结果。 这种方法最大的好处就是实现了实时多人姿态估计,不需要预设一个特定数量的人数。同时,在处理不同人的关键点时,以前的方法通常是对每个人都单独进行估计,而这个方法则对所有人的关键点一起进行估计,能够更好地处理人际交互。 总之,通过深度学习和部件置信图与连接映射图等技术手段的利用,Part Affinity Fields在解决实时多人二维姿态估计时具有很大的潜力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值