赞
踩
先思考下面几个问题;
1、什么是姿态估计?
参考:Point Detect任务,识别人体指定部分的关键点;
2、姿态估计中的难点是什么?
从干扰的角度,人体被遮挡对检测的影响很大;
如何对检测的点进行匹配拼接,并且是顺序拼接,而且确保是同一个人;
怎么提升性能,算法能够实时推理;
3、COCO数据集的关键点有几个,分别是?
数据集中是17个点,实际上训练时候还要加上一个脖子的点,通过两个肩膀的点计算;
4、应用领域有哪些?
5、姿态估计方法分为几个大类?
Top-down(自顶向下):先检测所有的人,再对每个框的人进行姿态估计输出结果
优点:准确率高,点的回归率高;
缺点:算法性能依赖检测效果,复杂度较高,实时性比较差;
主要用于一些离线的项目,对实时性没要求!!!
bottom-up(自底向上):先检测所有关键点,再进行匹配连接
优点:计算量较小,可以达到实时性的效果;
缺点:精度较差,匹配策略比较复杂;
论文地址:https://arxiv.org/pdf/1611.08050.pdf
参考文章:https://zhuanlan.zhihu.com/p/514078285
首先看一下整体的流程图:
首先输入一张W * H的图像进入网络,网络会有两个分支:
一是关键点检测,会生成一个热力图,检测出J个标签部位的点;
二是得到关键点的方向关系,也被称为PAF,对于每一个关键点都有其亲和域的方向;
最终结合二者,将所有关键点连接起来,输出一个完整的人体骨架;
重点的关注点:
1、关键点的heatmap标注生成,采用高斯函数;
2、PAF:部分亲和域,文章提出的重要方法;
3、匹配策略:匈牙利匹配;
采用的数据集为COCO数据集,其中人体骨骼点的标注信息为【x,y,label】,这里的label取值为0、1、2,分别表示不存在、遮挡、正常,其中不存在的关键点是需要去除的;
关键点高斯热力图实现:
def putGaussianMaps(center, accumulate_confid_map, sigma, grid_y, grid_x, stride): start = stride / 2.0 - 0.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 # 计算每个点和GT点的距离 exponent = d2 / 2.0 / sigma / sigma # 这里在做一个高斯计算 mask = exponent <= 4.6052 # 将在这个阈值范围内的点用True记录 cofid_map = np.exp(-exponent) # 这里做一个标准化 cofid_map = np.multiply(mask, cofid_map) # 取出对应关系为True的点 accumulate_confid_map += cofid_map # 将每个点计算的结果都累加到上一次的特征中 accumulate_confid_map[accumulate_confid_map > 1.0] = 1.0 # 对结果大于1的值,只取1 return accumulate_confid_map # 返回热力图(heatmap)
PAF数据计算的实现:
def putVecMaps(centerA, centerB, accumulate_vec_map, count, grid_y, grid_x, stride): centerA = centerA.astype(float) centerB = centerB.astype(float) thre = 1 # 表示宽度,也就是一个设定好的参数 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] # 向量叉乘根据阈值选择赋值区域,任何向量与单位向量的叉乘即为四边形的面积 # 这里是重点步骤,也就是论文中的公式,表示计算出两个向量组成四边形的面积 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 # 构建一个全为0的矩阵 # 这行代码主要作用是将mask扩展一个维度并且赋值给vec_map数组 vec_map[yy, xx] = np.repeat(mask[:, :, np.newaxis], 2, axis=2) # 在该区域上的都用对应的方向向量表示(根据mask结果表示是否在,通过乘法的方式) vec_map[yy, xx] *= limb_vec_unit[np.newaxis, np.newaxis, :] # #在特征图中(46*46)中 哪些区域是该躯干所在区域,判断x或者y向量都不为0 mask = np.logical_or.reduce( (np.abs(vec_map[:, :, 0]) > 0, np.abs(vec_map[:, :, 1]) > 0)) # 每次返回的accumulate_vec_map都是平均值,现在还原成实际值 accumulate_vec_map = np.multiply( accumulate_vec_map, count[:, :, np.newaxis]) accumulate_vec_map += vec_map # 加上当前关键点位置形成的向量 count[mask == True] += 1 # 该区域计算次数都+1 mask = count == 0 count[mask == True] = 1 # 没有被计算过的地方就等于自身(因为一会要除法) accumulate_vec_map = np.divide(accumulate_vec_map, count[:, :, np.newaxis]) # 算平均向量 count[mask == True] = 0 # 还原回去 return accumulate_vec_map, count
这两个函数是最重要的两个部分,也就是对训练数据的处理,生成出需要的训练数据,其中遍历原始数据的代码并没有展示,详细可以看看源代码中的data.py文件;
下面看一下总的网络架构:
模型的backbone可以选择比较多,这里选择VGG19,在Stage1之前得到一个46x46大小的特征图;
接下来通过几个stage不断地·扩大感受野,提升模型的效果,每次上面结构输出关键点的热力图(维度为19),下面结果输出关键点的PAF方向信息(维度为38),计算损失保存,接下来将两个维度的特征进行拼接,再传入接下来的stage中,通过不断的堆叠提升整个算法的精度;
预测模型这里,在官方源码中是将paf的处理封装在一个cpp库中,通过编译得到静态库,代码中可以调用;
这里PAF的处理主要是采用积分计算的方式,也就是对于连线的点的方向选择,采用积分的方式计算最佳连线策略;
运行picture_demo.py得到最终的结果,如下图所示:
模型结果及推理耗时:
可以看出模型的结构为Vgg19,这是可以替换的,并且特征图的输出维度为38和19,这也符合定义的骨骼点分类和连接方式;
一些细节例如匈牙利匹配算法、PAF权值计算、模型结构改进等在这里并没有过多介绍;
并且对于现在OPenpoe的使用,主要是将其编译成一个库,作为Python程序的调用;
并且相比于本次的代码,其实时性很好,符合大部分落地场景的需求;
如果有想要本次项目源码的,可以在评论区留下邮箱,也欢迎大家一起讨论,后续也会探讨更多最新的技术;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。