【第二十六周】:HRNet:用于人体姿态估计的深度高分辨率表征学习

devtools/2025/3/10 10:51:51/

摘要

本篇博客介绍了HRNet(High-Resolution Network),这是一种用于密集预测任务(如人体姿态估计、图像分割)的深度学习模型,其核心思想是通过全程保持高分辨率特征和多分辨率分支动态交互,解决传统网络中因反复下采样导致的细节丢失问题。针对特征提取过程中分辨率降低与空间精度损失的矛盾,HRNet提出并行多分辨率子网络,通过跨分辨率连接(上/下采样后特征叠加)实现多尺度信息融合,使高分辨率特征始终保留细节并融入全局语义。实验表明,HRNet在COCO、MPII等数据集上达到SOTA性能,尤其在关键点检测任务中显著提升了定位精度。然而,其计算复杂度较高,在遮挡场景下的表现仍有改进空间,未来可通过轻量化设计、动态分辨率机制及热图回归替代方案进一步优化。

Abstract

This blog introduces HRNet (High-Resolution Network), a deep learning model designed for dense prediction tasks such as human pose estimation and image segmentation. Its core innovation lies in maintaining high-resolution features throughout the network and enabling dynamic interactions between multi-resolution branches, addressing the loss of fine-grained details caused by repeated downsampling in traditional architectures. To resolve the conflict between resolution reduction and spatial accuracy degradation during feature extraction, HRNet employs parallel multi-resolution subnetworks that fuse multi-scale information via cross-resolution connections (feature aggregation after up/downsampling), ensuring high-resolution features retain local details while integrating global semantics. Experiments demonstrate that HRNet achieves state-of-the-art (SOTA) performance on datasets like COCO and MPII, with significant improvements in localization accuracy for keypoint detection. However, its high computational complexity and limitations in handling occluded scenarios leave room for optimization. Future enhancements may include lightweight designs, dynamic resolution mechanisms, and alternative heatmap regression approaches.


文章信息

Title:Deep High-Resolution Representation Learning for Human Pose Estimation
Author:Ke Sun, Bin Xiao, Dong Liu, Jingdong Wang
Source:https://arxiv.org/abs/1902.09212


引言

人体姿态估计是计算机视觉中一个基本但有挑战性的问题,其目标是对人体关键点进行定位,深度卷积神经网络为此提供了解决方案。
基于深度卷积神经网络的人体姿态估计主要有两种方法:
一种是直接回归关键点的位置,另一种是预测关键点热力图,然后选择热值最高的点作为关键点。
在这里插入图片描述
大多数用于关键点热图估计的卷积神经网络是先基于一个类似图像分类的网络以串联的方式降低分辨率,提取特征,然后再采用线性插值、转置卷积、膨胀卷积等方法来恢复图像的高分辨率,整体来说就是采用一个由高到低,再由低到高的框架。
分辨率由高到低旨在通过卷积获得高级表征(强语义),从低到高是要获得高分辨率的表征。在降低分辨率和恢复高分辨率时可能丢失掉细节信息,导致其预测不够精确。

本论文是采用预测热力图的方法来对单一2D人体姿态进行估计,论文提出一个能在整个过程中保持高分辨率的网络架构,并通过重复的多分辨率融合最终输出高分辨率表征来估计关键点。

方法

HRNet的核心思想是通过并行多分辨率分支多尺度信息融合,始终保持高分辨率特征表示,从而提升空间精度和语义表达能力。

网络结构

在这里插入图片描述
如上图,水平方向表示网络的深度,垂直方向表示特征图的尺度。随着网络的加深,每个阶段会逐步获取更低分辨率的特征图,且每种尺度的特征图在被添加之后就在整个网络中保持分辨率。每个阶段都会进行多分辨率融合以交换不同分辨率特征图的信息。
在这里插入图片描述
上图是关于HRNet-W32的模型结构简图,在论文中除了提出HRNet-W32外还有一个HRNet-W48的版本,两者区别仅仅在每个模块所采用的通道个数不同,网络的整体结构都是一样的。

HRNet首先通过两个卷积核大小为3*3,步幅为2,填充为1的卷积操作(每个卷积层后面都接BN和ReLU),通过这两个卷积后共下采样了四倍。然后通过layer1模块,重复堆叠Bottleneck(残差块),在不改变特征层大小的前提下改变通道数。
larger1:

