【BEV 视图变换】Fast-Ray 基于查找表LUT、多视角到单个三维体素转换

前言

在BEV感知方案中,将图像特征转为BEV特征,是关键的一步,这过程也称为2D视图变换。

本文介绍Fast-Ray方法,在Fast-BEV中被提出的,它是一种轻量级并且易于部署的视图转换方法,用于快速推理。

通过将多视图2D图像特征沿着相机射线投影到3D体素上,来获得BEV特征。此外,提出了查找表多视图到单体素操作,优化了在车载平台上的处理过程。

1、 Fast-Ray关键点

这里有三个关键点:

  1. 查找表(Look-Up-Table, LUT):为了快速从2D像素映射到3D体素,使用了一个查找表。这个表预先计算了从2D图像空间到3D体素空间的映射关系,从而在运行时减少计算量,加快处理速度。

  2. 多视角到单体素(Multi-View to One-Voxel):将来自多个相机视角的信息整合到单个三维体素特征中。这样做的目的是为了充分利用多个相机提供的信息,同时保持三维体素数据的紧凑性,避免生成过于稀疏的体素空间。

  3. 沿相机射线的均匀深度分布:这是一个简化的假设,它假定从相机出发的每一条射线在深度上的分布是均匀的。这使得可以在没有精确深度信息(例如,从激光雷达或深度相机获得的数据)的情况下,对空间进行简化的3D建模。

2、背景和现有方案存在问题

从2D图像特征进行3D感知的挑战

  • 目前先进的BEV方法或者使用基于查询的转换,或者使用隐式/显式基于深度的转换。
  • 但这些方法难以部署在车载芯片上,并且推理速度慢。
  • 基于查询的转换方法通常需要专用芯片来支持。
  • 而基于深度的转换方法往往需要速度不友好的体素池化操作,甚至在多线程CUDA核心上也不是最优解,且在资源有限或不支持CUDA加速推理库的芯片上运行不便。

 基于查询的转换(Query-Based Transformation)——代表论文BEVformer

这种方法使用注意力机制来从2D特征获取3D BEV特征。用公式表示:

  • 其中Fbev​(x,y,z) 是BEV空间中的3D特征。
  • q、k、v分别代表查询(query)、键(key)、值(value)。
  • q查询和k键是通过特征转换从输入2D特征 F2D​(u,v) 中生成,其中 u 和 v 是2D图像的坐标。
  • Pxyz​ 是3D空间中某点的坐标。基于查询的变换使用注意力机制,来关联2D图像的位置和3D空间的位置。

注意力操作在部署时对某些计算平台并不友好,这限制了这些方法的实际应用。

基于深度估计的转换 (Depth-Based Transformation)——代表论文LSS

这种方法计算2D特征和预测深度的外积,来获取3D BEV特征,用公式表示:

  • F2D​(u,v) 是从2D图像中提取的特征。D(u,v) 表示从2D特征预测的深度。
  • 运算符 ⊗ 表示外积,它结合了2D图像特征和深度信息。
  • 外积的结果是一个丰富的特征向量,其中包含了原始2D图像特征和深度信息。
  • 然后通过 Pool 函数对这些特征进行体素池化操作,将它们映射到3D空间中的位置 (x,y,z)。

可以通过并行化设计,提高GPU平台上的速度。但计算速度和特征维度较大时的资源需求限制了其在较小或者资源受限的处理器上的应用。

3、Fast-Ray原理与思路

假设在图像到BEV(2D到3D)视图转换期间,相机射线沿深度分布是均匀的,作者提出了一种名为Fast-Ray的转换方法。

Fast-Ray通过利用查找表(Look-Up-Table)和多视图到单体素操作(Multi-View to One-Voxel),优化了计算流程并减少了推理时间,有效加快了BEV转换的速度。

