关键点检测——HRNet原理详解篇
🍊作者简介:秃头小苏,致力于用最通俗的语言描述问题
🍊专栏推荐:深度学习网络原理与实战
🍊近期目标:写好专栏的每一篇文章
🍊支持小苏:点赞👍🏼、收藏⭐、留言📩
HRNet原理详解篇
写在前面
Hello,大家好,我是小苏👦🏽👦🏽👦🏽
今天我打算来给大家介绍一个新的专题——姿态估计。先让我来搜搜姿态估计,看看百度出来的结果,如下:
看到这些图,我觉得大家应该还是蛮熟悉的。这些都在对人体关键点进行检测,在计算机视觉领域,关键点检测是一个非常常见的任务,那么他和姿态估计有什么差异呢?我谈谈我的理解,它们之间确实是存在一定的区别,我感觉用“使用关键点检测技术来实现姿态估计”这句话来表示它们的关系是比较贴切的。也就是说,关键点检测是一项技术,而姿态估计是一种应用。
那么今天所讲的HRNet其实就是一个实现关键点检测任务的网络,作者是我们的中国人——王井东老师。话不多说,让我们进入到本节的HRNet网络原理的讲解中。🚀🚀🚀
大家阅读此篇博客前强烈建议先了解一下COCO数据集关键点检测标注文件,我已经写了相关博客,点击☞☞☞了解详情。
姿态估计概述
在具体介绍HRNet的网络结构之前,我想先给大家介绍一下姿态估计概述,包括常见方法、数据集和评价指标以及应用场景,为此,我绘制了一个思维导图供大家查看,如下:
【注:上图的一些细节可能看不清楚,需要的可以私信我,发Xmind源文件】
那么我们再来看看本文介绍的HRNet属于上述思维导图的哪种方法,其属于–2D姿态估计–>单人检测–>基于热力图–🌱🌱🌱
整体框架
我们先来看看实现的效果,如下图所示:
当我们将一张图片输入HRNet网络后,会得到一个输出的特征图,然后对输出的特征图做一些后处理,就可以得到在原图上关键点的坐标。
看了上面的图,我想你大概知道HRNet实现了一个什么样的功能了,下面我们将来详细分析一下HRNet的网络结果:
【这个图显示的不是很清楚,大家点击这个链接下载查看:https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Ftutouxiaosu.oss-cn-beijing.aliyuncs.com%2Fimg%2Fimg%2FHRNet.png&pos_id=img-9WM0HieN-1724679979842)】
我们可以大体来看一下这个结果,其实看上去并不是很复杂,主要还是将不同尺寸的特征进行融合,这里就不带大家分析为什么这么设计了。【哈哈哈哈因为我也不知道咋分析🍀🍀🍀大家感兴趣的可以看看关于HRNet对王井东老师的采访,看看当时他的灵感是怎么来的,可以点击☞☞☞前往观看。】
我觉得大家要搞清楚网络结构到底是如何实现的,自己动手调试是必要的。所以我也不会再对这个网络结构做太细致的介绍,只说一些需要注意的点。
-
上图中粉红色的Conv是指一个CBA结构,即卷积、BN和激活函数,橙色的Conv2d表示卷积
-
Layer1就是resnet中的layer1,我们可以看看相关代码,如下:
self.layer1 = nn.Sequential(Bottleneck(64, 64, downsample=downsample),Bottleneck(256, 64),Bottleneck(256, 64),Bottleneck(256, 64))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
-
剩下的就是
transition
和Stage
的结构,这里的代码我觉得写的很巧妙,我就不细说了,大家自己动手去调试调试叭,是很容易的。
可能大家想吐槽,这小节感觉什么也没说,只是放了一个网络结构图,一点解析都没有。确实是这样哈,这是因为我觉得这部分难度不大,大家完全可以自己看明白,更重要的是大家应该更关注王井东老师涉及这个网络的构思,想想他当时是怎么想出这个网络的,关于这点,可以去看前文给出的对王井东老师采访的视频。
原理详解
在上一小节,为大家介绍了HRNet的网络结构。在这一小节中,我想和大家唠唠这个网络的过程及原理。首先对于COCO数据集中的一张尺寸为H×W的3通道图片,我们会对齐进行一系列数据增强手段,如仿射变换、随机水平翻转等等,经过数据增强后,我们会将原来H×W×3的图像resize到256×196×3的大小,之后这个256×196×3的图像就作为网络的输入。
这里的数据增强是理解HRNet的重难点,因为在数据增强的过程中会涉及关键点位置的变换。HRNet中做了HalfBody
、AffineTransform
、RandomHorizontalFlip
等数据增强手段,关于这些我们将在HRNet源码实战篇详细为大家介绍。
除了数据增强外,由于HRNet是基于热力图的关键点检测方法,所以我们需要将关键点映射成热力图,那么其是怎么将关键点映射成热力图的呢,这里我们来结合代码来详细看看这一步骤:
首先,先来看看其__init__函数:
def __init__(self,heatmap_hw: Tuple[int, int] = (256 // 4, 192 // 4),gaussian_sigma: int = 2,keypoints_weights=None):self.heatmap_hw = heatmap_hwself.sigma = gaussian_sigmaself.kernel_radius = self.sigma * 3self.use_kps_weights = False if keypoints_weights is None else Trueself.kps_weights = keypoints_weights# generate gaussian kernel(not normalized)kernel_size = 2 * self.kernel_radius + 1kernel = np.zeros((kernel_size, kernel_size), dtype=np.float32)x_center = y_center = kernel_size // 2for x in range(kernel_size):for y in range(kernel_size):kernel[y, x] = np.exp(-((x - x_center) ** 2 + (y - y_center) ** 2) / (2 * self.sigma ** 2))# print(kernel)self.kernel = kernel
这段主要定义了存储热力图的宽度和高度、高斯标准差和关键点权重等信息,然后生成了一个大小为13*13的高斯核kernel(中间的值大,往四周扩散值越来越小),如下图所示:
接着我们来看__call__函数:
def __call__(self, image, target):kps = target["keypoints"]num_kps = kps.shape[0]kps_weights = np.ones((num_kps,), dtype=np.float32)if "visible" in target:visible = target["visible"]kps_weights = visibleheatmap = np.zeros((num_kps, self.heatmap_hw[0], self.heatmap_hw[1]), dtype=np.float32)heatmap_kps = (kps / 4 + 0.5).astype(np.int) # roundfor kp_id in range(num_kps):v = kps_weights[kp_id]if v < 0.5:# 如果该点的可见度很低,则直接忽略continuex, y = heatmap_kps[kp_id]ul = [x - self.kernel_radius, y - self.kernel_radius] # up-left x,ybr = [x + self.kernel_radius, y + self.kernel_radius] # bottom-right x,y# 如果以xy为中心kernel_radius为半径的辐射范围内与heatmap没交集,则忽略该点(该规则并不严格)if ul[0] > self.heatmap_hw[1] - 1 or \ul[1] > self.heatmap_hw[0] - 1 or \br[0] < 0 or \br[1] < 0:# If not, just return the image as iskps_weights[kp_id] = 0continue# Usable gaussian range# 计算高斯核有效区域(高斯核坐标系)g_x = (max(0, -ul[0]), min(br[0], self.heatmap_hw[1] - 1) - ul[0])g_y = (max(0, -ul[1]), min(br[1], self.heatmap_hw[0] - 1) - ul[1])# image range# 计算heatmap中的有效区域(heatmap坐标系)img_x = (max(0, ul[0]), min(br[0], self.heatmap_hw[1] - 1))img_y = (max(0, ul[1]), min(br[1], self.heatmap_hw[0] - 1))if kps_weights[kp_id] > 0.5:# 将高斯核有效区域复制到heatmap对应区域heatmap[kp_id][img_y[0]:img_y[1] + 1, img_x[0]:img_x[1] + 1] = \self.kernel[g_y[0]:g_y[1] + 1, g_x[0]:g_x[1] + 1]if self.use_kps_weights:kps_weights = np.multiply(kps_weights, self.kps_weights)plot_heatmap(image, heatmap, kps, kps_weights)target["heatmap"] = torch.as_tensor(heatmap, dtype=torch.float32)target["kps_weights"] = torch.as_tensor(kps_weights, dtype=torch.float32)return image, target
我给大家解释一下可能难理解的地方:
heatmap_kps = (kps / 4 + 0.5).astype(np.int)
这句是将关键点的坐标映射到热力图上,因为最终的热力图相较于原图像下采样了4倍,所以要除以4,这里加上0.5是起到一个四舍五入的作用,因为后面要将坐标转为int格式。
ul = [x - self.kernel_radius, y - self.kernel_radius] # up-left x,y
br = [x + self.kernel_radius, y + self.kernel_radius] # bottom-right x,y
这两句是找到某个关键点对应热力图的左上角(ul)和右下角(br)的坐标,kernel_radius是高斯核的半径,如下图所示,hw坐标系表示热力图坐标,中间的⚪表示关键点在热力图上的坐标,坐标为(x,y):
# 如果以xy为中心kernel_radius为半径的辐射范围内与heatmap没交集,则忽略该点(该规则并不严格)
if ul[0] > self.heatmap_hw[1] - 1 or \ul[1] > self.heatmap_hw[0] - 1 or \br[0] < 0 or \br[1] < 0:# If not, just return the image as iskps_weights[kp_id] = 0continue
这句是看看以xy为中心kernel_radius为半径的辐射范围内(就是上图中的正方形区域内)与heatmap(就是上图的hw坐标系,当然其h=64,w=48,并不是无线延长的坐标系)有没有交集,若无交集,则将kps_weights[kp_id]置为0。
# Usable gaussian range
# 计算高斯核有效区域(高斯核坐标系)
g_x = (max(0, -ul[0]), min(br[0], self.heatmap_hw[1] - 1) - ul[0])
g_x = (max(0, -ul[1]), min(br[1], self.heatmap_hw[0] - 1) - ul[1])
# image range
# 计算heatmap中的有效区域(heatmap坐标系)
img_x = (max(0, ul[0]), min(br[0], self.heatmap_hw[1] - 1))
img_y = (max(0, ul[1]), min(br[1], self.heatmap_hw[0] - 1))
这几句分别计算高斯核有效区域和heatmap中的有效区域,为下一步将将高斯核有效区域复制到heatmap对应区域做准备:
if kps_weights[kp_id] > 0.5:# 将高斯核有效区域复制到heatmap对应区域heatmap[kp_id][img_y[0]:img_y[1] + 1, img_x[0]:img_x[1] + 1] = \self.kernel[g_y[0]:g_y[1] + 1, g_x[0]:g_x[1] + 1]
这几句到底实现了什么呢,其实就是把高斯核kernel复制到热力图中,至于复制到什么位置,复制多少,就看g_x、g_x、img_x和img_y了。我调试帮助大家理解一下,比如现在g_x=(0,12)、g_y=(0,12)、img_x=(25,37)和img_y=(12,24)。
g_x[0]:g_x[1]+1=0:12+1、g_y[0]:g_y[1]+1=0:12+1表示复制kernel的x方向(0,12+1)范围内的值和y方向(0,12+1)范围内,你看kernel的shape你会发现,其大小为13*13,那么这个(0,12+1)就是复制整个kernel数组**(这里刚好是整个数组,你调试的话会有不同的结果)**:
那么把这个数组复制到哪里呢,其实就是热力图的对应区域,这是就用到了img_x=(25,37)和img_y=(12,24),将其复制到热力图w方向(25,37+1)和h方向(12,24+1)的位置,如下图所示:
这里展示一下图片和产生热力图的结果,如下图所示:【注:由于不是同一次调试的结果,所以这里的图像和之前的有所差异】
最后我还想说一个小点,就是kps_weights
这个值,表示的是关键点的权重,如果没有指定这个参数,那么其就默认是关键点的可见性,如果指定了这个参数,其会让原来的可见性乘这个指定的参数,在HRNet中,这个kps_weights默认如下:
热力图构建完成后,我们一切的准备工作就做完了,接下来就会将这个256×196×3的图像送入HRNet中,其会得到一个大小为64×48×17的特征图。我们可以看到输出特征图的宽和高相较于输入下采样了4倍,然后这个17表示有17个关键点的特征图,每个特征图的尺寸都为64×48大小的。【注:COCO数据集中标注了17个人体关键点位置,不清楚的可以看看我这篇对COCO数据集关键点检测的分析。】
其实我们对这个64×48×17大小的特征图进行一些后处理操作,就可以得到17个关键点的坐标信息,具体怎么做的,我们来结合代码为大家介绍一下。首先我们要将得到的特征图变成坐标,实现方法如下:
def get_max_preds(batch_heatmaps):"""get predictions from score mapsheatmaps: numpy.ndarray([batch_size, num_joints, height, width])"""assert isinstance(batch_heatmaps, torch.Tensor), 'batch_heatmaps should be torch.Tensor'assert len(batch_heatmaps.shape) == 4, 'batch_images should be 4-ndim'batch_size, num_joints, h, w = batch_heatmaps.shapeheatmaps_reshaped = batch_heatmaps.reshape(batch_size, num_joints, -1)maxvals, idx = torch.max(heatmaps_reshaped, dim=2)maxvals = maxvals.unsqueeze(dim=-1)idx = idx.float()preds = torch.zeros((batch_size, num_joints, 2)).to(batch_heatmaps) preds[:, :, 0] = idx % w # column 对应最大值的x坐标preds[:, :, 1] = torch.floor(idx / w) # row 对应最大值的y坐标pred_mask = torch.gt(maxvals, 0.0).repeat(1, 1, 2).float().to(batch_heatmaps.device)preds *= pred_maskreturn preds, maxvals
这段代码实现了什么呢,我来解释一下,首先会将刚刚(1,17,64,48)的特征图resize到(1,17,3072),即将高度和宽度合并成一维,这个维度表示有17个一维向量(17个表示17个关键点),每个一维向量有3072个值,我们计算出每个一维向量即3072个值中的最大值和最大值对应的索引,然后通过最大值索引来计算关键点的坐标,为了方便大家理解,作图如下:
最后还需要将设置一个模板,过滤掉maxvals小于0的坐标,如下:
pred_mask = torch.gt(maxvals, 0.0).repeat(1, 1, 2).float().to(batch_heatmaps.device)
preds *= pred_mask
这个maxvals其实就是一个置信度分数,这步操作完后,我们就有了关键点在特征图上的坐标和置信度分数了,接下来其实就只要将这个坐标映射到原图上就可以了,如下:
for i in range(coords.shape[0]):preds[i] = affine_points(preds[i], trans[i])
def affine_points(pt, t):ones = np.ones((pt.shape[0], 1), dtype=float)pt = np.concatenate([pt, ones], axis=1).Tnew_pt = np.dot(t, pt)return new_pt.T
这里是通过仿射变换的逆变换将关键点从特征图映射回原图上的,因为我们在图像预处理过程中使用了仿射变换。但是代码中还对刚刚得到的坐标做了后处理,如下:
# post-processing
if post_processing:for n in range(coords.shape[0]):for p in range(coords.shape[1]):hm = batch_heatmaps[n][p]px = int(math.floor(coords[n][p][0] + 0.5))py = int(math.floor(coords[n][p][1] + 0.5))if 1 < px < heatmap_width - 1 and 1 < py < heatmap_height - 1:diff = torch.tensor([hm[py][px + 1] - hm[py][px - 1],hm[py + 1][px] - hm[py - 1][px]]).to(batch_heatmaps.device)coords[n][p] += torch.sign(diff) * .25preds = coords.clone().cpu().numpy()
这段代码主要是想得到更加精确的坐标,画图帮大家理解:
这样有了关键点的坐标,就可以将其映射到原图上,下图展示了映射一个关键点nose的结果,其它的关键点原理相同:
损失计算
HRNet中的损失计算非常简单,使用的是MSE均方误差,关键代码如下:
self.criterion = torch.nn.MSELoss(reduction='none')
loss = self.criterion(logits, heatmaps).mean(dim=[2, 3])
这里criterion
有两个传入的值,一个是logits,一个是heatmaps,这两个都是什么呢,我来解释一下。logits很好理解,其就是网络的输出结果,是一个64×48×17大小的特征图;那么heatmaps是什么呢,我们知道,损失计算肯定是要用到预测值和真实值,logits是网络输出,logits是预测值,那么heatmaps就应该是真实值。但是heatmaps到底是什么呢?我们关键点检测的真实值不是关键的检测的坐标吗【坐标的话应该维度应该是2×17】,怎么会是一个heatmaps?【两个进行MSE损失计算,heatmaps维度应该为64×48×17,和logits一致】
不知道大家能否想到,其实啊,这就是基于热力图(heatmaps)进行关键点检测的关键,如果标签是单纯的坐标,那么其实就是基于回归的方式实现关键点检测。又说回来,HRNet怎么将关键点坐标转换成热力图的呢?其实就是在对图像进行数据增强时使用了transforms.KeypointToHeatMap
,关于此方法在HRNet源码详解篇有详细介绍,大家一定要去看,对你理解HRNet有很大帮助。
评价指标
在关键点检测的任务中,我们一般使用OKS来衡量预测keypoints和真实keypoints的相似程度,它取值在0~1之间,越大表示越相似,其表达式如下:
O K S = ∑ i [ e − d i 2 / 2 s 2 k 1 2 ⋅ δ ( v i > 0 ) ] ∑ i [ δ ( v i > 0 ) ] \mathrm{OKS}=\frac{\sum_{\mathrm{i}}\left[\mathrm{e}^{-\mathrm{d}_{\mathrm{i}}^{2} / 2 \mathrm{~s}^{2} \mathrm{k}_{1}^{2}} \cdot \delta\left(\mathrm{v}_{\mathrm{i}}>0\right)\right]}{\sum_{\mathrm{i}}\left[\delta\left(\mathrm{v}_{\mathrm{i}}>0\right)\right]} OKS=∑i[δ(vi>0)]∑i[e−di2/2 s2k12⋅δ(vi>0)]
看到这个公式你懵了,我也懵了。🥀🥀🥀对相关变量做一定的解释:
- i i i表示第 i i i个关键点
- d i d_i di表示第 i i i个预测关键点和真实关键点的欧式距离
- s表示groundtruth中所占面积的平方根,是可以直接获取的,那么 s 2 s^2 s2即表示面积
- k i k_i ki表示第 i i i个骨骼点的归一化因子,是个常数
- v i v_i vi表示第i个关键点的可见性
- δ ( x ) \delta(x) δ(x)表示当x为True时,值为1,当x为False时,值为0。 δ ( v i > 0 ) \delta(v_i > 0) δ(vi>0)表示当关键点在图像中标注了(v=1或v=2),则为1,没有标注(v=0)则为0
结合COCO中相关的代码来解释一下,主要看看这个公式是不是和代码一致,代码如下:
def computeOks(self, imgId, catId):p = self.params# dimention here should be Nxmgts = self._gts[imgId, catId]dts = self._dts[imgId, catId]inds = np.argsort([-d['score'] for d in dts], kind='mergesort')dts = [dts[i] for i in inds]if len(dts) > p.maxDets[-1]:dts = dts[0:p.maxDets[-1]]# if len(gts) == 0 and len(dts) == 0:if len(gts) == 0 or len(dts) == 0:return []ious = np.zeros((len(dts), len(gts)))sigmas = p.kpt_oks_sigmasvars = (sigmas * 2)**2k = len(sigmas)# compute oks between each detection and ground truth objectfor j, gt in enumerate(gts):# create bounds for ignore regions(double the gt bbox)g = np.array(gt['keypoints'])xg = g[0::3]; yg = g[1::3]; vg = g[2::3]k1 = np.count_nonzero(vg > 0)bb = gt['bbox']x0 = bb[0] - bb[2]; x1 = bb[0] + bb[2] * 2y0 = bb[1] - bb[3]; y1 = bb[1] + bb[3] * 2for i, dt in enumerate(dts):d = np.array(dt['keypoints'])xd = d[0::3]; yd = d[1::3]if k1>0:# measure the per-keypoint distance if keypoints visibledx = xd - xgdy = yd - ygelse:# measure minimum distance to keypoints in (x0,y0) & (x1,y1)z = np.zeros((k))dx = np.max((z, x0-xd),axis=0)+np.max((z, xd-x1),axis=0)dy = np.max((z, y0-yd),axis=0)+np.max((z, yd-y1),axis=0)e = (dx**2 + dy**2) / vars / (gt['area']+np.spacing(1)) / 2if k1 > 0:e=e[vg > 0]ious[i, j] = np.sum(np.exp(-e)) / e.shape[0]return ious
我们主要来看最后几行:
e = (dx**2 + dy**2) / vars / (gt['area']+np.spacing(1)) / 2
if k1 > 0:e=e[vg > 0]
ious[i, j] = np.sum(np.exp(-e)) / e.shape[0]
先来看这句:e = (dx**2 + dy**2) / vars / (gt['area']+np.spacing(1)) / 2
,它就对应公式的 d i 2 / 2 s 2 k 1 2 {{\mathrm{d}_{\mathrm{i}}^{2} / 2 \mathrm{~s}^{2} \mathrm{k}_{1}^{2}}} di2/2 s2k12,其中dx**2 + dy**2
表示预测关键点和真实关键点的欧式距离的平方,即 d i 2 d_i^2 di2。vars为表示的是 k 1 2 k_1^2 k12,是个常数, (gt['area']+np.spacing(1))
表示 s 2 s^2 s2,加上np.spacing(1)
是防止分母为0。
综上, e = d i 2 / 2 s 2 k 1 2 e={{\mathrm{d}_{\mathrm{i}}^{2} / 2 \mathrm{~s}^{2} \mathrm{k}_{1}^{2}}} e=di2/2 s2k12,再来看if k1 > 0: e=e[vg > 0]
表示如果存在可见关键点,就从之前计算的 e
中筛选出可见的关键点对应的值。**【注意一下代码中的K1和公式中 k 1 k_1 k1表示的不是一个,代码中K1表示关键点的个数,即 ∑ i [ δ ( v i > 0 ) ] {\sum_{\mathrm{i}}\left[\delta\left(\mathrm{v}_{\mathrm{i}}>0\right)\right]} ∑i[δ(vi>0)]】**这步对应公式 d i 2 / 2 s 2 k 1 2 ⋅ δ ( v i > 0 ) {{\mathrm{d}_{\mathrm{i}}^{2} / 2 \mathrm{~s}^{2} \mathrm{k}_{1}^{2}}} \cdot \delta (\mathrm{v}_{\mathrm{i}}>0) di2/2 s2k12⋅δ(vi>0)
最后再经过ious[i, j] = np.sum(np.exp(-e)) / e.shape[0]
,这里的e.shape[0]
其实就是K1,表示可见关键点个数,那么经过这步之后,ious[i, j]
的值就表示OKS,即 ∑ i [ e − d i 2 / 2 s 2 k 1 2 ⋅ δ ( v i > 0 ) ] ∑ i [ δ ( v i > 0 ) ] \frac{\sum_{\mathrm{i}}\left[\mathrm{e}^{-\mathrm{d}_{\mathrm{i}}^{2} / 2 \mathrm{~s}^{2} \mathrm{k}_{1}^{2}} \cdot \delta\left(\mathrm{v}_{\mathrm{i}}>0\right)\right]}{\sum_{\mathrm{i}}\left[\delta\left(\mathrm{v}_{\mathrm{i}}>0\right)\right]} ∑i[δ(vi>0)]∑i[e−di2/2 s2k12⋅δ(vi>0)]
小结
这节就为大家介绍到这里啦,我觉得看到这里大家都是懵懵的,没关系,因为HRNet我是准备分三小结来为大家介绍,所以这节内容写的较为简略。在下一节,我将花一万字给大家好好解析HRNet的源码,大家看完所有的内容,再回来消化消化这部分,说不定有意想不到的收获喔。🍊🍊🍊
拜拜啦~~~我们下期见。🥗🥗🥗
参考链接
HRNet论文🍁🍁🍁
HRNet网络简介🍁🍁🍁
相关文章:

关键点检测——HRNet原理详解篇
🍊作者简介:秃头小苏,致力于用最通俗的语言描述问题 🍊专栏推荐:深度学习网络原理与实战 🍊近期目标:写好专栏的每一篇文章 🍊支持小苏:点赞👍🏼、…...

25考研总结
11408确实难,25英一直接单科斩杀😭 对过去这一年多备考的经历进行复盘,以及考试期间出现的问题进行思考。 考408的人,政治英语都不能拖到最后,408会惩罚每一个偷懒的人! 政治 之所以把政治写在最开始&am…...

网络安全态势感知
一、网络安全态势感知(Cyber Situational Awareness)是一种通过收集、处理和分析网络数据来理解当前和预测未来网络安全状态的能力。它的目的是提供实时的、安全的网络全貌,帮助组织理解当前网络中发生的事情,评估风险,…...

在K8S中,节点状态notReady如何排查?
在kubernetes集群中,当一个节点(Node)的状态变为NotReady时,意味着该节点可能无法运行Pod或不能正确相应kubernetes控制平面。排查NotReady节点通常涉及以下步骤: 1. 获取基本信息 使用kubectl命令行工具获取节点状态…...

深度学习在光学成像中是如何发挥作用的?
深度学习在光学成像中的作用主要体现在以下几个方面: 1. **图像重建和去模糊**:深度学习可以通过优化图像重建算法来处理模糊图像或降噪,改善成像质量。这涉及到从低分辨率图像生成高分辨率图像,突破传统光学系统的分辨率限制。 …...

树莓派linux内核源码编译
Raspberry Pi 内核 托管在 GitHub 上;更新滞后于上游 Linux内核,Raspberry Pi 会将 Linux 内核的长期版本整合到 Raspberry Pi 内核中。 1 构建内核 操作系统随附的默认编译器和链接器被配置为构建在该操作系统上运行的可执行文件。原生编译使用这些默…...

本地小主机安装HomeAssistant开源智能家居平台打造个人AI管家
文章目录 前言1. 添加镜像源2. 部署HomeAssistant3. HA系统初始化配置4. HA系统添加智能设备4.1 添加已发现的设备4.2 添加HACS插件安装设备 5. 安装cpolar内网穿透5.1 配置HA公网地址 6. 配置固定公网地址 前言 大家好!今天我要向大家展示如何将一台迷你的香橙派Z…...

SpringBoot返回文件让前端下载的几种方式
01 背景 在后端开发中,通常会有文件下载的需求,常用的解决方案有两种: 不通过后端应用,直接使用nginx直接转发文件地址下载(适用于一些公开的文件,因为这里不需要授权)通过后端进行下载&#…...

人工智能及深度学习的一些题目
1、一个含有2个隐藏层的多层感知机(MLP),神经元个数都为20,输入和输出节点分别由8和5个节点,这个网络有多少权重值? 答:在MLP中,权重是连接神经元的参数,每个连接都有一…...

15-利用dubbo远程服务调用
本文介绍利用apache dubbo调用远程服务的开发过程,其中利用zookeeper作为注册中心。关于zookeeper的环境搭建,可以参考我的另一篇博文:14-zookeeper环境搭建。 0、环境 jdk:1.8zookeeper:3.8.4dubbo:2.7.…...

【Rust自学】8.5. HashMap Pt.1:HashMap的定义、创建、合并与访问
8.5.0. 本章内容 第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。 第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编…...

未来网络技术的新征程:5G、物联网与边缘计算(10/10)
一、5G 网络:引领未来通信新潮流 (一)5G 网络的特点 高速率:5G 依托良好技术架构,提供更高的网络速度,峰值要求不低于 20Gb/s,下载速度最高达 10Gbps。相比 4G 网络,5G 的基站速度…...

LLM(十二)| DeepSeek-V3 技术报告深度解读——开源模型的巅峰之作
近年来,大型语言模型(LLMs)的发展突飞猛进,逐步缩小了与通用人工智能(AGI)的差距。DeepSeek-AI 团队最新发布的 DeepSeek-V3,作为一款强大的混合专家模型(Mixture-of-Experts, MoE&a…...

Uniapp在浏览器拉起导航
Uniapp在浏览器拉起导航 最近涉及到要在浏览器中拉起导航,对目标点进行路线规划等功能,踩了一些坑,找到了使用方法。(浏览器拉起) 效果展示 可以拉起三大平台及苹果导航 点击选中某个导航,会携带经纬度跳转…...

公平联邦学习——多目标优化
前言 前段时间接触到了联邦学习(Federated Learning, FL)。涉猎了几年多目标优化的我,惊奇地发现横向联邦学习里面也有用多目标优化来做的。于是有感而发,特此写一篇博客记录记录,如有机会可以和大家多多交流。遇到不…...

奇怪的Python:为何字符串要设置成不可变的?
你好!我是老邓。今天我们来聊聊 Python 中字符串不可变这个话题。 1、问题简介: Python 中,字符串属于不可变对象。这意味着一旦字符串被创建,它的值就无法被修改。任何看似修改字符串的操作,实际上都是创建了一个新…...

Vue-Router之嵌套路由
在路由配置中,配置children import Vue from vue import VueRouter from vue-routerVue.use(VueRouter)const router new VueRouter({mode: history,base: import.meta.env.BASE_URL,routes: [{path: /,redirect: /home},{path: /home,name: home,component: () &…...

MyBatis使用的设计模式
目录 1. 工厂模式(Factory Pattern) 2. 单例模式(Singleton Pattern) 3. 代理模式(Proxy Pattern) 4. 装饰器模式(Decorator Pattern) 5. 观察者模式(Observer Patt…...

arm rk3588 升级glibc2.31到2.33
一、查看glibc版本 rootztl:~# ldd --version ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31 Copyright (C) 2020 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNE…...

【Linux系列】sed命令的深入解析:如何使用sed删除文件内容
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

C++ 设计模式:桥接模式(Bridge Pattern)
链接:C 设计模式 链接:C 设计模式 - 装饰模式 桥接模式(Bridge Pattern)是一种结构型设计模式,它通过将抽象部分(业务功能)与实现部分(平台实现)分离,使它们…...

MATLAB中whitespacePattern函数用法
目录 语法 说明 示例 匹配空白字符 替换非标准空白 更正错误的间距 whitespacePattern函数的功能是匹配空白字符。 语法 pat whitespacePattern pat whitespacePattern(N) pat whitespacePattern(minCharacters,maxCharacters) 说明 pat whitespacePattern 创建一…...

Django多字段认证的实现
Django多字段认证 需求: django认证的检查用户是username,如果使用 username和 手机号验证登录。 重写: ModelBackend 类下的 authenticate 方法 # 在对应应用下创建 utils.py""" 修改Django认证类,为了实现 …...

【AndroidAPP】权限被拒绝:[android.permission.READ_EXTERNAL_STORAGE],USB设备访问权限系统报错
一、问题原因 1.安卓安全性变更 Android 12 的安全性变更,Google 引入了更严格的 PendingIntent 安全管理,强制要求开发者明确指定 PendingIntent 的可变性(Mutable)或不可变性(Immutable)。 但是…...

SQL进阶技巧:如何分析连续签到领金币数问题?
目录 0 题目需求 1 数据准备 2 问题分析 2.1 代码实现 2.2 代码功能分析 第一段 SQL...

1、ELK的架构和安装
ELK简介 elk:elasticsearch logstash kibana,统一日志收集系统。 elasticsearch:分布式的全文索引引擎的非关系数据库,json格式,在elk中存储所有的日志信息,架构有主和从,最少需要2台。 …...

Vue2/Vue3使用DataV
Vue2 注意vue2与3安装DataV命令命令是不同的Vue3 DataV - Vue3 官网地址 注意vue2与3安装DataV命令命令是不同的 vue3vite 与 Vue3webpack 对应安装也不同vue3vite npm install kjgl77/datav-vue3全局引入 // main.ts中全局引入 import { createApp } from vue import Da…...

汇编环境搭建
学习视频 将MASM所在目录 指定为C盘...

Android 系统 `android.app.Fragment` 类的深度定制与常见问题解析
Android 系统 android.app.Fragment 类的深度定制与常见问题解析 目录 引言Fragment 概述Fragment 的生命周期Fragment 的系统层深度定制 4.1 Fragment 的创建与初始化4.2 Fragment 的布局与视图4.3 Fragment 的通信机制4.4 Fragment 的动画与过渡4.5 Fragment 的状态保存与恢…...

linux ueditor nginx https 后台配置项返回格式出错,上传功能将不能正常使用
jsp的版本 如果出现了这个错误,上传的图标都亮起的情况,还是提示这个, 可以试试修改 uedtior.all.js 8082行 isJsonp utils.isCrossDomainUrl(configUrl); 改为 // isJsonp utils.isCrossDomainUrl(configUrl); isJsonp true; 如果还不…...