MapTR自动驾驶感知端到端建图方案,三十分钟全搞定

在这里插入图片描述

01 背景和阅读之前

家人们大家好,我是老张。今天分享的论文出自地平线和华科,发表在ICLR上。工作背景是端到端的生成实例级别的地图标线,地图标线分为人行道、分割线、路沿3个类别,并预测线的方向。
在这里插入图片描述

我看到论文的效果图就在想,如果这种端到端的方法可以表征各种不规则的几何形状,那显然拥有不小的可拓展性,除了自动驾驶中预测地面标线,还有许多其他应用场景。

看论文之前,由于我没有关注过这个任务,所以先想了想这个任务可能会怎么做:

  • 最朴素的想法应该是看作一个分割任务(Seg),把每条线实例分割出来,相信有不少工作也是这么做的。但有两个问题:

    • 分割首先就很难端到端,分割出类别的概率图是一块一块的连通域,要想得到宽度为1px的细线,可以用卷积来实现类似【图像处理-侵蚀】的操作;但是仍然不好预测线的方向,还需要另一个头预测特征图中点的方向性。
    • 其次是性能,由于任务关心的就那几条特定线或者形状,如果做全图分割,后很多地方的计算是浪费的。
  • 从图中看,作者是将预测不规则几何图形表示为预测点集,那问题变得像姿态估计(Pose Estimation),也就是估计图片中人的动作姿态,都是预测一些特殊的点,并将这些点连起来,需要明确:

    • 如何确定哪些点属于同一个实例

    • 如何确定同一个实例中的点间连接顺序

    • 如先框出实例,再预测实例内部的点,画了个丑图示意一下:预测黑色的实例的框可能不好学,毕竟线在框中所包含的语义信息很少,预测实例框更可能是蓝色这样。

在这里插入图片描述

  • 所以比较好的表示应该是自底向上的,先预测属于同一实例的所有点,再想办法把点连起来。所以论文会如何端到端的预测点之间的连接呢:)

02 方法

在这里插入图片描述
在这里插入图片描述
首先就要说到本文提出的排列等效模型,用来增广GT(Groud Truth,标注真值,后文都缩写为GT)点列的排列方式。作者将道路标线点集抽象为表示的两种几何形状,线和多边形,标注的点序列是顺序的,需要关心的是点序列的方向性:

  • 如果gt是一条线,将扩充为两种连接方式:如上图左所示,左侧点作为起始或右侧点作为起始;
  • 如果gt是多边形,每个点都可能作为多边形的起始点。N个点都可以作为起始点,有顺时针逆时针2种连接方式,扩充为2 * N种连接方式,如上图右所示;

贴一下代码吧,一条线时num_shifts等于2,多边形时num_shifts等于多变形点个数 * 2

@property
    def shift_fixed_num_sampled_points_v4(self):
        """
        args: single image point list [instances_num,            fixed_num, 2]
        return:shifts point list     [instances_num, num_shifts, fixed_num, 2]
        """
        fixed_num_sampled_points = self.fixed_num_sampled_points
        instances_list = []
        is_poly = False
        # is_line = False
        for fixed_num_pts in fixed_num_sampled_points:
            # [fixed_num, 2]
            is_poly = fixed_num_pts[0].equal(fixed_num_pts[-1])
            pts_num = fixed_num_pts.shape[0]
            shift_num = pts_num - 1
            shift_pts_list = []
            if is_poly:
                pts_to_shift = fixed_num_pts[:-1,:]
                for shift_right_i in range(shift_num):
                    shift_pts_list.append(pts_to_shift.roll(shift_right_i,0))
                flip_pts_to_shift = pts_to_shift.flip(0)
                for shift_right_i in range(shift_num):
                    shift_pts_list.append(flip_pts_to_shift.roll(shift_right_i,0))
            else:
                shift_pts_list.append(fixed_num_pts)
                shift_pts_list.append(fixed_num_pts.flip(0))
            shift_pts = torch.stack(shift_pts_list,dim=0)

            if is_poly:
                _, _, num_coords = shift_pts.shape
                tmp_shift_pts = shift_pts.new_zeros((shift_num*2, pts_num, num_coords))
                tmp_shift_pts[:,:-1,:] = shift_pts
                tmp_shift_pts[:,-1,:] = shift_pts[:,0,:]
                shift_pts = tmp_shift_pts

            shift_pts[:,:,0] = torch.clamp(shift_pts[:,:,0], min=-self.max_x,max=self.max_x)
            shift_pts[:,:,1] = torch.clamp(shift_pts[:,:,1], min=-self.max_y,max=self.max_y)

            if not is_poly:
                padding = torch.full([shift_num*2-shift_pts.shape[0],pts_num,shift_pts.shape[-1]], self.padding_value)
                shift_pts = torch.cat([shift_pts,padding],dim=0)
            instances_list.append(shift_pts)
        instances_tensor = torch.stack(instances_list, dim=0)
        instances_tensor = instances_tensor.to(dtype=torch.float32)
        return instances_tensor

