LaneATT代码解析【整理自用】

1.1 项目结构


  • cfgs: 默认/预设配置文件
  •  lib:
  •         datasets

                culane.py: culane数据集加载器
                lane_dataset.py: 将来自LaneDatasetLoader中的未经过处理的 annotations 转换为模型可以使用的形式
                lane_dataset_loader.py: 每个数据集加载器实现的抽象类
                llamas.py:  llamas数据集加载器
                nolabel_dataset.py: 加载不需注释的的数据集
                tusimple.py: tusimple数据集加载器

  • models:

                laneatt.py:  LaneATT模型的实现
                matching.py: 用于gt和proposal匹配的效用函数
                resnet.py: resnet 实现部分
                nms:  LaneATT模型的实现
                config.py:  LaneATT模型的实现
                experiment.py: 跟踪和存储有关每个实验的信息
                focal_loss.py: focal loss的实现
                lane.py: 车道线表示
                runner.py: 训练和测试循环

  • utils:

                culane_metric.py: 非官方的CULane数据集度量实现
                gen_anchor_mask.py: 计算数据集中要在锚点筛选步骤中使用的每个锚点的频率(论文提到锚点的数量会限制速度,所以挑选使用频率最大的部分锚点)
                gen_video.py: 从模型预测生成视频
                llamas_metric.py  llamas数据集的实用程序函数
                speed.py: 测量模型的效率相关指标
                tusimple_metric.py: tusimple数据集图片度量的官方实现
                viz_dataset.py: 显示从数据集采样的图像(增强后) 

  • main.py:

                 运行实验的训练或测试阶段

2.1Anchor的含义与创建

         Anchor用通俗的语言是指在特征图层中生成锚点,以起始点坐标根据锚点的坐标连接成线,呈现出一种射线状。如图所示

 下面解析LaneATT中的Anchor模块的代码:

       def __init__(self,
                 backbone='resnet34',
                 pretrained_backbone=True,
                 S=72,
                 img_w=640,
                 img_h=360,  # 规定高度采样数与输入图像的期望宽度与高度
                 anchors_freq_path=None,
                 topk_anchors=None,
                 anchor_feat_channels=64):
        super(LaneATT, self).__init__()
       
         # Some definitions
        self.feature_extractor, backbone_nb_channels, self.stride = get_backbone(backbone, pretrained_backbone)
        self.img_w = img_w
        self.n_strips = S - 1         # 高度方面的采样点
        self.n_offsets = S             # x方向上的偏移量
        self.fmap_h = img_h // self.stride
        fmap_w = img_w // self.stride # 算出fmap_w = 11
        self.fmap_w = fmap_w
        self.anchor_ys = torch.linspace(1, 0, steps=self.n_offsets, dtype=torch.float32)
        self.anchor_cut_ys = torch.linspace(1, 0, steps=self.fmap_h, dtype=torch.float32)
        self.anchor_feat_channels = anchor_feat_channels

        # Anchor angles, same ones used in Line-CNN

        # 左、下、右方创建anchor的角度范围,由于车道线主要聚集在下方,bottom的角度范围最多

        self.left_angles = [72., 60., 49., 39., 30., 22.]
        self.right_angles = [108., 120., 131., 141., 150., 158.]
        self.bottom_angles = [165., 150., 141., 131., 120., 108., 100., 90., 80., 72., 60., 49., 39., 30., 15.]

        # Generate anchors
        # 定义72 , 128个start的起始点数量
        self.anchors, self.anchors_cut = self.generate_anchors(lateral_n=72, bottom_n=128)

整段代码就是初始化一些参数,offsets =72,说明Lanatt是将车道线回归成72个点来组成最终的车道线。

    def cut_anchor_features(self, features):
        # definitions
        batch_size = features.shape[0] # 批次大小
        n_proposals = len(self.anchors) # 锚点数量
        n_fmaps = features.shape[1] # 特征图深度
        batch_anchor_features = torch.zeros((batch_size, n_proposals, n_fmaps, self.fmap_h, 1), device=features.device) 
        # 存储剪裁后的锚点特征张量

        # actual cutting
        for batch_idx, img_features in enumerate(features):
            rois = img_features[self.cut_zs, self.cut_ys, self.cut_xs].view(n_proposals, n_fmaps, self.fmap_h, 1) 
        # 完成裁剪动作,形状为(n_proposals, n_fmaps, self.fmap_h, 1)
            rois[self.invalid_mask] = 0 # 区域外的点赋为0
            batch_anchor_features[batch_idx] = rois

        return batch_anchor_features

