【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/1543125.html

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

相关文章

旺店通ERP集成金蝶EAS(金蝶EAS主供应链)

源系统成集云目标系统 金蝶EAS介绍 金蝶EAS是一款全球首款融合TOGAF标准SOA架构的企业管理软件&#xff0c;专门为大中型企业设计&#xff0c;以“创造无边界信息流”为产品设计理念&#xff0c;支持云计算、SOA和动态流程管理的整合技术平台。 旺店通介绍 旺店通ERP系统是一…

CORE Kestrel Web、InProcess、OutOfProcess、启动配置

Kestrel 服务 ASP.NET Core是一个跨平台框架。 这意味着它支持在不同类型的操作系统&#xff08;例如Windows&#xff0c;Linux或Mac&#xff09;上开发和运行应用程序。 Kestrel是ASP.NET Core应用程序的跨平台Web服务器。 这意味着该服务器支持ASP.NET Core支持的所有平台和…

防火墙详解(二)通过网页登录配置华为eNSP中USG6000V1防火墙

配置步骤 步骤一 打开eNSP&#xff0c;建立如下拓扑。防火墙使用&#xff1a;USG6000V1。 Cloud的作用是通过它可以连接本地的网卡&#xff0c;然后与我们的电脑进行通信。 由于防火墙USG6000V&#xff0c;不能直接开启&#xff0c;需要的导入包&#xff0c;所以需要在华为官网…

一文了解什么是大模型?到底大模型有什么用呢?

党中央、国务院面向未来准确把握时代大势&#xff0c;已于十三五期间部署推进数字中国建设&#xff0c;《国民经济和社会发展第十四个五年规划和2035年远景目标纲要》更是将“加快数字化发展&#xff0c;建设数字中国”单列成篇&#xff0c;要求“提高数字政府建设水平”&#…

数据安全评估工程师CCRC-DSA

数据安全评估工程师这一角色&#xff0c;要求从事者具备特定的条件与能力。 本文旨在阐述如何成为数据安全评估工程师&#xff0c;包括所需条件及该职业的难易程度。 一、数据安全评估工程师的角色 数据安全评估工程师专注于对企业数据的安全风险进行评估。 他们通过对数据…

Android平台Unity3D下如何同时播放多路RTMP|RTSP流?

技术背景 好多开发者&#xff0c;提到希望在Unity的Android头显终端&#xff0c;播放2路以上RTMP或RTSP流&#xff0c;在设备性能一般的情况下&#xff0c;对Unity下的RTMP|RTSP播放器提出了更高的要求。实际上&#xff0c;我们在前几年发布Unity下直播播放模块的时候&#xf…

橙子质量检测系统源码分享

橙子质量检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

数字化转型的实践指南:业务能力建模的全景应用与企业创新路径

在当今快速变化的商业环境中&#xff0c;数字化转型已成为企业持续创新和提升竞争力的关键战略。然而&#xff0c;如何有效规划、构建并管理企业的核心业务能力&#xff0c;确保企业在数字化时代能够敏捷应对市场变化&#xff0c;是许多企业面临的挑战。《业务能力指南》为这一…

如何使用ChatGPT撰写文献综述?7个步骤轻松搞定

大家好,感谢关注。我是七哥,一个在高校里不务正业,折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥(yida985)交流,多多交流,相互成就,共同进步,为大家带来最酷最有效的智能AI学术科研写作攻略。 撰写文献综述对于研究人员和学生来说…

STM32F407-03

PWM PWM指的是脉冲宽度的控制,是一种利用微处理器的数字输出能力来控制模拟电路技术 PWM有两个关键参数一个是占空比 和 频率 频率指的是STM32的定时器通道的脉冲次数 占空比指的是一个周期内高电平所占的比例 PWM一般是用在工业控制领域 在这里可以看到PF9引脚和TIM14是相关…

白酒冷知识 普通人判断酒好坏这三招就够了

摩擦法&#xff1a;手心滴几滴白酒反复摩擦假酒: 发酸发臭真酒:粮食香气 兑水法&#xff1a;酒中加1/3的水 假酒: 无任何反应纯粮酒&#xff0c;会变浑浊 火烧法倒满酒用火烧假酒: 无颜色有臭味 纯粮酒:烧完浑浊酒糟香

java项目之城镇保障性住房管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的城镇保障性住房管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 城镇保障性住房管…

9.23今日错题解析(软考)

前言 这是用来记录我每天备考软考设计师的错题的&#xff0c;大部分错题摘自希赛中的题目&#xff0c;但相关解析是原创&#xff0c;有自己的思考&#xff0c;为了复习&#xff1a;&#xff09;&#xff0c;最后希望各位报考软考的小伙伴都能上岸&#xff01;&#xff01;&…

10分钟了解什么是多模态大模型(MM-LLMs)

1. 什么是多模态 Multimodality 多模态&#xff08;Multimodality&#xff09;是指集成和处理两种或两种以上不同类型的信息或数据的方法和技术。在机器学习和人工智能领域&#xff0c;多模态涉及的数据类型通常包括但不限于文本、图像、视频、音频和传感器数据。多模态系统的…

企业微信not allow to access from your ip 解决方案

正文 不用看&#xff0c;你可能的是本地测试企业微信接口 公司网络的对外ip是会变的&#xff0c;你可以去下图这里查&#xff0c;然后填到上图那边就可以了。 下面是废话 我知道企业微信这里坑很多&#xff0c;但是我也不清楚35岁的我还能做多久这行多久&#xff0c;只能说&a…

Kotlin 函数和变量(四)

导读大纲 1.1 基本要素: 函数和变量1.1.1 声明变量以存储数据1.1.2 将变量标记为只读或可重新赋值1.1.3 更简单的字符串格式化: 字符串模板 1.1 基本要素: 函数和变量 本节将向你介绍每个 Kotlin 程序都包含的基本元素: 函数和变量 你将编写自己的第一个 Kotlin 程序,了解 Kotl…

18_Python文件操作

计算机中的文件 文件是存储在计算机上的数据集合&#xff0c;它可以是文本、图片、音频、视频或其他任何类型的数据。 在计算机系统中&#xff0c;文件通常用来长期保存信息。 文本文件&#xff1a;一种以字符编码&#xff08;如ASCII、UTF-8、UTF-16等&#xff09;的形式存储…

高速滑环在摄像领域的应用分析

高速滑环在现代摄像技术中扮演着至关重要的角色。随着摄像设备向高速度、高精度的方向发展&#xff0c;传统的信号传输方式已无法满足需求。高速滑环作为连接旋转部件与固定部件的重要组件&#xff0c;能够有效地传递电信号和数据&#xff0c;为摄像设备的高效运转提供保障。 …

[arcgis插件]在批量出图时,如何把图层属性表以动态表格的形式插入到布局页面

在Arcmap&#xff0c;如何把图层属性表以动态表格的形式插入到布局页面? 众所周知&#xff0c;在属性表的左上角&#xff0c;有个功能是“把表添加到布局”&#xff0c;就可以把属性表以表格的形式添加到布局页面。 但是今天要说的是并不是这个&#xff0c;今天要说的是&…

神经网络(一):神经网络入门

文章目录 一、神经网络1.1神经元结构1.2单层神经网络&#xff1a;单层感知机1.3两层神经网络&#xff1a;多层感知机1.4多层神经网络 二、全连接神经网络2.1基本结构2.2激活函数、前向传播、反向传播、损失函数2.2.1激活函数的意义2.2.2前向传播2.2.3损失函数、反向传播2.2.4梯…