【BEV】学习笔记之FastBEV(原理+代码注释)

这篇具有很好参考价值的文章主要介绍了【BEV】学习笔记之FastBEV(原理+代码注释)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、前言

BEV模型部署一直是难以解决的问题,在车载芯片上运行要占用大量计算资源,为此FastBEV的作者提出了更加轻量级的方法,不需要transformer来提取BEV特征,仅使用卷积网络来完成,简单而有效。本文将会记录学习过程中的一些知识点,包括如果在本地运行、测试、随后对代码进行注释,便于进一步了解FastBEV的结构。
BEV交流群,会有许多智驾内推机会,v群:Rex1586662742、q群:468713665。

2、本地训练测试
2.1、数据集制作

原作者训练的数据为完整的nuscences数据集,项目里面提供的pkl文件对应真个nuscences数据集,而对于个人学习来说,一般只能使用mini数据集。因为就需要生成mini数据集的okl文件,这部分原项目没有提及,因此也是花了很多时间来解决这个问题,感谢原作者的耐心回答。生成mini数据的pkl可以参考以下步骤:

  • 利用BEVFormer项目生成 nuscenes_infos_temporal_train.pkl 、…test.pkl、 …vak.pkl
  • BEVFormer下的数据集剪切到FastBEV下面,并l重命名为nuscenes_infos_train.pkl、nuscenes_infos_val.pkl、nuscenes_infos_test.pkl
  • 运行tools/data_converter/nuscenes_seq_converter.py生成训练所需要的nuscenes_infos_train_4d_interval3_max60.pkl、…val_4d_interval3_max60.pkl…文件。

目前只找到这个方法,如果有大佬知道更简洁的方法,可以指出。按照上面的方法就可以得到训练和测试mini数据集,本文也提供百度云链接:https://pan.baidu.com/s/1FGvYLqShz8VzWs6syq8uUw?pwd=qgpw 提取码: qgpw

2.2、训练

如果直接按照项目里面的命令进行训练,很有可能报各种错误,只要还是本地环境和作者环境有较大的差异,因此需要修改一些参数来方便训练。本文训练的选择的配置参数为configs/fastbev/exp/paper/fastbev_m0_r18_s256x704_v200x200x4_c192_d2_f4.py,需要修改如下内容:

# 取消注释
file_client_args = dict(backend="disk")
并注释下面
# file_client_args = dict(
#     backend='petrel',
#     path_mapping=dict({
#         data_root: 'public-1424:s3://openmmlab/datasets/detection3d/nuscenes/'}))

然后需要将里面的"SyncBN" 全部修改为 “BN”,优化器的"Adam2" 参数修改为"Adam"!!!! 不然会报错

由于原作者使用 bash tools/trian/fastbev_run.sh的方式进行训练和测试,通过会发现很多条件不满足,因此就想着直接使用python tools.train.py的方式进行训练,需要修改的参数为:

    parser.add_argument(
        "--config",
        default="configs/fastbev/exp/paper/fastbev_m0_r18_s256x704_v200x200x4_c192_d2_f4.py",
        help="train config file path",
    )
     parser.add_argument(
        "--work-dir", default="work_dir", help="the dir to save logs and models"
    )
    group_gpus.add_argument(
        "--gpu-ids",
        type=int,
        default=[0],
        help="ids of gpus to use " "(only applicable to non-distributed training)",
    )

修改完毕之后运行python train.py即可运行

3、FastBEV原理简介

FastBEV有如下特点:

  • 时序融合,能够解决遮挡等问题,是目前主流的BEV特征提取方式,参考BEVFormer
  • 数据增强,能够在图片以及BEV空间进行数据增强,得益于显示的BEV特征,参考BEVDet
  • Fast-Ray变换,假设沿光线的深度分布是均匀的,这意味着沿摄影头光线的所有体素都填充了和2D空间中单个像素对应相同的特征,可以极大缩减生成BEV特征的时间,参考M2BEV
  • 高效BEV编码器,适用于车载芯片的部署

网络结构如下图所示:
【BEV】学习笔记之FastBEV(原理+代码注释)

  • 通过特征提取获得多尺度特征层
    【BEV】学习笔记之FastBEV(原理+代码注释)

  • 将每个视角的2D特征转移到统一的体素空间
    【BEV】学习笔记之FastBEV(原理+代码注释)

  • 多帧融合
    【BEV】学习笔记之FastBEV(原理+代码注释)

  • 图像以及BEV特征的数据增强
    【BEV】学习笔记之FastBEV(原理+代码注释)

4、训练代码解析

1、mmdet3d/models/detectors/fastbev.py