这种方法预先计算图像到三维体素索引(使用查找表)来加速投影时间,并允许所有相机投影到相同的密集体素(多视图到单体素)。

  • Fast-Ray目的是减少从图像空间到体素空间转换的延迟。
  • 通过预先计算投影索引并将其作为静态查找表存储,这种方法能显著提高转换速度,从而提高整体效率。
  • 此外,通过将所有摄像机视图的特征投影到同一个三维体素特征中(多视图到单体素),避免了昂贵的体素聚合操作。
  • 这种方法与之前依赖CUDA并行计算的方法不同,Fast-Ray转换在CPU上也可以实现高速计算,因此非常适合部署。

如下图所示,展示了BEV视图中离散体素填充的两种不同方法

(a) 基础视图变换

  • 在基本的视图变换中,每个摄像头都有一个稀疏体素,意味着大多数体素位置都是空的(只有约17%的位置是非零的)。
  • 这种情况下,需要一个昂贵的聚合操作来结合来自各个摄像头的稀疏体素。

(b) Fast-BEV提出的方法:所有相机特征的投影都汇集到一个密集的三维体素上

  • Fast-BEV提议让所有摄像头都投影到一个密集的体素上。这样做避免了昂贵的体素聚合过程。
  • 在图b中,可以看到更多的体素被填充(红色代表非零体素),这显示了一种更加高效的数据合并方式,因为不需要在后处理中合并多个稀疏体素集。

4、关键点——查找表(Look-Up-Table)

视图变换是从2D图像空间到3D BEV空间转换特征的关键组成部分。

  • 查找表用于映射2D图像空间中的像素到3D体素空间中的位置。
  • 前提假设,认为光线沿着射线的深度分布是均匀的。
  • 利用这一假设,一旦获得摄像头的内部参数和2D到3D的投影参数,就能够简单地计算图像2D特征点和3D体素特征点之间的矩阵映射。
  • 此过程不涉及学习参数,便于计算对应矩阵。 

简介:

  • 构建查找表LUT,它与3D体素空间的尺寸相同,并通过相机参数矩阵projection计算出每个体素单元对应的2D像素坐标。
  • 如果得到的2D像素坐标是合法的,那么它们就会被填充到LUT中,建立一个与数据无关的索引映射。

 

简单来说,3D体素空间,默认设定分辨率为200x200x6,能得到空间中每个三维坐标(x, y, z)

  • 其中,6表示高度方向(从地面向上)被划分为6个体素层。
  • 这个数字代表在从摄像头到对象的垂直范围内,空间被分成了多少层,每层都是体素网格的一个水平切片。

然后基于相机的内外参,将 三维坐标(x, y, z)投影到图像中,得到映射关系了,形成映射表。

算法流程:

  1. 输入:摄像头参数矩阵 projection,它包含了进行2D到3D坐标转换所需的摄像头内参和外参。

  2. 输出:查找表 LUT,用于在推理过程中快速查找每个体素对应的2D图像中的像素位置。

  3. 算法步骤

    • 算法开始遍历体素空间,体素空间的大小由 volume_size 定义。
    • 对于每个体素的位置 offset,初始化 LUT 为 (-1, -1, -1),这意味着初始时没有有效的映射。
    • 接着遍历所有图像 imgimg_size 定义了有多少图像(即摄像头数量)。
    • 对于每个图像,计算3D坐标 offset 对应的2D像素坐标 (x, y, z),这通过 projection 矩阵实现。
    • 如果计算出的2D坐标 (x, y) 在图像的尺寸范围内(0 ≤ x < w 并且 0 ≤ y < h)且对应的深度 z 大于0,则将这个坐标和图像索引 img 存入 LUT,这样 LUT[offset] 就包含了从3D到2D映射的信息。
    • 一旦为某个体素 offset 找到了一个有效映射,就停止对当前体素的进一步搜索。