python"># layer1downsample = nn.Sequential(nn.Conv2d(64, 256, kernel_size=1, stride=1, bias=False),nn.BatchNorm2d(256, momentum=BN_MOMENTUM))self.layer1 = nn.Sequential(Bottleneck(64, 64, downsample=downsample),Bottleneck(256, 64),Bottleneck(256, 64),Bottleneck(256, 64))

其中,Bottleneck类的定义如下:

python">class Bottleneck(nn.Module):expansion = 4def __init__(self, inplanes, planes, stride=1, downsample=None):super(Bottleneck, self).__init__()self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,padding=1, bias=False)self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1,bias=False)self.bn3 = nn.BatchNorm2d(planes * self.expansion,momentum=BN_MOMENTUM)self.relu = nn.ReLU(inplace=True)self.downsample = downsampleself.stride = stridedef forward(self, x):residual = xout = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out = self.relu(out)out = self.conv3(out)out = self.bn3(out)if self.downsample is not None:residual = self.downsample(x)out += residualout = self.relu(out)return out
  • inpianes:输入通道数
  • planes:中间通道数

该模块将通道数为inplanes的特征图变为通道数为planes*expansion的特征图。

接这特征图通过一系列的Transition和Stage结构,每通过一个 Transition 结构都会新增一个尺度分支。比如 Transition1 ,它在layer1的输出基础上通过并行两个卷积核大小为3x3的卷积层得到两个不同的尺度分支,即下采样4倍的尺度以及下采样8倍的尺度。Transition2 中已有的两个尺度分支基础上再新加一个下采样16倍的尺度,注意这里是直接在下采样8倍的尺度基础上通过一个卷积核大小为3x3步距为2的卷积层得到下采样16倍的尺度,而不是像论文的图中是通过融合不同尺度的特征层得到的,许多实现都是直接采用3x3的卷积直接下采样。

每通过一层Transition后要通过若干层的Stage,为了方便说明stage的结构,这里以stage3为例。对于每个尺度的分支,都会先通过四个Basic Block,然后与其他分支的信息进行融合。
在这里插入图片描述
Basic Block的构造如下:

python">class BasicBlock(nn.Module):expansion = 1def __init__(self, inplanes, planes, stride=1, downsample=None):super(BasicBlock, self).__init__()self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)self.relu = nn.ReLU(inplace=True)self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)self.downsample = downsampleself.stride = stridedef forward(self, x):residual = xout = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)if self.downsample is not None:residual = self.downsample(x)out += residualout = self.relu(out)return out

stage中每个分支的输出都是融合了所有分支的信息,比如说对于下采样4倍分支的输出,它是分别将下采样4倍分支的输出(不做任何处理) 、 下采样8倍分支的输出通过Up x2上采样2倍 以及下采样16倍分支的输出通过Up x4上采样4倍进行相加最后通过ReLU得到下采样4倍分支的融合输出,每个分支的输出保持尺寸不变。图中的X4表示需要重复堆叠四次stage块。
stage类的构造如下:

