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

news2024/11/15 18:20:14

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, nx

batch_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]
    # 扩展至41x22x8
    distance = 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 3
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    # Convert frustum tensor to numpy array for visualization
    frustum_np = frustum.numpy()

    # Extract x, y, d coordinates
    x = frustum_np[..., 0].flatten()
    y = frustum_np[..., 1].flatten()
    d = frustum_np[..., 2].flatten()

    # Plot the points in 3D space
    ax.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 points

def plot_XYZ_frustum(frustum,path):
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    # Convert frustum tensor to numpy array for visualization
    for i in range(len(frustum)):
        frustum_np = frustum[i].numpy()
        # Extract x, y, d coordinates
        x = frustum_np[..., 0].flatten()
        y = frustum_np[..., 1].flatten()
        d = frustum_np[..., 2].flatten()
        # Plot the points in 3D space
        ax.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 == 发生变化的位置,记为kept
    kept = 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_feat


def plot_bev(bev, name = f'bev'):
    # ---- tensor -> array ----#
    array1 = bev.squeeze(0).cpu().detach().numpy()
    # ---- array -> mat ----#
    array1 = array1 * 255
    mat = 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.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')

二、逐步代码讲解+图解

完整流程:
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.coloradmin.cn/o/2154634.html

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

相关文章

Springboot3 + MyBatis-Plus + MySql + Uniapp 商品加入购物车功能实现(最新教程附源码)

Springboot3 MyBatis-Plus MySql Uniapp 商品加入购物车功能实现&#xff08;针对上一篇sku&#xff09; 1、效果展示2、后端代码2.1 model2.2 mapper server serverImpl 参照上一篇自动生成2.3 controller 3、前端代码3.1 index.js3.2 shop-info.vue3.3 ShopBottomButton.v…

掌上高考爬虫逆向分析

