动手学深度学习——锚框(带代码详解)
1. 前言
在前面的内容中,我们已经知道:
-
物体检测不仅要识别“是什么”,还要定位“在哪里”
-
边界框用于表示目标位置
-
数据集中的标签需要同时包含类别和边界框信息
但新的问题马上就出现了:
一张图片中目标的位置、大小、形状都不固定,模型到底该从哪里开始找目标?
如果让模型直接从整张图中“凭空”预测边界框,难度会非常大。
因此,目标检测中引入了一种非常重要的机制:锚框(Anchor Box)。
锚框的核心思想是:
先在图像上预设大量可能包含目标的候选框,再由模型判断哪些框中有目标,并进一步修正它们的位置。
这一节我们不仅要理解锚框的概念,还要结合《动手学深度学习》中的代码,真正看懂锚框是怎么生成的。
2. 什么是锚框
锚框,英文叫 Anchor Box,也常被称为先验框。
它指的是:
在图像的每个像素位置(或者特征图的每个位置)上,预先放置若干个不同大小、不同长宽比的矩形框,用来作为候选检测框。
你可以把它理解成:
-
我们先在图像上“撒”很多框
-
这些框形状各不相同
-
真实目标总会和某些框比较接近
-
模型再从这些框中筛选并修正出最终结果
所以:
锚框不是最终结果,而是检测的起点。
3. 为什么需要锚框
3.1 目标位置不固定
图像中的目标可能出现在任意位置。
如果没有参考框,模型就必须直接回归所有可能坐标,学习难度很高。
有了锚框后,模型就不需要“从零开始”,而是在已有候选框基础上微调。
3.2 目标大小不同
有些目标很小,有些目标很大。
如果只用一种固定大小的框,就很难同时适应不同尺度的目标。
因此锚框通常会设置多个尺度。
3.3 目标形状不同
例如:
-
行人通常偏瘦高
-
汽车通常偏宽
-
某些物体接近正方形
所以锚框不仅要有不同大小,还要有不同的宽高比。
3.4 将检测问题转化为更容易学习的问题
锚框机制建立后,模型只需要做两件事:
-
分类:这个锚框里有没有目标,属于什么类别
-
回归:这个锚框应该往哪里偏移,才能更贴近真实框
这样一来,复杂的检测问题就被转成了“候选框分类 + 候选框回归”。
4. 李沐代码中锚框的核心函数
在《动手学深度学习》中,生成锚框使用的核心函数是:
d2l.multibox_prior
这个函数的作用可以概括为一句话:
以输入特征图的每个像素位置为中心,生成一组不同尺度和宽高比的锚框。
5. 先看一段核心代码
先看最经典的一段代码:
import torch
from d2l import torch as d2l
img = d2l.plt.imread('catdog.jpg')
h, w = img.shape[:2]
X = torch.rand(size=(1, 3, h, w))
Y = d2l.multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape
输出结果通常是:
torch.Size([1, h*w*5, 4])
如果书里的图片大小是 561 × 728,那么结果就是:
torch.Size([1, 2042040, 4])
6. 这段代码到底在干什么
我们一行一行看。
6.1 读取图片尺寸
img = d2l.plt.imread('catdog.jpg')
h, w = img.shape[:2]
这里读取图片后,取出:
-
h:图片高度 -
w:图片宽度
假设:
-
h = 561 -
w = 728
6.2 构造一个输入张量
X = torch.rand(size=(1, 3, h, w))
这里并不是要真的拿随机数做检测,而是因为 multibox_prior 只关心输入张量的空间尺寸,也就是高和宽,不关心里面具体是什么数值。
这个张量形状是:
(批量大小, 通道数, 高, 宽)
也就是:
(1, 3, h, w)
这和卷积神经网络常见输入格式一致。
6.3 生成锚框
Y = d2l.multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
这里设置了:
-
sizes=[0.75, 0.5, 0.25] -
ratios=[1, 2, 0.5]
意思是:
在每个像素位置,都生成若干个不同尺度、不同宽高比的锚框。
7. 为什么每个像素位置会生成 5 个锚框
这是最容易考的点,也是最容易懵的地方。
先看参数:
-
sizes有 3 个:[0.75, 0.5, 0.25] -
ratios有 3 个:[1, 2, 0.5]
很多人第一反应会觉得:
不是应该 3 × 3 = 9 个锚框吗?
其实不是。
在李沐这套实现中,每个像素位置生成的锚框个数是:
len(sizes) + len(ratios) - 1
所以这里就是:
3 + 3 - 1 = 5
8. 为什么是 m + n - 1
设:
-
m = len(sizes) -
n = len(ratios)
生成方式不是所有尺度和所有宽高比全部两两组合,
而是采用下面这种策略:
-
先固定
ratio = 1,配合所有sizes -
再固定
size = sizes[0],配合其余所有ratios
具体到这里:
第一组:固定 ratio = 1
配合所有尺度:
-
(0.75, 1) -
(0.5, 1) -
(0.25, 1)
共 3 个。
第二组:固定 size = 0.75
配合剩余宽高比:
-
(0.75, 2) -
(0.75, 0.5)
共 2 个。
总共就是:
3 + 2 = 5
所以每个像素位置生成 5 个锚框。
9. 输出形状为什么是 [1, h*w*5, 4]
现在就能理解这个输出了:
Y.shape = (1, h*w*5, 4)
含义如下:
第一个维度 1
表示 batch size,也就是这里只有 1 张图。
第二个维度 h*w*5
表示总锚框数量:
-
一共有
h*w个像素位置 -
每个位置生成 5 个锚框
所以总数为:
h * w * 5
第三个维度 4
每个锚框用 4 个数表示:
(xmin, ymin, xmax, ymax)
所以最后维度是 4。
10. 以书中图片为例计算
若图片大小为:
h = 561
w = 728
则总像素点数为:
561 * 728 = 408408
每个像素生成 5 个锚框,所以总锚框数为:
408408 * 5 = 2042040
所以最终输出形状就是:
(1, 2042040, 4)
这也正是书里的结果。
11. 如何查看某个像素位置的锚框
代码通常还会继续写成:
boxes = Y.reshape(h, w, 5, 4)
boxes[250, 250, 0, :]
这里的作用是:
把原来扁平化保存的所有锚框,重新整理成更容易理解的形式:
(h, w, 每个位置的锚框数, 4)
也就是:
(h, w, 5, 4)
这样:
-
boxes[i, j, k, :]
就表示第i行、第j列像素位置的第k个锚框。
例如:
boxes[250, 250, 0, :]
就是查看 (250, 250) 这个位置处的第 1 个锚框。
12. 锚框坐标为什么会出现负数或大于 1
查看某些锚框时,经常会看到类似这样的结果:
tensor([-0.08, -0.13, 0.52, 0.69])
甚至可能出现小于 0 或大于 1 的值。
这并不奇怪,因为这里的坐标通常是归一化坐标,相对于整张图宽高而言:
-
左上角是
(0, 0) -
右下角是
(1, 1)
如果某个锚框中心靠近边缘,而锚框本身又比较大,那么它的一部分就可能“伸出图像边界”,于是:
-
左上角可能小于 0
-
右下角可能大于 1
这是正常现象,后续处理中通常会裁剪或者在匹配时处理。
13. 锚框的可视化代码
为了更直观地理解锚框,通常会把某个像素位置生成的多个锚框画出来。
常见代码如下:
bbox_scale = torch.tensor((w, h, w, h))
fig = d2l.plt.imshow(img)
d2l.show_bboxes(fig.axes,
boxes[250, 250, :, :] * bbox_scale,
['s=0.75, r=1', 's=0.5, r=1', 's=0.25, r=1',
's=0.75, r=2', 's=0.75, r=0.5'])
d2l.plt.show()
14. 这段可视化代码怎么理解
14.1 bbox_scale
bbox_scale = torch.tensor((w, h, w, h))
因为 boxes 中保存的是归一化坐标,范围大多在 0 到 1 附近。
而画图时需要真实像素坐标,所以这里乘上:
(w, h, w, h)
把归一化坐标还原到像素尺度。
14.2 boxes[250, 250, :, :]
表示:
-
取
(250, 250)这个像素位置 -
该位置所有 5 个锚框
-
每个锚框有 4 个坐标
14.3 d2l.show_bboxes(...)
这个函数的作用是:
-
把多个边界框画到图片上
-
同时标注每个框对应的尺寸和宽高比
这样我们就能非常直观地看到:
-
大尺度锚框更大
-
小尺度锚框更小
-
不同宽高比的锚框形状不同
15. 锚框中的“尺度”和“宽高比”到底表示什么
这是理解锚框的关键。
15.1 尺度 size
尺度控制的是锚框整体大小。
值越大,框越大;值越小,框越小。
例如:
-
0.75:较大 -
0.5:中等 -
0.25:较小
15.2 宽高比 ratio
宽高比控制的是框的形状。
例如:
-
ratio = 1:正方形 -
ratio = 2:宽高比更宽 -
ratio = 0.5:更高更瘦
所以:
尺度决定“多大”,宽高比决定“长什么样”。
16. 锚框的本质理解
到这里可以把锚框机制总结成一句非常重要的话:
锚框是在每个位置预先放置的一组不同形状参考框,用来覆盖各种可能出现的目标。
真实目标出现时,总会有一些锚框和它比较接近。
模型再学习:
-
哪些锚框应该保留
-
它们属于什么类别
-
它们还需要怎样微调
所以锚框本质上是一种“先验假设”。
17. 锚框机制的优点
17.1 让检测有了明确起点
模型不用从零开始瞎猜,而是基于候选框学习。
17.2 能覆盖不同大小目标
通过多尺度锚框,可以检测大目标和小目标。
17.3 能覆盖不同形状目标
通过不同宽高比,可以适配行人、汽车等不同形状物体。
18. 锚框机制的缺点
18.1 数量太多
每个位置都生成多个锚框,整张图下来会有海量候选框,计算成本很高。
18.2 需要人工设计参数
尺度设多少、比例设多少,往往需要经验。
18.3 不够灵活
如果真实目标和预设锚框差异太大,匹配效果就不好。
这也是后来很多 anchor-free 检测方法出现的原因。
19. 本节总结
这一节我们结合代码,系统学习了锚框的生成过程,重点包括:
19.1 锚框是什么
锚框是预先生成的候选框,不是最终检测框。
19.2 为什么需要锚框
因为目标位置、大小、形状都不固定,锚框为检测提供了参考模板。
19.3 multibox_prior 的作用
它会在每个像素位置生成多个不同尺度、不同宽高比的锚框。
19.4 每个像素位置生成多少个锚框
数量公式为:
len(sizes) + len(ratios) - 1
19.5 输出形状如何理解
(1, h*w*每位置锚框数, 4)
其中最后一个 4 表示一个锚框的四个坐标。
20. 学习感悟
锚框这一节,往往是目标检测真正开始“上强度”的地方。
因为从这里开始,我们不再只是看图片和类别,而是开始考虑:
-
每个位置生成多少候选框
-
候选框尺度怎么设计
-
宽高比怎么选择
-
框与真实目标如何匹配
这些问题都比图像分类更复杂,但也更能体现目标检测的核心思想。
如果说分类任务是在问:
“这张图是什么?”
那么锚框机制已经开始在问:
“目标可能在哪?应该从哪些候选位置开始找?”
这正是目标检测和普通分类任务本质不同的地方。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)