[BEV] 学习笔记之BEVDet(原理+代码解析)

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

前言

基于LSS的成功,鉴智机器人提出了BEVDet,目前来到了2.0版本,在nuscences排行榜中以mAP=0.586暂列第一名。本文将对BEVDet的原理进行简要说明,然后结合代码对BEVDet进深度解析。
repo: https://github.com/HuangJunJie2017/BEVDet
paper:https://arxiv.org/abs/2211.17111
欢迎进入BEV感知交流群,一起解决学习过程发现的问题,同时会有许多智驾内推机会,v:Rex1586662742或者q:468713665。

模型简介

BEVDet的主要包含一下四个步骤,如下图所示:

  • Image-view Encoder: 提取环视图片的特征
  • View Transformer:提取BEV特征
  • BEV Encoder:BEV特征编码
  • Head:检测头
    [BEV] 学习笔记之BEVDet(原理+代码解析)

通过View Transformer,可以获得显示的BEV特征,类似点云特征,因此可以对BEV特征进行增强,同时使用cuda对Voxel Pooling过程进行加速,并且在Head中使用改进的NMS,下面就进入到代码部分了。

代码解析

1、tools/test.py

if not distributed:
    outputs = single_gpu_test(...)
    # -> mmdet3d/apis/test.py
else:
    ...

2、mmdet3d/apis/test.py

    if return_loss:
        return self.forward_train(**kwargs)
    else:
        return self.forward_test(**kwargs)
        # -> mmdet3d/models/detectors/base.py

3、mmdet3d/models/detectors/bevdet.py

class BEVDet(...):
    def __init__(...):
        ...
        
    def forward_test(...):
        if not isinstance(img_inputs[0][0], list):
            return self.simple_test(...)
    
    def simple_test(...):
        img_feats, _, _ = self.extract_feat(...)
        # 参考centerpoint
        bbox_pts = self.simple_test_pts(img_feats, img_metas, rescale=rescale)
    
    def extract_feat(...):
         img_feats, depth = self.extract_img_feat(...)
         pts_feats = None
         return (img_feats, pts_feats, depth)
        
    def extract_img_feat(...):
        # 提取环视图片的特征 
        x = self.image_encoder(img[0])
        #  BEV 特征 
        x, depth = self.img_view_transformer([x] + img[1:7])
        # -> mmdet3d/models/necks/view_transformer.py
    
        x = self.bev_encoder(x)
        return [x],depth

4、mmdet3d/models/necks/view_transformer.py

Voxel pooling的关键步骤为voxel_pooling_prepare_v2,为了更好的理解,在代码下方准备了图例来进行理解。

