目录

1.整体架构:

2.代码梳理:

3.模块详解:

1)ResNet模块【感觉不用详解大家都清楚...跳过】

2)关键的特征融合部分:HighResolutionModule

函数一和二:

函数三:_make_fuse_layers--融合模块

最后的forward部分:

3.关键点预测模块:PoseHighResolutionNet

1)函数一:_make_transition_layer

2)函数三:_make_stage

最后是forward部分:


前提要做coco关键点比赛,第一次接触到关键点的论文,没有跟其他论文对比分析之类的,单纯了解学习网络架构原理

【Deep High-Resolution Representation Learning for Human Pose Estimation】

github:https://github.com/leoxiaobin/deep-high-resolution-net.pytorch

论文:https://arxiv.org/abs/1902.09212

参考文献:https://segmentfault.com/a/1190000019167646

1.整体架构:

如下图所示经过了前期基本的特征提取网络,就进入关键的特征融合模块

从forward部分可以看到,特征融合模块可以分为两个部分,第一部分transition层生成要进行特征融合的特征层;第二部分stage层,得到特征融合后的输出。

    def forward(self, x):"""基础提取特征的前期卷积网络"""x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.conv2(x)x = self.bn2(x)x = self.relu(x)x = self.layer1(x)"""关键部分:涉及到transition层和stage层,代码中一共有3个stage,论文图只给出了2个stage的示例"""# stage2,从2开始x_list = []for i in range(self.stage2_cfg['NUM_BRANCHES']):if self.transition1[i] is not None:x_list.append(self.transition1[i](x))else:x_list.append(x)y_list = self.stage2(x_list)# stage3x_list = []for i in range(self.stage3_cfg['NUM_BRANCHES']):if self.transition2[i] is not None:x_list.append(self.transition2[i](y_list[-1]))else:x_list.append(y_list[i])y_list = self.stage3(x_list)# stage4x_list = []for i in range(self.stage4_cfg['NUM_BRANCHES']):if self.transition3[i] is not None:x_list.append(self.transition3[i](y_list[-1]))else:x_list.append(y_list[i])y_list = self.stage4(x_list)x = self.final_layer(y_list[0])return x

2.代码梳理:

/lib/models/pose_hrnet.py:完成功能构建hrnet网络架构包含
1.基础框架:
1)resnet网络的两种形式:class BasicBlock(nn.Module)class Bottleneck(nn.Module)
2)关键部分:高分辨率模块:class HighResolutionModule(nn.Module)该模块的重要函数:def _make_one_branchdef _make_branchesdef _make_fuse_layers
2.关键点预测模块【完整的网络】: class PoseHighResolutionNet(nn.Module)该模块的重要函数:def _make_transition_layerdef _make_layerdef _make_stage

3.模块详解:

1)ResNet模块【感觉不用详解大家都清楚...跳过】

2)关键的特征融合部分:HighResolutionModule

函数一和二:

_make_one_branch:生成一个分支;def _make_branches:利用for循环函数,使用_make_one_branch函数生成所有的分支,如下图所示,框出的两个分支就是stage2需要的生成分支部分。①是经过transition层的输入【后面讲】,②是生成的分支

  • (1) 首先判断是否降维或者输入输出的通道(num_inchannels[branch_index]和 num_channels[branch_index] * block.expansion(通道扩张率))是否一致,不一致使用1z1卷积进行维度升/降,后接BN,不使用ReLU;
  • (2) 顺序搭建num_blocks[branch_index]个block,第一个block需要考虑是否降维的情况,所以单独拿出来,后面1 到 num_blocks[branch_index]个block完全一致,使用循环搭建就行。此时注意在执行完第一个block后将num_inchannels[branch_index重新赋值为 num_channels[branch_index] * block.expansion

def _make_one_branch(self, branch_index, block, num_blocks, num_channels,stride=1):downsample = None"""如果输入输出的维度不一致的话,downsample就重新构建"""if stride != 1 or \self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion:downsample = nn.Sequential(nn.Conv2d(self.num_inchannels[branch_index],num_channels[branch_index] * block.expansion,kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(num_channels[branch_index] * block.expansion,momentum=BN_MOMENTUM),)layers = []"""分支的第一个层,的输入输出维度是不同的所以单独构建"""layers.append(block(self.num_inchannels[branch_index],num_channels[branch_index],stride,downsample))self.num_inchannels[branch_index] = \num_channels[branch_index] * block.expansionfor i in range(1, num_blocks[branch_index]):layers.append(block(self.num_inchannels[branch_index],num_channels[branch_index]))return nn.Sequential(*layers)

函数三:_make_fuse_layers--融合模块

  • 如果分支数等于1,返回None,说明不需要使用融合模块;
  • 双层循环:for i in range(num_branches if self.multi_scale_output else 1):的作用是,如果需要产生多分辨率的结果,就双层循环num_branches 次,如果只需要产生最高分辨率的表示,就将i确定为0。

  def _make_fuse_layers(self):"""特征融合模块,判断特征层是不是需要上采样或者下采样"""if self.num_branches == 1:return Nonenum_branches = self.num_branchesnum_inchannels = self.num_inchannelsfuse_layers = []"""两个for循环,比对分支ind,决定采样方式"""for i in range(num_branches if self.multi_scale_output else 1):fuse_layer = []for j in range(num_branches):"""如果当分支j大于分支i,则需要上采样""""""先使用1x1卷积将j分支的通道数变得和i分支一致,进而跟着BN,然后依据上采样因子将j分支分辨率上采样到和i分支分辨率相同,此处使用最近邻插值"""if j > i:fuse_layer.append(nn.Sequential(nn.Conv2d(num_inchannels[j],num_inchannels[i],1, 1, 0, bias=False),nn.BatchNorm2d(num_inchannels[i]),nn.Upsample(scale_factor=2**(j-i), mode='nearest')))"""如果当分支j等于分支i,则不做任何操作"""elif j == i:fuse_layer.append(None)"""如果当分支j小于分支i,则需要下采样,这里直接采用3×3卷积做下采样"""else:"""里面包含了一个双层循环,要根据下采样的尺度决定循环的次数:当i-j > 1时,两个分支的分辨率差了不止二倍,此时还是两倍两倍往上采样,例如i-j = 2时,j分支的分辨率比i分支大4倍,就需要上采样两次,循环次数就是2;"""conv3x3s = []for k in range(i-j):"""无论经过多少次下采样,最后一次是不使用ReLU的"""if k == i - j - 1:num_outchannels_conv3x3 = num_inchannels[i]conv3x3s.append(nn.Sequential(nn.Conv2d(num_inchannels[j],num_outchannels_conv3x3,3, 2, 1, bias=False),nn.BatchNorm2d(num_outchannels_conv3x3)))else:num_outchannels_conv3x3 = num_inchannels[j]conv3x3s.append(nn.Sequential(nn.Conv2d(num_inchannels[j],num_outchannels_conv3x3,3, 2, 1, bias=False),nn.BatchNorm2d(num_outchannels_conv3x3),nn.ReLU(True)))fuse_layer.append(nn.Sequential(*conv3x3s))fuse_layers.append(nn.ModuleList(fuse_layer))
"""最后返回的是一个存储了每个分支对应的融合模块的二维数组,比如说两个分支中,对于分支1,fuse_layers[0][0]=None,fuse_layers[0][1]=上采样的操作"""
return nn.ModuleList(fuse_layers)

最后的forward部分:

  def forward(self, x):"""x表示每个分支输入的特征,如果有两个分支,则x就是一个二维数组,x[0]和x[1]就是两个输入分支的特征""""""如果只有一个分支就直接返回,不做任何融合"""if self.num_branches == 1:return [self.branches[0](x[0])]"""有多个分支的时候,对每个分支都先用_make_branch函数生成主特征网络,再将特定的网络特征进行融合"""for i in range(self.num_branches):x[i] = self.branches[i](x[i])x_fuse = []"""这里是利用融合模块进行特征融合"""for i in range(len(self.fuse_layers)):"""对每个分支用_make_fuse_layer生成要进行融合操作的层,函数三的输出上面有说"""y = x[0] if i == 0 else self.fuse_layers[i][0](x[0])for j in range(1, self.num_branches):"""进行特征融合,举个例子,运行到分支一的时候,self.fuse_layers[i][0](x[0]先生成对于分支一的融合操作,for循环得到每个分支对于分支一的采样结果【当i=j,就是分支一本身不进行任何操作直接cat,i>j的时候就是分支2对于分支一来说要进行上采样,然后cat得到结果】,并cat得到最后的输出"""if i == j:y = y + x[j]else:y = y + self.fuse_layers[i][j](x[j])x_fuse.append(self.relu(y))"""输出的是最后融合的特征【例如两个分支的时候,输出分支一+分支二的上采样和分支一的下采样+分支二】"""return x_fuse

3.关键点预测模块:PoseHighResolutionNet

1)函数一:_make_transition_layer

目的是生成要进行融合的特征,就是刚刚讲过的特征融合模块的输入特征,这里不太好解释,看图就很明白了。

框出的部分是transition1-transition2

  def _make_transition_layer(self, num_channels_pre_layer, num_channels_cur_layer):num_branches_cur = len(num_channels_cur_layer)num_branches_pre = len(num_channels_pre_layer)transition_layers = []for i in range(num_branches_cur):if i < num_branches_pre:if num_channels_cur_layer[i] != num_channels_pre_layer[i]:transition_layers.append(nn.Sequential(nn.Conv2d(num_channels_pre_layer[i],num_channels_cur_layer[i],3, 1, 1, bias=False),nn.BatchNorm2d(num_channels_cur_layer[i]),nn.ReLU(inplace=True)))else:transition_layers.append(None)else:conv3x3s = []for j in range(i+1-num_branches_pre):inchannels = num_channels_pre_layer[-1]outchannels = num_channels_cur_layer[i] \if j == i-num_branches_pre else inchannelsconv3x3s.append(nn.Sequential(nn.Conv2d(inchannels, outchannels, 3, 2, 1, bias=False),nn.BatchNorm2d(outchannels),nn.ReLU(inplace=True)))transition_layers.append(nn.Sequential(*conv3x3s))return nn.ModuleList(transition_layers)

2)函数三:_make_stage

