苏州市建设工程质量监督站网站,全国企业信用信息查询公示系统,国内工程机械行业网站建设现状,图片制作成视频自动驾驶#xff1a;BEV开山之作LSS#xff08;lift,splat,shoot#xff09;原理代码串讲前言Lift参数创建视锥CamEncodeSplat转换视锥坐标系Voxel Pooling总结前言 目前在自动驾驶领域#xff0c;比较火的一类研究方向是基于采集到的环视图像信息#xff0c;去构建BEV视角…
自动驾驶BEV开山之作LSSlift,splat,shoot原理代码串讲前言Lift参数创建视锥CamEncodeSplat转换视锥坐标系Voxel Pooling总结前言 目前在自动驾驶领域比较火的一类研究方向是基于采集到的环视图像信息去构建BEV视角下的特征完成自动驾驶感知的相关任务。所以如何准确的完成从相机视角向BEV视角下的转变就变得由为重要。目前感觉比较主流的方法可以大体分为两种
显式估计图像的深度信息完成BEV视角的构建在某些文章中也被称为自下而上的构建方式利用transformer中的query查询机制利用BEV Query构建BEV特征这一过程也被称为自上而下的构建方式
LSS最大的贡献在于提供了一个端到端的训练方法解决了多个传感器融合的问题。传统的多个传感器单独检测后再进行后处理的方法无法将此过程损失传进行反向传播而调整相机输入而LSS则省去了这一阶段的后处理直接输出融合结果。
Lift
参数
我们先介绍一下一些参数 感知范围 x轴方向的感知范围 -50m ~ 50my轴方向的感知范围 -50m ~ 50mz轴方向的感知范围 -10m ~ 10m BEV单元格大小 x轴方向的单位长度 0.5my轴方向的单位长度 0.5mz轴方向的单位长度 20m BEV的网格尺寸 200 x 200 x 1 深度估计范围 由于LSS需要显式估计像素的离散深度论文给出的范围是 4m ~ 45m间隔为1m也就是算法会估计41个离散深度也就是下面的dbound。 why dbound 因为二维像素可以理解为现实世界中的某一个点到相机中心的一条射线我们如果知道相机的内外参数就是知道了对应关系但是我们不知道是射线上面的那一个点也就是不知道depth所以作者在距离相机5m到45m的视锥内每隔1m有一个模型可选的深度值(这样每个像素有41个可选的离散深度值)。
代码如下
ogfH128
ogfW352
xbound[-50.0, 50.0, 0.5]
ybound[-50.0, 50.0, 0.5]
zbound[-10.0, 10.0, 20.0]
dbound[4.0, 45.0, 1.0]
fH, fW ogfH // 16, ogfW // 16创建视锥
什么是视锥
代码 def create_frustum(self):# make grid in image planeogfH, ogfW self.data_aug_conf[final_dim]fH, fW ogfH // self.downsample, ogfW // self.downsampleds torch.arange(*self.grid_conf[dbound], dtypetorch.float).view(-1, 1, 1).expand(-1, fH, fW)D, _, _ ds.shapexs torch.linspace(0, ogfW - 1, fW, dtypetorch.float).view(1, 1, fW).expand(D, fH, fW)ys torch.linspace(0, ogfH - 1, fH, dtypetorch.float).view(1, fH, 1).expand(D, fH, fW)# D x H x W x 3frustum torch.stack((xs, ys, ds), -1)return nn.Parameter(frustum, requires_gradFalse)根据代码可知它的尺寸是根据一个2dimage构建的它的尺寸为D * H * W * 3维度3表示【 xydepth】。我们可以把这个视锥理解为一个长方体长x宽y 高depth视锥中的每个点都是长方体的坐标。
CamEncode
这部分主要是通过Efficient Net来提取图像的features首先看代码
class CamEncode(nn.Module):def __init__(self, D, C, downsample):super(CamEncode, self).__init__()self.D Dself.C Cself.trunk EfficientNet.from_pretrained(efficientnet-b0)self.up1 Up(320112, 512)# 输出通道数为DCD为可选深度值个数C为特征通道数self.depthnet nn.Conv2d(512, self.D self.C, kernel_size1, padding0)def get_depth_dist(self, x, eps1e-20):return x.softmax(dim1)def get_depth_feat(self, x):# 主干网络提取特征x self.get_eff_depth(x)# 输出通道数为DCx self.depthnet(x)# softmax编码相理解为每个可选深度的权重depth self.get_depth_dist(x[:, :self.D])# 深度值 * 特征 2D特征转变为3D空间(俯视图)内的特征new_x depth.unsqueeze(1) * x[:, self.D:(self.D self.C)].unsqueeze(2)return depth, new_xdef forward(self, x):depth, x self.get_depth_feat(x)return x起初与以往的相同到了init函数的最后一句把feature的channel下采样到了 D CD与上面的视锥的D一致用来储存深度特征C为图像的语义特征然后对channel为D的那部分在执行softmax 用来预测depth的概率分布然后把D这部分与C这部分单独拿出来让二者做外积就得到了shape为BNDCHW的feature。 demo代码如下
a torch.ones(36*4).resize(4,6,6)1
demo1 a.unsqueeze(1)
print(demo1.shape)
b torch.ones(36*4).resize(4,6,6)3
demo2 b.unsqueeze(0)
print(demo2.shape)
c demo1*demo2
print(c.shape)
torch.Size([4, 1, 6, 6])
torch.Size([1, 4, 6, 6])
torch.Size([4, 4, 6, 6])我们观察右面的网格图首先解释一下网格图的坐标其中a代表某一个深度softmax概率大小为H * Wc代表语义特征的某一个channel的feature那么ac就表示这两个矩阵的对应元素相乘于是就为feature的每一个点赋予了一个depth 概率然后广播所有的ac就得到了不同的channel的语义特征在不同深度channel的feature map经过训练重要的特征颜色会越来越深由于softmax概率高反之就会越来越暗淡趋近于0.
Splat
得到了带有深度信息的feature map那么我们想知道这些特征对应3D空间的哪个点我们怎么做呢
由于我们的视锥对原图做了16倍的下采样而在上面得到feature map的感受野也是16那么我们可以在接下来的操作把feature map映射到视锥坐标下。
转换视锥坐标系
首先我们之前得到了一个2D的视锥现在通过相机的内外参数把它映射到车身以车中心为原点坐标系。
代码如下
def get_geometry(self, rots, trans, intrins, post_rots, post_trans):B, N, _ trans.shape # B: batch size N环视相机个数# undo post-transformation# B x N x D x H x W x 3# 抵消数据增强及预处理对像素的变化points self.frustum - post_trans.view(B, N, 1, 1, 1, 3)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)# (bs, N, depth, H, W, 3)其物理含义# 每个batch中的每个环视相机图像特征点其在不同深度下位置对应# 在ego坐标系下的坐标return pointsVoxel Pooling
代码
def voxel_pooling(self, geom_feats, x):# geom_feats(B x N x D x H x W x 3)在ego坐标系下的坐标点# x(B x N x D x fH x fW x C)图像点云特征B, N, D, H, W, C x.shapeNprime B*N*D*H*W # 将特征点云展平一共有 B*N*D*H*W 个点x x.reshape(Nprime, C) # flatten indicesgeom_feats ((geom_feats - (self.bx - self.dx/2.)) / self.dx).long() # ego下的空间坐标转换到体素坐标计算栅格坐标并取整geom_feats geom_feats.view(Nprime, 3) # 将体素坐标同样展平geom_feats: (B*N*D*H*W, 3)batch_ix torch.cat([torch.full([Nprime//B, 1], ix,devicex.device, dtypetorch.long) for ix in range(B)]) # 每个点对应于哪个batchgeom_feats torch.cat((geom_feats, batch_ix), 1) # geom_feats: (B*N*D*H*W, 4)# filter out points that are outside box# 过滤掉在边界线之外的点 x:0~199 y: 0~199 z: 0kept (geom_feats[:, 0] 0) (geom_feats[:, 0] self.nx[0])\ (geom_feats[:, 1] 0) (geom_feats[:, 1] self.nx[1])\ (geom_feats[:, 2] 0) (geom_feats[:, 2] self.nx[2])x x[kept]geom_feats geom_feats[kept]# get tensors from the same voxel next to each otherranks geom_feats[:, 0] * (self.nx[1] * self.nx[2] * B)\ geom_feats[:, 1] * (self.nx[2] * B)\ geom_feats[:, 2] * B\ geom_feats[:, 3] # 给每一个点一个rank值rank相等的点在同一个batch并且在在同一个格子里面sorts ranks.argsort()x, geom_feats, ranks x[sorts], geom_feats[sorts], ranks[sorts] # 按照rank排序这样rank相近的点就在一起了# cumsum trickif not self.use_quickcumsum:x, geom_feats cumsum_trick(x, geom_feats, ranks)else:x, geom_feats QuickCumsum.apply(x, geom_feats, ranks)# griddify (B x C x Z x X x Y)final torch.zeros((B, C, self.nx[2], self.nx[0], self.nx[1]), devicex.device) # final: bs x 64 x 1 x 200 x 200final[geom_feats[:, 3], :, geom_feats[:, 2], geom_feats[:, 0], geom_feats[:, 1]] x # 将x按照栅格坐标放到final中# collapse Zfinal torch.cat(final.unbind(dim2), 1) # 消除掉z维return final # final: bs x 64 x 200 x 200
总结
优点
1.LSS的方法提供了一个很好的融合到BEV视角下的方法。基于此方法无论是动态目标检测还是静态的道路结构认知甚至是红绿灯检测前车转向灯检测等等信息都可以使用此方法提取到BEV特征下进行输出极大地提高了自动驾驶感知框架的集成度。
2.虽然LSS提出的初衷是为了融合多视角相机的特征为“纯视觉”模型而服务。但是在实际应用中此套方法完全兼容其他传感器的特征融合。如果你想融合超声波雷达特征也不是不可以试试。
缺点
1.极度依赖Depth信息的准确性且必须显示地提供Depth 特征。当然这是大部分纯视觉方法的硬伤。如果直接使用此方法通过梯度反传促进Depth网络的优化如果Depth 网络设计的比较复杂往往由于反传链过长使得Depth的优化方向比较模糊难以取得较好效果。当然一个好的解决方法是先预训练好一个较好的Depth权重使得LSS过程中具有较为理想的Depth输出。
2.外积操作过于耗时。虽然对于机器学习来说这样的计算量不足为道但是对于要部署到车上的模型当图片的feature size 较大 且想要预测的Depth距离和精细度高时外积这一操作带来的计算量则会大大增加。这十分不利于模型的轻量化部署而这一点上Transformer的方法反而还稍好一些。