class LSSViewTransformer(...):

    def create_frustum(...):
        ...
    
    def forward(self, input)""" Transform image-view feature into bird-eye-view feature.
    Args:
        input: [image-view feature,rots,trans,intrins,post_rots,post_trans]
        image-view feature:环视图片特征
        rots:由相机坐标系->车身坐标系的旋转矩阵
        trans:相机坐标系->车身坐标系的平移矩阵
        intrinsic:相机内参
        post_rots:由图像增强引起的旋转矩阵
        post_trans:由图像增强引起的平移矩阵
    """
    # LIFT, x:[6, 139, 16, 44] 
    # 前self.D为预测的离散距离,后self.out_channels为深度特征
    x = self.depth_net(x)
    
    # 深度
    depth_digit = x[:, :self.D, ...]
    # 特征
    tran_feat = x[:, self.D:self.D + self.out_channels, ...]
    # 深度概率分布
    depth = depth_digit.softmax(dim=1)
    # 转化到bev空间
    return self.view_transform(input, depth, tran_feat)
    
    def view_transform(...):
        return self.view_transform_core(input, depth, tran_feat)
        
    def view_transform_core(...):
        ''' 
        Args:
            input:[1, 6, 512, 16, 44],环视相机特征
            depth:[6, 59, 16, 44],# 深度概率分布
            tran_feat: [6, 80, 16, 44],深度特征
        '''
        if ...:
            ...
        else:
            # 获得点云
            coor = self.get_lidar_coor(*input[1:7])
            # 将点云投影到BEV空间
            # 讲解链接可参考 https://zhuanlan.zhihu.com/p/586637783
            bev_feat = self.voxel_pooling_v2(...)
            # bev_feat:[1, 80, 128, 128] depth:[6, 59, 16, 44]
            return bev_feat,depth
            
    def get_lidar_coor(...):
        # self.frustum 视锥
        # 减去数据增强的平移矩阵
        points = self.frustum.to(rots) - post_trans.view(B, N, 1, 1, 1, 3)
        #  乘以图像预处理的旋转矩阵的逆矩阵
        points = torch.inverse(post_rots).view(B, N, 1, 1, 1, 3, 3).matmul(points.unsqueeze(-1))
        
        # 图像坐标系 -> 归一化相机坐标系 -> 相机坐标系 -> 车身坐标系
        # lamda * [xs, ys, 1 ] ->  lamda * xs ,lamda * ys , lamda,在多个项目中都有体现,像素坐标系转相机坐标系
        points = torch.cat((points[..., :2, :] * points[..., 2:3, :], points[..., 2:3, :]), 5)
        # 相机内参 
        combine = rots.matmul(torch.inverse(cam2imgs))
        # 相机坐标系转车身坐标系
        points = combine.view(B, N, 1, 1, 1, 3, 3).matmul(points).squeeze(-1)
        points += trans.view(B, N, 1, 1, 1, 3)
        #  bad 为BEV 特征下的增强矩阵,这里为单位矩阵
        #  解释来源为 https://github.com/Megvii-BaseDetection/BEVDepth/issues/44
        points = bda.view(B, 1, 1, 1, 1, 3,3).matmul(points.unsqueeze(-1)).squeeze(-1)
        return points
        
     def voxel_pooling_v2(self, coor, depth, feat):
        """
        Args:
            coor:车身坐标系下的视锥点坐标
            depth:离散深度概率分布
            feat:深度特征
        """
        ranks_bev, ranks_depth, ranks_feat, interval_starts, interval_lengths = self.voxel_pooling_prepare_v2(coor)
        
    def voxel_pooling_prepare_v2(...):
        """Data preparation for voxel pooling
        """
        B, N, D, H, W, _ = coor.shape
        num_points = B * N * D * H * W  # 总视锥点个数
        ranks_depth = torch.range(0, num_points - 1, dtype=torch.int, device=coor.device) # 0~249215
        # 每一层feat的位置索引 [0,1,2,3..4223,0,1,2...,4223,...,0,1,2...,4223]
        ranks_feat = ...
        # 将原点移动到左下角并且将坐标系转到BEV空间的尺度
        #  [-51.2,51.2] -> [0,102.4] -> [0,128]
        coor = ((coor - self.grid_lower_bound.to(coor)) /  self.grid_interval.to(coor))
        coor = coor.long().view(num_points, 3
        # 记录当前视锥点在哪个batch
        batch_idx = torch.range(0, B - 1).reshape(B, 1). expand(B, num_points // B).reshape(num_points, 1).to(coor)
        coor = torch.cat((coor, batch_idx), 1)

        # 过滤掉不在bev空间下的视锥点
        kept = (coor[:, 0] >= 0) & (coor[:, 0] < self.grid_size[0]) & \
               (coor[:, 1] >= 0) & (coor[:, 1] < self.grid_size[1]) & \
               (coor[:, 2] >= 0) & (coor[:, 2] < self.grid_size[2])
        if len(kept) == 0:
            return None, None, None, None, None
        # 挑选BEV空间下的视锥点
        coor, ranks_depth, ranks_feat = coor[kept], ranks_depth[kept], ranks_feat[kept]
        # 利用视锥 点的batch,x,y 计算出 视锥点在BEV特征下的全局索引(128*128)
        ranks_bev = coor[:, 3] * (self.grid_size[2] * self.grid_size[1] * self.grid_size[0])
        ranks_bev += coor[:, 2] * (self.grid_size[1] * self.grid_size[0])
        ranks_bev += coor[:, 1] * self.grid_size[0] + coor[:, 0]
        # 排序,将BEV空间下,全局索引为相同的值排列在一起
        order = ranks_bev.argsort()
        ranks_bev, ranks_depth, ranks_feat = ranks_bev[order], ranks_depth[order], ranks_feat[order]
        kept = torch.ones(ranks_bev.shape[0], device=ranks_bev.device, dtype=torch.bool)
        # 错位比较,可以使得索引位置相同的,收个位置为True,如图所示。
        kept[1:] = ranks_bev[1:] != ranks_bev[:-1]
        interval_starts = torch.where(kept)[0].int()
        if len(interval_starts) == 0:
            return None, None, None, None, None
        interval_lengths = torch.zeros_like(interval_starts)
        # 每个为True的索引位置,向前累加的长度
        interval_lengths[:-1] = interval_starts[1:] - interval_starts[:-1]
        interval_lengths[-1] = ranks_bev.shape[0] - interval_starts[-1]
        return ranks_bev.int().contiguous(), ranks_depth.int().contiguous(
        ), ranks_feat.int().contiguous(), interval_starts.int().contiguous(
        ), interval_lengths.int().contiguous()
    

Voxel Pooling 图例
[BEV] 学习笔记之BEVDet(原理+代码解析)

5、mmdet3d/ops/bev_pool_v2/src/bev_pool_cuda.cu

void bev_pool_v2(...) {
    """
    Args:
        c:80,bev特征channel维度
        n_intervals:Nd,位置为true的索引的集合
        其他参数见上方的 voxel_pooling_prepare_v2函数
    """
    # 索引位置为True的视锥点,每个视锥点的特征深度为80 一共开辟 视锥点个数*80个thread
    # 共有(int)ceil(((double)n_intervals * c / 256)) 个block ,每个block有 256个线程 ,为每个深度特征的每一层(80层)创建一个thread 
    bev_pool_v2_kernel<<<(int)ceil(((double)n_intervals * c / 256)), 256>>>(...);
}


__global__ void bev_pool_v2_kernel(...) {
    // out:输出的bev特征 [1,1,128,128,80]
    int idx = blockIdx.x * blockDim.x + threadIdx.x;  //当前thread的全局索引 
    int index = idx / c;  // 当前处理哪一个视锥点
    int cur_c = idx % c;  // 当前处理哪一个视锥点的第 cur_c 层的数据 (共80层)
    if (index >= n_intervals) return;
    int interval_start = interval_starts[index];  // 为True的索引
    int interval_length = interval_lengths[index]; // 向前累加多少个长度
    float psum = 0; //某层深度特征的累加和
    const float* cur_depth;
    const float* cur_feat;
    // 累加
    for(int i = 0; i < interval_length; i++){
        cur_depth = depth + ranks_depth[interval_start+i];  # 视锥点的预测深度
        cur_feat = feat + ranks_feat[interval_start+i] * c + cur_c;  # 视锥点深度特征
        psum += *cur_feat * *cur_depth;   # 相乘
    }
    
    const int* cur_rank = ranks_bev + interval_start;    // ranks_bev + interval_start  在bev特征的位置索引(128*128)中的位置索引
    float* cur_out = out + *cur_rank * c + cur_c; // 在BEV特征中的位置索引(128*128*80)中的位置索引
    *cur_out = psum;
}

总结

通过对BEVDet的了解,进一步理解了LSS的思想,同时也理解了Voxel Pooling中的各种操作,后续希望能够再深入了解几篇自顶而下的论文,如PETR\PETRv2等。文章来源地址https://www.toymoban.com/news/detail-450787.html

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

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

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

相关文章

  • fast_bev 学习笔记

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

    2024年04月13日
    浏览(38)
  • 机器学习|DBSCAN 算法的数学原理及代码解析

    聚类是机器学习领域中一项重要的任务,它可以将数据集中相似的样本归为一类。 DBSCAN(Density-Based Spatial Clustering of Applications with Noise) 是一种是一种经典的密度聚类算法,它能够有效地发现任意形状的聚类簇,并且可以识别出噪声点。在本文中,我们将深入探讨 DBSCAN 算法

    2024年02月11日
    浏览(49)
  • 机器学习:基于梯度下降算法的逻辑回归实现和原理解析

    当涉及到二元分类问题时,逻辑回归是一种常用的机器学习算法。它不仅简单而且有效,通常是入门机器学习领域的第一步。本文将介绍逻辑回归的基本概念、原理、应用场景和代码示例。 逻辑回归是一种用于解决二元分类问题的统计学习方法。尽管其名称中包含\\\"回归\\\"一词

    2024年02月09日
    浏览(53)
  • 机器学习:基于梯度下降算法的线性拟合实现和原理解析

    当我们需要寻找数据中的趋势、模式或关系时,线性拟合和梯度下降是两个强大的工具。这两个概念在统计学、机器学习和数据科学领域都起着关键作用。本篇博客将介绍线性拟合和梯度下降的基本原理,以及它们在实际问题中的应用。 线性拟合是一种用于找到数据集中线性

    2024年02月10日
    浏览(37)
  • 【现代机器人学】学习笔记十三:配套代码解析

    最近一直忙于工作,每天都在写一些业务代码。而目前工程中的技术栈并没有使用旋量这一套机器人理论系统,因此时间长了自己都忘记了。 于是决定把这本书配套的代码内容也过一遍,查漏补缺,把这本书的笔记内容完结一下。 代码来源于github:https://github.com/NxRLab/Moder

    2024年02月12日
    浏览(45)
  • MMDetection学习笔记(五):整体构建流程与代码解析

    写在前面:建议先看完博主的另一篇博客核心组件分析,再去理解整个代码逻辑,结合代码反复阅读,抓住其中面向对象编程的核心思想,祝顺利,欢迎留言评论,博主会定期解答! 按照数据流过程,训练流程可以简单总结为: 获取config配置并初始化各种类的实例化,通过

    2024年02月13日
    浏览(79)
  • ALSA框架学习笔记3:声卡注册流程(代码解析)

    以Amlogic V918D为例,介绍驱动如何将设备树中的声卡节点注册为声卡设备 一、设备树中的声卡节点 声卡名为\\\"AML-AUGESOUND\\\",下面有8个dai-link,每一个dai-link下面都有cpu dai和codec dai设备节点。每个dai-link都会被注册为一个pcm设备,这里先列出简化的流程: 从上一遍文章知道cpu da

    2024年02月16日
    浏览(36)
  • 机器学习中高维组合特征的处理方法+推荐系统使用矩阵分解为用户推荐的原理解析,《百面机器学习》学习笔记

    为了提高复杂关系的拟合能力,在特征工程中经常会把一阶离散特征进行组合,构成高阶组合特征。 假设有A B两组特征,C为受到A B两种特征影响的因素,且对特征A来说,其有 A i , i ∈ [ 0 , 1 ] {A^i,iin [0,1]} A i , i ∈ [ 0 , 1 ] 两种特征取值。同时,对于特征B来说,其有 B j , j ∈

    2024年02月05日
    浏览(47)
  • 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日
    浏览(62)
  • PSCAD学习笔记(2)——python调用PSCAD自动化库代码解析:组件控制

    该学习笔记结合官方文件和个人学习见解撰写,主要分享一些常见实用功能,欢迎讨论、补充、指正。PSCAD相关免费学习资源实属稀缺,如果本文对您有所帮助,麻烦点赞评论支持一下。您的支持是我更新的动力。 PSCAD版本:4.6.3 python版本:3.7 mhrc-automation版本:1.2.4 python编辑

    2024年02月22日
    浏览(87)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包