☰
基于关键点的目标检测:从框回归到点预测的范式转换与实战
2026/10/12 23:21:32 网站建设 项目流程

1. 为什么要在目标检测里引入关键点

目标检测这个方向,做了几年的人都会有一个共同的感受:框回归这条路,能挖的差不多都挖完了。从最早的滑动窗口加手工特征,到后来 R-CNN 系列把候选框生成和分类拆开,再到 YOLO、SSD 把检测做成端到端的回归问题,精度和速度的平衡点被反复优化。但如果你仔细观察那些真正难啃的场景,会发现一个尴尬的事实——矩形框这个表示形式本身,在很多情况下就是不够用的。

我举个很典型的例子。假设你要检测一张密集摆放的货架上的商品,两个瓶子紧挨着,甚至部分重叠。用传统的水平矩形框去标,两个框会大面积交叠,NMS 一压,要么漏掉一个,要么位置飘得厉害。再比如遥感图像里的飞机、舰船,它们有明确的朝向,水平框会把大量背景包进来,导致分类特征被稀释。还有一类更极端的,像人群计数、细胞检测、文字检测,目标本身形状不规则,用框去描述本身就是一种信息损失。

基于关键点的目标检测,本质上是在回答一个问题:能不能不用框,而是用一组语义明确的点来定义目标?这个思路最早在姿态估计领域被验证得很充分。人体姿态估计里,我们不去框整个人,而是定位关节点——肩膀、手肘、手腕,这些点连起来就构成了一个人。把这个逻辑迁移到通用目标检测上,就变成了:用目标的几个关键位置点(比如中心点、角点、极值点)来表征目标,再通过点的组合还原出检测结果。

这条路线的价值在于三点。第一,表示更紧凑,一个目标用几个点就能描述,避免了框回归里那些冗余的自由度。第二,对旋转和形变更鲁棒,点与点之间的相对关系比框的绝对坐标更能抵抗形变。第三,天然适合密集场景,因为点与点之间可以通过热力图峰值来区分,不像框那样容易在 NMS 阶段互相伤害。

我最初接触这个方向是因为一个工业质检的项目,检测对象是细长的金属件,长宽比能到 10:1 以上,水平框标注时背景占比太高,模型学得很吃力。后来换成关键点表示,精度直接上了一个台阶。从那以后我就意识到,关键点检测不是姿态估计的专属,它是目标检测的一个通用范式。

这篇文章我会把基于关键点的目标检测从头到尾拆一遍。包括它的核心设计思路、几种主流的关键点表示方式、热力图编解码的细节、损失函数的设计考量、以及实际落地时那些文档里不会写的坑。适合已经做过基础目标检测、想拓展技术栈的读者,也适合正在被密集场景或旋转目标折磨的同行。

2. 关键点检测的核心设计思路拆解

2.1 从框回归到点预测的范式转换

传统检测器的输出是一个四维向量 (x, y, w, h),网络要学的是从特征到这四个数的映射。这个映射有个天然的问题:四个量之间存在强耦合。w 和 h 的变化会直接影响 x、y 的语义,网络在回归时经常出现"框在抖"的现象,尤其是小目标。

关键点检测把这个四维回归拆成了若干个独立的点预测问题。每个点就是一个二维坐标 (x, y),网络为每个点输出一张热力图,热力图的峰值位置就是点的位置。这样做的好处是,每个点的预测是解耦的,一个点预测偏了不会直接污染其他点。而且热力图这种表示天然带有空间信息,卷积网络处理起来非常顺手。

但这里有个关键问题需要想清楚:用哪些点?这是整个方案设计的核心。点的选择直接决定了后续的匹配策略、损失函数、以及最终的解码方式。选得不好,要么点之间歧义太大,要么还原目标时信息不够。

2.2 几种主流的关键点表示方案对比

目前业界比较成熟的关键点表示方案,我梳理成下面这张表,方便你根据场景选型。