论文的整体流程图如下,Backbone用于从环视图中提取特征,诸如ResNet或Swin;MapEncoder用于从环视图特征中提取BEV视角特征,诸如BEVFormer或LSS;本文工作主要在MapDecoder上,所以我们就直接从BEV特征开始往下看:
在这里插入图片描述

【红字1】

举个栗子来说,我们为一张图包含50个实例查询Query,每个实例定义20个点查询Query,嵌入维度为emb;则整张图查询token长度为50 * 20=1000,查出来的特征张量【50 * 20, emb】。查出的这1000个点,每20个划分到一个实例中,同一个实例中的20个点默认是顺序连接的,截取了对应代码如下:

"""......code......"""
instance_embedding = nn.Embedding(num_vec, embed_dims * 2)
pts_embedding      = nn.Embedding(num_pts_per_vec, embed_dims * 2)

"""......code......"""
pts_embeds          = pts_embedding.weight.unsqueeze(0) #【point, embed * 2】 
instance_embeds     = instance_embedding.weight.unsqueeze(1) #【instance, embed * 2】 
object_query_embeds = (pts_embeds + instance_embeds).flatten(0, 1).to(dtype) #【point * instance, embed * 2】 

【红字2和3】

将查出的实例和点与GT做二分匹配,确定正负样本划分,计算Loss,与Detr是类似的。这个二分匹配需要计算4个Cost,分别是【分类损失】、【框损失】、【IoU损失】和【点损失】

  • 分类损失FocalLossCost:每个实例包含点个数相同,计算点特征的均值作为实例特征,经过形如Linear(emb, 3)的层,计算出实例类别,计算Focal Loss
  • 框损失BBoxL1Cos:先计算出点集的外包络矩形作为实例的框,框有【cx,cy,w,h】四个属性,和GT框计算L1损失
  • IoU损失IoUCost:同理,预测实例框和GT框计算IoU
  • 点损失OrderedPtsL1Cost:也称曼哈顿距离或倒角距离,定义如下,用于衡量点集中每个点之间的距离,tips:这个损失假设了两个点集点个数相等,如果点个数不等需要先对GT中点进行采样(线性插值)。前三种损失在检测任务中十分常见,我罗列了点的损失:
    在这里插入图片描述

我们查询了50个实例,假设GT中标注了10个实例,通过排列等效模型扩张为20个实例,则损失矩阵维度为【50,20】,进行二分匹配!实例就和增广的GT一对一配对了

【计算训练损失】

计算损失由二分匹配中4种损失加上【边损失】构成,这点论文和代码不太一致。首先是【点损失】和二分匹配时有点不同,论文在附录也做了讨论:
在这里插入图片描述

当然我们主要来说新出现的【边损失】。不严谨但通俗的说,用向量【1,1,1,1】表示边顺时针连接,向量【-1,-1,-1,-1】表示逆时针连接,边的损失是两个向量计算余弦相似度:
在这里插入图片描述
代码如下,计算CosineEmbeddingLoss。


@mmcv.jit(derivate=True, coderize=True)
@custom_weighted_dir_loss
def pts_dir_cos_loss(pred, target):
    """ Dir cosine similiarity loss
    pred (torch.Tensor): shape [num_samples, num_dir, num_coords]
    target (torch.Tensor): shape [num_samples, num_dir, num_coords]

    """
    if target.numel() == 0:
        return pred.sum() * 0
    # import pdb;pdb.set_trace()
    num_samples, num_dir, num_coords = pred.shape
    loss_func = torch.nn.CosineEmbeddingLoss(reduction='none')
    tgt_param = target.new_ones((num_samples, num_dir))
    tgt_param = tgt_param.flatten(0)
    loss = loss_func(pred.flatten(0,1), target.flatten(0,1), tgt_param)
    loss = loss.view(num_samples, num_dir)
    return loss

03 实验

在这里插入图片描述

对比试验中其他方法确实不熟,就让我们集中到本文方法,主要看消融实验吧。首先要消融的是排列等效模型(Permutation-equivalent Modeling),也就是对GT中对点序列进行扩展的方法

在这里插入图片描述
在这里插入图片描述

从实验也可以看出,尤其对人行道的提升比较大,因为只有人行道会表示为多边形:
如果GT点序列是【1,2,3】,如果预测是【2,3,1】,点的位置和连接顺序都预测对了,损失应该是较小的,画出来的人行道多边形和标注多边形是等价的。排列等效模型让Loss能够正确评价这种近似解。

第二个消融了【边损失】,这个结果就不太好理解了,尤其是对于人行道的预测。
在这里插入图片描述

04 阅读总结

阅读之前猜测论文的方法是先预测实例包含的无序点集,再通过类似方向Query的查询向量查出点之间的连接关系;没有猜对一点……论文方法端到端预测了有序点集,通过对GT点序列进行”排列组合“来增广GT和有序点集匹配计算损失。也是对诸如此类一对多映射问题的一个解决方法!
如果代码实现能选个简洁的库就好了……好了家人们,今天就到这吧,我是老张,我们有缘下次见!

  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值