openpifpaf 的decode过程:

网络的输出:

  1. pif, 原始的输出共有4个, 分别为:

    1. joint_intensity_fields, shape 为 [17, output_h, output_w]. 其实就是输出的每个位置上的confidence map, 17表示channel数, 在pose检测里面表示总共有多少个关键点需要检测.
    2. joint_offset_fields, shape 为[17, 2, output_h, output_w]. 为对应位置上的离其最近的关节点位置的偏移量. 这个是学习得到的, 2表示是两个方向(x, y)的偏移量. 所以关节点的真正位置需要把该位置的(x, y)和其两个方向的(x_offset, y_offset)相加起来得到.
    3. joint_b, shape为[17, output_h, output_w]. 论文里提到的spread b,是自适应并且经过网络学习得到的, 用来参与loss计算, 在decode的时候并没有用到.
    4. joint_scale_fields. shape为[17, output_h, output_w]. 自适应的scale值, 用来表明该关键点的scale大小.不确定是否有用在loss计算里. decode的时候则是作为类似gaussian的sigma值参与decode过程.
  2. paf, 原始的输出共有5个, 按照顺序为: (首先说明下, 论文提出的paf和之前OpenPose及PersonLab提出的连接方式都不一样. 该论文提出的paf连接为, 每个位置预测出哪两个点需要连接在一起, 因此不是单纯的两个关节点之间的直接连接, 而是经过了另外一个位置进行第三方连接)

    1. joint_intensity_fields, shape为[19, output_h, output_w]. 19表明共有多少个连接需要学习, 对应的是每个输出位置上的paf的confidence值

    2. joint1_fields, shape为[19, 2, output_h, output_w]. 这个位置表明的两个可以连接在一起的点中的第一个点的信息, 其实就是偏移值, (x_offset, y_offset).

    3. joint2_fields, shape为[19, 2, output_h, output_w]. 同上, 表示的是一条线段上的第二个点的偏移值.

    4. joint1_fields_logb, shape为[19, output_h, output_w]. 论文里提到的spread b,是joint1的, 用来参与loss计算和decode. 根据decode的过程来看, 网络输出的这个值是经过log计算后的, 所以叫做logb,在decode的时候需要先exp还原.

    5. joint2_fields_logb, shape为[19, output_h, output_w]. 同上, 只不过变成是第二个点的b了.

