Paperreading之五 Stacked Hourglass Networks(SHN)和源码阅读(PyTorch版本)
1.前言
这篇文章是ECCV2016的论文,Jia Deng组的工作,是top-down算法,非常经典,当时也是在各种公开数据集上霸榜。在FLIC和MPII上都是第一名(在当时),是sota算法。现在很多关于姿态估计的论文都有参考SHN或者会拿他作对比,可以说是比较典型的姿态估计算法。
2. 网络结构
SHN网络名字起的很不错,级联的沙漏网络,顾名思义,沙漏网络就表示该网络具有高度对称性,多个沙漏网络进行级联,其实不级联也是可以检测的,只是检测效果会差一些,作者认为人体关节点之间有较强的相关性,前面沙漏检测出的关键点对后面的检测有帮助,所以前面的输出可以作为后面的输入的一部分,见下图的虚线部分,这个后面再讨论。
级联的沙漏网络
2.1单个沙漏网络
单个沙漏网络如上图所示,这是一个4阶版本的沙漏网络,表示有四次下采样和四次下采样。方块大小表示feature maps大小,方块变小方式下采样,方块变大是上采样,加号表示按元素相加。其他全部都是残差模块,上方的连线方式也是一些残差模块,但是没有改变feature maps的大小,只是改变通道数,变成与下面相同,然后才可以按元素相加。
看一下更加具体的版本
该图来自https://blog.csdn.net/shenxiaolu1984/article/details/51428392。浅绿色部分是一些残差模块。看上去很明朗,就是一些残差模块,先下采样然后上采样,这样的网络结构提取特征很充分,在不同的分辨率有进行卷积,然后还有特征融合。但是也有一些弊端,不能使用pretrained model,因为它不像cpn那样,GlobalNet是resnet50或者resnet101,可以直接使用在ImageNet上预训练的模型进行初始化。没有预训练模型用来初始化,一般需要训练更久然后效果会更差一些,但是没有预训练的情况下,当数据很充分,训练也很充分,合理使用BN或者GN,炼丹能力较好的情况下,是可以达到预训练的效果(Kaiming He的最新论文的结论,Rethinking)。
2.2看一下Pytorch版本实现
- class HourglassNet(nn.Module):
- '''Hourglass model from Newell et al ECCV 2016'''
- def __init__(self, block, num_stacks=2, num_blocks=4, num_classes=16):
- """
- 参数解释
- :param block: hg块元素
- :param num_stacks: 有几个hg
- :param num_blocks: 在两个hg之间有几个block块
- :param num_classes: keypoint个数,也就是最后的heatmap个数
- """
- super(HourglassNet, self).__init__()
- self.inplanes = 64
- self.num_feats = 128
- self.num_stacks = num_stacks
- self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
- bias=True) # 第一次下采样
- self.bn1 = nn.BatchNorm2d(self.inplanes)
- self.relu = nn.ReLU(inplace=True)
- self.layer1 = self._make_residual(block, self.inplanes, 1) #self.planes = 64,有downsample(只是改变channel数)
- self.layer2 = self._make_residual(block, self.inplanes, 1) #有downsample(只是改变channel数)
- # 这一次的bottleneck没有downsample,因为self.planes == planes(self.num_feats=128)*2 = 256
- self.layer3 = self._make_residual(block, self.num_feats, 1)
- self.maxpool = nn.MaxPool2d(2, stride=2) #第二次下采样
- # build hourglass modules
- ch = self.num_feats*block.expansion #128*2=256
- hg, res, fc, score, fc_, score_ = [], [], [], [], [], []
- for i in range(num_stacks):
- hg.append(Hourglass(block, num_blocks, self.num_feats, 4)) #block, num_blocks, planes, depth=4
- res.append(self._make_residual(block, self.num_feats, num_blocks))
- fc.append(self._make_fc(ch, ch))
- score.append(nn.Conv2d(ch, num_classes, kernel_size=1, bias=True))
- if i < num_stacks-1:
- fc_.append(nn.Conv2d(ch, ch, kernel_size=1, bias=True))
- score_.append(nn.Conv2d(num_classes, ch, kernel_size=1, bias=True))
- self.hg = nn.ModuleList(hg)
- self.res = nn.ModuleList(res)
- self.fc = nn.ModuleList(fc)
- self.score = nn.ModuleList(score)
- self.fc_ = nn.ModuleList(fc_)
- self.score_ = nn.ModuleList(score_)
- def _make_residual(self, block, planes, blocks, stride=1): #planes = 64,blocks=4
- downsample = None
- if stride != 1 or self.inplanes != planes * block.expansion:
- # 这里的downsample只有改变通道数的功能,并没有下采样的功能,因为调用时stride固定为1
- downsample = nn.Sequential(
- nn.Conv2d(self.inplanes, planes * block.expansion,
- kernel_size=1, stride=stride, bias=True),
- )
- layers = []
- # 只在每个block的第一个bottleneck做downsample,因为channel数不相同
- layers.append(block(self.inplanes, planes, stride, downsample))
- self.inplanes = planes * block.expansion #self.planes是改变的,从最开始的64,128,256
- for i in range(1, blocks): #因为blocks=1 ,后面都不会执行
- layers.append(block(self.inplanes, planes))
- return nn.Sequential(*layers)
- def _make_fc(self, inplanes, outplanes):
- bn = nn.BatchNorm2d(inplanes)
- conv = nn.Conv2d(inplanes, outplanes, kernel_size=1, bias=True)
- return nn.Sequential(
- conv,
- bn,
- self.relu,
- )
- def forward(self, x):
- out = []
- x = self.conv1(x) #下采样
- x = self.bn1(x)
- x = self.relu(x)
- x = self.layer1(x)
- x = self.maxpool(x) #下采样
- x = self.layer2(x)
- x = self.layer3(x)
- for i in range(self.num_stacks):
- y = self.hg[i](x)
- y = self.res[i](y)
- y = self.fc[i](y)
- score = self.score[i](y)
- out.append(score)
- if i < self.num_stacks-1:
- fc_ = self.fc_[i](y)
- score_ = self.score_[i](score)
- x = x + fc_ + score_
- return out
在上面,Bottleneck是使用expansion=2的版本的残差Bottleneck,通常是是使用4阶版本的沙漏网络,结构就跟上图一样,很多残差模块加下采样和上采样。这个实现使用了递归实现,这么短的代码就实现了那么长的网络结构,PyTorch真香,呵呵~
2.3完整网络结构
看了上面的单个Hourglass结构,下面看下完整的网络结构。很简单,前面加了几层卷积,后面就是Hourglass的级联模式,Hourglass之间的级联稍微有一些特殊处理。
网络的从一个7*7的卷积开始,然后接着3个残差模块,这一共会经过两次下采样,如果输入是256*256的,那么经过这个前端网络处理feature maps变为64*64的尺寸。后面就开始级联多个Hourglass部分接口,只与多少个可能要根据实际情况确定。作者实验试过2,4,8,好像是越多越好,然后越到后面输出预测越准,符合直觉预期,说明经过级联是有效的,前面的输出对后面的训练是有帮助的。
下面是Hourglass之间的连接结构图,有一些特征融合在里面。
图来自https://blog.csdn.net/wangzi371312/article/details/81174452
如上图,N1代表第一个沙漏网络,提取出的混合特征经过1个1x1全卷积网络后,分成上下两个分支,上部分支继续经过1x1卷积后,进入下一个沙漏网络。下部分支先经过1x1卷积后,生成heat map,就是图中蓝色部分.
上图中蓝色方块比其他三个方块要窄一些,这是因为heat map矩阵的depth与训练数据里的节点数一致,比如 [1x64x64x16],其他几个则具有较高的depth,如 [1x64x64x256]
heat_map继续经过1x1卷积,将depth调整到与上部分支一致,如256,最后与上部分支合并,一起作为下一个沙漏网络的输入。
前面提到过,由于人体关节点之间的较强相关性,作者认为前面检测出的heat maps对后面的预测是有帮助的,最初的输入,heatmaps经过1*1卷积调整channels数,以及上一级Hourglass的输出三个做按元素相加,作为下一级Hourglass的输入。
2.4 完整网络结构PyTorch实现
- class HourglassNet(nn.Module):
- '''Hourglass model from Newell et al ECCV 2016'''
- def __init__(self, block, num_stacks=2, num_blocks=4, num_classes=16):
- """
- 参数解释
- :param block: hg块元素
- :param num_stacks: 有几个hg
- :param num_blocks: 在两个hg之间有几个block块
- :param num_classes: keypoint个数,也就是最后的heatmap个数
- """
- super(HourglassNet, self).__init__()
- self.inplanes = 64
- self.num_feats = 128
- self.num_stacks = num_stacks
- self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
- bias=True)
- self.bn1 = nn.BatchNorm2d(self.inplanes)
- self.relu = nn.ReLU(inplace=True)
- self.layer1 = self._make_residual(block, self.inplanes, 1) #self.planes = 64
- self.layer2 = self._make_residual(block, self.inplanes, 1)
- self.layer3 = self._make_residual(block, self.num_feats, 1) #这一次的bottleneck没有downsample,因为self.planes == planes(self.num_feats=128)*2 = 256
- self.maxpool = nn.MaxPool2d(2, stride=2) #TODO 这个maxpool需不需要。论文里是有2次下采样,从256降到64,
- # build hourglass modules
- ch = self.num_feats*block.expansion #128*2=256
- hg, res, fc, score, fc_, score_ = [], [], [], [], [], []
- for i in range(num_stacks):
- hg.append(Hourglass(block, num_blocks, self.num_feats, 4)) #block, num_blocks, planes, depth=4
- res.append(self._make_residual(block, self.num_feats, num_blocks))
- fc.append(self._make_fc(ch, ch))
- score.append(nn.Conv2d(ch, num_classes, kernel_size=1, bias=True))
- if i < num_stacks-1:
- fc_.append(nn.Conv2d(ch, ch, kernel_size=1, bias=True))
- score_.append(nn.Conv2d(num_classes, ch, kernel_size=1, bias=True))
- self.hg = nn.ModuleList(hg)
- self.res = nn.ModuleList(res)
- self.fc = nn.ModuleList(fc)
- self.score = nn.ModuleList(score)
- self.fc_ = nn.ModuleList(fc_)
- self.score_ = nn.ModuleList(score_)
- def _make_residual(self, block, planes, blocks, stride=1): #planes = 64,blocks=4
- downsample = None
- if stride != 1 or self.inplanes != planes * block.expansion:
- downsample = nn.Sequential(
- nn.Conv2d(self.inplanes, planes * block.expansion,
- kernel_size=1, stride=stride, bias=True),
- )
- layers = []
- layers.append(block(self.inplanes, planes, stride, downsample)) #只在每个block的第一个bottleneck做下采样,因为channel数不相同
- self.inplanes = planes * block.expansion #self.planes是改变的,从最开始的64,128,256
- for i in range(1, blocks):
- layers.append(block(self.inplanes, planes))
- return nn.Sequential(*layers)
- def _make_fc(self, inplanes, outplanes):
- bn = nn.BatchNorm2d(inplanes)
- conv = nn.Conv2d(inplanes, outplanes, kernel_size=1, bias=True)
- return nn.Sequential(
- conv,
- bn,
- self.relu,
- )
- def forward(self, x):
- out = []
- x = self.conv1(x)
- x = self.bn1(x)
- x = self.relu(x)
- x = self.layer1(x)
- x = self.maxpool(x)
- x = self.layer2(x)
- x = self.layer3(x)
- for i in range(self.num_stacks):
- y = self.hg[i](x)
- y = self.res[i](y)
- y = self.fc[i](y)
- score = self.score[i](y)
- out.append(score)
- if i < self.num_stacks-1:
- fc_ = self.fc_[i](y)
- score_ = self.score_[i](score)
- x = x + fc_ + score_
- return out
fc和score分别表示hourglass的输出两个支路,score是得到heatmaps,经过的卷积的channel 数好keypoints个数相同。fc_和score_分别表示当后面还需要级联Hourglass时,需要做一些1*1的卷积改变featuremaps的通道数,这样后面才能做按元素相加,然后作为后面的输入。
3. 中继监督
通过端到端地堆叠多个沙漏,我们将网络架构进一步细化,将一个沙漏的输出作为输入提供给下一个沙漏,但是每个Hourglass都会输出heatmaps,然后也会计算loss。这提供了具有用于重复自底向上、自顶向下的推理的机制的网络,允许在整个图像上重新评估初始估计和特征。这种方法的关键是预测我们可以应用损失的中间群体。预测是在通过每个沙漏之后生成的,其中网络有机会在本地和全局上下文中处理特性。随后的沙漏模块允许再次处理这些高级特征,以进一步评估和重新评估高阶空间关系。这与其他姿态估计方法类似,这些姿态估计方法在多个迭代阶段和中间监督下表现出很强的性能。
下面这个图挺有意思的,这个Ablation实验部分。作者实验了不同的级联方式对准确率的影响,和中间Hourglass输出heatmaps的准确率规律,在参数量几乎相同的情况下,每个残差模块有不同的个数,这样网络的总层数几乎相同。可以看到,小的Hourglass多级联几次有利于准确率提升,后面层的输出比前面的输出效果好非常多,在小的Hourglass上看的尤其明显,级联了8次,前面2级的效果很差。
作者还做了一些有趣的实验,loss计算位置,在网络结构相似的情况下,loss影响不是特别大,在每个Hourglass的单独输出上计算loss效果是最好的。
4. 训练设置
5. 结论与结果
- 设计了一个新的单人姿态估计网络Hourglass,效果也是棒棒的,如果用于多人需要单独的行人检测作为前端预处理。
- 中继监督的作用很大,
- 级联的Hourglass效果非常好,当时sota方法
- 但对一些遮挡问题难以处理,这是绝大部分算法的难题
参考文献:
[1] Newell A , Yang K , Deng J . Stacked Hourglass Networks for Human Pose Estimation[J]. 2016.
[2] https://github.com/bearpaw/pytorch-pose
[3]https://blog.csdn.net/wangzi371312/article/details/81174452
[4] https://blog.csdn.net/shenxiaolu1984/article/details/51428392
Paperreading之五 Stacked Hourglass Networks(SHN)和源码阅读(PyTorch版本)相关推荐
- Paperreading之五 Stacked Hourglass Networks(SHN)和源码阅读(PyTorch版本)
1.前言 这篇文章是ECCV2016的论文,Jia Deng组的工作,是top-down算法,非常经典,当时也是在各种公开数据集上霸榜.在FLIC和MPII上都是第一名(在当时),是sota算法.现在 ...
- Stacked Hourglass Networks 人体姿态检测
本文是人体关键点中非常经典的一篇文章:Stacked Hourglass Networks for Human Pose Estimation 论文地址: https://arxiv.org/abs/ ...
- (Stacked Hourglass Networks for Human Pose Estimation)用于人体姿势估计的堆叠沙漏网络
摘要 This work introduces( 提出) a novel(新奇的) convolutional network architecture for the task of human p ...
- Hadoop下载和源码阅读
访问官网:http://hadoop.apache.org/ 点击下载: hadoop-3.1.0.tar.gz 是320M,hadoop-3.1.0-src.tar.gz 是27M -------- ...
- Golang流媒体实战之五:lal推流服务源码阅读
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <Golang流媒体实战>系列的链接 体验 ...
- Stacked Hourglass Networks for Human Pose Estimation
介绍 这是一篇2016年做单人姿态估计的文章 实验用的是MPII sigle 和 FLIC ,指标PCKh 通过堆叠沙漏结构的网络进行人体姿态估计 沙漏结构指通过pooling得到低分辨率的特征,然后 ...
- 论文阅读 Hourglass:Stacked Hourglass Networks for Human Pose Estimation
摘要 本文介绍了一种新的卷积网络结构.为了最好的捕捉与身体相关的各种空间关系,所有尺度的特征都被处理和整合.我们展示了重复的自底向上.自顶向下的处理过程与中间监督结构一起使用是如何有效改善网络性能 ...
- 论文分享 Stacked Hourglass Networks for Human Pose Estimation
Alejandro Newell, Kaiyu Yang, and Jia Deng University of Michigan, Ann Arbor 2016.7 https://github.c ...
- sqlmapapi的基本使用和源码阅读
很多工程师都使用过sqlmap这个工具,那么你知道sqlmapapi是什么吗?顾名思义,就是sqlmap的接口吗? 其实你可以把sqlmapapi理解为云的概念,本质上就是用web包装了一下sqlma ...
最新文章
- 项目性能优化(实现页面静态化1)
- HashMap 的 7 种遍历方式与性能分析!(强烈推荐)
- TCP快速重传为什么是三次冗余ack,这个三次是怎么定下来的?
- 【设计模式】依赖倒转原则
- Android性能优化典范(转)
- h5输出文字write_免费下载:Write是用于手写的文字处理器
- 数据结构与算法专题——第十题 输入法跳不过的坎-伸展树
- poj 3660(floyd 变形)
- Java中使用队列Queue
- java 阻塞 socket_java socket非阻塞I/O
- eol python_乱记 EOL(End of Line)在windows和linux不同导致的Python问题
- CentOS7系列--5.2CentOS7中配置和管理Docker
- android ListView和GridView拖拽移位具体实现及拓展
- Java教程:Java选择结构和循环结构的总结
- linux中vim中文显示乱码
- UE4+Cesium
- 免费域名邮箱申请教程
- sort()基础知识总结+超简短的英文名排序写法
- spring boot 整合 jpa
- 【云原生】K8S master节点更换IP以及master高可用故障模拟测试
热门文章
- DDR3 controller 之储存器介绍
- 23岁美国女网红用AI分身交1000多男友!月入500万美元,谈恋爱按分钟计费
- geetest php,GitHub - lilwil/geetest: Geetest For ThinkPHP5
- 解决UOS家庭版桌面图标消失,文件管理器进不去
- Live800:阿里京东拼多多入局,关于C2M的新故事
- 5设计模式(基于C/C++实现)-王桂林-专题视频课程
- java要基础数学和英语吗,2022最新
- 小程序setData修改数组和对象
- idea字体颜色修改
- 开始使用TeXworks