表示方案关键点定义优势劣势典型适用场景
中心点表示目标中心一个点极简,无歧义,解码快丢失尺寸和朝向信息,需额外分支回归通用检测、密集小目标
角点表示左上、右下两个点保留尺寸信息,配对逻辑清晰角点易混淆,配对是难点通用检测、文档版面分析
极值点表示上下左右四个极值点对旋转目标友好点数量多,匹配复杂旋转目标、遥感
角点+中心点四角加中心共五点信息完整,可还原旋转框标注成本高,训练难旋转框检测、场景文字
边缘点表示目标轮廓采样点可描述任意形状点数不定,后处理重不规则目标、分割辅助

我重点说前两种,因为它们在实际项目里用得最多。

中心点表示的代表工作是 CenterNet 这一类。它的逻辑非常干净:把目标压缩成一个中心点,网络预测中心点热力图,同时回归该点对应的宽高。解码时取热力图峰值,再读宽高,框就出来了。这个方案最大的好处是没有 NMS 的烦恼,因为每个目标只有一个中心点,峰值提取时用 3x3 最大池化就能替代 NMS,速度极快。但它的软肋也很明显:当两个目标中心点靠得很近时,热力图峰值会糊在一起,这时候就需要靠宽高回归来区分,而宽高回归在密集场景下并不总是可靠。

角点表示的代表是 CornerNet 这一类。它预测左上角和右下角两张热力图,然后通过一个嵌入向量(embedding)来判断哪个左上角和哪个右下角属于同一个目标。这个方案的优雅之处在于,角点天然携带了尺寸信息,不需要额外回归宽高。但配对是个大问题,嵌入向量学不好,就会出现"张冠李戴",把 A 的左上角和 B 的右下角配成一对。我实测下来,嵌入向量的维度、损失权重、以及训练时的难例挖掘策略,对最终精度影响非常大。

2.3 为什么热力图是主流编码方式

你可能会问,为什么不直接回归点的坐标,非要用热力图?这个问题我当初也纠结过。直接回归坐标,输出两个数就完事了,多简单。但实际做下来会发现,直接回归坐标对卷积网络不友好。

原因在于,卷积特征图是空间有序的,每个位置对应原图的一个区域。如果让网络直接吐坐标,它需要把空间信息压缩成一个全局向量再解码,这个过程会丢失大量空间精度。而热力图保留了空间结构,网络只需要在每个位置判断"这里是不是关键点",这是一个逐像素的分类问题,卷积网络天生擅长。

另一个原因是热力图对标注噪声更宽容。关键点标注本身有误差,直接回归坐标时,一个像素的偏差就是一个明确的惩罚。而热力图用高斯核把标注点扩散成一个软标签,峰值附近的值是渐变的,网络预测偏一点,损失不会爆炸。这种软标签的设计,让训练稳定了很多。

热力图的生成方式通常是:在关键点位置放一个高斯核,核的大小和目标尺寸相关。目标大,核就大;目标小,核就小。这个尺寸自适应很关键,如果所有目标都用同一个核,小目标的热力图会糊成一片,大目标的热力图又太尖锐,网络学起来会顾此失彼。

3. 热力图编解码与损失函数的核心细节

3.1 高斯核半径的计算逻辑

热力图的生成,核心就是高斯核半径的确定。这个半径不是拍脑袋定的,它和目标框的尺寸有明确的数学关系。业界常用的做法是借鉴 CornerNet 里的那套逻辑:保证高斯核覆盖的区域,与目标框的交并比不低于某个阈值。

具体来说,假设目标框的宽高是 (w, h),我们希望在热力图上,以中心点为圆心、半径为 r 的圆,与目标框的 IoU 不低于 0.7。通过几何推导,可以得到半径 r 的一个近似解。这个推导过程涉及圆和矩形的交叠面积计算,比较繁琐,实际实现时通常用一个查表或者近似公式。