python">class StageModule(nn.Module):def __init__(self, input_branches, output_branches, c):"""构建对应stage,即用来融合不同尺度的实现:param input_branches: 输入的分支数,每个分支对应一种尺度:param output_branches: 输出的分支数:param c: 输入的第一个分支通道数"""super().__init__()self.input_branches = input_branchesself.output_branches = output_branchesself.branches = nn.ModuleList()for i in range(self.input_branches):  # 每个分支上都先通过4个BasicBlockw = c * (2 ** i)  # 对应第i个分支的通道数branch = nn.Sequential(BasicBlock(w, w),BasicBlock(w, w),BasicBlock(w, w),BasicBlock(w, w))self.branches.append(branch)self.fuse_layers = nn.ModuleList()  # 用于融合每个分支上的输出for i in range(self.output_branches):self.fuse_layers.append(nn.ModuleList())for j in range(self.input_branches):if i == j:# 当输入、输出为同一个分支时不做任何处理self.fuse_layers[-1].append(nn.Identity())elif i < j:# 当输入分支j大于输出分支i时(即输入分支下采样率大于输出分支下采样率),# 此时需要对输入分支j进行通道调整以及上采样,方便后续相加self.fuse_layers[-1].append(nn.Sequential(nn.Conv2d(c * (2 ** j), c * (2 ** i), kernel_size=1, stride=1, bias=False),nn.BatchNorm2d(c * (2 ** i), momentum=BN_MOMENTUM),nn.Upsample(scale_factor=2.0 ** (j - i), mode='nearest')))else:  # i > j# 当输入分支j小于输出分支i时(即输入分支下采样率小于输出分支下采样率),# 此时需要对输入分支j进行通道调整以及下采样,方便后续相加# 注意,这里每次下采样2x都是通过一个3x3卷积层实现的,4x就是两个,8x就是三个,总共i-j个ops = []# 前i-j-1个卷积层不用变通道,只进行下采样for k in range(i - j - 1):ops.append(nn.Sequential(nn.Conv2d(c * (2 ** j), c * (2 ** j), kernel_size=3, stride=2, padding=1, bias=False),nn.BatchNorm2d(c * (2 ** j), momentum=BN_MOMENTUM),nn.ReLU(inplace=True)))# 最后一个卷积层不仅要调整通道,还要进行下采样ops.append(nn.Sequential(nn.Conv2d(c * (2 ** j), c * (2 ** i), kernel_size=3, stride=2, padding=1, bias=False),nn.BatchNorm2d(c * (2 ** i), momentum=BN_MOMENTUM)))self.fuse_layers[-1].append(nn.Sequential(*ops))self.relu = nn.ReLU(inplace=True)def forward(self, x):# 每个分支通过对应的blockx = [branch(xi) for branch, xi in zip(self.branches, x)]# 接着融合不同尺寸信息x_fused = []for i in range(len(self.fuse_layers)):x_fused.append(self.relu(sum([self.fuse_layers[i][j](x[j]) for j in range(len(self.branches))])))return x_fused

在这里插入图片描述

  • Up的实现:通过卷积核大小为1*1,步幅为1,填充为0的卷积操作先将通道数变为原来的1/n,然后经过BN层,然后再通过上采样将长宽变为原来的n倍,其中的上采样默认采用 nearest 最邻近插值。
  • Down的实现:通过一系列卷积核大小为 3*3,步幅为 2,填充为 1 的卷积操作, 每个卷积都将长宽缩小两倍,而通道数不变,最后通过 BN 层将通道数变为原来的 n 倍。这就意味着若要将分辨率缩小为原来的 1 2 n \frac {1}{2^n} 2n1,则需要经过 n 层卷积核大小为 3X3,步幅为2,填充为1的卷积层和一个BN层。
    在这里插入图片描述

最后,需要注意的是stage 4的最终输出(最后一块stage 4)只有下采样4倍的分支,即只保留分辨率最高的特征层,然后街上一个卷积核大小为1X1,步幅为1,填充为0的卷积层,卷积核个数为17,这是因为数据集的关键点有17个,最后得到的17个特征图就是分别对应每个关键点的热力图。
HRNet的整体构造如下:

python">class HighResolutionNet(nn.Module):def __init__(self, base_channel: int = 32, num_joints: int = 17):super().__init__()# Stemself.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM)self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM)self.relu = nn.ReLU(inplace=True)# Stage1downsample = nn.Sequential(nn.Conv2d(64, 256, kernel_size=1, stride=1, bias=False),nn.BatchNorm2d(256, momentum=BN_MOMENTUM))self.layer1 = nn.Sequential(Bottleneck(64, 64, downsample=downsample),Bottleneck(256, 64),Bottleneck(256, 64),Bottleneck(256, 64))self.transition1 = nn.ModuleList([nn.Sequential(nn.Conv2d(256, base_channel, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(base_channel, momentum=BN_MOMENTUM),nn.ReLU(inplace=True)),nn.Sequential(nn.Sequential(  # 这里又使用一次Sequential是为了适配原项目中提供的权重nn.Conv2d(256, base_channel * 2, kernel_size=3, stride=2, padding=1, bias=False),nn.BatchNorm2d(base_channel * 2, momentum=BN_MOMENTUM),nn.ReLU(inplace=True)))])# Stage2self.stage2 = nn.Sequential(StageModule(input_branches=2, output_branches=2, c=base_channel))# transition2self.transition2 = nn.ModuleList([nn.Identity(),  # None,  - Used in place of "None" because it is callablenn.Identity(),  # None,  - Used in place of "None" because it is callablenn.Sequential(nn.Sequential(nn.Conv2d(base_channel * 2, base_channel * 4, kernel_size=3, stride=2, padding=1, bias=False),nn.BatchNorm2d(base_channel * 4, momentum=BN_MOMENTUM),nn.ReLU(inplace=True)))])# Stage3self.stage3 = nn.Sequential(StageModule(input_branches=3, output_branches=3, c=base_channel),StageModule(input_branches=3, output_branches=3, c=base_channel),StageModule(input_branches=3, output_branches=3, c=base_channel),StageModule(input_branches=3, output_branches=3, c=base_channel))# transition3self.transition3 = nn.ModuleList([nn.Identity(),  # None,  - Used in place of "None" because it is callablenn.Identity(),  # None,  - Used in place of "None" because it is callablenn.Identity(),  # None,  - Used in place of "None" because it is callablenn.Sequential(nn.Sequential(nn.Conv2d(base_channel * 4, base_channel * 8, kernel_size=3, stride=2, padding=1, bias=False),nn.BatchNorm2d(base_channel * 8, momentum=BN_MOMENTUM),nn.ReLU(inplace=True)))])# Stage4# 注意,最后一个StageModule只输出分辨率最高的特征层self.stage4 = nn.Sequential(StageModule(input_branches=4, output_branches=4, c=base_channel),StageModule(input_branches=4, output_branches=4, c=base_channel),StageModule(input_branches=4, output_branches=1, c=base_channel))# Final layerself.final_layer = nn.Conv2d(base_channel, num_joints, kernel_size=1, stride=1)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)x = [trans(x) for trans in self.transition1]  # Since now, x is a listx = self.stage2(x)x = [self.transition2[0](x[0]),self.transition2[1](x[1]),self.transition2[2](x[-1])]  # New branch derives from the "upper" branch onlyx = self.stage3(x)x = [self.transition3[0](x[0]),self.transition3[1](x[1]),self.transition3[2](x[2]),self.transition3[3](x[-1]),]  # New branch derives from the "upper" branch onlyx = self.stage4(x)x = self.final_layer(x[0])return x

损失的计算

训练采用的损失是均方误差Mean Squared Error。
训练时的标注GT是根据高斯生成的热力图。根据数据集可以得到原图中各关键点的位置,将坐标都除以4(缩放到heatmap尺度)在进行四舍五入得到每个关键点在heatmap尺度的位置,每个关键点对应一张热力图,每张热力图初始为全0,然后以对应关键点的位置为中心应用2D高斯分布,得到GT heatmap。
知道如何计算每个关键点对应的损失后还需要留意一个小细节。代码中在计算总损失时,并不是直接把每个关键点的损失进行相加,而是在相加前对于每个点的损失分别乘上不同的权重。代码中用到的权重如下:

python">"kps": ["nose","left_eye","right_eye","left_ear","right_ear","left_shoulder","right_shoulder","left_elbow","right_elbow","left_wrist","right_wrist","left_hip","right_hip","left_knee","right_knee","left_ankle","right_ankle"]
"kps_weights": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2, 1.5, 1.5, 1.0, 1.0, 1.2, 1.2, 1.5, 1.5]

评价准则

在关键点检测任务重,一般用OKS(Object Keypoint Similarity)来衡量预测的关键点与真实位置的相似程度,其计算如下:
在这里插入图片描述
其中:

  • i i i代表第 i i i个关键点。
  • v i v_i vi表示第 i i i个关键点的可见性,是有GT提供的。为0时表示关键点不在图像中,无法标注,为1时表示关键点在图像之内但不可见,但能猜出大概位置(也是有标注位置的),为2时表示关键点在图像中且可见,有标注。
  • δ ( x ) \delta(x) δ(x):当 x x x为True时函数值为1,反之为0。公式中这个函数可过滤掉没有标注的关键点,值计算有标注的关键点。
  • d i d_i di:第 i i i个关键点的预测与真实位置的欧氏距离。
  • s s s为目标面积的平方根,面积指分割面积,在数据集的标注信息中有提供。
  • k i k_i ki是用来控制关键点类别 i i i的衰减常数。

创新与不足

HRNet通过高分辨率特征保持和多分辨率融合的设计,在保持高效计算的同时,显著提升了姿态估计的精度。

创新

  1. 全程高分辨率特征保持
    传统网络(如ResNet、U-Net)通过串联下采样-上采样结构逐步压缩和恢复分辨率,导致空间细节丢失。而HRNet从输入到输出始终保持高分辨率分支,并通过并行低分辨率分支补充全局语义,避免了信息损失。这种设计显著提升了像素级任务(如人体姿态估计、分割)的精度。
  2. 多分辨率分支动态交互
    HRNet通过并行多分辨率子网络(如1/4、1/8、1/16分辨率)和交换块(Exchange Block)实现跨尺度信息融合。每个阶段的高分辨率特征与低分辨率特征通过上/下采样双向交互,既保留细节又增强语义表达能力,优于传统单向融合。
  3. 重复多尺度融合机制
    不同于仅在网络末端融合多尺度特征,HRNet在每个阶段重复融合不同分辨率特征,使高分辨率特征逐步吸收低分辨率分支的抽象信息。实验表明,融合次数越多,模型性能提升越显著。

不足

  1. 计算成本高
    高分辨率特征保持导致显存占用和计算量较大,且参数量大,在训练和推理时需要大量资源。
  2. 复杂遮挡场景性能受限
    在遮挡严重的场景(如CrowdPose数据集),HRNet依赖热图回归可能无法有效处理关键点模糊问题。

总结

HRNet作为高分辨率特征保持网络的代表,通过多阶段并行分支持续交互的工作机制,在人体姿态估计、图像分割等密集预测任务中实现了像素级精准定位。其核心在于全程保持高分辨率,逐步增加低分辨率分支,每个阶段通过跨分辨率连接(上/下采样后特征叠加)实现多尺度信息融合,最终将各分支特征统一上采样至高分辨率输出,避免了传统网络因反复下采样导致的空间信息丢失。该网络以多分支协同和重复融合为特点,在提升精度的同时面临计算复杂度高、遮挡场景适应性不足的局限,未来研究可探索轻量化设计、动态分辨率机制与热图回归替代方案,以平衡效率与性能的边界。


http://www.ppmy.cn/devtools/165980.html

相关文章

nvm list available为空

nvm list available为空 该问题主要是因为nvm 获取不到node导致&#xff0c;排查网络问题外&#xff0c;可能就是由于nvm环境变量配置问题导致&#xff0c;本次我这个问题就是由于环境变量配置缺少导致的。 第一步&#xff1a;排查并排除了网络问题。 第二步&#xff1a;排查环…

AI学习有感

和前辈聊天&#xff0c;谈到了现在的ai技术&#xff0c;这里对那天的谈话进行总结&#xff1a; AI是无状态的 我们在使用ai时有时候会有一个错觉&#xff0c;认为和ai聊天久了&#xff0c;ai就会像人与人之间交流一样&#xff0c;会保留一种对聊天对象的认知状态&#xff0c;这…

openharmory-鸿蒙生态设备之间文件互传发现、接入认证和文件传输

软件版本 OpenHarmony系统版本基线&#xff1a;基于 OpenHarmony-v5.0.0-Release。 图库应用版本&#xff1a;基于OpenHarmony-v5.0.0-Release。 文件管理器应用版本&#xff1a;基于OpenHarmony-v5.0.0-Release。 7 用户历程图 8 设备发现 8.1 设备交互流程图 8.2 设备发…

腾讯云大模型知识引擎LKE+DeepSeek结合工作流升级智能客服

前言 在上期教程中介绍了&#xff0c;如何通过知识库打造智能客服系统&#xff0c;本期教程将介绍如何结合工作流&#xff0c;打造更具交互性的&#xff0c;能够远程调用API的智能客服系统。 本图文教程的视频版已发布&#xff1a;腾讯云大模型知识引擎LKEDeepSeek结合工作流…

Python 自动化探索性数据分析(EDA)工具

1. Pandas Profiling 功能特点&#xff1a; 自动生成详细的统计报告&#xff0c;包含数据概览、单变量分析、相关性矩阵、缺失值分析等。支持交互式 HTML 报告&#xff0c;可导出为 PDF 或其他格式。适合快速生成数据集的全面摘要。 使用示例&#xff1a;import pandas as pd …

11-Agent中配置自己的插件

目录 关键词 摘要 速览 配置和集成自定义插件 使用AI插件在直播间绘制图像 API接口调用及配置说明 创建和配置API工具以生成图像 编写和配置参数及API调用说明 如何配置和使用API进行HTTP请求 配置和测试API插件的步骤 思维导图 发言总结 要点回顾 如何配置一个专…

箭头函数与普通函数的区别

箭头函数&#xff08;Arrow Function&#xff09;是 ES6 引入的一种新的函数语法&#xff0c;它相比于普通函数&#xff08;Function&#xff09;有一些显著的区别。这些区别不仅体现在语法上&#xff0c;还体现在行为特性上&#xff08;如 this 的绑定、arguments 对象等&…

Linux 内核自定义协议族开发:从 “No buffer space available“ 错误到解决方案

引言 在 Linux 内核网络协议栈开发中,自定义协议族(Address Family, AF)是实现新型通信协议或扩展内核功能的关键步骤。然而,开发者常因对内核地址族管理机制理解不足,遇到如 insmod: No buffer space available 的错误。本文将以实际案例为基础,深入分析错误根源,并提…