查找表的特点: 

  • 它不依赖于与数据相关的深度信息,因为摄像头位置和它们的内外参参数在感知系统构建时就已确定,并且对于每次输入都是相同的。
  • 因此,无需为每次输入重新计算投影索引,而是可以预先计算固定的投影索引并将其作为静态查找表存储起来。
  • 在推理(inference)过程中,可以通过查询这个查找表来获得投影索引,这是一个低成本的操作。
  • 无论是处理单帧还是多帧图像,都可以轻松预先计算相机的内外参数,并根据这些参数预先对齐到当前帧。

示例代码:

// 在CPU上构建查找表(LUT)的函数,将体积数据的体素映射到一系列图像上
void build_LUT_CPU(vector<int32_t> n_voxels, Tensor voxel_size, Tensor origin,
                    Tensor projection, int32_t n_images, int32_t height, int32_t width, int32_t n_channels,
                    std::shared_ptr<int32_t>& LUT, std::shared_ptr<int32_t>& valid, std::shared_ptr<float>& volume) {
    
    // 投影矩阵的维度:6 x 3 x 4 (N, 3, 4)
    // 从n_voxels向量中提取X、Y、Z轴上的体素维度。
    int n_x_voxels = n_voxels[0];
    int n_y_voxels = n_voxels[1];
    int n_z_voxels = n_voxels[2];
    
    // 获取体素大小的指针,并提取具体尺寸
    float* voxel_sizep = (float*)voxel_size.get_data();
    float size_x = voxel_sizep[0];
    float size_y = voxel_sizep[1];
    float size_z = voxel_sizep[2];

    // 获取原点位置的指针,并提取具体坐标
    float* originp = (float*)origin.get_data();
    float origin_x = originp[0];
    float origin_y = originp[1];
    float origin_z = originp[2];
    
    // 计算总体素数
    int nrof_voxels = n_x_voxels * n_y_voxels * n_z_voxels;
    
    // 从智能指针获取查找表(LUT)和有效标记数组的原始指针
    int32_t* LUTp = LUT.get();
    int32_t* validp = valid.get();
    
    // 辅助向量,用于计算和存储转换结果
    std::vector<float> ar(3);
    std::vector<float> pt(3);
    size_t offset = 0;  // LUT中的偏移量
    float count = 0.0;  // 用于计数
    
    // 遍历每个体素
    for (int zi = 0; zi < n_z_voxels; ++zi) {
        for (int yi = 0; yi < n_y_voxels; ++yi) {
            for (int xi = 0; xi < n_x_voxels; ++xi) {
                auto current_lut = &LUTp[offset * 2];
                *current_lut = -1;  // 初始设置为-1,表示无效映射
                *(current_lut + 1) = 0;  // 第二个值用于存储映射信息,初始为0
                // 遍历每个图像,尝试找到当前体素在这些图像中的映射
                for (int img = 0; img < n_images; img++) {
                    // 计算体素在世界坐标系中的位置
                    pt[0] = (xi - n_x_voxels / 2.0f) * size_x + origin_x;
                    pt[1] = (yi - n_y_voxels / 2.0f) * size_y + origin_y;
                    pt[2] = (zi - n_z_voxels / 2.0f) * size_z + origin_z;

                    // 使用投影矩阵将体素坐标转换为图像坐标
                    for (int i = 0; i < 3; ++i) {
                        ar[i] = ((float*)projection.get_data())[((img * 3) + i) * 4 + 3];
                        for (int j = 0; j < 3; ++j) {
                             ar[i] += ((float*)projection.get_data())[(img * 3 + i) * 4 + j] * pt[j];
                        }
                    }

                    // 计算投影后的图像坐标
                    int x = round(ar[0] / ar[2]);
                    int y = round(ar[1] / ar[2]);
                    float z = ar[2];

                    // 检查图像坐标是否有效
                    if ((x >= 0) && (y >= 0) && (x < width) && (y < height) && (z > 0)) {
                        *current_lut = img;  // 记录图像索引
                        *(current_lut + 1) = y * width + x;  // 记录图像内的具体位置
                        count+=1;
                        
                        validp[offset] = 1;  // 标记为有效
                        break;  // 找到有效映射后停止搜索
                    }
                }
                ++offset;  // 处理下一个体素
            }
        }
    }
}

