1. 前言
在前面的学习中,我们已经知道了锚框的基本思想:
在图像的每个位置预先生成若干个不同大小、不同宽高比的候选框,再由模型判断这些框中是否包含目标,并进一步修正位置。
但是新的问题很快就出现了。
现实中的目标并不是统一大小的,而是有明显的尺度差异。
例如在一张街景图像中:
-
近处的汽车可能很大
-
远处的行人可能很小
-
路边的交通标志可能更小
如果我们只在一种固定尺度上生成锚框,就很难同时覆盖这些不同大小的目标。
因此,目标检测中提出了一个非常重要的思想:
在不同尺度的特征图上生成不同尺度的锚框,用来检测不同大小的目标。
这就是本节要学习的内容:多尺度锚框。
2. 为什么需要多尺度锚框
2.1 真实目标大小差异很大
这是最根本的原因。
在同一张图中,不同目标可能有完全不同的尺寸:
-
小目标:远处行人、鸟、交通灯
-
中等目标:自行车、摩托车
-
大目标:汽车、公交车、整个人脸
如果所有锚框都设置成差不多大小,那么就会出现两种情况:
-
框太大,不适合检测小目标
-
框太小,不适合检测大目标
所以锚框的尺度必须根据目标大小变化。
2.2 深层特征图和浅层特征图的感受野不同
卷积神经网络越往后,特征图分辨率通常越小,但感受野越大。
这意味着:
-
浅层特征图:分辨率高,适合定位小目标
-
深层特征图:分辨率低,但语义更强,适合检测大目标
因此,不同层的特征图本来就适合负责不同尺度的目标。
如果能在这些不同尺度的特征图上分别生成锚框,那么检测效果就会更合理。
2.3 单一尺度难以兼顾检测效果
只用单一尺度的锚框,理论上也能覆盖部分目标,但效果通常不理想。
因为模型必须在同样的候选框设置下,同时适配大目标和小目标,这会增加学习难度。
而多尺度锚框相当于把任务分工了:
-
小目标交给高分辨率特征图
-
大目标交给低分辨率特征图
这样检测会更加自然。
3. 多尺度锚框的核心思想
多尺度锚框的本质可以概括成一句话:
在不同大小的特征图上,以每个像素为中心生成不同尺度的锚框。
这里有两个重点:
第一,不同大小的特征图
例如:
-
4 × 4 -
2 × 2 -
1 × 1
这些特征图尺度不同。
第二,不同尺度的锚框
在较大的特征图上生成较小的锚框,
在较小的特征图上生成较大的锚框。
这样整套锚框系统就能覆盖从小目标到大目标的不同情况。
4. 李沐这里是怎么讲"多尺度"的
在《动手学深度学习》中,这一节的核心不是一上来就讲复杂网络,而是先通过一个非常直观的例子来说明:
把图像划分成不同大小的网格,然后在网格中心生成锚框。
例如:
-
如果特征图是
4 × 4,说明图像被分成了 16 个位置 -
如果特征图是
2 × 2,说明图像被分成了 4 个位置 -
如果特征图是
1 × 1,说明整张图只有 1 个中心位置
可以看到:
-
特征图越大,位置越密,适合检测小目标
-
特征图越小,位置越稀,适合检测大目标
这就是多尺度锚框的直觉来源。
5. 用 4×4、2×2、1×1 特征图理解多尺度
这是这一节最核心的理解方式。
5.1 4 × 4 特征图
如果特征图大小为 4 × 4,那么说明有 16 个中心点可以生成锚框。
这些中心点分布比较密集,所以能够更细致地覆盖图像。
因此它更适合检测:
-
小目标
-
位置变化更细微的目标
因为小目标本来就占据图像中的区域较小,所以必须要有更密的采样位置。
5.2 2 × 2 特征图
如果特征图大小为 2 × 2,那么只有 4 个中心点。
相比 4 × 4,位置更稀疏,但每个位置对应的感受范围更大。
因此更适合检测中等偏大的目标。
5.3 1 × 1 特征图
如果特征图大小为 1 × 1,那么整张图只有一个中心点。
这时候生成的锚框基本就是围绕整张图的中心展开,
它更适合检测特别大的目标,甚至接近整张图的目标。
6. 多尺度锚框的代码思路
李沐这里通常会写一个辅助函数,用来在不同尺度特征图上显示锚框。
核心思路是:
-
指定一个特征图大小,例如
fmap_w, fmap_h -
构造这个大小的随机张量
-
调用
d2l.multibox_prior -
把生成的锚框映射回原图上显示
典型代码风格大致如下:
from d2l import torch as d2l
import torch
def display_anchors(fmap_w, fmap_h, s):
fmap = torch.zeros((1, 10, fmap_h, fmap_w))
anchors = d2l.multibox_prior(fmap, sizes=s, ratios=[1, 2, 0.5])
bbox_scale = torch.tensor((w, h, w, h))
d2l.show_bboxes(d2l.plt.imshow(img).axes,
anchors[0] * bbox_scale)
这段代码的核心不是输入值本身,而是:
只利用特征图的空间尺寸来决定锚框生成位置。
7. 这段代码怎么理解
7.1 fmap = torch.zeros((1, 10, fmap_h, fmap_w))
这里构造一个张量,形状是:
(批量大小, 通道数, 特征图高, 特征图宽)
例如:
(1, 10, 4, 4)
这里的数值内容不重要,重要的是 4 × 4 这个空间大小。
因为 multibox_prior 真正关心的是:
-
特征图有多高
-
特征图有多宽
它据此决定锚框中心的位置。
7.2 anchors = d2l.multibox_prior(...)
这一行负责在特征图每个位置生成锚框。
假设:
-
特征图是
4 × 4 -
每个位置生成若干个锚框
那么最终就会得到很多候选框。
7.3 bbox_scale = torch.tensor((w, h, w, h))
因为锚框坐标通常是归一化坐标,范围相对于整张图。
所以这里乘上原图的宽高,把它还原成像素坐标,方便画图显示。
7.4 d2l.show_bboxes(...)
把生成出来的锚框画在原图上,这样我们就能非常直观地看到:
-
4 × 4特征图生成的框更密集 -
2 × 2特征图生成的框更稀疏 -
1 × 1特征图生成的框覆盖范围最大
8. 多尺度锚框的三个典型例子
这一节博客里,最好就按李沐常见的三个例子来讲。
8.1 在 4 × 4 特征图上生成锚框
例如:
display_anchors(fmap_w=4, fmap_h=4, s=[0.15])
这里的特点是:
-
中心点多
-
锚框分布密
-
锚框本身比较小
所以更适合检测小目标。
你可以把它理解为:
用更密的"探测器"去扫描图像中的细小区域。
8.2 在 2 × 2 特征图上生成锚框
例如:
display_anchors(fmap_w=2, fmap_h=2, s=[0.4])
这里的特点是:
-
中心点减少
-
锚框分布更稀
-
锚框尺度变大
因此更适合检测中等大小目标。
8.3 在 1 × 1 特征图上生成锚框
例如:
display_anchors(fmap_w=1, fmap_h=1, s=[0.8])
这里的特点是:
-
整张图只有一个中心点
-
锚框很大
-
更适合覆盖大目标
这种设置相当于在整张图的全局层面寻找大物体。
9. 为什么大特征图适合小目标,小特征图适合大目标
这一点一定要在博客里讲透。
9.1 大特征图适合小目标
大特征图意味着:
-
位置采样更密
-
每个单元对应原图中更小的区域
-
更容易精确定位小物体
因为小目标本来占的区域就少,如果采样太稀,很容易直接错过它。
9.2 小特征图适合大目标
小特征图意味着:
-
位置采样更稀
-
每个单元对应更大的感受野
-
更能概括大目标整体信息
对于大目标来说,不需要特别密集的采样,因为它本身覆盖范围已经很大。
10. 多尺度锚框和 SSD 的关系
这一节其实就是在给 SSD 铺路。
SSD 的核心思想之一,就是:
在多个尺度的特征图上,分别预测不同尺度的目标。
也就是说:
-
前面较大的特征图负责小目标
-
后面较小的特征图负责大目标
而这些预测,本质上就是在这些不同尺度特征图上生成锚框,并对锚框进行分类与回归。
所以可以说:
多尺度锚框是 SSD 的基础。
如果这一节不懂,后面学 SSD 时就会觉得它为什么要在那么多层上做预测很突兀。
11. 多尺度锚框的本质理解
这一节最核心的不是记住某个具体参数,而是理解它背后的设计思想:
不同大小的目标,应该交给不同尺度的特征图去处理。
这其实体现了一种非常经典的深度学习视觉思想:
利用网络不同层的特征表示能力,分工处理不同难度、不同尺度的问题。
所以多尺度锚框不只是"多放几组框",而是一种结构化地组织检测任务的方法。
12. 本节总结
这一节我们学习了多尺度锚框,核心内容可以总结为以下几点。
12.1 为什么需要多尺度锚框
因为真实图像中的目标大小差异很大,单一尺度锚框难以同时覆盖小目标和大目标。
12.2 多尺度锚框的核心思想
在不同尺度的特征图上生成不同尺度的锚框,用来检测不同大小的目标。
12.3 大特征图和小特征图的分工
-
大特征图:位置更密,适合小目标
-
小特征图:感受野更大,适合大目标
12.4 它是 SSD 的重要基础
SSD 正是利用多尺度特征图和多尺度锚框来实现高效检测的。
13. 学习感悟
多尺度锚框这一节,看起来不像 R-CNN、YOLO 那样"有名",但它其实非常关键。
因为它第一次让我们真正意识到:
目标检测不是在单一层次上完成的,而是需要结合不同层特征图的能力,去共同处理不同尺度目标。
这也是现代检测模型非常典型的设计思想。
从这个角度看,多尺度锚框其实不只是一个技巧,而是对"检测任务如何分层解决"的一次非常清晰的表达。