因为函数二就是很普通的_make_layer函数,感觉不用解释哈

函数三的目的就是按照.yaml文件,利用刚刚讲过的HighResolutionModule类,生成融合模块;图里是给出了两个stage的示例,代码里给的是3个stage【stage2-stage4】

可以看到不是每个分支的每个输出都做了融合,而是在②部分做特征融合

   def _make_stage(self, layer_config, num_inchannels,multi_scale_output=True):num_modules = layer_config['NUM_MODULES']num_branches = layer_config['NUM_BRANCHES']num_blocks = layer_config['NUM_BLOCKS']num_channels = layer_config['NUM_CHANNELS']block = blocks_dict[layer_config['BLOCK']]fuse_method = layer_config['FUSE_METHOD']"""按照给定的分支数,每个分支有多少block数,每个分支的输入输出通道数按上述模块进行特征融合"""modules = []for i in range(num_modules):# multi_scale_output is only used last module"""需要注意的是特征融合的操作不是出现在每个分支的每个block的输出特征中,而是在每个分支的最后block的输出特征之间进行特征融合,看图就很容易明白了"""if not multi_scale_output and i == num_modules - 1:reset_multi_scale_output = Falseelse:reset_multi_scale_output = Truemodules.append(HighResolutionModule(num_branches,block,num_blocks,num_inchannels,num_channels,fuse_method,reset_multi_scale_output))num_inchannels = modules[-1].get_num_inchannels()return nn.Sequential(*modules), num_inchannels

最后是forward部分:

    def forward(self, x):"""最初的数据特征提取基础部分"""x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.conv2(x)x = self.bn2(x)x = self.relu(x)x = self.layer1(x)"""开始stage2的时候,第一次特征融合,先使用transition层得到特征融合部分的输入特征,就是将原有的一个分支,变为两个分支啦"""x_list = []for i in range(self.stage2_cfg['NUM_BRANCHES']):if self.transition1[i] is not None:x_list.append(self.transition1[i](x))else:x_list.append(x)"""输入transition层得到的特征,进行特征融合得到特征融合后的输出,输入给第二个transition层得到stage3的输入特征,后面就类推啦"""y_list = self.stage2(x_list)"""stage3部分"""x_list = []for i in range(self.stage3_cfg['NUM_BRANCHES']):if self.transition2[i] is not None:x_list.append(self.transition2[i](y_list[-1]))else:x_list.append(y_list[i])y_list = self.stage3(x_list)"""stage4部分"""x_list = []for i in range(self.stage4_cfg['NUM_BRANCHES']):if self.transition3[i] is not None:x_list.append(self.transition3[i](y_list[-1]))else:x_list.append(y_list[i])y_list = self.stage4(x_list)"""这里只要stage4部分输出特征的第一个"""x = self.final_layer(y_list[0])return x

差不多完全能够看懂啦,总的来说这篇论文很好的使用了并行的网络结构,将不同分辨率的特征进行融合,获得了更好的特征信息,更有利于关键点的检测。

