【BEV 视图变换】Ray-based(2): 代码复现+画图解释 基于深度估计、bev_pool

paper:Lift, Splat, Shoot: Encoding Images from Arbitrary Camera Rigs by Implicitly Unprojecting to 3D
code:https://github.com/nv-tlabs/lift-splat-shoot

一、完整复现代码(可一键运行)和效果图

在这里插入图片描述

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import cv2
import numpy as np# 根据世界坐标范围和一个像素代表的世界坐标距离来计算bev_size
# dx:[0.5,0.5,20]代表单位长度,bx是[-49.75,49.75,0]代表起始网格点的中心,nx[200,200,1] 代表网格数目
xbound = [-50.0, 50.0, 0.5]  # 前后100米,1个pixel=0.5米 -> x方向: 200 pixel
ybound = [-50.0, 50.0, 0.5]  # 左右100米,1个pixel=0.5米 -> y方向: 200 pixel
zbound = [-10.0, 10.0, 20.0]  # 上下20米, 1个pixel=20米  -> z方向: 1   pixel
dbound = [4.0, 45.0, 1.0]  # 深度4~45米, 1个pixel=1米 -> d方向: 41  pixel
D_ = int((dbound[1]-dbound[0])/dbound[2])def gen_dx_bx(xbound, ybound, zbound):dx = torch.Tensor([row[2] for row in [xbound, ybound, zbound]])bx = torch.Tensor([row[0] + row[2]/2.0 for row in [xbound, ybound, zbound]])nx = torch.LongTensor([(row[1] - row[0]) / row[2] for row in [xbound, ybound, zbound]])dx = nn.Parameter(dx, requires_grad=False)bx = nn.Parameter(bx, requires_grad=False)nx = nn.Parameter(nx, requires_grad=False)return dx, bx, nxbatch_size = 1
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 模型输入尺寸及下采样倍数
in_H = 128
in_W = 352
scale_downsample = 16
# 模型输出尺寸
feat_W16 = in_W // scale_downsample
feat_H16 = in_H // scale_downsample
semantic_channels = 64# 相机参数(两个相机)
num_cams = 2
rots=torch.Tensor([[[[ 8.2076e-01, -3.4144e-04,  5.7128e-01],[-5.7127e-01,  3.2195e-03,  8.2075e-01],[-2.1195e-03, -9.9999e-01,  2.4474e-03]],[[-9.3478e-01,  0, 0],[ 3.5507e-01,  0, -9.3477e-01],[-1.0805e-02, -9.9981e-01, 0]]]])
intrins = torch.Tensor([[[[1.2726e+03, 0.0, 0],[0.0000e+00, 1.2726e+03, 4.7975e+02],[0.0000e+00, 0.0000e+00, 1.0000e+00]],[[1.2595e+03, 0.0000e+00, 8.0725e+02], [0.0000e+00, 1.2595e+03, 5.0120e+02],[0.0000e+00, 0.0000e+00, 1.0000e+00]]]])
post_rots = torch.Tensor([[[[0.2200, 0.0000, 0.0000],[0.0000, 0.2200, 0.0000],[0.0000, 0.0000, 1.0000]],[[0.2200, 0.0000, 0.0000],[0.0000, 0.2200, 0.0000],[0.0000, 0.0000, 1.0000]]]])
post_trans =torch.Tensor([[[  0.],[  0.]], [[0.], [0.]], [[  0.],[  0.]]])
trans = torch.Tensor([[[ 1.5239,  0.4946,  1.5093], [ 1.0149, -0.4806,  1.5624]]])def create_uvd_frustum():# 41米深度范围,值在[4,45]# 扩展至41x22x8distance = torch.arange(*dbound, dtype=torch.float).view(-1, 1, 1).expand(-1, feat_H16, feat_W16)D, _, _ = distance.shape# 22格,值在[0,128]# 再扩展至[41,8,22]x_stride = torch.linspace(0, in_W - 1, feat_W16, dtype=torch.float).view(1, 1, feat_W16).expand(D, feat_H16, feat_W16)# 8格,值在[0,352]# 再扩展至[41,8,22]y_stride = torch.linspace(0, in_H - 1, feat_H16, dtype=torch.float).view(1, feat_H16, 1).expand(D, feat_H16, feat_W16)# 创建视锥: [41,8,22,3]frustum = torch.stack((x_stride, y_stride, distance), -1)# 不计算梯度,不需要学习return nn.Parameter(frustum, requires_grad=False)def plot_uvd_frustum(frustum): # 41 8 22 3fig = plt.figure()ax = fig.add_subplot(111, projection='3d')# Convert frustum tensor to numpy array for visualizationfrustum_np = frustum.numpy()# Extract x, y, d coordinatesx = frustum_np[..., 0].flatten()y = frustum_np[..., 1].flatten()d = frustum_np[..., 2].flatten()# Plot the points in 3D spaceax.scatter(x, y, d, c=d, cmap='viridis', marker='o')ax.set_xlabel('u')ax.set_ylabel('v')ax.set_zlabel('d')plt.show()path = f'uvd_frustum.png'plt.savefig(path)def get_geometry_feat(frustum,rots, trans, intrins, post_rots, post_trans):B, N, _ = trans.shape# 视锥逆数据增强points = frustum - post_trans.view(B, N, 1, 1, 1, 3)# 加上B,N(6 cams)维度points = torch.inverse(post_rots).view(B, N, 1, 1, 1, 3, 3).matmul(points.unsqueeze(-1))#根据相机内外参将视锥点云从相机坐标映射到世界坐标points = torch.cat((points[:, :, :, :, :, :2] * points[:, :, :, :, :, 2:3],points[:, :, :, :, :, 2:3]), 5)combine = rots.matmul(torch.inverse(intrins))points = combine.view(B, N, 1, 1, 1, 3, 3).matmul(points).squeeze(-1)points += trans.view(B, N, 1, 1, 1, 3)return pointsdef plot_XYZ_frustum(frustum,path):fig = plt.figure()ax = fig.add_subplot(111, projection='3d')# Convert frustum tensor to numpy array for visualizationfor i in range(len(frustum)):frustum_np = frustum[i].numpy()# Extract x, y, d coordinatesx = frustum_np[..., 0].flatten()y = frustum_np[..., 1].flatten()d = frustum_np[..., 2].flatten()# Plot the points in 3D spaceax.scatter(x, y, d, c=d, cmap='viridis', marker='o')ax.set_xlabel('X')ax.set_ylabel('Y')ax.set_zlabel('Z')plt.show()plt.savefig(path)def cumsum_trick(cam_feat, geom_feat, ranks):# 最后一个维度累计,前缀和cam_feat = cam_feat.cumsum(0)# 过滤# [42162,64]->[7268,64] [42162,4]->[7268,4]# 将rank错位比较,找到rank中 == voxel_id == 发生变化的位置,记为keptkept = torch.ones(cam_feat.shape[0], device=cam_feat.device, dtype=torch.bool)kept[:-1] = (ranks[1:] != ranks[:-1])# 利用kept筛选得到x, 错位相减,从而实现将落在相同voxel特征求和cam_feat, geom_feat = cam_feat[kept], geom_feat[kept]cam_feat = torch.cat((cam_feat[:1], cam_feat[1:] - cam_feat[:-1])) # 错位相减得到的特征和return cam_feat, geom_featdef plot_bev(bev, name = f'bev'):# ---- tensor -> array ----#array1 = bev.squeeze(0).cpu().detach().numpy()# ---- array -> mat ----#array1 = array1 * 255mat = np.uint8(array1)mat = mat.transpose(1, 2, 0)# ---- vis ----#cv2.imshow(name, mat)cv2.waitKey(0)if __name__ == "__main__":# 1.创建三维tensor(2d image + depth)uvd_frustum = create_uvd_frustum()plot_uvd_frustum(uvd_frustum)# 2.视锥化(使用相机内外参,将三维tensor转到EGO坐标系下)XYZ_frustum = get_geometry_feat(uvd_frustum,rots, trans, intrins, post_rots, post_trans)plot_XYZ_frustum(XYZ_frustum[0],path = f'EGO_XYZ_frustum.png')# 3.体素化dx, bx, nx = gen_dx_bx(xbound, ybound, zbound)geom_feats = ((XYZ_frustum - (bx - dx / 2.)) / dx).long()plot_XYZ_frustum(geom_feats[0], path = f'voxel.png')# 4.bev_pool# 4.1. cam_feats,geom_feats 展平cam_feats = torch.rand(batch_size, num_cams, D_, feat_H16, feat_W16, semantic_channels)B, N, D, H, W, C = cam_feats.shapeL__ = B * N * D * H * Wcam_feats = cam_feats.reshape(L__, C)geom_feats = geom_feats.view(L__, 3)# 4.2.geom_feat增加batch维度batch_index = torch.cat([torch.full([L__ // B, 1], ix, device=cam_feats.device, dtype=torch.long) for ix in range(B)])geom_feats = torch.cat((geom_feats, batch_index), 1)# 4.3.filter by (X<200,Y<200,Z<1)kept = (geom_feats[:, 0] >= 0) & (geom_feats[:, 0] < nx[0]) & (geom_feats[:, 1] >= 0) & (geom_feats[:, 1] < nx[1]) & (geom_feats[:, 2] >= 0) & (geom_feats[:, 2] < nx[2])cam_feats = cam_feats[kept]geom_feats = geom_feats[kept]# 4.4.voxel index 位置编码,排序ranks = (geom_feats[:, 0] * (nx[1] * nx[2] * B)  # X+ geom_feats[:, 1] * (nx[2] * B)  # Y+ geom_feats[:, 2] * B  # Z+ geom_feats[:, 3])  # batch_indexsorts = ranks.argsort()cam_feats, geom_feats, ranks = cam_feats[sorts], geom_feats[sorts], ranks[sorts]# 4.5. sumcam_feats, geom_feats = cumsum_trick(cam_feats, geom_feats, ranks)# 4.6.根据视锥获取相应的cam_feat, final:[1,64,1,200,200]final = torch.zeros((B, C, nx[2], nx[0], nx[1]), device=cam_feats.device)final[geom_feats[:, 3], :, geom_feats[:, 2], geom_feats[:, 0], geom_feats[:, 1]] = cam_feats# 4.7.去掉Z维度, dim_Z维度属于dim=2, 生成bev图final = torch.cat(final.unbind(dim=2), 1)# 5.bev_encoderbev_encoder = nn.Conv2d(semantic_channels, 1, kernel_size=1, stride=1, padding=0,bias=False)bev = bev_encoder(final)plot_bev(bev, name = f'bev')