我一般用的近似公式是这样的:先算目标框的面积,再根据一个缩放系数反推半径。缩放系数需要根据数据集调,我试过的经验值是,对于中等尺寸目标,半径在 2 到 5 个像素之间比较合适。小目标取小值,大目标取大值。如果数据集里目标尺寸跨度很大,建议按尺寸分桶,每个桶用不同的半径系数。

注意:高斯核半径如果设得太大,密集场景下相邻目标的热力图会粘连,峰值提取时容易漏检;设得太小,网络对位置误差的容忍度降低,训练容易震荡。这个参数值得花时间调。

3.2 热力图峰值提取的两种策略

训练完之后,推理阶段要从热力图上把点抠出来。这一步叫峰值提取,有两种主流策略。

第一种是局部最大值法。对热力图做 3x3 最大池化,保留那些等于池化结果的位置,这些就是局部峰值。然后设定一个阈值,过滤掉低置信度的峰值。这个方法简单快速,适合中心点表示这种"一点一目标"的方案。但它有个前提假设:每个目标的峰值是独立的。如果两个目标靠得太近,峰值合并,就会漏检。

第二种是Top-K 加 NMS 法。先取热力图上响应最高的 K 个位置,然后对这些位置做 NMS,保留独立的峰值。这个方法比局部最大值灵活,K 可以设得比实际目标数大一些,给 NMS 留出筛选空间。但 NMS 的 IoU 阈值需要调,调小了会保留重复峰值,调大了会误删相邻目标。

我实测下来的经验是:目标稀疏时用局部最大值,目标密集时用 Top-K 加 NMS。如果数据集里两种情况都有,可以先用局部最大值粗筛,再对峰值附近的区域做一次精细的 NMS。这个组合策略在工业质检项目里帮我省了不少事。

3.3 损失函数设计的三个关键考量

关键点检测的损失函数,核心是焦点损失(Focal Loss)的变体。为什么用 Focal Loss?因为热力图上正样本(关键点位置)极少,负样本(背景)极多,标准的交叉熵会被负样本淹没。Focal Loss 通过调制因子,降低易分类样本的权重,让网络聚焦在难样本上。

但直接用 Focal Loss 还不够,关键点检测有几个特殊之处需要处理。

第一,正负样本的定义是软性的。热力图上的值不是 0 或 1,而是高斯分布。峰值位置是 1,周围逐渐衰减到 0。所以损失函数要能处理这种连续标签。常见的做法是,对正样本区域用 (1-p)^2 加权,对负样本区域用 p^2 加权,这样峰值附近的预测误差会被放大,远离峰值的误差被抑制。

第二,不同关键点的损失需要平衡。比如角点表示里,左上角和右下角的损失权重如果一样,网络可能会偏向某一侧。我一般会给每个关键点分支设一个可学习的权重,让网络自己调整。如果不想增加复杂度,手动设权重也行,经验值是让各分支的初始损失量级接近。

第三,偏移量的回归不能忽略。热力图的尺寸通常是原图的 1/4,这意味着从热力图坐标还原到原图坐标时,会有量化误差。这个误差最大能到 3 个像素,对小目标来说是致命的。所以需要额外回归一个偏移量,补偿这个量化损失。偏移量的损失用 L1 就行,权重不用太大,因为它只是一个微调。

4. 完整实操流程与关键环节实现

4.1 数据准备与标注格式转换

假设你手上是一批常规的矩形框标注数据,格式是 (x_min, y_min, x_max, y_max)。要转成关键点检测需要的格式,核心是从框生成热力图和偏移量标签。

流程是这样的:先算目标中心点坐标,再算目标宽高。然后根据宽高计算高斯核半径,在热力图上以中心点为中心画高斯核。同时记录中心点在热力图上的整数坐标,以及它和真实坐标之间的偏移量。

这里有个细节容易被忽略:如果两个目标的中心点落在同一个热力图格子怎么办?这种情况在密集小目标里很常见。我的处理方式是,取高斯核响应更大的那个目标,另一个目标的标签丢弃。听起来有点粗暴,但实测下来比强行叠加两个高斯核要好,因为叠加会导致峰值位置偏移,网络学到的中心点会飘。