decode过程:

  1. normalize_pif. 就是把网络的pif 4个输出整合在一起, 首先是对joint_intensity_fieldsjoint_scale_fields进行扩维, 把shape从[17, output_h, output_w]变成[17, 1, output_h, output_w]. 接着是根据joint_offset_fields对[output_h, output_w]这么大的矩阵上, 对应的位置(x, y) + offset. 举例来说, 原来的joint_offset_fields, 其中[1, :, 4, 5] 这个位置表示的offset值为(1, 2), 那么意思就是在(4, 5)这个位置坐标(4, 5)上, 需要加上偏移(1, 2)才是真正的这个位置所表示的关键点坐标值, 也即是(5, 7), 因此最后[1, :, 4, 5] == [5, 7]. 最后把更新后的这三个矩阵concatenate一起, 变成[17, 4, output_h, output_w]的pif信息, 4表示每个位置上都有四个值, 分别是[confidence, x, y, scale]. 这个时候的[x, y]就是真正的这个点表示的关键点的坐标值.

  2. normalize_paf. 同pif一样, 也是把网络的paf 5个输出整合在一起. 除了对joint1_fieldsjoint2_fields进行同样的offset相加外(和pif的操作一样), 还对两个logb进行了exp操作, 然后对joint_intensity_fields和两个b进行扩维成[19, 1, output_h, output_w]. 最后对更新后的joint_intensity_fields, joint1_fields, joint1_logb进行concatenate一起, 得到[19, 4, output_h, output_w]大小的矩阵, 这个存储的全是有关joint1的, 同理, 对joint2做同样的操作, 同样得到[19, 4, output_h, output_w]大小的矩阵. 4表示[confidence, x, y, b]. 需要注意的是joint1和joint2共享同样的confidence值. 最后, 对这两个矩阵进行stack一起,得到最终的[19, 2, 4, output_h, output_w]输出.

  3. _target_intensities: 根据上面得到的新的pif信息, 利用文章提出的公式1, 得到在高维空间(也就是网络的输入分辨率下)的pif_hr, pifhr_scales信息. hr表示的就是high resolution的意思. 方法就是首先找到pif里所有confidence > v_th的值, 先把(x, y, s) 都乘上网络的scale值, 然后针对这些位置, 以这些位置为中心, 位置对应的scale值为gaussian的sigma, 范围为当前位置对应的confidence/16(不是很理解为什么是这个值), 接着对这个范围内的值做高斯变换, 和pif里原来的位置信息相加, 使得本来confidence就很高的位置值更高, confidence值低的位置值更低. (具体图示可以参考文章图3). 源码里的scalar_square_add_gaussian函数就是这个作用, 这样就得到了pif_hr. cumulative_average函数的作用好像是对这个区域的scale求个平均? 没有很理解这个函数的作用, 但结果是得到对应的scale值.

  4. _score_paf_target: 这个函数的作用就是根据上面得到的paf信息, 得出哪些点是连在一起的. 因为paf的shape是[19, 2, 4, output_h, output_w], 因此对于每个连接, 其对应的paf field shape均为[2, 4, output_h, output_w]. 首先, 对于单个线段的信息, 假设为第一条线段的信息, 为fourds, shape = [2, 4, output_h, output_w]. 首先, 找到fourds里每个位置上的confidence值最小的那个(源码是scores = np.min(fourds[:, 0], axis=0), 但我感觉因为对于同一个feature map上的位置而言, joint1 和 joint2的confidence是一致的, 因此其实就是把feature map上对应位置的score值去出来). 然后, 找到满足scores > score_th条件的位置, 把这些位置取出来, 组成个新的fourds. 同理, scores也取出来, 组成个新的scores. 这时fourds的维度就是[2, 4, n], scores的维度是[n, ], n为满足前面score条件的点的个数. 然后, 找到第一条线段对应的pif的channel位置, 例如第一条线段在coco是[15, 13], 那么就把pif_hr[15]当作是这个线段的joint1所在的featuremap, pif_hr[13]就是这个线段的joint2所在的featuremap. 因为此时fourds的shape为[2, 4, n], 那么fourds[0]就是所有满足刚才那个条件的joint1集合, fourds[1]就是joint2集合. 地一个函数scalar_values的意思, 就是找到在pif_hr[15]上, 位置为(fourds[0, 1] * self.stride, fourds[0, 2]*self.sride)的confidence值,(注意这个confidence是指在pif_hr上的confidence, 不知道为啥程序里变量名起做pifhr_b.) 此时得到的pifhr_b就是joint1对应的在pifhr上的confidence值, 接着执行代码scores_b = scores * (pifhr_floor + (1.0 - pifhr_floor) * pifhr_b), 我的理解是这行代码的作用就是根据confidence对scores进行更新, pifhr_floor是0.1, 如果本身的pifhr_b值很大, 那么其对应的在paf的score就还是很大, 如果小, 相应的也缩小了其对应的paf的score值. 更新完后得到scores_b, 再根据这个值进一步过滤找到符合条件的joint1, joint2点, 最后把符合条件的点集合, 得到scored_backward里的一个元素. 因为paf总共有19个channel, 所以scored_backward总共有19个元素, 每个元素都是[7, m]大小的矩阵, 存储的信息分别是[score_b, joint2_x, joint2_y, joint2_b, joint1_x, joint1_y, joint1_b]. m是scores_b中符合条件的点的个数. 因为元素是先joint2元素信息再是joint1信息, 所以叫做scored_backward. 下面还会有在**_pifhr[j2i] (_pifhr[13])**搜索符合条件的joint2的信息, 仿照上面的步骤, 同样得到一个[7, n]的矩阵, 信息为[score_b, joint1_x, joint1_y, joint1_b, joint2_x, joint2_y, joint2_b], 因为这个是先jioint1的信息再是joint2的信息, 因此其组成的列表又叫做scored_forward, 其实就是看是以joint1为出发点还是终止点表示的这个线段信息.

  5. 总结下先, 3执行完之后, 得到了一个在高分辨率下的pifhr信息和pif_scales信息, shape均为[17, hr_h, hr_w]. 4执行完后, 得到了两个均含有19个元素的列表, 分别叫做scored_forward 和 scored_backward. 列表里的每个元素, 都是满足一系列条件的点的信息, 例如对于scored_forward[0]来说, shape大小为[7, m], m为点的个数, 7表示[score_b, joint1_x, joint1_y, joint1_b, joint2_x, joint2_y, joint2_b], 形象话说, 就是对于7*m的矩阵, 每一列都能找到两个点用来表示线段0. 需要明确的一点, 这里面的两个点, 都还是通过paf信息得到的点的位置, 并不是在pif信息上的点的位置

  6. 接着就是根据3和4得到的结果, 用来连线, 判断哪些线段应该连起来组成一个人**.**

  7. _pif_seeds函数: pif_seeds函数用来在self.pif上找到confidence符合阈值的点的信息, , 然后再在pifhr上找到该点对应的confidence值, 接着把(v, field_i, xx, yy, ss)组成一个seed放入seeds中, v是在pifhr上的confidence, field_i是用来表明这个点在第几个channel上, (xx, yy, ss)都是在pif上的位置和scale信息. 最后, 对seeds按照v的值降序排列, 越靠近前面的seed, confidence值更大.(比较奇怪的是为什么confidence是在pifhr找, 但(xx, yy, ss)都是在pif上面找)

  8. 首先根据3得到的pifhr_scales生成同样大小的矩阵occupied, 找到了pif_seeds之后, 按照排过序的seeds序号, 先从confidence值最大的seed进行寻找. 首先判断当前seed的(x, y)是否超过了occupied的范围, 如果超过就寻找下一个seed, 没有就返回当前occupied[y, x]的值. (这边同样有个问题, occupied初始化为0, 那寻找第一个seed的时候, 无论是否超出范围, 返回的值都为0, 也就是说按照源码来说, 第一个seed是没有用的?我尝试了把第一个seed当作正常运行放进去, 发现结果没有太大差别, 就是最后的anno score值变大了一些. 不过python格式的程序速度慢了将近一倍.) 然后会执行函数scalar_square_add_single, 这个函数的作用就是让occupied矩阵在(x*stride, y*stride) 为中心, 范围为max(4, s*stride)的范围内加1. 这样就相当于更新了以(x, y)为中心的occupied值. 接着, 根据seed的(x, y, v) 和 当前所处的channel编号f, 构建Annotation类. ann.data是一个[17, 3]的矩阵, 存储的就是一个完整的人体pose应该有的关节点个数. ann.skeleton_m1就是下标从0开始的[19, 3]的skeleton连接编号. 初始化ann的时候, 会根据f的值把传入的(x, y, v) 放进去ann.data[f]上. 接着, 就会以当前ann为已有的skeleton信息, 以前面得到的_paf_forward, _paf_backward连接信息进行_grow操作.

  9. _grow函数: 在进行从_paf_forward, _paf_backward抽取信息前, 会先对当前的ann执行ann.frontier_iter()函数. ann.frontier_iter()函数的意思就是找到当前ann.data里, 应该和ann.data里已有值的点相连的点, 但是却没有在ann.data里面的点. 然后, 根据这个点是在已有点的后面(即已有点是joint1, 未找到点是joint2)还是在前面, 判断该点是处于forward状态还是backward状态. 接着, 对这些所有点进行按照已有值的点的confidence值降序排列. (函数最终会得到一个列表的集合, 每个列表的元素都是[confidence, connection_i, True/False, j1i, j2i], confidence是已有点的confidence, connection_i是这个点应在的线段连接编号, True表明已有点是joint1, 放进去的这个点是joint2, j1i 和 j2i就是这个connection连接的两个点的channel编号). 最后, 从这个列表frontier里取出第一个值, 表明是当前已有的ann.data里, confidence值最高的那个点应该连接的点的信息. 取出的值就是上面所讲的列表的元素值信息, 如果是True(表明为forward), 则xyv = ann.data[j1i], directed_paf_field = paf_forward[i], directed_paf_field_reverse = paf_backward[i], 否则的话, xyv = ann.data[j2i], directed_paf_field = paf_forward[i], directed_paf_field_reverse = paf_backward[i]. xyv是从ann.data里取出的目前confidence值最高且需要额外的一个点和它连接的点(new_xyv), directed_paf_field就是new_xyv所在的paf_field, directed_paf_field_reverse就是刚好xyv所在的paf_field. (刚好一个是paf_forward, 一个是paf_backward). 然后, 根据得到的directed_paf_field, 执行_grow_connection函数.

  10. **_grow_connection**函数. 这个函数比较简单, 首先是在paf_field上, 以(x,y)为中心, 找到在paf_field里的所有点符合条件的点, 拿出来构成新的paf_field, 然后, 在这个新的paf_field里面, 计算这些点和xy这个点的距离, 并根据距离更新里面点的score值, 最后选择score值最大的那个点作为返回值. 为了保证找的这个点是正确的, 会根据找到的这个点进行reverse match, 步骤和前面的一致, 判断通过reverse找到点是否和(x,y)满足距离阈值条件. 如果是, 就把找打的这个点当作新的一个点放进ann.data里面. 这样循环遍历, 就能把ann给尽量的填充满.

  11. 当对当前的seed点进行grow_connection之后, 会根据之前得到的pifhr_scalesann.data的每个点加上对应的scale值, 然后根据目前已有的ann.data值, 再更新一遍每个ann.data里的点对应的occupied矩阵, 就是执行函数scalar_square_add_single函数. 最后选择下一个seed点进行grow, 直到所有的seed点都遍历过一次. 最后再对所有的anns执行complete_annotations操作, 就是寻找对于每个ann上额外的空置的点(应该有点和它连接的实际上没有)是否还有可能有其它点和它连在一起, 就是再执行一边_grow操作.

  12. **soft_nms**函数: 对11得到的annos执行nms操作, 过滤掉一些有可能不符合要求的ann. ann.score值是其中ann.data中每个点的score值的加权平均.

