神奇喵喵屋:自驾多模态数据集训练数据对齐的踩坑记录

这篇文章上次修改于 5 个月前,可能部分内容已经不适用,如有疑问可询问作者。

神奇喵喵屋:自驾多模态数据集训练数据对齐的踩坑记录

前言

毕设的过程踩了很多很多坑,尤其是想拿自己的数据集去训练一些比较新的网络。然后网上基本上都没有任何记载,这里权当一个踩坑个人记载。包括但不限于以下几种情况。

  1. 环境配置

  2. KITTI类训练数据组织格式

  3. KITTI数据的内参细节和各种文件转换方式

环境配置记录

killed

在我运行PENet的虚拟点生成时候,经常还没开始Print就突然中断,显示Killed。

实际是触发了系统的OOM。我一开始倒是没想到这个玩意这么占内存

直接一气呵成修改wsl2的SWAP和MEM——C:/Users/用户名字/.wslconfig

添加设置:把内存,SWAP,处理器数量都增大

[wsl2]
memory=12GB
swap=4G
processors=12
Copy

最后重启wsl2:

POWERSHELL
wsl --shutdown
Copy

数据文件夹组织

先说一个总结。在训练自己新数据集里面的内容,我们最需要关注的点在于数据格式的匹配问题。

从大了说,就是文件夹各个文件的组织形式

从小了说,各个子文件自己的格式(特别是激光雷达的bin数据,以及calib里面的内参参数文件)

KITTI格式

使用STF数据集时候想转化为KITTI的格式遇到了一些小问题。总得来说,VirConv首先需求的标准的KITTI如下:

VirConv
├── data
│   ├── kitti
│   │   │── ImageSets
│   │   │── training
│   │   │   ├──calib & velodyne & label_2 & image_2 & (optional: planes) & velodyne_depth
│   │   │── testing
│   │   │   ├──calib & velodyne & image_2 & velodyne_depth
│   │   │── semi (optional)
│   │   │   ├──calib & velodyne & label_2(pseudo label) & image_2 & velodyne_depth
│   │   │── gt_database_mm
│   │   │── gt_databasesemi
│   │   │── kitti_dbinfos_trainsemi.pkl
│   │   │── kitti_dbinfos_train_mm.pkl
│   │   │── kitti_infos_test.pkl
│   │   │── kitti_infos_train.pkl
│   │   │── kitti_infos_trainsemi.pkl
│   │   │── kitti_infos_trainval.pkl
│   │   │── kitti_infos_val.pkl
├── pcdet
├── tools
Copy

一般来说训练的流程是先读取ImageSets里面的txt,搜寻相关的子文件夹。

对于 train.txt 来说,其子文件夹就是/training

根据网络的不同,所必须的数据文件夹包括 calib(内参文件) ; velodyne (激光雷达bin文件); label_2 (标注); image_2 (图像,如果是双目相机,通常是左目的图像)

然后这里还多了一个velodyne的文件夹,其实是VirConv作者用PENet生成的虚拟点。

然后还有对照的gt文件夹和pcdet生成的pkl文件。

这几个文件算是缺一不可。然后组织形式来说,读取 train.txt 然后生成预处理的pkl文件。这里贴上一个介绍的文章,以便解释后面的坑。

【CSDN】3d目标检测框架-openpcdet中的kitti数据集pkl文件解析

PCDet

对于生成的pkl,如果阅读了上面的介绍,会发现其读取的是P2相机。

问题是STF这玩意的相机来说,P0,P1是左右目相机,而P2和P3则是其他的门控相机等等。而拍摄的图像又是左目相机的。

所以要打开文件里面改一下需求的相机。并重新生成P0(也就是新数据集的左目相机的编号)对应的pkl训练文件。

(4.2 更新, STF数据集的左目相机就是P2,不用修改序号)

ps: P(number)后面数字对应的是采集汽车的相机编号,不同数据会有很大的不同。

数据文件转换

激光雷达bin文件的不匹配问题

STF的激光雷达是二次回波,而正常需求得到的点云都是bin文件reshape成[N,4]这样的格式,其中4个数据分别是x,y,z,i(反射强度)。二次回波两个i,所以我们针对[N,4]要改为[N,5]。然后取前4个。

虚拟点生成与多模态格式对齐

超级大坑。

在我训练过程出问题时候才发现。(训出一堆很低的值,然后我用VirConv作者训练好的包evalue了以下。。全是0)

