在BEV空间下进行视觉任务逐渐成为自动驾驶中的技术主流,为了搞懂如何在BEV下进行视觉任务,打算利用BEVFormer这个项目来理解其步骤,本文为BEVFormer的运行以及整体框架的梳理(源码看的有点乱了),后续如果源码看的比较熟练了,再准备出一个比较详细的注释。
BEVFormer源码:https://github.com/fundamentalvision/BEVFormer
BEVFormer学习文章:https://zhuanlan.zhihu.com/p/543335939
BEVFormer学习视频:https://www.bilibili.com/video/BV1rK411o7PS/?spm_id_from=333.788&vd_source=2055b62125c0277a0d6878f41c89fec2
本人也是初学者,欢迎正在学习或者想学习BEV模型的朋友加入交流群一起讨论、学习论文或者代码实现中的问题 ,v:Rex1586662742,q群:468713665
学习一个项目的必经之路首先是要将这个项目运行起来,建议完全按照官方的安装环境的方式,避免发生问题,装完环境后,按照官方的命令运行即可。那么如何进行debug来逐行进行查看呢?我使用的是pycharm进行debug,可以分为如下两个步骤
sudo ln -s /home/***/miniconda3/envs/bev/lib/python3.8/site-packages/torch/distributed/launch.py ./
--nproc_per_node=1
--master_port=29503
/data/rex/BEV/BEVFormer-master/tools/test.py
/data/rex/BEV/BEVFormer-master/projects/configs/bevformer/bevformer_tiny.py
/data/rex/BEV/bevformer_tiny_epoch_24.pth
--launcher
pytorch
--eval
bbox
print(os.getcwd())
sys.path.append(os.getcwd())
然后将项目里面的data数据集路径链接到os.getcwd()路径下面下面,运行时就不会报错了。
按照惯例,首先还是要放一张论文中的示意图来进行说明,下图中,主要分为三个 部分,最左边的backbone,中间×6 的encoder,中间上面的Det/Seg Heads。
第一部分就是ResNet + FPN,BEVFormer主要的主要实在第二部分encoder进行了创新,即Temporal Self-Attention,Spatial Cross-Attention。
本文采用的是tiny模型进行测试,几个模型之间的不同点主要在于bev_query的大小以及FPN的多尺度特征个数配置文件为projects/configs/bevformer/bevformer_tiny.py,模型的网络结构在此进行定义,运行时,首先会对下面的模块进行注册,从上到下基本上就是forward的步骤了。
model = dick(
type='BEVFormer',
...,
# 主干网络
img_backbone = dict(
type='ResNet',
...
)
# 提取不同尺度的特征
img_neck=dict(
type='FPN',
...
)
# 编解码
pts_bbox_head = dict(
type='BEVFormerHead',
...
transformer=dict(
type='PerceptionTransformer',
...
# 编码网络
encoder=dict(
type='BEVFormerEncoder',
...
# 单个block 推理时将会重复6次
transformerlayers=dict(
type='BEVFormerLayer',
attn_cfgs=[
dict(
type='TemporalSelfAttention'
...
),
dict(
type='SpatialCrossAttention',
deformable_attention=dict(
type='MSDeformableAttention3D'
...
)
)
]
)
)
# 解码网络
decoder=dict(
type='DetectionTransformerDecoder',
...
# decode block
transformerlayers=dict(
type='DetrTransformerDecoderLayer',
attn_cfgs=[
dict(
type='MultiheadAttention',
),
dict(
type='CustomMSDeformableAttention',
...
)
]
)
)
)
bbox_coder = dict(
type='NMSFreeCoder'
...
)
# 可学习的位置编码
positional_encoding = dict(
type='LearnedPositionalEncoding',
...
)
)
能够进行debug后,就可以逐行代码进行查看变量的shape了,由于该项目涉及了很多模块,而且是用openmmlab实现的,刚接触时会有点绕,于是通过多次调试,我记录了推理的大致流程,基本上可以按下面的数字依次进行。
1、tools/test.py
outputs = custom_multi_gpu_test(model, data_loader, args.tmpdir,args.gpu_collect)
# 进入到projects/mmdet3d_plugin/bevformer/apis/test.py
2、projects/mmdet3d_plugin/bevformer/apis/test.py
def custom_multi_gpu_test(...):
...
for i, data in enumerate(data_loader):
with torch.no_grad():
result = model(return_loss=False, rescale=True, **data)
# 进入到 projects/mmdet3d_plugin/bevformer/detectors/bevformer.py
...
3、projects/mmdet3d_plugin/bevformer/detectors/bevformer.py
class BEVFormer(...):
def forward(...):
if return_loss:
return self.forward_train(**kwargs)
else:
return self.forward_test(**kwargs)
# 进入到下面 self.forward_test
def forward_test(...):
...
# forward
new_prev_bev, bbox_results = self.simple_test(...)
# 进入到下面 self.simple_test
...
def simple_test(...):
# self.extract_feat 主要包括两个步骤 img_backbone、img_neck,通过卷积提取特征
# 网络为resnet + FPN
# 如果是base模型,img_feats 为四个不同尺度的特征层
# 如果是small、tiny,img_feats 为一个尺度的特征层
img_feats = self.extract_feat(img=img, img_metas=img_metas)
# Temproral Self-Attention + Spatial Cross-Attention
new_prev_bev, bbox_pts = self.simple_test_pts(img_feats, img_metas, prev_bev, rescale=rescale)
# 进入到下面 self.simple_test_pts
def simple_test_pts(...):
# 对特征层进行编解码
outs = self.pts_bbox_head(x, img_metas, prev_bev=prev_bev)
# 进入到 projects/mmdet3d_plugin/bevformer/dense_heads/bevformer_head.py
4、projects/mmdet3d_plugin/bevformer/dense_heads/bevformer_head.py
class BEVFormerHead(DETRHead):
def forward(...):
...
if only_bev:
...
else:
outputs = self.transformer(...)
# 进入到 projects/mmdet3d_plugin/bevformer/modules/transformer.py
for lvl in range(hs.shape[0]):
# 类别
outputs_class = self.cls_branches[lvl](hs[lvl])
# 回归框信息
tmp = self.reg_branches[lvl](hs[lvl])
outs = ...
return out
# 返回到 projects/mmdet3d_plugin/bevformer/detectors/bevformer.py simple_test_pts函数中
5、projects/mmdet3d_plugin/bevformer/modules/transformer.py
class PerceptionTransformer(...):
def forward(...):
# 获得bev特征
bev_embed = self.get_bev_features(...)
def get_bev_features(...):
# 获得bev特征 block * 6
bev_embed = self.encoder(...)
# 进入到projects/mmdet3d_plugin/bevformer/modules/encoder.py
...
# decoder
inter_states, inter_references = self.decoder(...)
# 进入到 projects/mmdet3d_plugin/bevformer/modules/decoder.py 中
return bev_embed, inter_states, init_reference_out, inter_references_out
# 返回到projects/mmdet3d_plugin/bevformer/dense_heads/bevformer_head.py
6、projects/mmdet3d_plugin/bevformer/modules/encoder.py
class BEVFormerEncoder(...):
def forward(...):
...
for lid, layer in enumerate(self.layers):
out = ...
# 进入到下面的 class BEVFormerLayer
class BEVFormerLayer(...):
def forward(...):
# 通过layer进入到不同的模块中
for layer in self.operation_order:
# tmporal_self_attention
if layer == 'self_attn':
# self.attentions 为 temporal_self_attention模块
query = self.attentions[attn_index]
# 进入到projects/mmdet3d_plugin/bevformer/modules/temporal_self_attention.py
# Spatial Cross-Attention
elif layer == 'cross_attn':
query = self.attentions[attn_index]
# 进入到 projects/mmdet3d_plugin/bevformer/modules/spatial_cross_attention.py
7、projects/mmdet3d_plugin/bevformer/modules/temporal_self_attention.py
class TemporalSelfAttention(...):
def forward(...):
output = ...
# 残差链接 返回的结果为 Spatial Cross-Attention 模块的输入
return self.dropout(output) + identity
# 返回到projects/mmdet3d_plugin/bevformer/modules/encoder.py
8、projects/mmdet3d_plugin/bevformer/modules/spatial_cross_attention.py
class SpatialCrossAttention(...):
def forward(...):
queries = self.deformable_attention(...)
# 进入到下面的MSDeformableAttention3D
return self.dropout(slots) + inp_residual
# 返回到返回到projects/mmdet3d_plugin/bevformer/modules/encoder.py
# self.deformable_attention
class MSDeformableAttention3D(BaseModule):
def forward(...):eights
output = ...
return output
# 返回到上面 SpatialCrossAttention
9、projects/mmdet3d_plugin/bevformer/modules/decoder.py
class DetectionTransformerDecoder(...):
def forward(...):
for lid, layer in enumerate(self.layers):
output = layer(...)
# 进入到下面CustomMSDeformableAttention
...
if self.return_intermediate:
intermediate.append(output)
intermediate_reference_points.append(reference_points)
return output, reference_points
# 返回到 projects/mmdet3d_plugin/bevformer/modules/transformer.py
class CustomMSDeformableAttention(...):
def forward(...):
'''
query: [900, 1, 256]
query_pos:[900, 1, 256] 可学习的位置编码
'''
output = multi_scale_deformable_attn_pytorch(...)
output = self.output_proj(output)
return self.dropout(output) + identity
# 返回到上面的DetectionTransformerDecoder
经过上面的步骤,基本疏通了BEVFormer的推理步骤,但是里面存在许多细节,由于还在看源码,以及有一些问题还没解决,后续的详解版本会对代码里面的变量进行详细注解(已经在写了,如果没啥问题的话),维度以及作用,一方面是加深对BEVFormer的理解,另一方面提高自己对BEV模型的认知。