OpenPifPaf decode源码解析相关推荐

  1. Android之图片加载框架Picasso源码解析

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/76645535 本文出自:[顾林海的博客] 个人开发的微信小程序,目前功 ...

  2. dubbo源码解析(九)远程通信——Transport层

    远程通讯--Transport层 目标:介绍Transport层的相关设计和逻辑.介绍dubbo-remoting-api中的transport包内的源码解析. 前言 先预警一下,该文篇幅会很长,做好 ...

  3. Alibaba-Dexposed Bug框架原理及源码解析

    目录(?)[+] Alibaba的AndFix热修复:  Alibaba-AndFix Bug热修复框架的使用  Alibaba-AndFix Bug热修复框架原理及源码解析 上一篇中已经介绍了Ali ...

  4. dubbo源码解析(四十一)集群——Mock

    集群--Mock 目标:介绍dubbo中集群的Mock,介绍dubbo-cluster下关于服务降级和本地伪装的源码. 前言 本文讲解两块内容,分别是本地伪装和服务降级,本地伪装通常用于服务降级,比如 ...

  5. Android Glide图片加载框架(二)源码解析之into()

    文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...

  6. dubbo源码解析(十)远程通信——Exchange层

    远程通讯--Exchange层 目标:介绍Exchange层的相关设计和逻辑.介绍dubbo-remoting-api中的exchange包内的源码解析. 前言 上一篇文章我讲的是dubbo框架设计中 ...

  7. Java String源码解析

    String类概要 所有的字符串字面量都属于String类,String对象创建后不可改变,因此可以缓存共享,StringBuilder,StringBuffer是可变的实现 String类提供了操作 ...

  8. dubbo(5) Dubbo源码解析之服务调用过程

    来源:https://juejin.im/post/5ca4a1286fb9a05e731fc042 Dubbo源码解析之服务调用过程 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与 ...

  9. SOFA BOLT源码解析之设计要点-线程模型

    1 设计要点解析 1.1  线程模型 此部分内容主要介绍蚂蚁为什么选择Netty4作为基础网络编程框架,来源于蚂蚁技术团队发布的一篇文章: 文章名称为:蚂蚁通信框架实践: 链接地址为:https:// ...

