目录
《深入解析 MMDetection 中的 AnchorGenerator》
在目标检测领域,锚点(anchors)是一种常用的技术,用于在不同的特征图上生成可能包含目标的区域。MMDetection 中的AnchorGenerator
类就是用于生成这些锚点的重要工具。本文将深入剖析这个类的代码,帮助读者更好地理解其工作原理和功能。
一、类概述
AnchorGenerator
是一个标准的锚点生成器,用于基于锚点的二维目标检测器。它可以根据不同的参数生成不同尺度、比例和位置的锚点,并可以在多个特征级别上生成锚点网格。这个类的主要功能包括:
- 初始化锚点生成器的参数,包括步长、比例、尺度、基本尺寸、中心位置等。
- 生成基本锚点,根据给定的参数生成单个级别上的基本锚点。
- 生成网格锚点,在多个特征级别上生成网格锚点,即根据特征图的大小和步长,将基本锚点放置在特征图的每个位置上。
- 生成稀疏锚点,根据给定的索引生成稀疏锚点。
- 生成有效标志,确定哪些锚点在图像的有效区域内。
二、关键代码分析
1. 初始化函数
def __init__(self,
strides: Union[List[int], List[Tuple[int, int]]],
ratios: List[float],
scales: Optional[List[int]] = None,
base_sizes: Optional[List[int]] = None,
scale_major: bool = True,
octave_base_scale: Optional[int] = None,
scales_per_octave: Optional[int] = None,
centers: Optional[List[Tuple[float, float]]] = None,
center_offset: float = 0.,
use_box_type: bool = False) -> None:
# check center and center_offset
if center_offset!= 0:
assert centers is None, 'center cannot be set when center_offset' \
f'!=0, {centers} is given.'
if not (0 <= center_offset <= 1):
raise ValueError('center_offset should be in range [0, 1], '
f'{center_offset} is given.')
if centers is not None:
assert len(centers) == len(strides), \
'The number of strides should be the same as centers, got ' \
f'{strides} and {centers}'
# calculate base sizes of anchors
self.strides = [_pair(stride) for stride in strides]
self.base_sizes = [min(stride) for stride in self.strides
] if base_sizes is None else base_sizes
assert len(self.base_sizes) == len(self.strides), \
'The number of strides should be the same as base sizes, got ' \
f'{self.strides} and {self.base_sizes}'
# calculate scales of anchors
assert ((octave_base_scale is not None
and scales_per_octave is not None) ^ (scales is not None)), \
'scales and octave_base_scale with scales_per_octave cannot' \
' be set at the same time'
if scales is not None:
self.scales = torch.Tensor(scales)
elif octave_base_scale is not None and scales_per_octave is not None:
octave_scales = np.array(
[2**(i / scales_per_octave) for i in range(scales_per_octave)])
scales = octave_scales * octave_base_scale
self.scales = torch.Tensor(scales)
else:
raise ValueError('Either scales or octave_base_scale with '
'scales_per_octave should be set')
self.octave_base_scale = octave_base_scale
self.scales_per_octave = scales_per_octave
self.ratios = torch.Tensor(ratios)
self.scale_major = scale_major
self.centers = centers
self.center_offset = center_offset
self.base_anchors = self.gen_base_anchors()
self.use_box_type = use_box_type
在初始化函数中,设置了各种参数,并检查了参数的合法性。然后,计算了锚点的基本尺寸和尺度,并生成了基本锚点列表。
2.1 gen_base_anchors
函数
-
功能:这个函数用于生成多尺度的基础锚点(base anchors),每个特征层级对应一组基础锚点。
-
工作流程:
- 初始化一个空列表
multi_level_base_anchors
来存储不同层级的基础锚点。 - 遍历
self.base_sizes
列表,这个列表通常代表不同特征层级的基础尺寸。 - 对于每个基础尺寸:
- 如果
self.centers
不为None
,则从self.centers
中获取对应层级的中心坐标,否则将中心设为默认值(根据center_offset
和基础尺寸计算)。 - 调用
self.gen_single_level_base_anchors
函数生成该层级的基础锚点,并将其添加到multi_level_base_anchors
列表中。
- 如果
- 最后返回包含所有层级基础锚点的列表。
- 初始化一个空列表
2.2 gen_single_level_base_anchors
函数
-
功能:生成单个特征层级的基础锚点。
-
工作流程:
- 接收参数:
base_size
:单个特征层级的基础锚点尺寸。scales
:锚点的缩放比例。ratios
:锚点的高宽比。center
(可选):该层级基础锚点的中心坐标,如果为None
则使用默认计算方式。
- 初始化变量:
- 如果
center
为None
,根据center_offset
和基础尺寸计算锚点中心坐标x_center
和y_center
。
- 如果
- 计算高宽比相关变量:
- 计算高宽比的平方根得到
h_ratios
。 - 计算宽度比例为高宽比平方根的倒数得到
w_ratios
。
- 计算高宽比的平方根得到
- 根据
scale_major
的不同情况计算锚点宽度和高度:- 如果
scale_major
为真:ws
是通过基础尺寸乘以宽度比例(按列展开)再乘以缩放比例(按行展开),最后展平得到的张量,表示不同锚点的宽度。- 类似地,
hs
表示不同锚点的高度。
- 否则:
- 以另一种方式计算
ws
和hs
。
- 以另一种方式计算
- 如果
- 生成基础锚点张量:
- 通过锚点中心坐标和宽度、高度计算出四个坐标值,分别表示锚点的左上角和右下角坐标。
- 将这四个坐标值组成一个列表
base_anchors
。 - 使用
torch.stack
将这个列表沿着最后一个维度堆叠成一个张量并返回,这个张量代表该层级的基础锚点。
- 接收参数:
以给定的调试过程为例:
-
在
gen_base_anchors
函数中,遍历self.base_sizes
为[4, 8, 16, 32, 64]
,对于每个基础尺寸调用gen_single_level_base_anchors
生成对应层级的基础锚点。 -
在
gen_single_level_base_anchors
函数中,当处理基础尺寸为4
时:- 由于
center
为None
,计算出x_center = 0
和y_center = 0
。 - 计算出
h_ratios = tensor([0.7071, 1.0000, 1.4142])
和w_ratios = tensor([1.4142, 1.0000, 0.7071])
。 - 根据
scale_major
的情况(这里假设为真或假进行不同的计算),计算出ws = tensor([45.2548, 32.0000, 22.6274])
和hs = tensor([22.6274, 32.0000, 45.2548])
。 - 最终生成基础锚点张量为
tensor([[-22.6274, -11.3137, 22.6274, 11.3137], [-16.0000, -16.0000, 16.0000, 16.0000], [-11.3137, -22.6274, 11.3137, 22.6274]])
。
- 由于
config
:
anchor_generator=dict(
type='AnchorGenerator',
scales=[8],
ratios=[0.5, 1.0, 2.0],
strides=[4, 8, 16, 32, 64])
-
首先分析配置:
scales=[8]
表示锚点的缩放比例只有一个值 8。ratios=[0.5, 1.0, 2.0]
给出了三种不同的高宽比。strides=[4, 8, 16, 32, 64]
这个参数在这里暂时不直接参与锚点生成的计算,但可能在后续的其他操作中有作用,比如确定不同特征层级对应的步长等。
-
gen_base_anchors
函数执行过程:- 遍历
self.base_sizes
,这里假设self.base_sizes
与strides
相同,即[4, 8, 16, 32, 64]
。对于每个基础尺寸,调用gen_single_level_base_anchors
函数生成该层级的基础锚点。
- 遍历
-
gen_single_level_base_anchors
函数执行过程:-
以基础尺寸为 4 为例:
- 初始化
w = 4
,h = 4
,因为center
为None
,所以计算出x_center = self.center_offset * w = 0
,y_center = self.center_offset * h = 0
。 h_ratios = torch.sqrt(ratios)
,即计算三种高宽比的平方根,得到tensor([0.7071, 1.0000, 1.4142])
。w_ratios = 1 / h_ratios
,得到tensor([1.4142, 1.0000, 0.7071])
。- 由于只有一个缩放比例 8,且假设
scale_major
为真,计算过程如下:ws = (w * w_ratios[:, None] * scales[None, :]).view(-1)
,即4 * tensor([1.4142, 1.0000, 0.7071])[:, None] * tensor([8.])[None, :]
,得到tensor([45.2548, 32.0000, 22.6274])
。hs = (h * h_ratios[:, None] * scales[None, :]).view(-1)
,即4 * tensor([0.7071, 1.0000, 1.4142])[:, None] * tensor([8.])[None, :]
,得到tensor([22.6274, 32.0000, 45.2548])
。
- 生成基础锚点:
base_anchors = [x_center - 0.5 * ws, y_center - 0.5 * hs, x_center + 0.5 * ws, y_center + 0.5 * hs]
,得到[tensor([-22.6274, -16.0000, -11.3137]), tensor([-11.3137, -16.0000, -22.6274]), tensor([22.6274, 16.0000, 11.3137]), tensor([11.3137, 16.0000, 22.6274])]
。base_anchors = torch.stack(base_anchors, dim=-1)
,得到最终的锚点张量tensor([[-22.6274, -11.3137, 22.6274, 11.3137], [-16.0000, -16.0000, 16.0000, 16.0000], [-11.3137, -22.6274, 11.3137, 22.6274]])
。
- 初始化
-
对于其他基础尺寸(8、16、32、64),执行类似的计算过程,只是基础尺寸不同,会得到不同大小的锚点。
-
2. 生成基本锚点函数
def gen_base_anchors(self) -> List[Tensor]:
multi_level_base_anchors = []
for i, base_size in enumerate(self.base_sizes):
center = None
if self.centers is not None:
center = self.centers[i]
multi_level_base_anchors.append(
self.gen_single_level_base_anchors(
base_size,
scales=self.scales,
ratios=self.ratios,
center=center))
return multi_level_base_anchors
def gen_single_level_base_anchors(self,
base_size: Union[int, float],
scales: Tensor,
ratios: Tensor,
center: Optional[Tuple[float]] = None) \
-> Tensor:
w = base_size
h = base_size
if center is None:
x_center = self.center_offset * w
y_center = self.center_offset * h
else:
x_center, y_center = center
h_ratios = torch.sqrt(ratios)
w_ratios = 1 / h_ratios
if self.scale_major:
ws = (w * w_ratios[:, None] * scales[None, :]).view(-1)
hs = (h * h_ratios[:, None] * scales[None, :]).view(-1)
else:
ws = (w * scales[:, None] * w_ratios[None, :]).view(-1)
hs = (h * scales[:, None] * h_ratios[None, :]).view(-1)
base_anchors = [
x_center - 0.5 * ws, y_center - 0.5 * hs, x_center + 0.5 * ws,
y_center + 0.5 * hs
]
base_anchors = torch.stack(base_anchors, dim=-1)
return base_anchors
gen_base_anchors
函数生成多个级别的基本锚点列表,通过调用gen_single_level_base_anchors
函数生成每个级别的基本锚点。gen_single_level_base_anchors
函数根据给定的基本尺寸、尺度和比例,生成单个级别的基本锚点。
3. 生成网格锚点函数
def grid_priors(self,
featmap_sizes: List[Tuple],
dtype: torch.dtype = torch.float32,
device: DeviceType = 'cuda') -> List[Tensor]:
assert self.num_levels == len(featmap_sizes)
multi_level_anchors = []
for i in range(self.num_levels):
anchors = self.single_level_grid_priors(
featmap_sizes[i], level_idx=i, dtype=dtype, device=device)
multi_level_anchors.append(anchors)
return multi_level_anchors
def single_level_grid_priors(self,
featmap_size: Tuple[int, int],
level_idx: int,
dtype: torch.dtype = torch.float32,
device: DeviceType = 'cuda') -> Tensor:
base_anchors = self.base_anchors[level_idx].to(device).to(dtype)
feat_h, feat_w = featmap_size
stride_w, stride_h = self.strides[level_idx]
shift_x = torch.arange(0, feat_w, device=device).to(dtype) * stride_w
shift_y = torch.arange(0, feat_h, device=device).to(dtype) * stride_h
shift_xx, shift_yy = self._meshgrid(shift_x, shift_y)
shifts = torch.stack([shift_xx, shift_yy, shift_xx, shift_yy], dim=-1)
all_anchors = base_anchors[None, :, :] + shifts[:, None, :]
all_anchors = all_anchors.view(-1, 4)
if self.use_box_type:
all_anchors = HorizontalBoxes(all_anchors)
return all_anchors
grid_priors
函数生成多个级别的网格锚点列表,通过调用single_level_grid_priors
函数生成每个级别的网格锚点。single_level_grid_priors
函数根据给定的特征图大小、级别索引和数据类型,生成单个级别的网格锚点。
以下是对这两个函数的详细解析:
3.1 grid_priors
函数
-
函数功能:
- 这个函数用于在多个特征级别上生成网格锚点。它遍历每个特征级别,调用
single_level_grid_priors
函数生成该级别的网格锚点,并将结果收集到一个列表中返回。
- 这个函数用于在多个特征级别上生成网格锚点。它遍历每个特征级别,调用
-
参数解释:
featmap_sizes
(List[Tuple]
):一个包含多个元组的列表,每个元组表示一个特征图的大小,通常是(高度,宽度)。dtype
(torch.dtype
):锚点的数据类型,默认为torch.float32
。device
(DeviceType
):锚点将被放置的设备,可以是字符串表示的设备名称(如’cuda’)或torch.device
对象。
-
函数执行过程:
- 参数检查:
assert self.num_levels == len(featmap_sizes)
:检查锚点生成器的级别数量是否与输入的特征图大小列表的长度相等。
- 生成多级锚点:
multi_level_anchors = []
:创建一个空列表,用于存储多个级别的锚点。for i in range(self.num_levels)
:遍历每个特征级别。anchors = self.single_level_grid_priors(featmap_sizes[i], level_idx=i, dtype=dtype, device=device)
:调用single_level_grid_priors
函数生成当前级别的网格锚点。multi_level_anchors.append(anchors)
:将当前级别的锚点添加到列表中。
- 返回结果:
return multi_level_anchors
:返回包含多个级别锚点的列表。
- 参数检查:
3.2single_level_grid_priors
函数
-
函数功能:
- 这个函数用于生成单个特征级别上的网格锚点。它根据输入的特征图大小、级别索引、数据类型和设备,生成该级别上的所有锚点。
-
参数解释:
featmap_size
(Tuple[int, int]
):当前特征图的大小,是一个元组(高度,宽度)。level_idx
(int
):当前特征图的级别索引。dtype
(torch.dtype
):锚点的数据类型,默认为torch.float32
。device
(DeviceType
):锚点将被放置的设备,可以是字符串表示的设备名称(如’cuda’)或torch.device
对象。
-
函数执行过程:
- 获取基本锚点:
base_anchors = self.base_anchors[level_idx].to(device).to(dtype)
:从锚点生成器的基本锚点列表中获取当前级别的基本锚点,并将其移动到指定的设备和数据类型。
- 获取特征图尺寸和步长:
feat_h, feat_w = featmap_size
:解包输入的特征图大小,得到高度和宽度。stride_w, stride_h = self.strides[level_idx]
:获取当前级别的步长。
- 生成位移张量:
shift_x = torch.arange(0, feat_w, device=device).to(dtype) * stride_w
:生成在 x 方向上的位移张量,范围从 0 到特征图宽度,步长为当前级别的 x 方向步长,并转换为指定的数据类型和设备。shift_y = torch.arange(0, feat_h, device=device).to(dtype) * stride_h
:生成在 y 方向上的位移张量,范围从 0 到特征图高度,步长为当前级别的 y 方向步长,并转换为指定的数据类型和设备。
- 生成网格:
shift_xx, shift_yy = self._meshgrid(shift_x, shift_y)
:使用_meshgrid
函数生成二维网格的 x 和 y 坐标。shifts = torch.stack([shift_xx, shift_yy, shift_xx, shift_yy], dim=-1)
:将 x 和 y 坐标堆叠成一个形状为(网格点数,4)的张量,表示四个角的位移。
- 生成所有锚点:
all_anchors = base_anchors[None, :, :] + shifts[:, None, :]
:将基本锚点广播到与位移张量相同的形状,然后将它们相加,得到所有可能位置的锚点。all_anchors = all_anchors.view(-1, 4)
:将锚点张量重塑为形状为(网格点数 * 基本锚点数,4)的张量。
- 应用框类型(可选):
- 如果
self.use_box_type
为真,将锚点张量转换为特定的框类型。
- 如果
- 返回结果:
return all_anchors
:返回当前特征级别上的所有锚点张量。
- 获取基本锚点:
4. 生成稀疏锚点函数
def sparse_priors(self,
prior_idxs: Tensor,
featmap_size: Tuple[int, int],
level_idx: int,
dtype: torch.dtype = torch.float32,
device: DeviceType = 'cuda') -> Tensor:
height, width = featmap_size
num_base_anchors = self.num_base_anchors[level_idx]
base_anchor_id = prior_idxs % num_base_anchors
x = (prior_idxs //
num_base_anchors) % width * self.strides[level_idx][0]
y = (prior_idxs // width //
num_base_anchors) % height * self.strides[level_idx][1]
priors = torch.stack([x, y, x, y], 1).to(dtype).to(device) + \
self.base_anchors[level_idx][base_anchor_id, :].to(device)
return priors
sparse_priors
函数根据给定的索引、特征图大小和级别索引,生成稀疏锚点。
5. 生成有效标志函数
def valid_flags(self,
featmap_sizes: List[Tuple[int, int]],
pad_shape: Tuple,
device: DeviceType = 'cuda') -> List[Tensor]:
assert self.num_levels == len(featmap_sizes)
multi_level_flags = []
for i in range(self.num_levels):
anchor_stride = self.strides[i]
feat_h, feat_w = featmap_sizes[i]
h, w = pad_shape[:2]
valid_feat_h = min(int(np.ceil(h / anchor_stride[1])), feat_h)
valid_feat_w = min(int(np.ceil(w / anchor_stride[0])), feat_w)
flags = self.single_level_valid_flags((feat_h, feat_w),
(valid_feat_h, valid_feat_w),
self.num_base_anchors[i],
device=device)
multi_level_flags.append(flags)
return multi_level_flags
def single_level_valid_flags(self,
featmap_size: Tuple[int, int],
valid_size: Tuple[int, int],
num_base_anchors: int,
device: DeviceType = 'cuda') -> Tensor:
feat_h, feat_w = featmap_size
valid_h, valid_w = valid_size
assert valid_h <= feat_h and valid_w <= feat_w
valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device)
valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device)
valid_x[:valid_w] = 1
valid_y[:valid_h] = 1
valid_xx, valid_yy = self._meshgrid(valid_x, valid_y)
valid = valid_xx & valid_yy
valid = valid[:, None].expand(valid.size(0),
num_base_anchors).contiguous().view(-1)
return valid
valid_flags
函数生成多个级别的有效标志列表,通过调用single_level_valid_flags
函数生成每个级别的有效标志。single_level_valid_flags
函数根据给定的特征图大小、有效大小和基本锚点数量,生成单个级别的有效标志。
以下是对这两个函数的详细解析:
5.1 valid_flags
函数
-
函数功能:
- 这个函数用于生成多个特征级别上锚点的有效标志列表。它遍历每个特征级别,调用
single_level_valid_flags
函数生成该级别的有效标志,并将结果收集到一个列表中返回。
- 这个函数用于生成多个特征级别上锚点的有效标志列表。它遍历每个特征级别,调用
-
参数解释:
featmap_sizes
(List[Tuple[int, int]]
):一个包含多个元组的列表,每个元组表示一个特征图的大小,通常是(高度,宽度)。pad_shape
(Tuple
):图像的填充形状,通常是(高度,宽度,…)。device
(DeviceType
):有效标志将被放置的设备,可以是字符串表示的设备名称(如’cuda’)或torch.device
对象。
-
函数执行过程:
- 参数检查:
assert self.num_levels == len(featmap_sizes)
:检查锚点生成器的级别数量是否与输入的特征图大小列表的长度相等。
- 生成多级有效标志:
multi_level_flags = []
:创建一个空列表,用于存储多个级别的有效标志。for i in range(self.num_levels)
:遍历每个特征级别。anchor_stride = self.strides[i]
:获取当前级别的步长。feat_h, feat_w = featmap_sizes[i]
:解包当前特征图的大小,得到高度和宽度。h, w = pad_shape[:2]
:解包图像的填充形状,获取高度和宽度。valid_feat_h = min(int(np.ceil(h / anchor_stride[1])), feat_h)
:计算当前级别上在高度方向上的有效特征图高度,取图像高度除以 y 方向步长的向上取整结果与当前特征图高度的较小值。valid_feat_w = min(int(np.ceil(w / anchor_stride[0])), feat_w)
:计算当前级别上在宽度方向上的有效特征图宽度,取图像宽度除以 x 方向步长的向上取整结果与当前特征图宽度的较小值。flags = self.single_level_valid_flags((feat_h, feat_w), (valid_feat_h, valid_feat_w), self.num_base_anchors[i], device=device)
:调用single_level_valid_flags
函数生成当前级别的有效标志。multi_level_flags.append(flags)
:将当前级别的有效标志添加到列表中。
- 返回结果:
return multi_level_flags
:返回包含多个级别有效标志的列表。
- 参数检查:
5.2 single_level_valid_flags
函数
-
函数功能:
- 这个函数用于生成单个特征图上锚点的有效标志。它根据输入的特征图大小、有效大小、基本锚点数量和设备,确定每个锚点在特征图上是否有效,并返回一个布尔张量表示有效标志。
-
参数解释:
featmap_size
(Tuple[int, int]
):特征图的大小,是一个元组(高度,宽度)。valid_size
(Tuple[int, int]
):特征图的有效大小,是一个元组(有效高度,有效宽度)。num_base_anchors
(int
):基本锚点的数量。device
(DeviceType
):有效标志将被放置的设备,可以是字符串表示的设备名称(如’cuda’)或torch.device
对象。
-
函数执行过程:
- 获取特征图和有效大小:
feat_h, feat_w = featmap_size
:解包输入的特征图大小,得到高度和宽度。valid_h, valid_w = valid_size
:解包输入的有效大小,得到有效高度和有效宽度。assert valid_h <= feat_h and valid_w <= feat_w
:确保有效大小不超过特征图大小。
- 生成 x 和 y 方向的有效标志张量:
valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device)
:创建一个长度为特征图宽度的全零布尔张量,表示 x 方向的有效标志。valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device)
:创建一个长度为特征图高度的全零布尔张量,表示 y 方向的有效标志。valid_x[:valid_w] = 1
:将 x 方向有效标志张量的前有效宽度个元素设置为 1,表示这些位置是有效的。valid_y[:valid_h] = 1
:将 y 方向有效标志张量的前有效高度个元素设置为 1,表示这些位置是有效的。
- 生成二维有效标志:
valid_xx, valid_yy = self._meshgrid(valid_x, valid_y)
:使用_meshgrid
函数生成二维网格的有效标志。valid = valid_xx & valid_yy
:对 x 和 y 方向的有效标志进行逻辑与操作,得到二维有效标志。
- 扩展有效标志:
valid = valid[:, None].expand(valid.size(0), num_base_anchors).contiguous().view(-1)
:将二维有效标志在第二个维度上扩展基本锚点数量倍,然后重塑为一维张量,表示每个锚点的有效标志。
- 返回结果:
return valid
:返回单个特征级别上每个锚点的有效标志张量。
- 获取特征图和有效大小:
6. 字符串表示函数
def __repr__(self) -> str:
indent_str = ' '
repr_str = self.__class__.__name__ + '(\n'
repr_str += f'{indent_str}strides={self.strides},\n'
repr_str += f'{indent_str}ratios={self.ratios},\n'
repr_str += f'{indent_str}scales={self.scales},\n'
repr_str += f'{indent_str}base_sizes={self.base_sizes},\n'
repr_str += f'{indent_str}scale_major={self.scale_major},\n'
repr_str += f'{indent_str}octave_base_scale='
repr_str += f'{self.octave_base_scale},\n'
repr_str += f'{indent_str}scales_per_octave='
repr_str += f'{self.scales_per_octave},\n'
repr_str += f'{indent_str}num_levels={self.num_levels}\n'
repr_str += f'{indent_str}centers={self.centers},\n'
repr_str += f'{indent_str}center_offset={self.center_offset})'
return repr_str
__repr__
函数返回一个字符串,表示AnchorGenerator
对象的参数。
三、结语
AnchorGenerator
类是 MMDetection 中用于生成锚点的重要工具。它可以根据不同的参数生成不同尺度、比例和位置的锚点,并可以在多个特征级别上生成锚点网格和有效标志。通过深入理解这个类的代码,我们可以更好地掌握 MMDetection 中锚点生成的原理和方法,为目标检测任务提供更好的支持。