二、逐步代码讲解+图解

完整流程:
1.创建uv coord + depth estimation (2d image + depth)
2.视锥化(uv coord -> world coord) (根据相机内外参,构建4x3的投影矩阵)
3.体素化(world coord -> voxel coord) (会有到世界范围划分及各自维度的刻度)
4.bev_pool(voxel coord -> bev coord)(去掉Z轴)

1.创建uv coord + depth estimation (2d image + depth)

uvd_frustum = create_uvd_frustum()
plot_uvd_frustum(uvd_frustum)

在这里插入图片描述
注意
1.坐标范围,u,v范围代表模型输入尺寸(352,128),d范围为(4,45)。
2.u轴有22个柱子(pillar),22=352//16;v轴有8个柱子(pillar),8=128//16;d轴有41个刻度,41=(45-4)//1

2.视锥化(uv coord -> world coord) (根据相机内外参,构建4x3的投影矩阵)

XYZ_frustum = get_geometry_feat(uvd_frustum,rots, trans, intrins, post_rots, post_trans)
plot_XYZ_frustum(XYZ_frustum[0],path = f'EGO_XYZ_frustum.png')

在这里插入图片描述
我这里为了看起来更直观点,选了两个相机,实际在使用过程中,可以灵活使用1个,2个,4个,6个相机。