最新文章

  1. 网站防火墙探测工具Wafw00f
  2. Java-switch选择结构
  3. android悬浮功能实现,Android实现系统级悬浮按钮
  4. 成也标签败也标签--真实的模型案例分享
  5. 首届国际数字科技节启动仪式暨主办单位战略合作签约仪式在京举行
  6. python两张图片融合_python实现两张图片的像素融合
  7. 产生滚动条时JPanel的大小发生变化
  8. 【转载】深入理解Java内存模型——final
  9. 中英文对照 —— 几何(数学)
  10. WEB前端性能优化及应用服务器性能优化和存储性能优化
  11. yii2 表单提交没有对应的路由参数解决办法
  12. 项目进度管理表模板_IT项目进度管理—你能驾驭的项目节奏
  13. 让Office 2003与Office 2010完美共存
  14. P问题、NP问题、NPC问题、NP hard问题
  15. win10分辨率设置正确但屏幕却被拉伸了,如何处理
  16. python股票量化交易(13)---使用pyqt5构建股票交易K线形态
  17. 实战pixi+gsap,仿刹车动画
  18. java安装及设置eclipse
  19. Linux就该这么学第十三节课学习心得
  20. 受保护的Hyper-V环境和受保护的虚拟机

热门文章

  1. MATLAB连续LTI系统的时域分析(十)
  2. excel汇总怎么做?
  3. 达人评测酷睿i5 12450h和锐龙R5 6600u选哪个 i512450h和锐龙R56600u对比
  4. VMware16调整了路径后界面全部变成了英文
  5. k1658停运_2016年10月20日火车停运信息
  6. 客户服务呼叫中心解决方案
  7. C++友元函数实现两个复数相加
  8. 发那科2021参数_发那科系统FANUC:参数修改。
  9. 中西方历史发展和根源
  10. ***JLink Error: Supply voltage too low (1 Volt is required, Measured: 0.4 Volt).Please check target