class FastBEV(BaseDetector):
    def __init__():
        ...
        
    def forward_train(...):
        feature_bev, valids, features_2d = self.extract_feat(img, img_metas, "train")  # 提取BEV特征
        
        if self.bbox_head is not None:
            x = self.bbox_head(feature_bev)  # 解码头  -> mmdet3d/models/dense_heads/anchor3d_head.py
            loss_det = self.bbox_head.loss(*x, gt_bboxes_3d, gt_labels_3d, img_metas) # 计算损失  -> mmdet3d/models/dense_heads/free_anchor3d_head.py
    
    def extract_feat(self, img, img_metas, mode)
        """
        args:
            img:[1, 24, 3, 256, 704]  -> [1, 4*6, 3, 256, 704] 取4帧的环视图片,用于时序融合
        """
        x = self.backbone(x)  #提取图片多尺度特征   [24,64,64,176] [24,128,32,88] [24,256,16,44]  [24,512,8,22]
        # 多尺度特征融合
        def _inner_forward(x):
            out = self.neck(x)
            return out  #  # [24, 64, 64, 176]; [24, 64, 32, 88]; [24, 64, 16, 44]; [24, 64, 8, 22])
        
        if self.with_cp and x.requires_grad:
            ...
        else:
            mlvl_feats = _inner_forward(x)
        if self.multi_scale_id is not None:
            for msid in self.multi_scale_id:
                if getattr(self, f'neck_fuse_{msid}', None) is not None:
                    fuse_feats = [mlvl_feats[msid]] # [24, 64, 64, 176] 选择最大的特征层
                    for i in range(msid + 1, len(mlvl_feats)):# 遍历其他特征层
                        resized_feat = resize(...) # 将其他特征层缩放到最大的的特征层尺寸
                        fuse_feats.append(resized_feat)
                    if len(fuse_feats) > 1:
                        fuse_feats = torch.cat(fuse_feats, dim=1) #[24, 256, 64, 176] 特征层叠加
                    else:
                        ...
                    fuse_feats = getattr(self, f'neck_fuse_{msid}')(fuse_feats) # 特征融合 [24, 64, 64, 176]
                    mlvl_feats_.append(fuse_feats)
                    
        mlvl_volumes = []
        for lvl, mlvl_feat in enumerate(mlvl_feats):
            stride_i = math.ceil(img.shape[-1] / mlvl_feat.shape[-1]) # 当前特征图与原图之间的比例
            # [bs*seq*nv, c, h, w] -> [bs, seq*nv, c, h, w]
            mlvl_feat = mlvl_feat.reshape([batch_size, -1] + list(mlvl_feat.shape[1:])) #  
            mlvl_feat_split = torch.split(mlvl_feat, 6, dim=1) # [1, 6, 64, 64, 176] * 4  # 4帧环视图片特征
            volume_list = []
            # 遍历4帧环视图片特征
            for seq_id in range(len(mlvl_feat_split)):
                volumes = []
                for batch_id, seq_img_meta in enumerate(img_metas):
                    # 第i帧
                    feat_i = mlvl_feat_split[seq_id][batch_id] 
                    img_meta["lidar2img"]["extrinsic"] = img_meta["lidar2img"]["extrinsic"][seq_id*6:(seq_id+1)*6] # 相机外参
                    # 相机内参@相机外参
                    projection = self._compute_projection(...)
                    if self.style in ['v1', 'v2']:
                        n_voxels, voxel_size = self.n_voxels[0], self.voxel_size[0] # [200,200,4]  [0.5,0.5,1.5]   #体素个数,体素尺寸
                    else:
                        ...
                    # ego坐标下的点云
                    points = get_points(...)
                    # 利用点云和特征层,获得3d的特征表示
                    if self.backproject == 'inplace':
                        volume = backproject_inplace(feat_i[:, :, :height, :width], points, projection)
                    if self.style in ['v1', 'v2']:
                        mlvl_volumes = torch.cat(mlvl_volumes, dim=1)  # [bs, lvl*seq*c, vx, vy, vz]
                    else:
                        ...
                    def _inner_forward(x):
                        out = self.neck_3d(x)
                    return out
                if self.with_cp and x.requires_grad:
                    x = cp.checkpoint(_inner_forward, x)
                else:
                    x = _inner_forward(x)  #  [1, 256, 200, 200, 4] ->  [1, 192, 100, 100]  fusion  BEV encode
                    return x, None, features_2d
                    
    def _compute_projection(img_meta, stride, noise=0):
        # 相机内参
        intrinsic = torch.tensor(img_meta["lidar2img"]["intrinsic"][:3, :3])
        intrinsic[:2] /= stride  # 图片缩放系数
        for extrinsic in extrinsics:
            if noise > 0:
                ...
            else:
                projection.append(intrinsic @ extrinsic[:3])  # 内参@外参
        return torch.stack(projection)
        
    def get_points(n_voxels, voxel_size, origin):
        points = torch.stack(...)
        points = points * ...  # [3, 200, 200, 4] #每个体素代表的真实距离 
        return points  
        