5、关键点——多视角到单体素(Multi-View to One-Voxel)

基于预先计算的查找表LUT,将多视图到单个三维体素中,进行视图转换。

算法流程:

  1. 输入:包括来自多个相机的2D图像特征(features)和预先计算的查找表(LUT)。

  2. 输出:算法输出是一个3D体素特征体积(volume),这是3D BEV空间的一个表示。

  3. 处理流程

    • 遍历3D体素特征体积(volume)中的每个体素(由offset索引)。
    • 使用查找表(LUT)找到每个体素在2D图像中对应的像素坐标(img, x, y)。
    • 如果这个坐标是有效的(即该像素在图像的边界内),那么从2D图像特征(features[img][x][y])中提取相应的特征,并将它填充到3D体素特征体积中相应的offset位置。
    • 这个过程对每个体素重复执行,直到整个3D体素特征体积被填充。

示例代码:

// 根据查找表(LUT)将从2D图像中提取的特征回投影到3D体积中
void backproject_LUT_CPU(Tensor features, std::shared_ptr<int32_t> LUT, std::shared_ptr<float> volume,
                        vector<int32_t> n_voxels) {
    
    // 从特征张量获取图像的数量、高度、宽度和通道数
    int32_t n_images = features.get_shape().d[0];
    int32_t height = features.get_shape().d[1];
    int32_t width = features.get_shape().d[2];
    int32_t n_channels = features.get_shape().d[3];
    
    // 从提供的向量中获取3D体积的维度(X、Y、Z轴上的体素数)
    int n_x_voxels = n_voxels[0];
    int n_y_voxels = n_voxels[1];
    int n_z_voxels = n_voxels[2];
    
    // 获取指向特征数据和体积数据的原始指针
    float* featuresp = (float*)features.get_data();
    float* volumep = volume.get();
    
    // 计算3D体积中总的数据点数量(注意体积*2是因为LUT的每个条目都是成对出现的)
    size_t volume_count = n_x_voxels * n_y_voxels * n_z_voxels * 2;
    // 计算每次迭代中需要复制的数据字节数
    size_t copy_size_per_iter = n_channels * sizeof(float);
    
    // 获取LUT的原始指针
    int32_t* LUTp = LUT.get();
    
    // 遍历LUT中的每个条目,以将特征从2D图像回投影到3D体积中
    for (size_t offset = 0; offset < volume_count; offset=offset+2) {
        // 从LUT中获取当前体素对应的图像索引和目标像素位置
        int img = LUTp[offset];
        int target = LUTp[offset+1];
        
        // 检查图像索引是否有效
        if (img >= 0) {
            // 计算源数据指针:指向特征张量中对应图像的对应像素的特征向量
            float* src = featuresp + img * height * width * n_channels + target * n_channels;
            // 计算目标数据指针:指向3D体积中对应体素的特征向量
            float* dst = volumep + offset/2 * n_channels;
            // 将特征数据从源复制到目标
            memcpy(dst, src, copy_size_per_iter);
        }
    }
}

6、实验测试与效果

实验基本信息: 

  • 在nuScenes数据集上测试,摄像头有六个视角:前左,前,前右,后左,后,后右。
  • 默认3D体素分辨率设置为200x200x6。其中,6表示高度方向(从地面向上)被划分为6个体素层。
  • 这个数字代表在从摄像头到对象的垂直范围内,空间被分成了多少层,每层都是体素网格的一个水平切片。
  • Fast-Ray是应用在Fast-BEV方案中,以下测试是基于Fast-BEV方案的。