标注转换的代码逻辑,我用伪代码示意一下:

for each bbox in annotations: cx = (x_min + x_max) / 2 cy = (y_min + y_max) / 2 w = x_max - x_min h = y_max - y_min radius = compute_radius(w, h) hm_cx = int(cx / stride) hm_cy = int(cy / stride) if heatmap[hm_cy, hm_cx] > 0: continue # 已有更高响应,跳过 draw_gaussian(heatmap, (hm_cx, hm_cy), radius) offset[hm_cy, hm_cx] = (cx / stride - hm_cx, cy / stride - hm_cy) size[hm_cy, hm_cx] = (w, h)

这段逻辑里,compute_radius是核心,stride是热力图相对于原图的缩放比例,通常是 4。draw_gaussian就是在热力图上画高斯核,注意要处理边界情况,别让高斯核画到图外面去。

4.2 网络结构的关键设计点

关键点检测的网络结构,主干网络可以用 ResNet、Hourglass、或者轻量级的 MobileNet,看你的速度要求。关键是头部设计。

头部通常有三个分支:热力图分支、偏移量分支、尺寸分支。热力图分支输出通道数等于关键点类别数,比如中心点表示就是 1 个通道,角点表示就是 2 个通道。偏移量分支输出 2 个通道,对应 x 和 y 的偏移。尺寸分支输出 2 个通道,对应宽高。

这里有个设计选择:尺寸分支要不要?如果你用的是角点表示,角点本身就携带了尺寸信息,不需要额外的尺寸分支。但如果你用中心点表示,中心点只有一个坐标,必须靠尺寸分支回归宽高。我个人的偏好是,能用角点就用角点,因为少一个分支就少一份回归误差,而且角点的配对逻辑虽然复杂,但一旦配对成功,框的精度比中心点加尺寸回归要高。

头部还有一个细节:上采样方式。热力图的分辨率通常比主干输出高,需要上采样。我试过转置卷积和双线性插值两种,转置卷积能学到更锐利的峰值,但容易产生棋盘伪影;双线性插值更平滑,但峰值会糊。折中方案是,先用双线性插值上采样,再接一个 3x3 卷积微调。这个组合在多个项目里表现都比较稳。

4.3 训练策略与参数设置

训练关键点检测网络,有几个参数需要特别注意。

学习率:关键点检测对学习率比普通检测更敏感。我一般用 1e-4 起步,配合余弦退火。如果发现损失震荡,先降学习率,再检查高斯核半径是不是设得太小。

批量大小:热力图分支的损失是逐像素的,批量大小对梯度稳定性影响很大。如果显存允许,尽量用 8 以上。如果只能用 2 或 4,建议开梯度累积,累积步数设 4 到 8。

数据增强:关键点检测对几何变换的增强要小心。翻转、旋转、缩放都可以用,但关键点的坐标要同步变换。我见过有人只变换图像忘了变换关键点,训练出来的模型完全不能用。另外,裁剪增强要慎用,因为裁剪可能把关键点裁掉,导致标签丢失。如果要用,得加一个判断:裁剪后关键点还在框内的才保留。

训练轮数:关键点检测收敛比普通检测慢,因为热力图的学习需要时间。我一般会训练 200 到 300 个 epoch,前 100 个 epoch 损失下降明显,后面是精细调整。如果 100 个 epoch 损失就平了,大概率是学习率太大或者高斯核半径有问题。

4.4 推理后处理与框还原

推理阶段,从热力图到最终框,需要几步后处理。

第一步,峰值提取。按前面说的策略,从热力图里抠出峰值位置和对应的置信度。

第二步,偏移量补偿。把峰值位置的整数坐标,加上偏移量分支预测的偏移,得到亚像素级的中心点坐标。

第三步,尺寸还原。如果是中心点表示,读尺寸分支的宽高;如果是角点表示,把配对的角点坐标相减得到宽高。

