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数据集
论文地址 https://arxiv.org/abs/1904.01416 SemanticKITTI: A Dataset for Semantic Scene Understanding of LiDAR Sequences
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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。这也适用于移动车辆,但也适用于环路闭合后看到的静态对象。
数据集下载后的格式[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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,其中包含每个体素的一个标志,该标志指定该体素是被激光雷达测量占据还是被用于生成完成场景的所有姿势的视线中的体素遮挡。
蓝色文件仅用于训练数据,标签文件必须针对语义分割任务进行预测。
整体思路如论文中所述
从代码层面 作者的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()
注:这里是作者已经把数据变为需要的数据后,相当于洗好了数据然后进行的操作,具体按照论文里的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的学习
可以参照论文的这张图来看 代码里分为了三部分 一部分是 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)
### 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