3.体素化(world coord -> voxel coord) (会有到世界范围划分及各自维度的刻度)

dx, bx, nx = gen_dx_bx(xbound, ybound, zbound)
geom_feats = ((XYZ_frustum - (bx - dx / 2.)) / dx).long()
plot_XYZ_frustum(geom_feats[0], path = f'voxel.png')

在这里插入图片描述
为什么上面和下面的形状不一样呢?因为1.相机内外参数的影响 2.因为(旋转,平移)数据增强的影响
注意观察,此时的XYZ轴的范围已经落在(200,200,1)的bev尺寸范围里了!

4.bev_pool(voxel coord -> bev coord)(去掉Z轴)

  • 4.1. cam_feats,geom_feats 展平
cam_feats = torch.rand(batch_size, num_cams, D_, feat_H16, feat_W16, semantic_channels)
B, N, D, H, W, C = cam_feats.shape
L__ = B * N * D * H * W
cam_feats = cam_feats.reshape(L__, C)geom_feats = geom_feats.view(L__, 3)
  • 4.2.geom_feat增加batch维度
batch_index = torch.cat([torch.full([L__ // B, 1], ix, device=cam_feats.device, dtype=torch.long) for ix in range(B)])
geom_feats = torch.cat((geom_feats, batch_index), 1)
  • 4.3.filter by (X<200,Y<200,Z<1)
kept = (geom_feats[:, 0] >= 0) & (geom_feats[:, 0] < nx[0]) & (geom_feats[:, 1] >= 0) & (geom_feats[:, 1] < nx[1]) & (geom_feats[:, 2] >= 0) & (geom_feats[:, 2] < nx[2])
cam_feats = cam_feats[kept]
geom_feats = geom_feats[kept]

在这里插入图片描述

  • 4.4.voxel index 位置编码,排序
ranks = (geom_feats[:, 0] * (nx[1] * nx[2] * B)  # X+ geom_feats[:, 1] * (nx[2] * B)  # Y+ geom_feats[:, 2] * B  # Z+ geom_feats[:, 3])  # batch_index
sorts = ranks.argsort()
cam_feats, geom_feats, ranks = cam_feats[sorts], geom_feats[sorts], ranks[sorts]

可以参考我画的示意图
在这里插入图片描述

  • 4.5. sum
cam_feats, geom_feats = cumsum_trick(cam_feats, geom_feats, ranks)
  • 4.6.根据视锥获取相应的cam_feat, final:[1,64,1,200,200]
final = torch.zeros((B, C, nx[2], nx[0], nx[1]), device=cam_feats.device)
final[geom_feats[:, 3], :, geom_feats[:, 2], geom_feats[:, 0], geom_feats[:, 1]] = cam_feats
  • 4.7.去掉Z维度, dim_Z维度属于dim=2, 生成bev图
final = torch.cat(final.unbind(dim=2), 1)

5.bev_encoder

bev_encoder = nn.Conv2d(semantic_channels, 1, kernel_size=1, stride=1, padding=0,bias=False)
bev = bev_encoder(final)
plot_bev(bev, name = f'bev')

在这里插入图片描述
bev尺寸为200x200

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

8587 行编辑程序

### 思路 1. **初始化栈**&#xff1a;创建一个空栈用于存储有效字符。 2. **读取输入**&#xff1a;读取输入的行数 n&#xff0c;然后逐行读取字符。 3. **处理字符**&#xff1a; - 如果是 #&#xff0c;则弹出栈顶字符&#xff08;如果栈不为空&#xff09;。 - 如果…

谷歌的AI反击战:创始人谢尔盖·布林的回归与大模型组合的未来

近年来&#xff0c;随着AI技术的迅猛发展&#xff0c;尤其是ChatGPT等大语言模型的出现&#xff0c;全球科技格局正发生剧烈变化。作为曾经引领AI潮流的谷歌&#xff0c;在这场竞争中逐渐失去了领头羊的地位。然而&#xff0c;谷歌的创始人之一谢尔盖布林&#xff08;Sergey Br…

黑马智数Day1

src文件夹 src 目录指的是源代码目录&#xff0c;存放项目应用的源代码&#xff0c;包含项目的逻辑和功能实现&#xff0c;实际上线之后在浏览器中跑的代码就是它们 apis - 业务接口 assets - 静态资源 &#xff08;图片&#xff09; components - 组件 公共组件 constants…

【WEB】序列一下

1、 2、反序列化 <?phpclass Polar{public $url polarctf.com;public $ltsystem;public $bls /;function __destruct(){$a $this->lt;$a($this->b);} }$a new Polar(); echo serialize($a); ?>###O:5:"Polar":3:{s:3:"url";s:12:"…

某乐指数爬虫逆向分析

目标网站 aHR0cHM6Ly93d3cuY2hpbmFpbmRleC5uZXQvcmFua2xpc3QvNS8w 一、抓包分析 明显请求参数有sign加密&#xff0c;有经验的很容易就知道这就是个MD5加密&#xff0c;在一个就是响应数据也加密了 二、逆向分析 搜索sign&#xff0c;直接定位到加密位置 进入方法内部 hae方…

win11 wsl2安装ubuntu22最快捷方法

操作系统是win11&#xff0c;wsl版本是wsl2&#xff0c;wsl应该不用多介绍了&#xff0c;就是windows上的虚拟机&#xff0c;在wsl上可以很方便的运行Linux系统&#xff0c;性能棒棒的&#xff0c;而且wsl运行的系统和win11主机之间的文件移动是无缝的&#xff0c;就是两个系统…

某建筑市场爬虫数据采集逆向分析

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 目标网站 aHR0cHM6Ly9qenNjLm1vaHVyZC5nb3YuY24vZGF0YS9jb21wYW55P2NvbXBsZXhuYW1lPSVFNiVCMCVCNA 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面…

Spring AI Alibaba,阿里的AI Java 开发框架

源码地址 https://github.com/alibaba/spring-ai-alibaba

【linux-Day4】linux的基本指令<下>

【linux-Day4】linux的基本指令<下> linux下的基本指令&#x1f4e2;date&#xff1a;显示时间&#x1f4e2;cal&#xff1a;显示公历日历&#x1f4e2;whereis &#xff1a; 查找指令->可执行文件/源代码/帮助手册所在的位置&#x1f4e2;find &#xff1a;在目录中搜…

入门Django

Django Django 简介URL组成部分详解第一个Django项目创建一个Django项目运行Django项目项目结构介绍project和app的关系 URL与视图函数的映射URL的两种传参方式在URL中携带参数 path函数url路由模块化url反转 Django 简介 Django 是一个高级的 Python Web 框架&#xff0c;用于…

握手传输 状态机序列检测(记忆科技笔试题)_2024年9月2日

发送模块循环发送0-7&#xff0c;在每个数据传输完成后&#xff0c;间隔5个clk&#xff0c;发送下一个 插入寄存器打拍处理&#xff0c;可以在不同的时钟周期内对信号进行同步&#xff0c;从而减少亚稳态的风险。 记忆科技笔试题&#xff1a;检测出11011在下一个时钟周期输出…

深度学习03-神经网络01-什么是神经网络?

神经网络的基本概念 人工神经网络&#xff08;Artificial Neural Network&#xff0c;ANN&#xff09;&#xff1a; 是一种模仿生物神经网络的计算模型。由多个神经元&#xff08;或称为节点&#xff09;组成&#xff0c;这些节点通过不同的连接来传递信息。 每个神经元可以接…

【Proteus51单片机仿真】PWM直流电机调速

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 ** 基于AT89C51&#xff0c;L298N驱动两个电机&#xff0c;因为是平台&#xff0c;最后用两个电机驱动&#xff0c;然后第一个按键控制所有电机停止&#xff0c;第二个按键按下&#xff0c…

华为HarmonyOS地图服务 4 - 通过“地图相机“控制地图的可见区域

场景介绍 华为地图的移动是通过模拟相机移动的方式实现的,您可以通过改变相机位置,来控制地图的可见区域,效果如图所示。 本章节将向您介绍相机的各个属性与含义,并移动相机。 相机移动前 接口…

计算机的错误计算(一百)

摘要 探讨 与 的计算精度问题。 从计算机的错误计算&#xff08;九十九&#xff09;知&#xff0c;运算 与 均被列在IEEE754-2019中。然而&#xff0c;似乎并没有哪种语言实现内置了第二个运算。 例1. 计算 与 不妨在Python 3.12.5 下计算&#xff0c;则有 然而&#…

黄酮类化合物及其衍生物生物合成的进展:构建酵母细胞工厂的系统策略-

Advances in Flavonoid and Derivative Biosynthesis: Systematic Strategies for the Construction of Yeast Cell FactoriesCli 黄酮类化合物及其衍生物生物合成的进展&#xff1a;构建酵母细胞工厂的系统策略 摘要 黄酮类化合物是一类重要的天然多酚化合物&#xff0c;具有…

Windows系统文件夹中的文件名排序

一天张三、李四的同事周五接到王哥的一个任务需求&#xff0c;有一个文件夹&#xff0c;里面有许多图片文件&#xff0c;网页访问某个分类展示文件的时候&#xff0c;王哥希望文件名的展示顺序可以按照Windows资源管理器中文件名升序排序的方式展示。 网站图片目录中有如下图片…

[Python学习日记-26] Python 中的文件操作

[Python学习日记-26] Python 中的文件操作 简介 操作模式 循环文件 其他功能 混合模式 修改文件 简介 在 Python 中的文件操作其实和我们平时使用的 Word 的操作是比较类似的&#xff0c;我们先说一下 Word 的操作流程&#xff0c;流程如下&#xff1a; 找到文件&#x…

鸿蒙开发项目中你是怎么理解生命周期?你知道的生命周期函数有那些, 说一下执行时机?(页面和自定义组件生命周期)

生命周期流程如下图所示&#xff0c;下图展示的是被Entry装饰的组件&#xff08;页面&#xff09;生命周期。 #一、怎么理解生命周期&#xff1f; 生命周期&#xff1a;简单点理解就是从创建到销毁的过程 #二、你知道的生命周期函数有那些, 说一下执行时机&#xff1f; 自定义…

前端框架的比较与选择详解

目录 引言 一、前端框架概述 1.1 什么是前端框架 1.2 为什么使用前端框架 二、主流前端框架比较 2.1 React 2.1.1 概述 2.1.2 特点 2.1.3 优点 2.1.4 缺点 2.1.5 适用场景 2.2 Angular 2.2.1 概述 2.2.2 特点 2.2.3 优点 2.2.4 缺点 2.2.5 适用场景 2.3 Vue.j…