文章目录
为什么要选择mmdet3d?
现在很多最新的领域上都是基于mmdet3d上面制作,所以我们要利用好的工具,减少自己的研究时间,不要再制造轮子了。
因为mmdet3d,是在 mmdetection里面上面改的房子,所以我们先来熟悉一下mmdetection的操作
register 机制 和 hook 机制
MMDetection的函数注册机制:
- 创建自定义类:首先,用户需创建一个类,该类实现了所需的自定义功能。
- 注册类:将新建的类注册到一个特定的查询表中,使用
register_module
实现这一步骤。 - 配置初始化参数:在MMDetection的配置文件中明确指定该类模块的初始化参数。
- 实例化模块:利用
build_from_cfg
函数,根据配置参数对该模块进行实例化操作。 - 执行功能:使用实例化后的对象执行定义的功能函数,完成特定任务。
MMDetection的Hook机制:
- 定义Hook类:定义一个类,该类继承自Hook基类,以便使用Hook机制。
- 重写函数:根据自定 义Hook的需求,选择性地重写Hook基类中的一些函数,以实现特定的逻辑处理。
- 注册Hook模块:将自定义的Hook类注册到
HOOKS
查询表中,使用register_module
完成注册。 - 实例化并注册Hook:实例化自定义的Hook模块,并将其注册到Runner中,这一步骤通过
register_hook
函数完成。 - 调用Hook函数:在适当的时机,使用回调函数调用之前重写的Hook函数,以执行预定的逻辑。
参考地址: https://blog.csdn.net/qq_16137569/article/details/121316235
SurroundOcc Occ-head 代码
SurroundOcc代码库的整体架构提供了一个清晰的框架,旨在处理BEV(鸟瞰视图)/Occupancy(占用)等任务,其结构设计允许用户快速理解和应用到其他相关代码库中。以下是对其主要组成部分的详细解读:
-
docs(文档):
- 该文件夹包含了一系列说明文档,目的是为了帮助用户理解项目的整体设计、功能以及如何使用各种功能。
-
projects(项目):
- 主要包含各种配置文件(config),这些配置文件定义了训练和测试任务的pipeline。通过这些配置文件,用户可以轻松设定和调整实验的参数、模型结构以及数据处理流程等。
-
mmdet3d_plugin(插件):
- 这是一种典型的实现方法,旨在通过插件方式实现代码的模块化和可插拔性。这种设计使得用户可以在不干扰mmdet3d原始代码库的基础上,加入新的功能实现。具体而言,插件中定义了一系列基于mmdet3d的新实现,例如:
- 数据集的Pipeline和Samplers:根据特定任务需求对数据预处理和采样策略进行定制化改动。
- 模型的Backbone和Hooks:引入新的网络结构或调整训练过程中的特定行为。
- SurroundOcc任务的Head/Loss/Hooks等新增内容:为SurroundOcc任务特别设计的模型头、损失函数以及钩子函数等。
- 这是一种典型的实现方法,旨在通过插件方式实现代码的模块化和可插拔性。这种设计使得用户可以在不干扰mmdet3d原始代码库的基础上,加入新的功能实现。具体而言,插件中定义了一系列基于mmdet3d的新实现,例如:
-
tools(工具):
- 包含了一系列实用脚本,如数据分析、数据转换、模型转换和可视化等。这个文件夹下的工具主要是用来处理工程中的一些辅助任务,使得项目管理更加方便高效。
通过上述组成部分的协同工作,SurroundOcc代码库不仅为用户提供了一个灵活、可扩展的研究和开发环境,同时也展示了在BEV/Occupancy等视觉任务中应用深度学习的一种有效方法。
提取2D 特征
采用卷积的方式,通过FPN将图像特征提取出来
self.transfer_conv = nn.ModuleList() # 创建一个可以存储神经网络模块的容器
norm_cfg = dict(type='GN', num_groups=16, requires_grad=True) # 归一化配置 (Group Normalization)
conv_cfg = dict(type='Conv2d', bias=True) # 卷积层配置
for i in range(self.fpn_level): # 遍历FPN的层数
transfer_layer = build_conv_layer(
conv_cfg,
in_channels=self.img_channels[i], # 输入通道数
out_channels=self.embed_dims[i], # 输出通道数
kernel_size=1, # 卷积核大小
stride=1 # 卷积步长
) # 构建 1x1 卷积层
transfer_block = nn.Sequential(transfer_layer, # 构建转换块
nn.ReLU(inplace=True)) # ReLU 激活函数
self.transfer_conv.append(transfer_block) # 将转换块加入容器
将2D的特征转化为3D的特征
采用了deformer-able transform的方式
def _init_layers(self):
self.transformer = nn.ModuleList() # 创建一个存储 Transformer 模块的容器
for i in range(self.fpn_level):
transformer = copy.deepcopy(self.transformer_template) # 深拷贝一份 Transformer 模板
# 调整 embedding 维度
transformer.embed_dims = transformer.embed_dims[i]
# 调整可变形注意力层的采样点数
transformer.encoder.transformerlayers.attn_cfgs[0].deformable_attention.num_points = \
self.transformer_template.encoder.transformerlayers.attn_cfgs[0].deformable_attention.num_points[i]
# 调整前馈神经网络的通道数
transformer.encoder.transformerlayers.feedforward_channels = \
self.transformer_template.encoder.transformerlayers.feedforward_channels[i]
# 调整其他 embedding 维度 (重复多次) ...
# 调整编码器中的 Transformer 层数
transformer.encoder.num_layers = self.transformer_template.encoder.num_layers[i]
transformer_i = build_transformer(transformer) # 构建 Transformer 实例
self.transformer.append(transformer_i) # 将 Transformer 添加到容器
Surroundocc 流程图里的 3D volume features生成的部分
volume_embed_reshape = [] # 创建一个空列表,用于存储重塑后的 volume_embed
for i in range(self.fpn_level):
volume_h = self.volume_h[i] # 获取 volume_h, volume_w, volume_z
volume_w = self.volume_w[i]
volume_z = self.volume_z[i]
# 重塑 volume_embed,并转换维度顺序,之后加入列表
volume_embed_reshape_i = volume_embed[i].reshape(bs, volume_z, volume_h, volume_w, -1).permute(0, 4, 3, 2, 1)
volume_embed_reshape.append(volume_embed_reshape_i)
outputs = [] # 创建空列表,存储解码块的输出
result = volume_embed_reshape.pop() # 弹出重塑后的 volume_embed 的最后一个元素
for i in range(len(self.deblocks)):
result = self.deblocks[i](result) # 使用解码块处理 result
if i in self.out_indices:
outputs.append(result) # 将 result 添加到 outputs 列表
elif i < len(self.deblocks) - 2:
volume_embed_temp = volume_embed_reshape.pop() # 弹出重塑后的 volume_embed 元素
result = result + volume_embed_temp # 将 result 与 volume_embed_temp 相加
# 预测占用率(occupancy)
occ_preds = []
for i in range(len(outputs)):
occ_pred = self.occ[i](outputs[i]) # 使用 self.occ 进行占用率预测
occ_preds.append(occ_pred)
outs = {
'volume_embed': volume_embed,
'occ_preds': occ_preds,
} # 构造输出字典
代码具体位置