本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
🍊作者简介:秃头小苏,致力于用最通俗的语言描述问题🍊专栏推荐:深度学习网络原理与实战
🍊近期目标:写好专栏的每一篇文章
🍊支持小苏:点赞👍🏼、收藏⭐、留言📩
Openpose原理详解篇
写在前面
Hello,大家好,我是小苏👦👦👦
在前三节中,我分别从数据集、原理及源码三个方面给大家介绍了一种单人检测的关键点检测算法------HRNet(用于多人检测属于自顶向下的算法),不熟悉的可以点击下述链接进行查看:
2、HRNet原理详解篇🌱🌱🌱
3、HRNet源码解析篇🌱🌱🌱
那么这篇将为大家介绍一种自底向上的关键点检测算法------Openpose。🥗🥗🥗关于关键点检测算法分类不熟悉的可以看一下我绘制的思维导图:
【注:上图的一些细节可能看不清楚,需要的可以私信我,发Xmind源文件】
这里我也简单来叙述一下上节所述自顶向下的HRNet算法和本节自底向上Openpose算法的区别。首先要注意不管是自顶向下还是自底向上都是针对多人检测来说的。对于自顶向下的算法,在处理一张图像的时候,会先对图片进行目标检测,获取到人体的位置,然后将目标检测的得到的人体分别送入到单人检测的关键点检测算法(如HRNet中)进行关键点检测。因此这种自顶向下方法的速度往往受限于图片中人物的数据,即如果图片中人物越多,就需要越多次的调用单人关键点检测算法。为方便大家理解,绘制自顶向下的关键点算法流程如下:
而自底向上的算法则是先直接检测到图片中所有关键点的位置,然后将属于同一个人的关键点链接起来,这种方式的检测速度则不会显著的受图片中人物数量影响,因为它是一次性的检测到图片中的所有的关键点。同样,绘制自底向上的关键点算法流程,如下图所示:
通过上面的介绍,我想你对自顶向下和自底向上的关键点算法有了一个初步的印象。对于自顶向下的算法,我们需要目标检测算法和单人关键点检测算法,而这两种算法在我之前的博客都有介绍,不清楚的可以去我的主页看看其它内容;而对于自底向上的关键点检测算法主要可以分为两步,如下:
- 检测图像中所有关键点
- 将属于同一个人的关键点连接起来
这两步就是自底向上算法的关键,下文也将主要围绕这两点来讲解自底向上的关键点检测算法。本节将以著名的Openpose为叙述对象,介绍这种自底向上关键点检测算法的原理。准备好了嘛,快来跟随小苏的脚步一起来学学叭~~~🌞🌞🌞
数据集介绍
本节使用的数据集同样为COCO关键点检测数据集,对COCO数据集不清楚的一定要先阅读这篇博客:COCO数据集------关键点检测标注文件解析🥎🥎🥎。COCO数据集一共标注了17种关键点,包括鼻子、左眼、右眼等等。Openpose在COCO数据集的基础上添加了一个新的关键点neck(脖子),注意这个关键点不是在标注中添加的,而是在代码中用左键和右肩的中点来拟合的新的关键点,关键代码也很简单,如下:
python
neck = (right_shoulder + left_shoulder) / 2
这样一来,Openpose就一共有17+1=18个关键点的信息。还需要特别注意的是,Openpose中将COCO关键点的顺序和连接关系也做了改变【注意:这也同样是在代码中调整的,在下节源码解析篇进行讲解】,最终的关键点顺序和连接关系如下图所示:
为了方便大家阅读,绘制关键点索引和对应名称的表格如下:
索引 | 关键点名称 | 中文名称 |
---|---|---|
0 | nose | 鼻子 |
1 | neck | 脖子 |
2 | right_shoulder | 右肩 |
3 | right_elbow | 右手肘 |
4 | right_wrist | 右手腕 |
5 | left_shoulder | 左肩 |
6 | left_elbow | 左手肘 |
7 | left_wrist | 左手腕 |
8 | right_hip | 右臀部 |
9 | right_knee | 右膝盖 |
10 | right_ankle | 右脚踝 |
11 | left_hip | 左臀部 |
12 | left_knee | 左膝盖 |
13 | left_ankle | 左脚踝 |
14 | right_eye | 右眼 |
15 | left_eye | 左眼 |
16 | right_ear | 右耳 |
17 | left_ear | 左耳 |
Openpose实现
在上文我们谈到Openpose这类自底向上的关键点检测算法主要可以分为如下两步:
- 检测图像中所有关键点
- 将属于同一个人的关键点连接起来
那么如何实现上述的两个步骤呢?对于第一点------检测图像中所有关键点,这个就是关键点的检测嘛,我们在HRNet中的任务和这个是很相似的,只不过HRNet每次只检测图片中一个人,而Openpose是检测图片中所有人的关键点。那么还记得HRNet是怎么做的嘛,总结HRNet检测关键点的方法就是先构建基于热力图的关键点标签,然后将图片输入网络得到的输出结果与标签计算损失,最后根据输出结果预测关键点位置。其实呀,HRNet的重中之重就是如何构建基于热力图的关键点标签,至于后面的计算损失和预测和一般深度学习算法步骤是很类似的。那么Openpose的关键其实也是如何构建基于热力图的关键点标签,关于这点,我们将在下文叙述。🥗🥗🥗
那么对于第二点------将属于同一个人的关键点连接起来,这可怎么办呢?我也不卖关子了,我认为这里就是Openpose最重要、也是最难理解的地方。大家也不要慌,这里的核心思想其实也是构建一种独特的标签,然后与网络输出结果进行损失计算。但是如何构建标签确实是一个难点,后文将重点介绍这一步骤。🍚🍚🍚
关键点热图标签构建
在上节数据集介绍中,我们知道Openpose要检测18种关键点的位置,那么我们就需要对18种关键点构建热图标签。关于如何制作热图标签,在HRNet源码解析中已经很详细的介绍了,Openpose和其构造方式类似,但是代码实现的过程略有差别,具体细节我们将在Openpose源码解析篇为大家介绍。这里简述一下热图标签制作前后的差异:在我们的COCO关键点检测数据集标注数据中,一个关键点标签就是一个(x,y)坐标,也就是一个像素,而将标签制作成热图就是将一个像素的标签变成一个高斯范围内的标签,如下图所示:
你可能会好奇为什么要这么制作标签,给出几点解释:
- 如果使用一个像素做标签,除此标签外的点都会被认为是负样本,但是实际上该点附近的点也是上图左眼的范围,被直接当成负样本不合适。
- 在标签标注过程中会存在误差,只要标注的位置大致和左眼位置匹配,都认为是合格的,所以往往某个范围内都可以被认为是左眼。
- 制作成的高斯热图,其中心是标签的像素点位置,值最大,即置信度最大,往四周扩散值越来越小,这样更符合直觉。
基于上述几点,采用基于热图标签的关键点检测算法精度往往比直接使用像素坐标标签精度高。【注意:并不是说不能直接使用像素坐标作为标签,也有一些工作直接使用像素坐标做标签,并取得了不错的效果】
注意到上述所说的例子中只有一个人,但是我们知道Openpose是用于多人检测的,并且第一步要检测出图片中所有人的关键点位置。对于一张图片中存在多人,就会导致同一种关键点之间存在距离过近或重叠的情况,这又会不会导致我们在构建关键点标签时有所变化呢?其实啊,对于多人情况,如果不同人之间的同一种关键点的热图存在重叠,则会将重叠部分的值相加,示意图如下图所示:
上图展示了右眼这个关键点的情况,其它关键点的情况类似。本小节就为大家介绍到这里啦,更详细的介绍将在下节源码解析篇为大家讲述喔。🎈🎈🎈
连接关系标签构建
如何构建关键点之间的连接关系标签呢?在Openpose中我们叫做PAF(Part Affinity Fields),翻译成中文叫做部分亲和力场,可以理解为PAF是用来描述不同关键点之间的亲和力。🌱🌱🌱那该怎么理解这里的亲和力呢?我们知道,在Openpose中我们首先会检测出图片中所有人的关键点,并且事先定义了不同关键点的连接关系(数据集介绍小节中有连接关系示意图),比如右肩要和右手肘相连,但是如果图像中存在多人,那么就会有多个右肩和右手肘。亲和力可以看成是关键点匹配的度量标准,即属于同一个人的右肩和右手肘亲和力大,不同人的右肩和右手肘亲和力小。为了有助于大家理解,作图如下:【注意:我这里使用的是卡通图像,是为了使展示效果简约、清新,实际检测过程中需要真人】
上图检测到了两个右肩和两个右手肘,那么我们应该如何正确的进行匹配呢?我们可以看到左边的小奶龙(卡通人物名称)右肩分别对应两个右手肘,关键点连接线分别为1和2,那么到底该选择哪条线呢,这就要用到RAF了,其表示两个关键点之间的亲和力,上图线段越粗,表示亲和力越大,显然线段1较粗,亲和力较大,表示左边奶龙应该选择线段1作为关键点匹配标准;同理可知,右边的小奶龙应该选择线段3作为关键点匹配标准。
上文已经为大家展示了RAF的作用,就是用来选择合适的关键点进行匹配的。🥗🥗🥗但是又该如何实现RAF呢,RAF的标签又应该如何构建呢?首先我们需要知道在上小节所说的关键点热图标签构建是针对关键点所说的,那么RAF是针对什么的呢?其实啊,他是针对骨骼而言的,骨骼就是我们之间所说的连接关系,可以参考数据集介绍小节的内容。在Openpose中,一共定义了19种骨骼,不信的话大家可以去数数喔。🍚🍚🍚那么我们就需要对这19种骨骼构建相应的标签,拿下图中小奶龙的右肩和右手肘来叙述标签是如何构建的:
AB表示从右肩连接到右手肘的骨骼,我们可以发现,其是一个向量,即假设A点坐标为( <math xmlns="http://www.w3.org/1998/Math/MathML"> x 1 x_1 </math>x1, <math xmlns="http://www.w3.org/1998/Math/MathML"> y 1 y_1 </math>y1),B点的坐标为( <math xmlns="http://www.w3.org/1998/Math/MathML"> x 2 x_2 </math>x2, <math xmlns="http://www.w3.org/1998/Math/MathML"> y 2 y_2 </math>y2),则向量AB为( <math xmlns="http://www.w3.org/1998/Math/MathML"> x 2 − x 1 x_2-x_1 </math>x2−x1, <math xmlns="http://www.w3.org/1998/Math/MathML"> y 2 − y 1 y_2-y_1 </math>y2−y1)。为什么要介绍这个向量AB呢,因为我们的标签就是依据这个向量来构建的。我们知道,向量既有大小,又有方向,但是大家可以思考一下,向量大小信息对我们是否有用,其实大小信息作用相对很小,一方面因为骨骼长度往往和图片尺寸和人物视角的远近关系很大,另一方面对于一些高度重叠的人物,很难通过距离来判断关键点是不是属于同一个人,因此我们会去除向量大小的影响,只通过向量方向来构建标签,即使用单位向量。用AB向量除以AB的模长,得到单位向量 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v, <math xmlns="http://www.w3.org/1998/Math/MathML"> v = A B ∣ ∣ A B ∣ ∣ v= \frac{AB}{||AB||} </math>v=∣∣AB∣∣AB。然后我们会规定如果一点P在AB某个矩形范围内,则P的方向和v一致,即和AB一致,否则P为0向量。如下图所示:
这样最终的标签会是这样的,如下:
上图展示的标签在某个矩形的范围内的点都是和AB方向一致的,不在矩形范围内的点都是0向量。W和H是矩形的宽和高,分别对应论文中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ l σ_l </math>σl和 <math xmlns="http://www.w3.org/1998/Math/MathML"> l c , k l_{c,k} </math>lc,k。上述内容对应Openpose论文中的如下图内容,表述方式不一致,但表达的意思相同:
和关键点热图标签构建类似,在连接关系标签构建的过程中也会存在多人交叉的情况,比如扳手腕,如下图所示:
可以看到,相交区域的值会等于两个区域值相加再除2,这就得到最终的标签。关于此,论文中也有相关表述,如下图所示:
Openpose网络模型
其实Openpose的模型也是比较简单啦,我们来看论文中的示意图:
可以看到,Openpose模型首先是由一个个重复的Stage构成的,然后每个Stage有两个分支,分别为Branch 1和Branch 2。
首先,对于一张图片,会先进行一系列卷积操作,得到特征图F,然后将特征图F分别送入Branch 1和Branch 2分支。
先来介绍Branch 1 分支,他的作用是输出与关键点热图标签大小相同的特征图,然后与标签计算损失。这样的话我们就来看看关键点热图标签的尺寸。我们知道,在关键点热图标签中,我们需要对18种关键点进行标签构建,因此我们的特征图尺寸应该为 <math xmlns="http://www.w3.org/1998/Math/MathML"> h ′ × w ′ × 18 h'×w'×18 </math>h′×w′×18,这18个通道的每个通道都对应某种关键点信息。但是Openpose中增加了额外背景信息,即将添加了一个通道并设置成背景,这样有利于网络的学习。因此最终Branch 1 分支的特征图大小为 <math xmlns="http://www.w3.org/1998/Math/MathML"> h ′ × w ′ × 19 h'×w'×19 </math>h′×w′×19。🌼🌼🌼
再来介绍Branch 2分支,他的作用是输出与PAF标签大小相同的特征图,然后与标签计算损失。同样的,我们来看看PAF标签的尺寸。我们知道,在PAF标签中,我们需要对19个骨骼构建一个亲和力场,这是一个向量,因此每个都需要x,y两个坐标,即一共有19×2=38个数据。因此我们Branch 2分支的特征图尺寸应该为 <math xmlns="http://www.w3.org/1998/Math/MathML"> h ′ × w ′ × 38 h'×w'×38 </math>h′×w′×38。🌼🌼🌼
得到Branch 1和Branch 2的输出后,Openpose会将Branch 1、Branch 2的输出和原始特征图F三者相加得到新的特征图 <math xmlns="http://www.w3.org/1998/Math/MathML"> F ′ F' </math>F′,然后 <math xmlns="http://www.w3.org/1998/Math/MathML"> F ′ F' </math>F′会按照相同的操作送入Stage 2中,依此类推。🍖🍖🍖
Openpose检测效果
上文已经对Openpose的原理进行了详细的介绍,这节为大家展示一下Openpose的检测效果,如下图所示,是不是还不错腻。🍄🍄🍄
小结
这节Openpose原理详解篇就为大家介绍到这里啦,如果有疑问欢迎评论区讨论交流,或者去下节源码解析篇看看能不能寻得答案,拜拜啦,我们下期间。🌞🌞🌞
如若文章对你有所帮助,那就🛴🛴🛴