资讯详情

资讯详情

多尺度锚框机制详解:从公式推导到工程实践

做目标检测绕不开的基础模块里多尺度锚框绝对算一个。很多人第一次听到这个词觉得它高深其实拆开看就两件事什么是锚框为什么要多个尺度。如果你已经能跑通图像分类正打算往检测方向走这个节点值得静下心来推一遍。主流检测框架基本都依赖锚框机制候选框生成、正负样本分配、边框回归全是从这条线延伸出去的。这篇文章会把多尺度锚框的来龙去脉、背后的几何原理、代码实现和训练时的常见坑一次性讲透适合刚入门的初学者也适合想回头补原理细节的调参选手。1. 多尺度锚框到底在解决什么问题1.1 目标检测的“找”和“认”是两件事图像分类任务只需要回答“这是什么”模型看完整张图给出一个类别概率就行。目标检测要再多回答一个问题“在哪”。这就涉及一个根本性的困境——计算机并不知道目标在图像的哪个位置需要先有一堆“候选位置”再判断每个位置里有没有目标、是什么目标。早期做法是穷举式滑窗用不同尺寸、不同宽高比的窗口在整张图上滑一遍每个窗口都送进分类器判断。这个思路直白但代价大得离谱。一张 512×512 的图如果窗口尺寸有 20 种、宽高比有 5 种每个位置还要滑好几个偏移算下来窗口数量轻松破百万。纯滑窗方案只适合目标尺度固定、场景简单的任务没法直接用于通用检测。锚框的出现就是把“穷举”变成了“带先验的取样”。它不再沿图像逐个滑窗而是预定义一组固定位置、固定尺寸、固定宽高比的矩形框均匀撒在图上像在一张地图上提前布好网格。模型后续要做的不是从零找目标而是在这些预先撒好的框上做两件事判断框里有没有目标分类以及把框的位置和大小修正到贴近真实目标回归。1.2 只用一个网格为什么不够用如果只用一种固定尺寸的网格去覆盖目标会立刻撞上现实问题目标尺寸差异太大了。远处的一个交通标志可能只有 40×40 像素近处的卡车却能占据整张图的一半。两者在归一化坐标系里可能相差二十倍单一尺寸的先验框完全没有能力同时覆盖这两个量级。用小锚框去匹配大目标最直接的后果是每个锚框只覆盖了卡车的一个角IoU交并比只有零点零几模型永远无法把这样的框修正到完整覆盖卡车。反过来用大锚框去匹配小目标也会因为框体太大、背景占比过高而无法精准定位。更麻烦的是只在一个特征图层面上生成锚框模型的“视野”也是固定的。浅层特征图分辨率高、保留着小目标的边缘和纹理但语义信息弱没法判断“这个像素区域是车还是人”深层特征图语义信息强但分辨率低小目标信息早就被下采样抹掉了。视觉任务讲究“既能看清细节又能看懂全貌”单尺度锚框天然做不到这一点。1.3 锚框的本质模型预置的“扫描习惯”把锚框机制理解成“先验扫描习惯”很多设计上的疑问会豁然开朗。人眼看一幅图时从来不会用单一密度的格子扫描画面——你会先扫一眼整幅画面的构图再聚焦到某个小物体上去看细节。这种“由粗到细、由大到小”的扫描策略翻译到检测模型里就是多尺度锚框。每一组锚框其实代表模型对“目标大概会出现在什么位置、大概有多大”的一种先验假设。比如在浅层高分辨率特征图上设置一批小锚框等于告诉模型“我预期小目标会出现在这些位置附近请优先关注它们。”在深层低分辨率特征图上设置大锚框等于说“尺寸较大的目标由这边负责。”所以多尺度锚框的使命不是“让模型更聪明”而是先把搜索范围剪枝到合理空间把困难的位置搜索问题拆成多个尺度上的局部修正问题。锚框本身不是预测结果它只是一个待修正的候选起点。理解这一点后面看公式和代码时才不会迷失。2. 单尺度锚框生成先把公式推明白2.1 一个像素中心如何发展出多个框先从最简单的单尺度情况说起。假设输入图像宽度为 W、高度为 H我们要在一个大小为 fh×fw 的特征图上生成锚框。常规做法是取特征图的每个单元格中心作为锚框中心该中心映射回原图的归一化坐标就是cx (i 0.5) / fwcy (j 0.5) / fh这里 i 和 j 分别是特征图的横纵索引。之所以加 0.5是因为我们把单元格的中心当作锚点而不是左上角这样生成的框能以目标为中心均匀展开不会系统性偏向某个角落。确定中心后每个位置会生成多组框。框的尺寸由一个尺寸集合和一个宽高比集合排列组合决定。尺寸集合记为 sizes例如 [0.2, 0.35, 0.5]表示框的归一化边长在这三个取值之间变化宽高比集合记为 ratios例如 [1, 2, 0.5]表示框的宽高比例。排列组合下来每个中心点会生成 3×39 个候选框。具体每个框的宽高按如下公式计算框宽 s × sqrt(r)框高 s / sqrt(r)其中 s 是尺寸集合中的某个值r 是宽高比集合中的某个值。这个公式的精妙之处在于它能保证框的面积约等于 s² 乘以固定系数不会因为宽高比改变而出现面积忽大忽小的情况。比如 s0.4r2 时框宽约 0.566、框高约 0.283面积约 0.16r0.5 时框宽约 0.283、框高约 0.566面积同样是约 0.16。两种宽高比覆盖了横向目标和纵向目标面积却保持一致这让模型对不同形状物体的匹配难度更均衡。如果用 unnormalized 像素值表示直接乘上 W 和 H 即可框宽像素 W × s × sqrt(r)框高像素 H × s / sqrt(r)有了中心和宽高锚框的归一化坐标就可以写成 xmincx-w/2、ymincy-h/2、xmaxcxw/2、ymaxcyh/2 的形式。整个特征图上的锚框总数是 fh × fw × len(sizes) × len(ratios)以 64×64 特征图、3 种尺寸、3 种宽高比为例算下来就有 36864 个框可见锚框的数量规模从一开始就不小。2.2 交并比 IoU锚框与真实框的“贴合度量”生成这么多候选框之后训练时首先要回答一个问题哪些锚框是正样本、哪些是负样本答案由 IoU 决定。IoU 全称 Intersection over Union计算两个框交集面积与并集面积的比值取值范围在 0 到 1 之间。IoU 越接近 1说明两个框重叠程度越高越可能指向同一个目标。定义一个锚框 A 与真实目标框 G 的 IoUIoU (A 与 G 交集面积) / (A 面积 G 面积 - A 与 G 交集面积)如果锚框完全包裹了真实框IoU 有可能较低如果两个框完全重合IoU 等于 1。用代码实现时最方便的是向量化计算一次算完所有锚框与所有真实框的 IoU 矩阵。假设我们已经有了 N 个锚框和 M 个真实框可以用如下方式def box_iou(boxes1, boxes2): # boxes1, boxes2: (N, 4), (M, 4)坐标是 [xmin, ymin, xmax, ymax] area1 (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1]) area2 (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1]) lt torch.max(boxes1[:, None, :2], boxes2[None, :, :2]) rb torch.min(boxes1[:, None, 2:], boxes2[None, :, 2:]) wh (rb - lt).clamp(min0) inter wh[:, :, 0] * wh[:, :, 1] union area1[:, None] area2[None, :] - inter return inter / union这段代码一次返回 N×M 的 IoU 矩阵每一行对应一个锚框每一列对应一个真实框。实际训练中会以这个矩阵为基础做正负样本分配。一个常见的匹配策略分两步先让每个真实框与所有锚框计算 IoU挑选出 IoU 最大的锚框作为它的“专属正样本”保证每个真实目标至少有一个锚框负责预测随后再遍历所有锚框凡是与任意真实框的 IoU 超过阈值的也算作正样本。2.3 分类分支和回归分支各学什么锚框参与训练时每一组锚框都会对应两个学习目标分类和回归。分类分支学习的是“这个锚框区域内有没有目标、是什么类别”负样本的类别通常标为背景回归分支学习的是“锚框需要怎么平移、怎么缩放才能贴合真实框”。回归目标不是直接回归绝对坐标而是回归相对偏移量。常见的编码方式是这样的dx (gt_cx - anchor_cx) / anchor_wdy (gt_cy - anchor_cy) / anchor_hdw log(gt_w / anchor_w)dh log(gt_h / anchor_h)其中 gt_cx、gt_cy、gt_w、gt_h 是真实框的中心与宽高anchor_* 是对应锚框的量。把偏移量设计成相对量而不是绝对量是因为不同尺度的锚框本身就有不同量级直接用绝对像素偏移会让小锚框的回归任务显得“变化很大”大锚框又显得“变化很小”不利于统一优化。到了多尺度阶段锚框的生成逻辑和训练逻辑都不会改变改变的是“在不同特征图层面上放不同尺度的框”。所以单尺度的公式必须吃透因为它是多尺度体系中的最小单元。3. 多尺度的本质在不同特征层上分配不同先验3.1 特征图本身就带着尺度属性深度卷积网络从输入到输出特征图分辨率逐层下降但每个位置的感受野却在逐层扩大。浅层特征图比如输入下采样 4 倍得到的特征图保留了比较多的空间细节适合定位小目标深层特征图比如下采样 32 倍得到的特征图每个位置都看到了输入图上很大一块区域包含更抽象的语义信息适合检测占据画面大部分的大目标。如果只拿某一层特征图来生成锚框等于强迫这层特征既要做小目标的细节定位又要做大目标的语义识别显然超出它的能力范围。多尺度方案的核心思路就是“让每层特征图负责它擅长的目标尺度并且把这些不同尺度的预测结果汇总起来”。举个直观例子输入 512×512 的图像如果只在 16×16 的特征图上生成锚框每个锚框中心之间的间隔就是 32 像素锚框本身的尺度若设计得较小感受野却已经覆盖了原图很大一块区域小目标的信息很难从这层特征图里找回。这就是为什么必须让更浅、分辨率更高的特征图也参与小目标检测。3.2 特征金字塔与多层锚框的对齐方式要在多个特征层上做预测最简单的做法是直接把骨干网络的不同阶段特征拿来做预测但这样会存在一个明显问题浅层特征语义信息不足。于是就有了特征金字塔结构FPN的思路——高层特征通过上采样和侧向连接把语义信息逐层传递到浅层让每一层都兼具高分辨率和较强的语义表达能力。以典型的特征金字塔为例假设骨干网络输出四层特征图分别记为 P2、P3、P4、P5它们相对输入图的下采样倍数依次是 4、8、16、32。多尺度锚框就会这样配置P2 层设置一批小锚框覆盖几十像素的小目标P3 层设置中等偏小的锚框P4 层设置中等偏大的锚框P5 层设置大锚框覆盖数百像素的大目标。每一层使用的锚框宽高比一般保持一致以保证不同尺度下各种形状的目标都被照顾到。这种“金字塔层级与锚框尺度一一对应”的设计结构上非常干净。模型推理时不同层各司其职最后把各层的检测结果做非极大值抑制NMS合并成最终输出。3.3 锚框尺度的工程取值经验锚框尺度怎么定没有一个万能参数但工程上有比较可靠的参考经验相邻特征层之间的锚框面积通常按 2 到 4 倍的倍率递增换算成边长就是约 1.4 到 2 倍。这样能保证各层的召回范围平滑衔接不会出现大目标和小目标之间的“真空地带”。以输入 512×512 为例可以按下面的示意配置来理解各层锚框尺度特征图层级下采样倍数特征图尺寸示例锚框边长范围像素主要检测目标P24128×12832 ~ 64小目标P3864×6464 ~ 128中偏小目标P41632×32128 ~ 256中偏大目标P53216×16256 ~ 512大目标这个表格只是示意不是必须照抄的参数。实际项目里更科学的做法是先统计自己数据集里所有标注框的尺寸分布看看目标主要集中在哪些像素范围再决定每个特征层放多大范围的锚框。如果数据集里小目标比例很高P2 层就要额外加密如果大目标居多P5 层就要多塞几组更大的锚框。宽高比方面一般默认包含 1:1、2:1、1:2 这组基础比例处理横竖形目标。如果数据集中有大量极端细长物体比如桥梁、电线杆可以再加入 3:1、1:3 甚至更极端的比例。每层宽高比数量建议控制在 3 到 5 个不是越多越好多一个 ratio 就多好几万个候选框训练匹配的计算量和显存占用都会上升。4. 直接动手实现一个多尺度锚框生成器4.1 准备工作与完整代码框架动手环节我用的环境是 PyTorchCPU 就能跑通整个流程不需要 GPU。核心要写两个函数一个是锚框生成函数输入特征图尺寸、尺寸集合和宽高比集合输出该特征图对应位置的全部锚框另一个是 IoU 计算函数用于验证生成的锚框能否匹配到真实目标。生成锚框的函数可以这样实现import torch def generate_anchors(feat_h, feat_w, img_h, img_w, sizes, ratios): 在大小为 feat_h x feat_w 的特征图上生成锚框。 坐标归一化到 [0,1]格式为 [xmin, ymin, xmax, ymax]。 sizes: 归一化边长集合例如 [0.1, 0.2, 0.4] ratios: 宽高比集合例如 [0.5, 1, 2] anchors [] for j in range(feat_h): for i in range(feat_w): cx (i 0.5) / feat_w cy (j 0.5) / feat_h for s in sizes: for r in ratios: w s * (r ** 0.5) h s / (r ** 0.5) anchors.append([cx - w / 2, cy - h / 2, cx w / 2, cy h / 2]) return torch.tensor(anchors)这里有个细节值得注意代码里生成的锚框宽高是归一化值没有直接乘以 img_w 和 img_h。这是因为中心坐标已经归一化到 [0,1]锚框的宽高同样归一化到 [0,1] 才方便与分类、回归目标统一处理。如果后续需要像素坐标只需把四个值分别乘以输入图的宽高即可。IoU 计算函数上面已经给出这里把二者串起来用。假设输入图是 512×512我们有四层特征图尺寸分别为 64×64、32×32、16×16、8×8每一层采用不同的尺寸集合宽高比统一用 [0.7, 1, 1.4]。4.2 多尺度调用与数量统计实际调用时各层的尺寸集合需要按前面说的“面积递增”原则来配置。例如sizes_p2 [0.06, 0.09, 0.14] # 约 30~70 像素 sizes_p3 [0.12, 0.2, 0.32] # 约 60~160 像素 sizes_p4 [0.24, 0.4, 0.6] # 约 120~300 像素 sizes_p5 [0.5, 0.7, 0.9] # 约 250~450 像素 anchors_p2 generate_anchors(64, 64, 512, 512, sizes_p2, [0.7, 1, 1.4]) anchors_p3 generate_anchors(32, 32, 512, 512, sizes_p3, [0.7, 1, 1.4]) anchors_p4 generate_anchors(16, 16, 512, 512, sizes_p4, [0.7, 1, 1.4]) anchors_p5 generate_anchors(8, 8, 512, 512, sizes_p5, [0.7, 1, 1.4])每层的锚框数量分别是 64×64×9、32×32×9、16×16×9、8×8×9也就是 36864、9216、2304、576 个四层加起来约 49000 个候选框。这个数量是可控的单卡训练没问题。如果每层再塞 5 个宽高比数量会涨到八万多个匹配和回归的计算量都会明显上升所以宽高比的选择要克制。有了这四层锚框就可以定义几个模拟真实目标验证锚框的覆盖效果。假设我们手工标注三个目标框分别是一个小目标、一个中等目标、一个大目标计算每个目标与四层锚框的最大 IoU看看它们分别被哪一层“接住”。4.3 验证覆盖效果为什么多尺度真的有用写一段简短的验证脚本定义三个真实框分别模拟小、中、大目标gt_boxes torch.tensor([ [0.1, 0.15, 0.2, 0.28], # 小目标约 50x65 像素 [0.35, 0.3, 0.55, 0.62], # 中等目标约 100x160 像素 [0.6, 0.1, 0.95, 0.5] # 大目标约 180x205 像素 ])然后分别把每个真实框与四层锚框计算 IoU记录最大 IoU。你会发现小目标几乎只能被 P2 层的锚框以较高 IoU 匹配到P5 层的最大 IoU 可能只有 0.15大目标则正好相反。这说明如果只保留高层特征图做预测小目标在匹配阶段就被漏掉了只有多尺度组合起来每个尺度的目标才都有对应的锚框兜底。实际训练时还会对锚框做中心位置约束超出图像边界的锚框会被裁剪或者直接丢弃“出界”的锚框来减少无效匹配。简单 demo 里可以忽略这个细节但完整训练代码里建议加上边界检查这部分在常见问题里还会说到。5. 训练多尺度锚框时最常踩的坑5.1 正负样本比例失衡几万甚至几十万个候选锚框里真正与真实目标重叠足够的正样本往往只有上百个正负比例可能达到 1:200。如果不加控制模型会学到“无脑输出背景”就能把 loss 压得很低检测框一个都出不来。解决思路一般有三条。第一条是限制训练时每张图参与 loss 的锚框数量比如正样本保留全部负样本随机采样到正样本的三倍保证 batch 内正负比例在 1:3 左右。第二条是使用难例挖掘策略OHEM把 loss 最高的那些负样本挑出来重点学习让模型把更多注意力放在容易误判的背景区域。第三条是用 Focal Loss 这类改进损失函数降低大量简单负样本的权重让模型更关注难分类的样本。我在实际项目中通常先用第一种稳定复现后再试第三种。5.2 IoU 阈值选多少才合适匹配真实框时IoU 阈值的设定直接决定正样本质量和数量。阈值设 0.5比较宽松能捞到不少中低质量的锚框作为正样本训练早期收敛快但框的定位精度上限不高阈值设 0.7正样本更少、质量更高模型学到的定位能力更强但训练可能因为正样本不足而波动。经验做法是训练早期用 0.5 甚至更低一点让模型先把“大概在哪”学出来训练后期如果想进一步提升定位精度再把阈值提到 0.6 或 0.7 微调。有些改进方案直接在匹配时同时用多个阈值比如 0.5 和 0.7 双分支复杂度更高但对于从小目标到大目标分布极不均匀的数据集效果确实更好。5.3 锚框数量爆炸显存和耗时双双爆表多尺度锚框把几个特征层的候选框加起来数量会非常可观。如果输入图很大、特征图又下采样不够再加 5 个宽高比显存很容易吃满。我见过有人把输入图设置成 1280×1280还用四层特征每层 5 个 ratio锚框数量直接逼近三十万单卡连前向传播都不稳。缓解方法很直接限制每层宽高比数量、控制参与训练的最大锚框数、对大分辨率输入做轻微下采样另外也可以在匹配时只保留与真实框中心距离在一定范围内的锚框减少无效计算。训练时醒着的显存如果长期超过九成优先检查锚框数量是不是失控了。5.4 回归目标发散导致训练崩溃锚框回归的 dw、dh 因为是 log 形式一旦真实框与锚框差得离谱比如锚框很小却要去拟合一个巨大目标log 值就会变得很大梯度也容易爆炸。很多人训练多尺度锚框模型时遇到 loss 突然变成 nan查半天发现是某些极端匹配对产生了超大回归值。对策有两个一是把回归目标的 dw、dh 裁剪到合理范围比如 [-2.5, 2.5]超出部分截断二是让匹配器优先为每个真实目标挑选尺度更接近的锚框而不是单纯按 IoU 排序。后者在多尺度场景里尤其重要因为不同特征层的锚框尺度差异很大IoU 高不代表尺度匹配尺度差太远时即使 IoU 达标回归难度也很大。5.5 常见问题速查表表现可能原因常用解决方法训练 loss 很低但没有检出框正样本太少模型全部预测为背景限制正负样本比例检查 IoU 阈值是否过高小目标漏检严重小尺度锚框数量不足或浅层特征未参与预测增加 P2/P3 层小锚框数量检查下采样倍数大目标框偏小或定位不稳高层特征锚框尺度不够大增大深层锚框边长调整面积递增倍率显存溢出锚框总量过大减少 ratios限制每层锚框数缩小输入分辨率loss 出现 nan回归目标过大或学习率偏高裁剪 dw/dh降低学习率检查梯度6. 最后说说我的调参习惯多尺度锚框这套体系我前前后后在不同项目里调了很多次最大的体会是不要在拿到代码后凭感觉写死一组锚框参数而是先量化分析数据集里标注框的尺寸分布。把每个真实框的宽和高统计出来画一张二维分布图看看目标主要集中在哪个像素区间然后反推每层特征图应该配置多大的锚框。这个习惯帮我少走了非常多次弯路比套用任何经典模型的默认参数都管用。另外新手阶段强烈建议先在一组小图上做多尺度锚框的可视化实验把每层锚框画出来、模拟几个真实目标看匹配结果再动手训练完整检测模型。原因很简单锚框匹配是目标检测一切逻辑的地基地基没搭实后面调什么都像在雾里找方向。等你能很自然地解释“为什么这个小目标会被 P2 层的一个 1:1.7 锚框接住”时再进训练调参阶段心态会完全不一样。多尺度锚框这块内容后续还可以往两个方向继续拓展一是自适应锚框让网络根据数据分布自己学习锚框尺寸省去手动统计的麻烦二是无锚框检测方法舍弃预置锚框直接从特征图每个位置中心做预测。把多尺度锚框的原理吃透再去看这些演进方案你会发现它们其实都是同一个问题的不同解法。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →