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 加 NMSK 设得太大后处理会拖慢整体速度。优化方向用局部最大值替代 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%大目标召回基本不变。
阅读完成 · 觉得有帮助?