第四步,坐标映射回原图。把热力图坐标乘以 stride,还原到原图尺度。

第五步,NMS 或峰值筛选。如果是中心点表示,通常不需要 NMS,因为峰值提取已经保证了唯一性。如果是角点表示,配对之后可能会有重复框,需要做一次 NMS。

这里有个坑:偏移量的符号。偏移量是真实坐标减去整数坐标,范围在 -0.5 到 0.5 之间。有些实现里会把它归一化到 0 到 1,还原时忘了减 0.5,导致框整体偏移半个像素。这个 bug 很隐蔽,因为半个像素在视觉上几乎看不出来,但 mAP 会掉一两个点。

5. 常见问题与排查技巧实录

5.1 热力图峰值粘连导致漏检

这是密集场景下最常见的问题。两个目标的中心点距离小于高斯核半径之和时,热力图峰值会合并成一个。网络学到的是一团糊在一起的响应,峰值提取时只能抠出一个点。

排查方法:把热力图可视化出来,看密集区域是不是有明显的粘连。如果粘连严重,先检查高斯核半径是不是设大了。如果半径已经很小还是粘连,那就是目标本身太密,需要考虑换表示方案。

解决方案有三个方向。一是减小高斯核半径,牺牲一点位置精度换召回。二是换角点表示,角点的分布比中心点分散,粘连概率低。三是引入注意力机制,让网络在密集区域学会区分不同目标的响应。我试过在头部加一个空间注意力模块,对密集场景的召回提升比较明显,但会增加一点计算量。

5.2 角点配对错误

角点表示里,配对错误是精度损失的主要来源。表现是框的位置对,但尺寸离谱,或者框跑到背景上去了。

排查方法:把配对结果可视化,看错误配对的角点有什么规律。常见规律是,同一类目标的角点嵌入向量区分度不够,或者不同类目标的角点嵌入向量混在一起。

解决方案:加大嵌入向量的维度,从 1 维提到 4 维甚至 8 维。调整嵌入损失的权重,让配对损失在总损失里占比更高。引入难例挖掘,对配对错误的样本加大惩罚。我实测下来,嵌入向量维度从 1 提到 4,配对准确率能提升 5 到 8 个百分点。

5.3 小目标关键点响应弱

小目标在热力图上的响应区域很小,如果高斯核半径没调好,响应会弱到被阈值过滤掉。

排查方法:统计不同尺寸目标的召回率,看小目标的召回是不是明显低于大目标。如果是,就是这个问题。

解决方案:按尺寸分桶设置高斯核半径,小目标用更小的半径,保证响应集中。降低峰值提取的阈值,给小目标更多机会。在损失函数里给小目标加权,让网络更关注小目标。我一般会给小目标的损失乘一个 1.5 到 2 的系数,效果比较直接。

5.4 训练损失不下降

如果训练了几个 epoch 损失纹丝不动,先别急着调网络结构,按下面这个顺序排查。

排查项检查方法常见问题
学习率打印梯度范数学习率太大导致震荡,太小导致不收敛
高斯核半径可视化热力图标签半径太小导致标签太稀疏,网络学不到
正负样本比例统计正样本像素占比正样本太少,损失被负样本主导
数据增强检查关键点是否同步变换关键点没变换,标签和图像不对应
损失权重打印各分支损失量级某分支损失过大,压制了其他分支

我踩过最坑的一次是数据增强忘了变换关键点,训练损失一直卡在 0.8 左右下不去,排查了两天才发现。从那以后我养成了一个习惯:每次改数据增强,先可视化一批增强后的样本,确认标签和图像对得上。

5.5 推理速度不达标

关键点检测的推理速度,瓶颈通常在峰值提取和后处理。如果用了 Top-K 加 NMS,K 设得太大,后处理会拖慢整体速度。