由于虚拟点生成的点只有428032个。所以作者只取了一部分图像。

但是我的图像大小和作者的严重不同,所以我resize了一下图像大小。然后差不多保持一个相同大小的数据格式输入进去。

然后这一步出现了大问题。

让我们来看看作者的处理方法:

PYTHON
image = np.array(io.imread(file_image_path), dtype=np.int32)
image= image[:352,:1216]
Copy

这里只截取了图像左上角的部分。

而我resize之后取了图像的中心部分。

然后,我忘了虚拟点的生成是靠的投影-对齐再生成。

也就是我resize之后,我的相机内参矩阵就有大问题了。

补充内参矩阵的作用:【CSDN】KITTI数据集内参文件解析

于是现在有两个方案——

  1. 修改虚拟点的生成。

在PENet的文件夹里面我找到了这一部分,然后直接修改图像参数:

PYTHON
args.val_h = 1024    # 注释:修改图像大小 原先是352,1216
args.val_w = 1920
Copy

尝试生成。但是 发现PENet的网络结构输出就是这个样子。。也就是出现报错矩阵的维度不匹配了。考虑到不想重新训练一个新的网络,只好修改内参矩阵试试。

  1. 修改内参矩阵

根据博客可以知道,fx, fy表示焦距,对应的就是矩阵的缩放因子。而cx, cy则表示中心点的位移,故选取裁剪则修改这一部分。

  • 那么我们首先得构造相应的公式:

N个3d点云坐标通常为(NX3)3代表(x,y,z)

需要在后面补1 (x,y,z,1)成为(NX4)

再与Tr_velo_to_cam的转置 (4X3)相乘 得到(NX3)的在各个相机坐标系下的3D点的坐标

随后再乘上R0_rect修正矩阵的转置(3X3),将3d点统一到P0相机坐标系下(3X3)

最最后,投影矩阵得到(uz,vz,z)

根据公式可以简单算出

cx' = gx(cx-dx)

fx' = gx fx

然后开始疯狂修改内参和PENet里面的一些函数。

修改过程中发现了作者的小trick:

PYTHON
# note: we will take the center crop of the images during augmentation
    # that changes the optical centers, but not focal lengths
    # K[0, 2] = K[0, 2] - 13  # from width = 1242 to 1216, with a 13-pixel cut on both sides
    # K[1, 2] = K[1, 2] - 11.5  # from width = 375 to 352, with a 11.5-pixel cut on both sides
    K[0, 2] = K[0, 2] - 13;
    K[1, 2] = K[1, 2] - 11.5;
Copy

这里作者取的图片是1242和375,但是取图片的正中心,也就是大概[13:1229][11:364]这个样子,原先雷达投影在0,0部分的点都在相机外,在13,11的点则是0,0。这边通过修改cx,cy来直接达到目的。不过我们是有图像的缩放的,所以直接修改全部的参数,根据公式来。然后把作者的trick给注释掉了。

其次,是修改label的数据

图像进行了缩放,需要根据缩放比例调整2D边界框的坐标(left, top, right, bottom)。

如果对图像进行了裁剪,需要根据裁剪的偏移量调整2D边界框的坐标。

不过好在我们的裁剪是取的中心。。

附上label的实际格式:

KITTI标注格式:

KITTI标注文件是一个文本文件(通常以.txt结尾),每行表示一个物体的标注信息,格式如下:

<object_type> <truncation> <occlusion> <alpha> <left> <top> <right> <bottom> <height> <width> <length> <x> <y> <z> <rotation_y>
Copy

object_type:物体类型(如Car、Pedestrian等)

truncation:物体被截断的比例(0.0表示完全可见,1.0表示完全不可见)。

occlusion:物体的遮挡程度(0表示完全可见,1表示部分遮挡,2表示大部分遮挡)。

alpha:物体的观察角度(相对于相机的x轴,范围为-π到π)。

left, top, right, bottom:2D边界框的坐标(像素值)。

height, width, length:物体的3D尺寸(米)。

x, y, z:物体在相机坐标系中的3D位置(米)。

rotation_y:物体绕y轴的旋转角度(弧度)。

根据2D图像的修改,我们得到如下的公式。

PYTHON
new_left = (left * s) + dx
new_top = (top * s) + dy
new_right = (right * s) + dx
new_bottom = (bottom * s) + dy
Copy