文章目录
1.1 问题提出
针孔相机和鱼眼相机的投影模型和畸变模型不一样,如果对鱼眼的模型不太了解可以到我的这篇博客【鱼眼镜头11】Kannala-Brandt模型和Scaramuzza多项式模型区别,哪个更好?,以及这个系列看看。
针孔相机可以对于深度depth的采样一个是均匀采样,鱼眼来说,一个是最大深度均匀采样合适。
1.1 看看DD3D 的深度是怎么处理的
在3d head 中输出了深度,看这个代码
box3d_quat, box3d_ctr, box3d_depth, box3d_size, box3d_conf, dense_depth = self.fcos3d_head(features)
特征x经过 box3d_tower 卷积层后输出
box3d_tower_out = self.box3d_tower(features)
depth = self.box3d_depth[_l](box3d_tower_out)
而这个box3d_tower就是一群3x3卷积
box3d_tower.append(
Conv2d(
in_channels,
in_channels,
kernel_size=3,
stride=1,
padding=1,
bias=norm_layer is None,
norm=norm_layer,
activation=F.relu
)
)
得到后得到深度
pixel_size = torch.norm(torch.stack([inv_intrinsics[:, 0, 0], inv_intrinsics[:, 1, 1]], dim=-1), dim=-1)
depth = depth / (pixel_size scale_depth_by_focal_lengths_factor)
与相机的内参(intrinsics)和深度图(depth map)的处理有关。
这两行代码的目的是根据相机的焦距和某个比例因子来调整深度图的值。这在计算机视觉和3D重建等任务中是很常见的操作。
inv_intrinsics
: 这应该是一个表示相机内参逆矩阵的Tensor。在相机标定中,内参矩阵通常是一个3x3的矩阵,包含了焦距(fx, fy)、主点(cx, cy)和可能的扭曲因子等。但是在这里,我们只关心对角线元素,即焦距的倒数(假设fx和fy已经被取倒数)。
torch.stack([inv_intrinsics[:, 0, 0], inv_intrinsics[:, 1, 1]], dim=-1)
: 这将逆内参矩阵的第一行第一列和第二行第二列的元素(即fx和fy的倒数)沿着最后一个维度(dim=-1)堆叠成一个新的Tensor。结果是一个形状为[batch_size, 2]
的Tensor,其中batch_size
是inv_intrinsics
的第一个维度的大小。
torch.norm(..., dim=-1)
: 这计算了沿着最后一个维度(dim=-1)的范数。但是,由于我们堆叠的是两个值(即两个焦距的倒数),实际上计算的是这两个值的L2范数,也就是它们的平方根之和(但在这个特殊情况下,由于只有两个正数,L2范数就是它们的几何平均值)。但是,因为这里两个数都是正数并且只是简单的堆叠,所以实际上torch.norm
在这里计算的是这两个值的平方根的和,这与直接取它们的平均值(或者只是取它们的和,如果后续操作是除法)在效果上可能差别不大。但几何平均值或平方根的和可能用于平滑或归一化目的。
pixel_size
: 最终得到的Tensor包含了每个图像或图像批次的“像素大小”的某种度量,这个度量是基于焦距的倒数来计算的。
depth
: 这是一个表示深度图的Tensor,其中每个值可能代表图像中对应像素的真实世界深度(或某种与深度相关的量度)。
pixel_size scale_depth_by_focal_lengths_factor
: 这部分计算了每个像素的“大小”与某个比例因子scale_depth_by_focal_lengths_factor
的乘积。这个比例因子可能是用于根据焦距来缩放或调整深度值的。
depth / ...
: 最后,原始的深度图被这个乘积除以,得到一个新的深度图,其中每个像素的深度值都根据焦距和比例因子进行了调整。这种调整可能是为了将深度图从某种原始度量(如像素单位的倒数)转换为更物理的度量(如米或厘米),或者为了校正由于相机内参引起的某种偏差。
给出代码示例
首先,我们需要确保inv_intrinsics
是一个形状为[batch_size, 3, 3]
的Tensor,其中包含了逆相机内参矩阵的集合,每个矩阵的(0, 0)
和(1, 1)
位置是焦距的倒数(即1/fx
和1/fy
)。同时,depth
应该是一个形状为[batch_size, ...]
的Tensor,其中包含了每个像素的深度值。
以下是代码示例,说明了如何计算pixel_size
并更新depth
:
import torch
# 假设 inv_intrinsics 是一个形状为 [batch_size, 3, 3] 的 Tensor
# 假设 depth 是一个形状为 [batch_size, ...] 的 Tensor
# 假设 scale_depth_by_focal_lengths_factor 是一个标量或者与 depth 形状相同的 Tensor
# 示例数据
batch_size = 4 # 假设批次大小为 4
inv_intrinsics = torch.randn(batch_size, 3, 3) # 随机生成逆内参矩阵
depth = torch.randn(batch_size, 10, 10) # 随机生成深度图,假设每个样本的深度图大小为 10x10
scale_depth_by_focal_lengths_factor = 0.5 # 假设的比例因子
# 计算 pixel_size
# 注意:我们只关心逆内参矩阵的对角线元素,即焦距的倒数
pixel_size = torch.sqrt(inv_intrinsics[:, 0, 0] 2 + inv_intrinsics[:, 1, 1] 2)
# 或者,如果你想使用 torch.norm 来计算两个元素的 L2 范数(几何平均值),但这里更简单的方法是直接相加然后开方
# pixel_size = torch.norm(torch.stack([inv_intrinsics[:, 0, 0], inv_intrinsics[:, 1, 1]], dim=-1), dim=-1)
# 更新 depth
depth = depth / (pixel_size.unsqueeze(1).unsqueeze(1) scale_depth_by_focal_lengths_factor)
# 注意:我们需要使用 unsqueeze 来扩展 pixel_size 的维度,以便它可以与 depth 进行广播(broadcast)
# 此时,depth 已经根据 pixel_size 和 scale_depth_by_focal_lengths_factor 进行了更新
在这个示例中,我使用了torch.sqrt
来直接计算两个焦距倒数的平方和的平方根,这是更简单且等价于使用torch.norm
计算L2范数的方法。同时,我使用了unsqueeze
来扩展pixel_size
的维度,以便它可以与depth
进行广播操作。这确保了当我们将pixel_size
与depth
相除时,广播规则能够正确地将pixel_size
扩展到与depth
相同的形状。
1.2 我们看看BEVDepth的代码
def _forward_single_sweep(...):
# 提取环视图片特征
# img_feats:[1, 1, 6, 512, 16, 44]
img_feats = self.get_cam_feats(sweep_imgs)
source_features = img_feats[:, 0, ...]
# 提取Depth以及context
depth_feature = self._forward_depth_net(...)
# 预测的距离分布 depth:[6, 112, 16, 44]
depth = depth_feature[:, :self.depth_channels].softmax(1)
# 对应论文中的 Context Feature Depth Distribution 操作
img_feat_with_depth = ... #
depth 是采用depth = depth_feature[:, :self.depth_channels].softmax(1)
均匀采样。商汤采样的的是最大深度均匀采样
。
预测的距离分布 depth:[6, 112, 16, 44]
注释中提到的depth:[6, 112, 16, 44]表示:
6:batch size(6个样本)
112:高度(height)
16:宽度(width)
44:深度通道数(self.depth_channels的值)
- 表示:每个位置有44个深度区间的概率分布
- 将每个空间位置的所有通道值转换为概率分布(和为1)
- 这表示模型预测的是"该位置属于不同深度区间的概率"
- 这是典型的"深度分类"方法(而非直接回归深度值):
- 将深度范围离散化为多个区间(bin)
- 预测每个像素属于各个区间的概率
- 最终深度值可以通过加权求和得到(概率×区间代表值)
- 最终深度 = sum(probability[i] bin_center[i])
采样策略:
最大深度均匀采样主要关注最大深度范围内的均匀性,而不太关心近处或中间深度层次的细节。
均匀深度分布采样则在整个深度范围内追求均匀性,确保每个深度层次都得到同等的关注。
数据分布:
最大深度均匀采样可能导致近处或中间深度层次的点或像素被过度采样,而远处深度层次的点或像素被采样不足。
均匀深度分布采样则在整个深度范围内保持相对均匀的数据分布。
适用场景:
最大深度均匀采样适用于那些主要关注远处目标或障碍物检测的应用,因为远处目标往往对任务结果有更大影响。
均匀深度分布采样适用于需要全面考虑场景深度的应用,如三维重建或增强现实。
鱼眼相机镜头对光线有折射作用,以此获得更大的可视范围,越靠近边缘区域,折射率越大,生成的图片畸变程度越大。
补充:LSS、BEVDet、BEVDepth 深度分布计算方法
1. 正确理解 BEV 模型中的深度预测
(1) LSS(Lift-Splat-Shoot)
- 深度预测方式:预测深度分布(离散分类)
- 原始 LSS 方法(如 PyTorch3D 或 FIERY 的实现)通过
softmax
输出深度概率分布。 - 例如:
depth_logits = depth_net(features) # [B, D, H, W] depth_prob = depth_logits.softmax(dim=1) # 深度分布
- 本质:将深度估计转化为分类问题(每个 bin 是一个深度区间)。
- 原始 LSS 方法(如 PyTorch3D 或 FIERY 的实现)通过
- 为什么用分布?
- 便于加权特征投影到 BEV 空间(概率×特征)。
- 能建模深度不确定性(如遮挡区域可能有多个深度假设)。
(2) BEVDet
- 深度预测方式:仍然是深度分布(分类)
- BEVDet 基于 LSS,因此同样预测深度分布,而非直接回归深度值。
- 代码示例(来自官方实现):
depth_feat = self.depth_net(img_feats) # [B, C, H, W] depth = depth_feat.softmax(dim=1) # 深度分布 [B, D, H, W]
- 与 LSS 的区别:
- BEVDet 是一个完整的检测框架(LSS + 检测头),但深度预测部分仍然是分布。
- 它没有显式用 LiDAR 监督深度(依赖端到端训练)。
(3) BEVDepth
- 深度预测方式:深度分布 + 显式几何约束
- BEVDepth 也预测深度分布(
softmax
输出),但增加了:- LiDAR 监督:用点云数据直接优化深度分布(让分布更准)。
- 相机几何约束:通过内外参建模,提升深度估计的物理合理性。
- 代码示例:
depth_feat = self.depth_net(img_feats, camera_params) # 显式建模几何 depth = depth_feat.softmax(dim=1) # 仍然是分布!
- 关键改进:
- 不是“从分布改为回归”,而是让分布更准(通过显式监督)。
- BEVDepth 也预测深度分布(
2. 术语混淆——深度估计
- 术语混淆:
- “深度估计”是一个广义概念(包含回归和分类两种方式)。
- 但严格来说,LSS/BEVDet/BEVDepth 都是用 分类(分布) 实现深度估计。
- BEVDepth 的“显式深度”误导:
- BEVDepth 用 LiDAR 监督深度,可能被误认为“改为回归”,但实际上仍是分布。
- 论文表述简化:
- 某些论文可能写“predict depth”,但实际代码仍是
softmax
分类。
- 某些论文可能写“predict depth”,但实际代码仍是
3. 正确的表述方式
方法 | 深度预测方式 | 监督信号 | 关键改进 |
---|---|---|---|
LSS | 深度分布(softmax) | 隐式学习(无监督) | 首次提出 BEV 特征提升 |
BEVDet | 深度分布(softmax) | 隐式学习(检测损失) | 端到端检测框架 |
BEVDepth | 深度分布(softmax) | LiDAR 显式监督 | 几何约束 + 深度分布更准 |
4. 实验验证
- 输出维度:
- 如果最后一个维度是
D
(depth_channels),且用了softmax
,就是分布。 - 如果输出是 1 个通道(
D=1
),才可能是回归。
- 如果最后一个维度是
- 损失函数:
- 分类:Cross-Entropy Loss 或 KL 散度(优化分布)。
- 回归:L1/L2 Loss(直接优化深度值)。
5. 为什么选择预测分布而非回归?
BEVDepth 和大多数单目/多目深度估计模型采用分布预测的原因:
- 分类问题更稳定:
- 直接回归深度值是连续值回归问题,容易受噪声和遮挡影响。
- 离散化后转化为分类问题,模型只需判断深度区间,训练更稳定。
- 概率化输出:
- 深度估计本身具有不确定性(如遮挡、纹理缺失),概率分布能反映这种不确定性。
- 后续的 BEV 特征融合(如 LSS 方法)可以利用概率分布加权投影。
- 与 LSS 方法兼容:
- BEVDepth 基于 Lift-Splat-Shoot(LSS) 框架,需要将图像特征“提升”到 3D 空间,依赖深度分布加权。
6. 深度分布如何转换为实际深度值?
虽然模型输出是分布,但实际使用时可以转换为连续值:
- 区间中点法:
- 假设深度区间为
[d_min, d_max]
,每个 bin 的代表值取中点。 - 最终深度 =
sum(probability[i] bin_center[i])
。
- 假设深度区间为
- 对数区间划分:
- 远距离物体深度变化大,常用对数间隔划分 bins,提高远距离分辨率。
7. 对比:直接回归深度值的方案
少数方法(如 DORN)会直接回归深度值,但需要特殊设计:
- 输出:每个像素预测一个深度值(回归)。
- 缺点:
- 需要更强的正则化(如逆深度、对数深度)。
- 对遮挡和远处小物体不鲁棒。
- BEV 中罕见:BEV 感知通常需要概率分布以支持多传感器融合。
8. 总结
- LSS、BEVDet、BEVDepth 都预测深度分布
- BEVDepth 的改进:
- 用 LiDAR 监督让分布更准。
- 增加相机几何约束(如内外参建模)。
补充:3D-2D映射方案
由于畸变,使用均匀的深度分布来生成的点云大部分位于感兴趣范围以外。