如下图所示,是3D体素分辨率对3D目标检测性能的影响。

  • 实验固定了图像分辨率为384x1056,并测试了不同的体素分辨率配置下模型的平均精度(mAP)和nuScenes检测分数(NDS)。
  • 当体素分辨率从200x200x4变化到200x200x12时,mAP从0.345提升到0.350,而NDS则在0.472到0.476之间略有波动。
  • 体素分辨率为300x300x6时,mAP为0.347,NDS为0.471。
  • 当体素分辨率增加到400x400x6时,mAP略有下降到0.337,NDS为0.467。
  • 而体素分辨率为400x400x12时,mAP回升到0.345,NDS增加到0.476。
  • 体素分辨率在200x200x6时,模型取得了较高的NDS值,达到了0.476。

下面两个表是基于Fast-BEV方案设置不同3D体素分辨率,在Xavier、Orin,和T4芯片上进行测试的。

对比不同视图转换方法:

检测效果:

分享完成~

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/529850.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

.net 6 集成NLog

.net 6 webapi项目集成NLog 上代码step 1 添加nugetstep 2 添加支持step 3 添加配置文件 结束 上代码 step 1 添加nuget 添加nuget 包 Roc step 2 添加支持 修改program.cs var builder WebApplication.CreateBuilder(args); // 添加NLog日志支持 builder.AddRocNLog();ste…

java中static关键字(尚未完善)

文章目录 static关键字static可修饰static方法举例static代码块拓展其他链接 static关键字 加载顺序类是构建对象的模板&#xff0c;一个类多个对象static修饰的方法或者变量都属于类&#xff0c;类独有的 static可修饰 修饰变量&#xff08;属于类变量&#xff0c;被创建出来…

极狐GitLab 如何在 helm 中恢复数据

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何在极狐GitLab …

mysql运维知识总结

1. 日志 1.1 错误日志 错误日志是 MySQL 中最重要的日志之一&#xff0c;它记录了当 mysqld 启动和停止时&#xff0c;以及服务器在运行过 程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时&#xff0c;建议首先查看此日志。 该日志是默认开启的&…

Linux: 工具: tshark 抓到了收方向的ESP明文包?

根据这个描述&#xff0c;看着是正常的&#xff0c; 抓到包之后&#xff0c;可以方便的分析问题&#xff0c;省去在wireshark里解码的问题。 经过调查发现是内核将ESP解开之后&#xff0c;如果是tunnel模式&#xff0c;内核又重新将skb丢给了interface去做处理。这样tshark/tcp…

搭建Grafana+Prometheus监控Spring Boot应用

Spring项目改造 maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency><dependency><groupId>io.micrometer</groupId><artif…

Linux服务器上搭建深度学习环境(安装anaconda、创建虚拟环境、安装pytorch)

Linux服务器的搭配 Linux服务器上安装anaconda创建虚拟环境linux上安装pytorchxshell连接服务器 Linux服务器上安装anaconda 链接 创建虚拟环境 参考教程&#xff1a;此处 linux上安装pytorch 链接 xshell连接服务器 链接

本地项目提交 Github

工具 GitIdeaGithub 账号 步骤 使用注册好的 Github 账号&#xff0c;登陆 Github&#xff1b; 创建 Repositories (存储库)&#xff0c;注意填写图上的红框标注&#xff1b; 创建完成之后&#xff0c;找到存储库的 ssh 地址或 https 地址&#xff0c;这取决于你自己的配置…

JRT判断数据是否存在优化

有一种业务情况类似下图&#xff0c;质控能做的项目是仪器关联的项目。这时候维护质控物时候开通项目时候要求加载仪器项目里面的项目&#xff08;没有开通的子业务数据的部分&#xff09;。对右边已经开通的部分要求加载仪器项目里面的项目&#xff08;有开通业务子数据的部分…

微信小程序使用iconfont

进入iconfont&#xff0c;添加至项目 进入项目&#xff0c;点击生成代码&#xff0c;或更新代码 点击打开样式 复制内容到小程序的style文件夹下 最后引入到app.wxss

