GNDnet 自动驾驶车辆的快速地平面估计和点云分割 代码学习

GNDnet 自动驾驶车辆的快速地平面估计和点云分割 代码学习

一些网站

ROS学习笔记(十一) rospy介绍(一)https://blog.csdn.net/weixin_44682965/article/details/107748350

在论文的代码中利用了ros的传递机制,可以复习一下

论文地址 [GndNet: Fast Ground plane Estimation and Point Cloud Segmentation for Autonomous Vehicles:https://hal.inria.fr/hal-02927350/document

论文的github:https://github.com/weiweizhang6338/GndNet

数据集

https://archive.org/details/semantickitti-gndnet-data 作者经过处理后的得到的数据集

http://www.semantic-kitti.org/ 原始的semantic-Kitti数据集

关于semantic-kitti数据集

论文地址 https://arxiv.org/abs/1904.01416 SemanticKITTI: A Dataset for Semantic Scene Understanding of LiDAR Sequences

Semantic Segmentation and Panoptic Segmentation

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q9uKUH0b-1638511761594)(http://www.semantic-kitti.org/images/folder_structure.svg)]

我们为原始KITTI里程计基准的sequence文件夹中velodyne文件夹的每次扫描XXXXXX.bin提供了一个文件XXXXXX.label(标签文件夹中的文件XXXXXX.label),其中每个点都包含一个二进制格式的标签。标签是每个点的32位无符号整数(也称为’uint32_t’),其中较低的16位对应于标签。上面的16位编码实例id,该id在整个序列中时间上是一致的,即,两次不同扫描中的同一对象获得相同的id。这也适用于移动车辆,但也适用于环路闭合后看到的静态对象。

Semantic Scene Completion

数据集下载后的格式[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tVrpcCed-1638511761595)(http://www.semantic-kitti.org/images/folder_structure_ssc.svg)]

我们在原始KITTI里程计基准的序列文件夹中为velodyne文件夹的每个扫描XXXXXX.bin提供,在体素文件夹中提供:-一个压缩二进制格式的XXXXXX.bin文件,其中包含每个体素(如果该体素被激光测量占用)。这是语义场景完成任务的输入,对应于单个激光雷达扫描的体素化。
-一个文件XXXXXX.label,它为已完成场景的每个体素包含一个二进制格式的标签。对于每个体素,标签是一个16位无符号整数(也称为“uint16\u t”)。
-一个压缩二进制格式的文件XXXXXX.invalid,该文件为每个体素包含一个标志,指示该体素是否被视为无效,即,从未从任何位置直接看到该体素以生成体素。评估中也不考虑这些体素。
-一个压缩二进制格式的文件XXXXXX.ocluded,其中包含每个体素的一个标志,该标志指定该体素是被激光雷达测量占据还是被用于生成完成场景的所有姿势的视线中的体素遮挡。
蓝色文件仅用于训练数据,标签文件必须针对语义分割任务进行预测。

代码学习

数据预处理部分

整体思路如论文中所述

  • 我们使用扩展的Sematockitti数据集训练我们的模型。此存储库中提供了一个示例数据,而完整的数据集可以从链接下载。我们使用以下步骤生成数据集:首先在(x,y)=[(-50,-50),(50,50)]范围内裁剪点云,并围绕x和y轴应用增量旋转[-10,10]度,以生成具有不同坡度和上坡的数据。(SemanticKITTI数据集主要记录在平坦地形上)增强点云作为NumPy文件存储在reduced_velo文件夹中。
  • 为了生成地面高程标签,我们使用[1]中描述的基于CRF的曲面拟合方法。
  • 我们将SematicKITTI数据集中的对象类细分为两类:地面(道路、人行道、停车场、其他地面、植被、地形)非地面(所有其他)。我们从简化的速度中过滤出非地面点,并仅使用CRF方法[1]和地面点生成高程地图。
  • 我们的地面高程表示为二维网格,单元分辨率为1m x 1m,大小为(x,y)=[(-50,-50),(50,50)],其中每个单元的值表示局部地面高程。
  • 地面高程地图作为NumPy文件存储在gnd_标签文件夹中。
  • 最后,GndNet使用gnd_标签和reduced_velo(包括地面和非地面点)进行训练。

从代码层面 作者的CRF方法是不公开的 ### This code is proprietary ###

semKitti_parser.py 文件提供了一个从semantic-kitti数据集到np数据结构的parser 解析器
semKitti_morph_data.py 利用morph方法转化 详细见论文
dataset_generator.py 生成最后的数据

论文的内容可以看前面写的,代码方面利用了ros的传递机制,因为以前做过一些机器人的比赛,可能对ros有所了解,如果没有学习过相关知识最好先搞懂ros之间通信,传递,节点之类的一些概念。

常量赋值

一些基本语句:基本赋值,简单来说就是定义一些常量并赋值

parser = argparse.ArgumentParser()
parser.add_argument('--print-freq', '-p', default=100, type=int, metavar='N', help='print frequency (default: 50)')
parser.add_argument('--resume', default='', type=str, metavar='PATH', help='path to latest checkpoint (default: none)')
parser.add_argument('--config', default='config/config_kittiSem.yaml', type=str, metavar='PATH', help='path to config file (default: none)')
parser.add_argument('-e', '--evaluate', dest='evaluate', action='store_true', help='evaluate model on validation set')
parser.add_argument('-s', '--save_checkpoints', dest='save_checkpoints', action='store_true',help='evaluate model on validation set')
parser.add_argument('--start_epoch', default=0, type=int, help='epoch number to start from')
args = parser.parse_args()

关于dataset(输入网络的数据)

注:这里是作者已经把数据变为需要的数据后,相当于洗好了数据然后进行的操作,具体按照论文里的CRF方法生成data还没有说

代码定义了get_train_loader和get_valid_loader

train_loader =  get_train_loader(cfg.data_dir, cfg.batch_size, skip = 4)
valid_loader =  get_valid_loader(cfg.data_dir, cfg.batch_size, skip = 6)
class kitti_gnd(Dataset):
   def __init__(self, data_dir, train = True, skip_frames = 1):
      self.train = train

      if self.train:
         self.train_data = []
         self.train_labels = []
         print('loading training data ')
         seq_folders = os.listdir(data_dir +"training/")
         for seq_num in seq_folders:
            seq_path = data_dir +"training/"+ "seq_"+ "%03d" % int(seq_num.split("_")[1])
            files_in_seq = os.listdir(seq_path + '/reduced_velo/')

            for data_num in range(0, len(files_in_seq),skip_frames): # too much of dataset we skipping files
               data_path = seq_path + '/reduced_velo/' + "%06d.npy" % data_num
               point_set = np.load(data_path) #(N,3) point set
               self.train_data.append(point_set)

               label_path = seq_path + '/gnd_labels/' + "%06d.npy" % data_num
               label = np.load(label_path) # (W x L)
               self.train_labels.append(label)

      else:
         self.valid_data = []
         self.valid_labels = []
         print('loading validation data ')
         seq_folders = os.listdir(data_dir +"validation/")
         for seq_num in seq_folders:
            seq_path = data_dir +"validation/"+ "seq_"+ "%03d" % int(seq_num.split("_")[1])
            files_in_seq = os.listdir(seq_path + '/reduced_velo/')

            for data_num in range(0, len(files_in_seq),skip_frames): # too much of dataset we skipping files
               data_path = seq_path + '/reduced_velo/' + "%06d.npy" % data_num
               point_set = np.load(data_path) #(N,3) point set
               self.valid_data.append(point_set)

               label_path = seq_path + '/gnd_labels/' + "%06d.npy" % data_num
               label = np.load(label_path) # (W x L)
               self.valid_labels.append(label)


   def __getitem__(self, index):
      if self.train:
         return self.train_data[index], self.train_labels[index]
      else:
         return self.valid_data[index], self.valid_labels[index]


   def __len__(self):
      if self.train:
         return len(self.train_data)
      else:
         return len(self.valid_data)

有关pytorch dataset的相关理解,可以看上一篇写的pytorch学习里面的dataset 主要就是init getitem len三个函数

def get_valid_loader(data_dir, batch = 4, skip = 1):

   use_cuda = torch.cuda.is_available()
   if use_cuda:
      print("using cuda")
      num_workers = 1
      pin_memory = True
   else:
      num_workers = 4
      pin_memory = True


   valid_loader = DataLoader(kitti_gnd(data_dir,train = False, skip_frames = skip),
               batch_size= batch, num_workers=num_workers, pin_memory=pin_memory,shuffle=True,drop_last=True)

   print("Valid Data size ",len(valid_loader)*batch)

   return valid_loader

dataloader函数详见前面pytorch的学习

关于dataset (原始数据生成data)

GNDnet 自动驾驶车辆的快速地平面估计和点云分割 代码学习_第1张图片

可以参照论文的这张图来看 代码里分为了三部分 一部分是 SemtiKki数据集到网格 网格到pillar pillar再到图像

grid_size = [-50, -50, 50, 50]
length = int(grid_size[2] - grid_size[0]) # x direction
width = int(grid_size[3] - grid_size[1])    # y direction

先划分网格

def np2ros_pub(points, pcl_pub, timestamp = None):
    npoints = points.shape[0] # Num of points in PointCloud
    points_arr = np.zeros((npoints,), dtype=[
                                        ('x', np.float32),
                                        ('y', np.float32),
                                        ('z', np.float32),
                                        ('intensity', np.float32)])
    points = np.transpose(points)
    points_arr['x'] = points[0]
    points_arr['y'] = points[1]
    points_arr['z'] = points[2]
    points_arr['intensity'] = points[3]
    # points_arr['g'] = 255
    # points_arr['b'] = 255

    if timestamp == None:
        timestamp = rospy.Time.now()
    cloud_msg = ros_numpy.msgify(PointCloud2, points_arr,stamp =timestamp, frame_id = "/kitti/velo_link")
    # rospy.loginfo("happily publishing sample pointcloud.. !")
    pcl_pub.publish(cloud_msg)

再把数据转化为ros可识别的数据 简单来说,就是gndnet把data转为marker形式方便publish

def gnd_marker_pub(gnd_label, marker_pub):
    gnd_marker = Marker()
    gnd_marker.header.frame_id = "/kitti/base_link"
    gnd_marker.header.stamp = rospy.Time.now()
    gnd_marker.type = gnd_marker.LINE_LIST
    gnd_marker.action = gnd_marker.ADD
    gnd_marker.scale.x = 0.05
    gnd_marker.scale.y = 0.05
    gnd_marker.scale.z = 0.05
    gnd_marker.color.a = 1.0
    gnd_marker.color.r = 0.0
    gnd_marker.color.g = 1.0
    gnd_marker.color.b = 0.0
    gnd_marker.points = []

# gnd_labels are arranged in reverse order
    for j in range(gnd_label.shape[0]):
        for i in range(gnd_label.shape[1]):
            pt1 = Point()
            pt1.x = i + grid_size[0]
            pt1.y = j + grid_size[1]
            pt1.z = gnd_label[j,i]

            if j>0 :
                pt2 = Point()
                pt2.x = i + grid_size[0]
                pt2.y = j-1 +grid_size[1]
                pt2.z = gnd_label[j-1, i]
                gnd_marker.points.append(pt1)
                gnd_marker.points.append(pt2)

            if i>0 :
                pt2 = Point()
                pt2.x = i -1 + grid_size[0]
                pt2.y = j + grid_size[1]
                pt2.z = gnd_label[j, i-1]
                gnd_marker.points.append(pt1)
                gnd_marker.points.append(pt2)

            if j < width-1 :
                pt2 = Point()
                pt2.x = i + grid_size[0]
                pt2.y = j + 1 + grid_size[1]
                pt2.z = gnd_label[j+1, i]
                gnd_marker.points.append(pt1)
                gnd_marker.points.append(pt2)

            if i < length-1 :
                pt2 = Point()
                pt2.x = i + 1 + grid_size[0]
                pt2.y = j + grid_size[1]
                pt2.z = gnd_label[j, i+1]
                gnd_marker.points.append(pt1)
                gnd_marker.points.append(pt2)

    marker_pub.publish(gnd_marker)

注 关于CRF-gnd-estimator这个函数

### This code is proprietary ### 是作者没有公开嘛?

网格结构

网络结构上沿用了segment 见网站 https://github.com/meetshah1995/pytorch-semseg

class segnetGndEst(nn.Module):
    def __init__(self, in_channels=64, is_unpooling=True):
        super(segnetGndEst, self).__init__()

        self.in_channels = in_channels
        self.is_unpooling = is_unpooling

        self.down1 = segnetDown2(self.in_channels, 128)
        self.down2 = segnetDown2(128, 256)

        self.up2 = segnetUp2(256, 128)
        self.up1 = segnetUp2(128, 64)

        self.regressor = nn.Conv2d(64, 1, kernel_size=3, padding=1)

    def forward(self, inputs):

        down1, indices_1, unpool_shape1 = self.down1(inputs)
        down2, indices_2, unpool_shape2 = self.down2(down1)

        up2 = self.up2(down2, indices_2, unpool_shape2)
        up1 = self.up1(up2, indices_1, unpool_shape1)
        gnd_pred = self.regressor(up1)

        return gnd_pred
class conv2DBatchNormRelu(nn.Module):
    def __init__(
        self,
        in_channels,
        n_filters,
        k_size,
        stride,
        padding,
        bias=True,
        dilation=1,
        is_batchnorm=True,
    ):
        super(conv2DBatchNormRelu, self).__init__()

        conv_mod = nn.Conv2d(
            int(in_channels),
            int(n_filters),
            kernel_size=k_size,
            padding=padding,
            stride=stride,
            bias=bias,
            dilation=dilation,
        )

        if is_batchnorm:
            self.cbr_unit = nn.Sequential(
                conv_mod, nn.BatchNorm2d(int(n_filters)), nn.ReLU(inplace=True)
            )
        else:
            self.cbr_unit = nn.Sequential(conv_mod, nn.ReLU(inplace=True))

    def forward(self, inputs):
        outputs = self.cbr_unit(inputs)
        return outputs

你可能感兴趣的:(毕业设计三维点云,计算机视觉,深度学习,机器学习)