目标网站 aHR0cHM6Ly93d3cuZ2Fva2FvLmNuL3NjaG9vbC9zZWFyY2g/cmVjb21zY2hwcm9wPSVFNSU4QyVCQiVFOCU4RCVBRg 一、抓包分析 二、逆向分析 搜索定位加密参数 本地生成代码 var CryptoJS require(crypto-js) var crypto require(crypto);f "D23ABC#56"function v(t…

机器学习之实战篇——图像压缩(K-means聚类算法)

机器学习之实战篇——图像压缩(K-means聚类算法&#xff09; 0. 文章传送1.实验任务2.实验思想3.实验过程 0. 文章传送 机器学习之监督学习&#xff08;一&#xff09;线性回归、多项式回归、算法优化[巨详细笔记] 机器学习之监督学习&#xff08;二&#xff09;二元逻辑回归 …

Unity自我实现响应式属性

其实只是写着玩,响应式编程建议使用UniRx插件(一套成熟的响应式编程解决方案),我写的主要是借鉴一下这个思想,实现的也不够优雅,不过逻辑也算严密可以正常使用.你可以查看我写的理解响应式属性的思想. 借鉴UniRx的ReactiveProperty类,且UniRx不仅有响应式属性. using System; …

光伏板缺陷红外检测数据集

光伏板缺陷红外检测数据集 包含以下4个数据文件&#xff1a; /train&#xff1a;训练集 /valid&#xff1a;验证集 /test&#xff1a;测试集 README.txt&#xff1a;数据说明 【数据说明】检测目标以Pascal VOC格式进行标注&#xff0c;对每个图像进行以下预处理&#xff0c;统…

【Linux笔记】如何将内容从一个文件复制到另一个文件

比如&#xff1a;将文件tmp_file.txt中的部分数据&#xff0c;复制到file01.txt中去 tmp_file.txt文中内容&#xff1a; file01.txt为空文档 一、使用vi编辑器 I、文件中直接使用:e 目标文件进行切换文件复制 1、打开被复制文件 vi tmp_file.txt 2、进入一般命令模式 默认情况为…

2024年华为杯-研赛更新时间轴-资料分享

本次 助攻CDF题 问题一二三问均已完成更新&#xff0c;更新计划轴如图所示 由于赛题之间存在紧密的联系&#xff0c;单独发布问题一二&#xff0c;有可能与明天最终论文不相符&#xff0c;会根据后面问题对前面几问进行调整。个人建议&#xff0c;等明天上午的完整论文即可 题 …

ACT训练调参技巧

ACT Tuning Tips 这里是针对斯坦福Aloha机械臂远程训练调参技巧的中文解释&#xff0c;初学者可能会对此感到陌生&#xff0c;不过不用担心&#xff0c;多尝试&#xff0c;多实验。 - Chunk size is the most important param to tune when applying ACT to a new environment…

【YOLO目标检测学生课堂行为数据集】共4266张、已标注txt格式、有训练好的yolov5的模型

目录 说明图片示例 说明 数据集格式&#xff1a;YOLO格式 图片数量&#xff1a;4266 标注数量(txt文件个数)&#xff1a;4266 标注类别数&#xff1a;3 标注类别名称&#xff1a;hand、read、write 数据集下载&#xff1a;学生课堂行为数据集 图片示例 数据集图片&#…

HTML5中新增元素介绍

引入了许多新元素&#xff0c;以增强网页的语义和功能。这些新元素大致可以按以下几类进行分类和介绍。 下面是对各标签的详解&#xff0c;section、header、footer、nav、article、aside、figure、code、dialog、meter、time、progress、video、audio、details、atagrid、menu…

AIGC7: 高通骁龙AIPC开发者沙龙过程记录A

图中是一座高耸的宫殿。 就像AI的出现&#xff0c;慢慢初现端倪&#xff0c;头角峥嵘。 背景 一直以来都比较关注AI的发展&#xff0c;有幸再一次参加异常AI的盛会。 从我的角度看。 高通是一家生产芯片的公司&#xff0c;国内的小米&#xff0c;荣耀&#xff0c;Oppo , Vi…

Qt_窗口界面QMainWindow的介绍

目录 1、菜单栏QMenuBar 1.1 使用QMainWindow的准备工作 1.2 在ui文件中设计窗口 1.3 在代码中设计窗口 1.4 实现点击菜单项的反馈 1.5 菜单中设置快捷键 1.6 菜单中添加子菜单 1.7 菜单项中添加分割线和图标 1.8 关于菜单栏创建方式的讨论 2、工具栏QToolBar …

[产品管理-32]:NPDP新产品开发 - 30 - 文化、团队与领导力 - 领导力与团队的可持续发展

目录 一、团队领导的领导力 1.1 领导力 1、领导力的定义 2、领导力的重要性 3、领导力的构成要素 4、如何提升领导力 1.2 情商 二、虚拟团队 1、团队定义与特征 2、团队优势 3、团队挑战与应对策略 三、可持续发展 四、团队管理和领导力中的度量指标 4.1 激励创新…

unix中的进程标识以及使用场景

一、前言 本文将介绍unix系统中的进程标识以及使用场景。进程标识和用户标识类似&#xff0c;只不过其指代的对象是一个进程。我们常把进程标识称为进程ID&#xff0c;本文将讨论如下内容&#xff1a; 1.什么是进程标识&#xff1f; 2.特殊的进程标识 3.如果获取以及使用进程标…

深度学习02-pytorch-09(pytorch完结篇)-基本使用介绍-线性回归案例

使用PyTorch的基本流程&#xff1a;数据准备&#xff1a;通过make_regression生成回归数据&#xff0c;使用 TensorDataset 和 DataLoader 来封装数据。 模型定义&#xff1a;使用 nn.Module 或内置层&#xff08;如 nn.Linear&#xff09;来定义模型结构。 损失函数和优化器…

【全网最全】2024年华为杯研赛D题成品论文获取入口(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片&#xff0c;那是获取资料的入口&#xff01; 点击链接加入【2024华为杯研赛资料汇总】&#xff1a;https://qm.qq.com/q/XzdIsvbiM0https://qm.qq.com/q/XzdIsvbiM0 你是否在寻找数学建模比赛的突破点…

【他山之石】优化 JavaScript 的乐趣与价值(下)

前言 继本文的 上篇 发表之后&#xff0c;没想到反响还挺好&#xff0c;看来大家在 JS 优化的问题上越来越注重“与国际接轨”了。一起来看本文的下篇&#xff0c;也是干货满满。 文章目录 6. Avoid large objectsWhat the eff should I do about this? 7. Use eval8. Use str…

多元形式助力商业价值最大化,王鹤棣商业影响力遥遥领先

明星商业代言层出不穷&#xff0c;但在个人影响力的升级玩法上&#xff0c;当代青年偶像王鹤棣以其独特的个人魅力和卓越的商业头脑&#xff0c;正逐步搭建起一个以个人形象为核心&#xff0c;与各大品牌相互成就的立体商业模型。通过一系列创新的商务合作模式&#xff0c;王鹤…

[Java并发编程] synchronized(含与ReentrantLock的区别)

文章目录 1. synchronized与ReentrantLock的区别2. synchronized的作用3. synchronized的使用3.1 修饰实例方法&#xff0c;作用于当前实例&#xff0c;进入同步代码前需要先获取实例的锁3.2 修饰静态方法&#xff0c;作用于类的Class对象&#xff0c;进入修饰的静态方法前需要…

React组件如何暴露自身的方法

一、研究背景 最近遇到一个如何暴露React组件自身方法的问题。在某些时候&#xff0c;我们需要调用某个组件内部的方法以实现某个功能&#xff0c;因此我们需要了解如何暴露组件内部API的方法。 二、实践过程 本文主要介绍React组件暴露子组件API的方法&#xff0c;以下是实…