[BEV] 学习笔记之BEVFormer(一)

1、前言

在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

2、运行

学习一个项目的必经之路首先是要将这个项目运行起来,建议完全按照官方的安装环境的方式,避免发生问题,装完环境后,按照官方的命令运行即可。那么如何进行debug来逐行进行查看呢?我使用的是pycharm进行debug,可以分为如下两个步骤

  • 链接launch.py
    首先找到当前conda环境(bev)下的的 launch.py文件链接到本项目
sudo ln -s /home/***/miniconda3/envs/bev/lib/python3.8/site-packages/torch/distributed/launch.py ./
  • 编辑配置
    点击运行,选择配置文件,脚本路径填入launch.py的绝对路径,形参中填入以下参数,需要修改为本机路径,一般来说就可以进行debug了,如果报错说找不到数据集里面的图片,那么就参考下文的第三条。
--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
  • 链接数据集
    运行时,有可能报错说找不到data数据集,可以在tools/test.py 里面加上
print(os.getcwd())
sys.path.append(os.getcwd())

然后将项目里面的data数据集路径链接到os.getcwd()路径下面下面,运行时就不会报错了。

3、网络结构

按照惯例,首先还是要放一张论文中的示意图来进行说明,下图中,主要分为三个 部分,最左边的backbone,中间×6 的encoder,中间上面的Det/Seg Heads。

第一部分就是ResNet + FPN,BEVFormer主要的主要实在第二部分encoder进行了创新,即Temporal Self-Attention,Spatial Cross-Attention。
在这里插入图片描述

4、模型配置文件

本文采用的是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',
        ...
    )
)

5、forward 流程

能够进行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

6、总结

经过上面的步骤,基本疏通了BEVFormer的推理步骤,但是里面存在许多细节,由于还在看源码,以及有一些问题还没解决,后续的详解版本会对代码里面的变量进行详细注解(已经在写了,如果没啥问题的话),维度以及作用,一方面是加深对BEVFormer的理解,另一方面提高自己对BEV模型的认知。

猜你喜欢

转载自blog.csdn.net/weixin_42108183/article/details/128426721