优化方向:用局部最大值替代 Top-K,速度能快一倍以上。把 NMS 换成峰值筛选,中心点表示天然不需要 NMS。用 TensorRT 或 ONNX Runtime 加速,热力图分支的卷积可以量化到 INT8,精度损失很小。我在一个边缘设备项目里,把后处理从 Python 换成 C++ 实现,整体延迟从 45ms 降到了 18ms。

提示:后处理的优化容易被忽视,但在实际部署里,它往往比网络前向还耗时。建议在项目早期就把后处理纳入性能预算。

6. 关键点检测的适用边界与选型建议

6.1 什么场景该用关键点检测

不是所有检测任务都适合关键点方案。我总结了一个简单的判断标准:如果目标的形状规则、尺寸适中、分布稀疏,传统框回归就够了,没必要上关键点。关键点方案的价值在下面这几类场景里才真正体现。

密集小目标:比如货架商品、细胞、人群。这些场景下框回归的 NMS 是精度瓶颈,关键点方案能绕开这个问题。

旋转目标:比如遥感图像里的飞机舰船、工业零件。水平框会引入大量背景,关键点方案可以用角点或极值点描述朝向。

不规则形状目标:比如文字、裂缝、血管。框本身就不是一个好的表示,关键点或轮廓点更合适。

需要实例分割辅助:关键点可以作为分割的初始化,比从框做分割更准。

6.2 关键点方案与传统方案的混合使用

实际项目里,纯关键点方案不一定是最优解。我比较推荐的做法是混合使用。

一种常见的混合是:用关键点做粗定位,用框回归做精修。先用中心点热力图找到目标的大致位置,再在局部区域回归精确的框。这样既有关键点的密集场景优势,又有框回归的精度优势。

另一种混合是:用关键点做旋转框的角点,用框回归做尺寸微调。角点给出朝向,框回归给出精确的宽高。这个方案在遥感项目里效果很好。

还有一种更轻量的混合:用关键点做辅助任务,主任务还是框回归。在训练时加一个关键点预测分支,让网络学到更丰富的空间表示,推理时只用框回归分支。这种做法能提升主任务的精度,推理速度不变。我在一个项目里试过,mAP 提升了 1.5 个点,代价只是训练时多了一个分支。

6.3 从零搭建关键点检测项目的检查清单

如果你准备从零开始做一个关键点检测项目,按下面这个清单走,能少踩很多坑。

  • 数据侧:确认标注质量,关键点标注的误差要控制在一个像素以内。检查标注是否覆盖了所有难例场景,密集、遮挡、小目标都要有。
  • 表示侧:根据场景选关键点表示方案。密集场景优先中心点,旋转场景优先角点或极值点。
  • 标签侧:高斯核半径按尺寸分桶设置,可视化确认标签合理。偏移量标签的符号和范围要确认。
  • 网络侧:头部三个分支的初始权重设好,避免某分支主导。上采样方式选双线性加卷积微调。
  • 训练侧:学习率从 1e-4 起步,批量大小尽量大,数据增强注意关键点同步变换。
  • 推理侧:峰值提取策略根据目标密度选,后处理用 C++ 或 TensorRT 加速。
  • 评估侧:除了 mAP,还要看密集区域的召回率和小目标的召回率,这两个指标更能反映关键点方案的实际表现。

我个人在实际操作中的体会是,关键点检测的精度上限,很大程度上取决于标签的质量和表示方案的选择,网络结构的影响反而没那么大。我见过太多人花大量时间调网络,却忽略了高斯核半径和峰值提取策略这两个真正影响精度的环节。把这两个环节调好,比换一个更深的骨干网络带来的提升要大得多。

最后再分享一个小技巧:如果你的数据集里目标尺寸跨度很大,不要用一个统一的高斯核半径,按目标面积分三到四个桶,每个桶单独设半径。这个改动很小,但在多尺度数据集上的召回提升非常明显。我在一个包含从 8 像素到 400 像素目标的遥感数据集上试过,分桶之后小目标召回从 62% 提到了 78%,大目标召回基本不变。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询