def backproject_inplace(...):
    '''
    function: 2d feature + predefined point cloud -> 3d volume
    input:
        features: [6, 64, 64, 176]
        points: [3, 200, 200, 4]
        projection: [6, 3, 4]
    output:
        volume: [64, 200, 200, 4]  # 将2d特征与3d point cloud 结合
    '''
    n_images, n_channels, height, width = features.shape  # [6, 64, 64, 176]
    n_x_voxels, n_y_voxels, n_z_voxels = points.shape[-3:]  # [200, 200, 4]  ,每个相机对应的 200 * 200 * 4 的点云
    points = points.view(1, 3, -1).expand(n_images, 3, -1)  # 6个相机 * 点云
    points = torch.cat((points, torch.ones_like(points[:, :1])), dim=1)  # x,y,z,1 齐次坐标
    # 3d 转 2d [6, 3, 160000]  每个点云在相机上的坐标
    points_2d_3 = torch.bmm(projection, points)
    
    valid = ... # 点云投影在像素坐标系后,提取在图片范围内的特征点
    volume = torch.zeros(...) # [64,160000]  1600000个特征点,每个特征点的特征维度为64
    for i in range(n_images): # 遍历当前帧的的环视图片
        # 提取有效特征
        volume[:, valid[i]] = features[i, :, y[i, valid[i]], x[i, valid[i]]] 

2、mmdet3d/models/dense_heads/anchor3d_head.py

class Anchor3DHead(...):
    def __init__(...):
        ...
    
    def forward_single(...):
        cls_score = self.conv_cls(x)  # [1, 80, 100, 100]  # 预测分类分支 
        bbox_pred = self.conv_reg(x)  # [1, 72, 100, 100]   # 预测框分支
        if self.use_direction_classifier:
            dir_cls_preds = self.conv_dir_cls(x)  # 方向分支

3、mmdet3d/models/dense_heads/free_anchor3d_head.py

        """Calculate loss of FreeAnchor head.
        Args:
            cls_scores (list[torch.Tensor]): Classification scores of
                different samples.
            bbox_preds (list[torch.Tensor]): Box predictions of
                different samples
            dir_cls_preds (list[torch.Tensor]): Direction predictions of
                different samples
            gt_bboxes (list[:obj:`BaseInstance3DBoxes`]): Ground truth boxes.
            gt_labels (list[torch.Tensor]): Ground truth labels.
            input_metas (list[dict]): List of input meta information.
            gt_bboxes_ignore (list[:obj:`BaseInstance3DBoxes`], optional):
                Ground truth boxes that should be ignored. Defaults to None.

        Returns:
            dict[str, torch.Tensor]: Loss items.

                - positive_bag_loss (torch.Tensor): Loss of positive samples.
                - negative_bag_loss (torch.Tensor): Loss of negative samples.
        """
        # 10个类别
        
        anchors = ... # [80000, 9]为每个特征点生成 8个锚框,共4个尺度,每个尺度两种锚框
        cls_scores = ... #[1, 80000, 10] 为每个特征点的8个anchor预测类别
        bbox_preds = torch.cat(bbox_preds, dim=1)  # [1, 80000, 9]  # 为每个特征点的8个anchor预测边界框
        dir_cls_preds = torch.cat(dir_cls_preds, dim=1)  # [1, 80000, 2] 为每个特征点的8个anchor预测方向
        
        for ... in ...:
            gt_bboxes_ = gt_bboxes_.tensor.to(anchors_.device) # 当前帧的GT
            with torch.no_grad():
                pred_boxes = self.bbox_coder.decode(anchors_, bbox_preds_)  # bbox_preds_是预测的偏移量
                object_box_iou = bbox_overlaps_nearest_3d(gt_bboxes_, pred_boxes) # 每个锚框与gt计算一个IOU
                .....
                loss_bbox = self.loss_bbox() # 预测框损失
                
        positive_loss = torch.cat(positive_losses).sum() / max(1, num_pos)  # 正样本损失
        negative_loss = ... # 负样本损失
        return losses 
5、总结

FastBEV提供了其中更加轻量化BEV特征提取方式以及BEV特征编码网络,同时引进了时序特征融合,这也是当前许多工作常用的做法,希望后续能够了解如何在移动端进行部署的。文章来源地址https://www.toymoban.com/news/detail-486358.html