【代码里有一点就是相同分辨率的特征,通道数也是相同的昂,我没弄明白为什么一定是相同的】

【论文阅读笔记】HRNet--从代码来看论文相关推荐

  1. [论文阅读笔记36]CASREL代码运行记录

    <[论文阅读笔记33]CASREL:基于标注与bert的实体与关系抽取>https://blog.csdn.net/ld326/article/details/116465089 总的来说 ...

  2. 论文阅读笔记|2023 AAAI 多模态论文研读

    本篇博客记录 AAAI 2023 论文合集中与本人研究方向相关的多模态论文泛读笔记 BridgeTower: Building Bridges Between Encodersin Vision-La ...

  3. [论文阅读] (23)恶意代码作者溯源(去匿名化)经典论文阅读:二进制和源代码对比

    <娜璋带你读论文>系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢.由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正,非常欢迎大家给我留言评论,学 ...

  4. 2019 sample-free(样本不平衡)目标检测论文阅读笔记

    点击我爱计算机视觉标星,更快获取CVML新技术 本文转载自知乎,已获作者同意转载,请勿二次转载 (原文地址:https://zhuanlan.zhihu.com/p/100052168) 背景 < ...

  5. 论文阅读笔记——VulDeePecker: A Deep Learning-Based System for Vulnerability Detection

    本论文相关内容 论文下载地址--Engineering Village 论文中文翻译--VulDeePecker: A Deep Learning-Based System for Vulnerabi ...

  6. 论文阅读笔记:《Hyperspectral image classification via a random patches network》(ISPRSjprs2018)

    论文阅读笔记:<Hyperspectral image classification via a random patches network>(ISPRSjprs2018) 论文下载地址 ...

  7. Finging tiny faces论文阅读笔记

    <Finding Tiny Faces>论文阅读笔记 基础知识 CNN(卷积神经网络) Resnet(深度残差学习网络) NMS(非极大值抑制) 论文翻译(粗翻) 摘要 介绍 Multi- ...

  8. Transfiguring Portraits论文阅读笔记

    Transfiguring Portraits论文阅读笔记 图1:我们系统的目标是让人们想象和探索在不同的国家,时代,发型,头发的颜色,年龄以及可以在图片搜索引擎中查询的其他内容的样子.上面的示例显示 ...

  9. 论文阅读笔记——基于CNN-GAP可解释性模型的软件源码漏洞检测方法

    本论文相关内容 论文下载地址--Engineering Village 论文阅读笔记--基于CNN-GAP可解释性模型的软件源码漏洞检测方法 文章目录 本论文相关内容 前言 基于CNN-GAP可解释性 ...

最新文章

  1. 简述Linux C下线程池的使用
  2. Linux第三方软件仓库
  3. 中如何刷新当前路由_企业装修和家庭改造中,路由器及接收器如何安放
  4. php判断百度ua展示不同页面,PHP 如何根据UA展示不同的前端模板
  5. vivox6Android版本,vivo x6有几个版本?vivo x6各版本区别对比评测
  6. 最新Activity与Fragment完全理解
  7. Express框架学习笔记-静态资源的处理
  8. AtCoder Beginner Contest 236 题解
  9. inputBox 与 Application.inputBox 的用法与区别。
  10. 微软商店上架WindowsOffice破解工具,并获5星好评?
  11. 网络编程 2 套接字socket
  12. Unity Shader 学习记录(3) —— CG语言和Shader文件
  13. linux下打印图片不显示出来的,为什么打印机打印不了图片_解决打印机打印不了图片的方法-系统城...
  14. 关于纳尔逊的内集理论
  15. VOLTE网络架构、接口与功能实体
  16. JavaWeb开发:历史变更记录(基于SSM框架)
  17. 小程序发布之后无法生成海报问题
  18. docx库段落 python_Python-docx添加段落
  19. 网络安全法剑指“黑帽黑客”
  20. Python判断经纬度点是否在城市(以广州为例)

热门文章

  1. python的有符号数和无符号数之间的转换
  2. 特定功能微生物多样性分析
  3. 想学IT的必看!如何化身BAT面试收割机?终局之战
  4. 爬取重庆交通大学新闻网站信息通知(爬虫)
  5. 【转】SCI投稿经验
  6. skynet入门笔记
  7. 解答篇:聊天表情发送功能实现
  8. TCP相关技术:重发机制
  9. 搭建Eclipse C/C++开发环境
  10. 预约美发平台开发,美容美发店老板可以借鉴上门推拿模式