鹅厂实习offer

#转眼已经银四了&#xff0c;你收到offer了吗# 本来都打算四月再投实习了&#xff0c;突然三月初被wxg捞了&#xff08;一年前找日常实习投的简历就更新了下&#xff09;&#xff0c;直接冲了&#xff0c;流程持续二十多天&#xff0c;结果是运气还不错&#xff0c;应该是部门比…

C# 之 Task、async和 await 、Thread 的简单整理

1、异步方法(async/await) 在 C# 5.0 中出现的 async 和 await &#xff0c;让异步编程变得更简单。 此方法利用了 .NET Framework 4.5 及更高版本、.NET Core 和 Windows 运行时中的异步支持。 编译器可执行开发人员曾进行的高难度工作&#xff0c;且应用程序保留了一个类似…

CAXA3D实体设计2022版 下载地址及安装教程

CAXA 3D是一款专业的实体设计软件&#xff0c;由中国软件公司CAXA开发。它提供了丰富的功能和工具&#xff0c;用于进行三维实体建模和设计。 CAXA 3D具备强大的建模和绘图功能&#xff0c;使用户能够创建复杂的三维实体模型。它支持多种建模方式&#xff0c;包括实体建模、曲…

智过网:报考中级注册安全工程师需要什么条件?

随着社会的快速发展和科技的日新月异&#xff0c;安全生产问题越来越受到人们的关注。中级注册安全工程师作为专业安全管理人才&#xff0c;其职责与角色日益凸显。那么&#xff0c;想要报考中级注册安全工程师&#xff0c;需要满足哪些条件呢&#xff1f; 首先&#xff0c;报考…

Spring Boot 入门指南:轻松上手图文教程

前言 什么是 Spring Boot&#xff1f; Spring Boot 是由 Pivotal 团队提供的全新框架。Spring Boot 是所有基于 Spring Framework 5.0 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。 设计目的&#xff1a; 用…

FreeRtos入门-10 裸机程序的不足

裸机的程序的框架 1&#xff09; 经典单片机程序 // 经典单片机程序 void main() {while (1){任务1();任务2();} } 缺点&#xff1a;任务1和任务2之间的互相影响 2&#xff09;前后台结构&#xff0c;前台&#xff1a;中断处理函数&#xff0c;后台main函数 void main()//后…

Training - 使用 WandB 配置 可视化 模型训练参数

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/137529140 WandB (Weights&Biases) 是轻量级的在线模型训练可视化工具&#xff0c;类似于 TensorBoard&#xff0c;可以帮助用户跟踪…

使用Vivado Design Suite进行功率优化

功率优化是一个可选步骤&#xff0c;它通过使用时钟门控来优化动态功率。它既可以在Project模式下使用&#xff0c;也可以在Non-Project模式下使用&#xff0c;并且可以在逻辑优化之后或布局之后运行&#xff0c;以减少设计中的功率需求。功率优化包括Xilinx的智能时钟门控解决…

STM32F407+FreeRTOS+LWIP UDP组播

开发环境介绍&#xff1a; MCU&#xff1a;STM32F407ZET6 网卡&#xff1a;LAN8720A LWIP版本&#xff1a;V1.1.0 FreeRTOS 版本&#xff1a;V10.2.1 LAN8720A硬件原理图&#xff1a; 硬件连接说明&#xff1a; MII_RX_CLK/RMII_REF_CLK ------>PA1 …

吴恩达2022机器学习专项课程(一) 5.7 检测梯度下降是否收敛

问题预览/关键词 什么是梯度下降收敛&#xff1f;哪些方法可以检测梯度下降是否收敛&#xff1f;什么是学习曲线&#xff1f;曲线上升代表什么&#xff1f;什么原因造成的&#xff1f;如何检测梯度下降是否收敛&#xff1f;多少次迭代&#xff0c;梯度下降会收敛&#xff1f;什…