这里要着重说明一下anchor与anchor_cut的区别:

        由于锚点所确定的线是一条射线,因此在特征图像之外势必也会存在锚线,cut则会利用边缘位置来切割这些异常的anchor的点得到anchor_cut,因此backbone_feature预测需要将这些点置零,避免从图像之外的anchor外训练。上述代码首先定义“裁剪框”,通过for遍历实现剪裁。

    def generate_anchors(self, lateral_n, bottom_n):
        # anchor: 2, 1 ,2(x,y) 72(offsets)
        # 2 * 72 * 6 , 128 * 15 = 2784

        left_anchors, left_cut = self.generate_side_anchors(self.left_angles, x=0., nb_origins=lateral_n)
        right_anchors, right_cut = self.generate_side_anchors(self.right_angles, x=1., nb_origins=lateral_n)
        bottom_anchors, bottom_cut = self.generate_side_anchors(self.bottom_angles, y=1., nb_origins=bottom_n)
        # 返回连接后的锚点的张量,与之对应的容器
        return torch.cat([left_anchors, bottom_anchors, right_anchors]), torch.cat([left_cut, bottom_cut, right_cut])

   

         设置左、下、右的边缘位置起始点位置。x=0则代表left,x=1则代表right。由于OpenCV的坐标问题,y=1代表下方的边缘位置。

 def generate_side_anchors(self, angles, nb_origins, x=None, y=None):
        # self.anchors, self.anchors_cut = self.generate_anchors(lateral_n=72, bottom_n=128) 在源码的48行已经规定
        if x is None and y is not None:
            # bottom: x从1——0取128个,y=1(因为OpenCV的坐标系问题,bottom对应y=1)

            starts = [(x, y) for x in np.linspace(1., 0., num=nb_origins)]
        elif x is not None and y is None:
            # left: x=0 y从1——0中取72个starts

            starts = [(x, y) for y in np.linspace(1., 0., num=nb_origins)]
        else:
            raise Exception('Please define exactly one of `x` or `y` (not neither nor both)')
        # 求出anchors的数量

        n_anchors = nb_origins * len(angles)

        两条if语句为互斥条件,以第一条if语句为例: if x is None and y is not None:则代表bottom位置,从0~1中取128个起始点坐标。反之,在左(右)生成72个起始坐标。之所以下方的起始坐标更多是因为,车道线一般在bottom位置更多的聚集,自行想象车辆的运行情况。

# each row, first for x and second for y:
        # 2 scores, 1 start_y, start_x, 1 lenght, S coordinates, score[0] = negative prob, score[1] = positive prob
        # an_cut作为一个容器,将feature_map中的anchor放到容器中,完成高度的转换
        # anchor: 2, 1 ,2(x,y) 72(offsets) 2个 :即 两个类别【正,负】,起始坐标,车道线len,以及offset
        anchors = torch.zeros((n_anchors, 2 + 2 + 1 + self.n_offsets))
        anchors_cut = torch.zeros((n_anchors, 2 + 2 + 1 + self.fmap_h))

        for i, start in enumerate(starts):
            for j, angle in enumerate(angles):
                # n_stars * n_angles
                k = i * len(angles) + j
                anchors[k] = self.generate_anchor(start, angle)
                anchors_cut[k] = self.generate_anchor(start, angle, cut=True)

        return anchors, anchors_cut
    def generate_anchor(self, start, angle, cut=False):
        if cut:
            anchor_ys = self.anchor_cut_ys
            # 创建anchor的张量,形状为2 + 2 + 1 + self.fmap_h
            anchor = torch.zeros(2 + 2 + 1 + self.fmap_h)
        else:
            anchor_ys = self.anchor_ys
            anchor = torch.zeros(2 + 2 + 1 + self.n_offsets)
        angle = angle * math.pi / 180.  # degrees to radians
        start_x, start_y = start # 获取起始坐标
        # 初始化anchor的第2,3个元素
        anchor[2] = 1 - start_y
        anchor[3] = start_x
        # 计算偏移量,注意opencv的坐标区别
        anchor[5:] = (start_x + (1 - anchor_ys - 1 + start_y) / math.tan(angle)) * self.img_w

        return anchor

———————————————————————————————————————————

先补一下LSS检测算法

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值