到了这里,关于【BEV】学习笔记之FastBEV(原理+代码注释)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 大语言模型-RLHF(七)-PPO实践(Proximal Policy Optimization)原理&实现&代码逐行注释

    从open AI 的论文可以看到,大语言模型的优化,分下面三个步骤,SFT,RM,PPO,我们跟随大神的步伐,来学习一下这三个步骤和代码实现,本章介绍PPO实践。 生活中,我们经常会遇到,希望chatgpt在指定内容范围内回答问题。目前的解决方案大致可以分为两大类,一类是知识库

    2024年02月12日
    浏览(29)
  • python教程 入门学习笔记 第3天 编程基础常识 代码注释 变量与常量

    编程基础常识 一、注释 1、对代码的说明与解释,它不会被编译执行,也不会显示在编译结果中 2、注释分为:单行注释和多行注释 3、用#号开始,例如:#这是我的第一个python程序 4、注释可以写在单独一行,也可以写在一句代码后面 5、不想执行编译,又不能删除的代码,可

    2024年02月14日
    浏览(41)
  • fast_bev 学习笔记

    原文:Fast-BEV: A Fast and Strong Bird’s-Eye View Perception Baseline FAST BEV是一种高性能、快速推理和部署友好的解决方案,专为自动驾驶车载芯片设计。该框架主要包括以下五个部分: Fast-Ray变换:这是一种轻量级的、部署友好的视图变换,它将多视图2D图像特征沿着相机射线的体素投

    2024年04月13日
    浏览(25)
  • redis存储原理与数据模型学习笔记

    redis-server 命令处理 网络事件的监听 bio close file 异步关闭大文件 bio aof fsync 异步 aof 刷盘 bio lazy free 异步清理大块内存 io thd * io 多线程 emalloc bg thd jemalloc 后台线程 单线程为什么快? server.h dict.h 注意 dictEntry **ht_table[2]; 怎么从key定位到value? 哈希原理: 数组 + hash(key) % 数组长

    2024年02月10日
    浏览(33)
  • 【深度学习模型】扩散模型(Diffusion Model)基本原理及代码讲解

    生成式建模的扩散思想实际上已经在2015年(Sohl-Dickstein等人)提出,然而,直到2019年斯坦福大学(Song等人)、2020年Google Brain(Ho等人)才改进了这个方法,从此引发了生成式模型的新潮流。目前,包括OpenAI的GLIDE和DALL-E 2,海德堡大学的Latent Diffusion和Google Brain的ImageGen,都基

    2023年04月22日
    浏览(31)
  • 【学习笔记】生成式AI(ChatGPT原理,大型语言模型)

    语言模型 == 文字接龙 ChatGPT在测试阶段是不联网的。 又叫自监督式学习(Self-supervised Learning),得到的模型叫做基石模型(Foundation Model)。在自监督学习中,用一些方式“无痛”生成成对的学习资料。 GPT1 - GPT2 - GPT3 (参数量增加,通过大量网络资料学习,这一过程称为预训

    2024年02月14日
    浏览(46)
  • 非极大值抑制详细原理(NMS含代码及详细注释)

    作者主页: 爱笑的男孩。的博客_CSDN博客-深度学习,YOLO,活动领域博主 爱笑的男孩。擅长深度学习,YOLO,活动,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域. https://blog.csdn.net/Code_and516?type=collect 个人介绍:打工人。 分享内容

    2023年04月21日
    浏览(41)
  • Stable Diffusion with Diffusers 学习笔记: 原理+完整pipeline代码

    参考链接: https://huggingface.co/blog/stable_diffusion#how-does-stable-diffusion-work 在这篇文章中,我们想展示如何使用Stable Diffusion with the 🧨 Diffusers library,,解释模型是如何工作的,最后深入探讨扩散器是如何允许自定义图像生成pipeline的。 如果你对扩散模型完全陌生,我们建议你阅读

    2024年02月05日
    浏览(42)
  • 《区块链原理与技术》学习笔记(四) ——以太坊的基本架构、账户模型和智能合约

    《区块链原理与技术》学习笔记 第四部分 三、以太坊 1. 以太坊简介 1.1 以太坊发展的阶段 1.2 以太坊与比特币对比 2. 以太坊的基本架构及原理 2.1 基本概念 2.2 状态转移 2.3 基本架构 3. 账户模型与转账 3.1 账户模型 4. 智能合约 4.1 合约账户与数据存储 4.2 驱动智能合约 以太坊

    2024年02月13日
    浏览(37)
  • 【综合评价分析】熵权算法确定权重 原理+完整MATLAB代码+详细注释+操作实列

    【综合评价分析】 熵权算法 确定权重 原理+完整MATLAB代码+详细注释+操作实列 文章目录 1. 熵权法确定指标权重 (1)构造评价矩阵 Ymn (2)评价矩阵标准化处理 (3)计算指标信息熵值 Mj (4)计算各指标权重 Nj 2.完整代码 2.1 熵权法(正向化指标) 2.2熵权法(负向化指标)

    2024年01月21日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包