文章目录
- O*、NeRF数据与代码解读(相机参数与坐标系变换)
- 1.总体概览
- 2.相机的内外参数
- 3.如何获得相机参数(colmap可估计img位姿)
- 3.5 colmap使用技巧:
- 4.缩放图像需要修改什么相机参数?
- 5.3D空间射线怎么构造
- 一、KITTI数据集介绍(重点是lidar-图像坐标系转换)
- 1.数据格式
- 1.激光雷达数据(data_object_velodyne)
- 可视化
- 2.标注数据label_2.
- 3.图像数据image_2
- 4.标定校准数据calib
- 2.KITTI数据集中的三个坐标系:
- 3.点云数据转鸟瞰图(BEV)
- 4.将标注画在鸟瞰图(BEV)上
- 5. 将标注画在点云上
- 二、几种点云可视化方式
- **方案一**:mayavi可视化点云
- **方案二**、matplotlib可视化点云
- **方案三**、CloudCompare可视化点云
- **Mayavi常用函数**
- 三、点云数据变换
- 1.pcd点云可视化与移动(利用open3d)
- 2.pcd点云旋转
- 1.1 根据欧拉角计算旋转矩阵
- 1.2 根据旋转向量(轴角)计算旋转矩阵
- 1.3 根据四元数计算旋转矩阵
- 3.点云仿射变换
- 4.点云缩放
- 5. ply 与其他数据处理
- ply 数据读取(open3d、plyfile、 直接打开)
- 保存ply文件
- ply转pcd
- ply转bin,任意点云格式转ply
- semantic kitti数据
- 7.下采样
- 最远点采样FPS
- 体素下采样(open3d)
- 均匀下采样(open3d)
- 随机下采样(open3d 与 numpy )
- 8.点云离群点剔除 (open3d)
- 9、聚类
- 四、点云数据增强
- 五、分割可视化
- 六、逆透视变换
- 七、NeRF-Studio 与 meshroom
- 1.使用meshroom裁剪全景图像
- 2.NeRF-Studio 估计图像位姿态
NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis 是一篇获得ECCV2020 Best Paper Honorable Mention的论文。给定一个场景的多视角的图像,神经辐射场(NeRF)通过图像重建误差优化一个神经场景表征。优化后可以实现逼真的新视角合成效果。被其逼真的图像合成效果所吸引,很多研究人员开始跟进该方向,并在最近的一两年时间里产生了大量的(好几百篇!)改进和拓展工作。
NeRF的技术其实很简洁,并不复杂。但与2D视觉里考虑的2维图像不同,NeRF考虑的是一个3D空间。下面列的是NeRF实现的几个关键部分:
有一个3D空间,用一个连续的场表示空间里存在一个感兴趣的物体区域处于不同位置和朝向的相机拍摄多视角图像对于一张图像,根据相机中心和图像平面的一个像素点,两点确定一条射线穿过3D空间在射线上采样多个离散的3D点并利用体素渲染像素的颜色。
这里面涉及到3D空间、物体区域、相机位置和朝向、图像、射线、以及3D采样点等。要想优化NeRF,我们需要能够表达刚刚提到的这些东西。
坐标系定义: 为了唯一地描述每一个空间点的坐标,以及相机的位置和朝向,我们需要先定义一个世界坐标系。一个坐标系其实就是由原点的位置与XYZ轴的方向决定。接着,为了建立3D空间点到相机平面的映射关系以及多个相机之间的相对关系,我们会对每一个相机定义一个局部的相机坐标系。下图为常见的坐标系定义习惯。
常见的相机坐标系定义习惯(右手坐标系)。注意:在OpenCV/COLMAP的相机坐标系里相机朝向+z轴,在LLFF/NeRF的相机坐标系中里相机朝向-z轴。有时我们会按坐标系的xyz朝向描述坐标系,如OpenCV/COLMAP里使用的RDF表述X轴指向right,Y轴指向Down,Z轴指向Foward。
相机的位置和朝向由相机的外参(extrinsic matrix)决定,投影属性由相机的内参(intrinsic matrix)决定。
注意:接下来的介绍假设矩阵是列矩阵(column-major matrix),变换矩阵左乘坐标向量实现坐标变换(这也是OpenCV/OpenGL/NeRF里使用的形式)。
1.相机外参
相机外参是一个4x4的矩阵M,其作用是将世界坐标系的点 Pworld=[x,y,z,1] 变换到相机坐标系Pcamera=M Pworld 下。我们也把相机外参叫做world-to-camera (w2c)矩阵。(注意用的是4维的齐次坐标)
相机外参的逆矩阵被称为camera-to-world (c2w)矩阵,其作用是把相机坐标系的点变换到世界坐标系。因为NeRF主要使用c2w,这里详细介绍一下c2w的含义。c2w矩阵是一个4x4的矩阵,左上角3x3是旋转矩阵R,又上角的3x1向量是平移向量T。有时写的时候可以忽略最后一行[0,0,0,1]。
Camera-to-world (c2w) 矩阵
具体的,旋转矩阵的第一列到第三列分别表示了相机坐标系的X, Y, Z轴在世界坐标系下对应的方向;平移向量表示的是相机原点在世界坐标系的对应位置。
如果这段描述还是有点抽象,可以尝试进行下列计算帮助自己理解。刚刚讲到c2w是将相机坐标系的向量变换到世界坐标系下,那我们如果将c2w作用到(即左乘)相机坐标系下的X轴[1,0,0,0],Y轴[0,1,0,0], Z轴[0,0,1,0],以及原点[0,0,0,1](注意方向向量的齐次坐标第四维等于0,点坐标第四维等于1),我们会得到它们在世界坐标系的坐标表示:
从上面可以看到可以看到,将c2w作用到相机坐标系下的X轴、Y轴、 Z轴、以及原点我们会依次得到c2w的四列向量。
2.相机内参
相机的内参矩阵将相机坐标系下的3D坐标映射到2D的图像平面,这里以针孔相机(Pinhole camera)为例介绍相机的内参矩阵K:
内参矩阵K包含4个值,其中fx和fy是相机的水平和垂直焦距(对于理想的针孔相机,fx=fy)。焦距的物理含义是相机中心到成像平面的距离,长度以像素为单位。cx和cy是图像原点相对于相机光心的水平和垂直偏移量。cx,cy有时候可以用图像宽和高的1/2近似:
NeRF算法假设相机的内外参数是提供的,那么怎么得到所需要的相机参数呢?这里分合成数据集和真实数据集两种情况。
合成数据
对于合成数据集,我们需要通过指定相机参数来渲染图像,所以得到图像的时候已经知道对应的相机参数,比如像NeRF用到的Blender Lego数据集。常用的渲染软件还有Mitsuba、OpenGL、PyTorch3D、Pyrender等。渲染数据比较简单,但是把得到的相机数据转到NeRF代码坐标系牵扯到坐标系之间的变换,有时候会比较麻烦。
真实数据
对于真实场景,比如我们用手机拍摄了一组图像,怎么获得相机位姿?目前常用的方法是利用运动恢复结构(structure-from-motion, SFM)技术估计几个相机间的相对位姿。这个技术比较熟了,现在学术界里用的比较多的开源软件包是COLMAP: https://colmap.github.io/。输入多张图像,COLMAP可以估计出相机的内参和外参(也就是sparse model)。
安装colmap:
下面是COLMAP官网教程给的三个命令行操作步骤,简单来说: 第一步是对所有的图像进行特征点检测与提取,第二步是进行特征点匹配,第三步是进行SFM恢复相机位姿和稀疏的3D特征点。具体的使用方法和原理还请阅读其官方文档。其实COLMAP也集成了multiview stereo (MVS)算法用于重建场景完整的三维结构(也称为dense model)。不过NeRF本身是一种新颖的场景表征和重建算法,我们只需要相机的位姿信息,所以我们不需要跑MVS进行dense重建。注意:如果没有标定信息,基于单目的SFM无法获得场景的绝对尺度。
使用COLMAP得到相机参数后只需要转成NeRF可以读取的格式即可以用于模型训练了。那这里面需要做什么操作?
LLFF真实数据格式
NeRF代码里用load_llff.py这个文件来读取真实的数据,第一次看到LLFF这个词可能不知道是什么意思。其实LLFF GitHub - Fyusion/LLFF: Code release for Local Light Field Fusion at SIGGRAPH 2019 是NeRF作者的上一篇做新视角合成的工作。为了和LLFF方法保持一致的数据格式,NeRF使用load_llff.py读取LLFF格式的真实数据,并建议大家使用LLFF提供的的imgs2poses.py文件获取所需相机参数。
COLMAP到LLFF数据格式
imgs2poses.py这个文件其实很简单,就干了两件事。
这里有一个细节需要注意,就是在pose_utils.py文件里load_colmap_data()函数的倒数第二行,有一个操作将colmap得到的c2w旋转矩阵中的第一列和第二列互换,第三列乘以负号:
还记得刚刚提到c2w旋转矩阵的三列向量分别代表XYZ轴的朝向,上述操作实际上就是把相机坐标系轴的朝向进行了变换:X和Y轴调换,Z轴取反,如下图所示:
poses_bounds.npy里有什么load_llff.py会直接读取poses_bounds.npy文件获得相机参数。poses_bounds.npy是一个Nx17的矩阵,其中N是图像的数量,即每一张图像有17个参数。其中前面15个参数可以重排成3x5的矩阵形式:
poses_bounds.npy的前15维参数。左边3x3矩阵是c2w的旋转矩阵,第四列是c2w的平移向量,第五列分别是图像的高H、宽W和相机的焦距f
后两个参数用于表示场景的范围Bounds (bds),是该相机视角下场景点离相机中心最近(near)和最远(far)的距离,所以near/far肯定是大于0的。
这两个值是怎么得到的?是在imgs2poses.py中,计算colmap重建的3D稀疏点在各个相机视角下最近和最远的距离得到的。
这两个值有什么用?之前提到体素渲染需要在一条射线上采样3D点,这就需要一个采样区间,而near和far就是定义了采样区间的最近点和最远点。贴近场景边界的near/far可以使采样点分布更加密集,从而有效地提升收敛速度和渲染质量。
poses_bounds.npy里最后两个参数(near/far)的作用示意图
- 航拍(像机焦距较大)时,像机参数如下:
“fl_x”: 7516.557593687107, # 焦距
“fl_y”: 7537.338324771301,
“cx”: 511.56096564540155,
“cy”: 268.4251482048674,
“w”: 1024, # 图像宽高
“h”: 540,
“camera_model”: “OPENCV”,
“k1”: -0.014968006153574012,
“k2”: -0.0005643351843003853,
“p1”: -0.0009081580152328081,
“p2”: 0.0001238689035023915,
wh: 图像宽高,fl_x : 焦距 ;k1、p1是像机的径向畸变和切向畸变的参数。在使用如此长的焦距时,姿态优化可能不稳定。可以尝试禁用它:
–pipeline.datamanager.camera-optimizer.mode off
增加“near”平面的值(可能需要更大的平面),以及增加远平面,–pipeline.model.far-plane
–pipeline.model.near-plane 默认值为0.05
–pipeline.model.far-plane–pipeline.datamanager.camera-optimizer.mode off nerfstudio-data
–center-method focus
–auto-scale-poses False
–scene-scale 0.1
relaunch training using all images, add --train-split-fraction 1.0
1e-2 threshold, sequential(更加依赖前后帧)
在_load_data()函数里,有一个用于图像缩放的factor比例参数,将HxW的图像缩放成(H/factor)x(W/factor)。这里面有一个问题是如果缩放了图像尺寸,相机的参数需要相应的做什么变化?
做法是:外参(位置和朝向)不变,相机的焦距f,cx, 和cy等比例缩放。下图的示意图展示了当相机位置不变,相机视野(Field of view, FOV)不变的情况下,图像的高和焦距长短的关系。
viewmatrix()
view_matrix是一个构造相机矩阵的的函数,输入是相机的Z轴朝向、up轴的朝向(即相机平面朝上的方向Y)、以及相机中心。输出下图所示的camera-to-world (c2w)矩阵。因为Z轴朝向,Y轴朝向,和相机中心都已经给定,所以只需求X轴的方向即可。又由于X轴同时和Z轴和Y轴垂直,我们可以用Y轴与Z轴的叉乘得到X轴方向。
下面是load_llff.py里关于view_matrix()的定义,看起来复杂一些。其实就是比刚刚的描述比多了一步:在用Y轴与Z轴叉乘得到X轴后,再次用Z轴与X轴叉乘得到新的Y轴。为什么这么做呢?这是因为传入的up(Y)轴是通过一些计算得到的,不一定和Z轴垂直,所以多这么一步。
poses_avg()
这个函数其实很简单,顾名思义就是多个相机的平均位姿(包括位置和朝向)。输入是多个相机的位姿。
1.第一步对多个相机的中心进行求均值得到center。
2.第二步对所有相机的Z轴求平均得到vec2向量(方向向量相加其实等效于平均方向向量)。
3.第三步对所有的相机的Y轴求平均得到up向量。
4.最后将vec2, up, 和center输入到刚刚介绍的viewmatrix()函数就可以得到平均的相机位姿了。
下图展示了一个poses_avg()函数的例子。左边是多个输入相机的位姿,右边是返回的平均相机姿态。可以看出平均相机位姿的位置和朝向是之前所有相机的均值。
(中间大的坐标系是世界坐标系,每一个小的坐标系对应一个相机的局部坐标系)
recenter_poses()
recenter_poses()函数的名字听起来是中心化相机位姿(同样包括位置和朝向)的意思。输入N个相机位姿,会返回N个相机位姿。
具体的操作了解起来可能有点跳跃。第一步先用刚刚介绍的poses_avg(poses)得到多个输入相机的平均位姿c2w,接着用这个平均位姿c2w的逆左乘到输入的相机位姿上就完成了归一化。
首先我们要知道利用同一个旋转平移变换矩阵左乘所有的相机位姿是对所有的相机位姿做一个全局的旋转平移变换,那下一个问题就是这些相机会被变到什么样的一个位置?我们可以用平均相机位姿作为支点理解,如果把平均位姿的逆c2w^-1左乘平均相机位姿c2w,返回的相机位姿中旋转矩阵为单位矩阵,平移量为零向量。也就是变换后的平均相机位姿的位置处在世界坐标系的原点,XYZ轴朝向和世界坐标系的向一致。
下图我们用一个例子帮助理解。左边和右边分别是输入和输出的相机位姿示意图。我们可以看到变换后的多个相机的平均位姿处在世界坐标系的原点,并且相机坐标系的XYZ轴与世界坐标系保持一致了。
render_path_spiral()
这个函数写的有点复杂,它和模型训练没有关系,主要是用来生成一个相机轨迹用于新视角的合成.下面只放了render_path_spiral()函数的定义,NeRF代码里还有一段是在准备输入参数,由于相关代码比较长就不贴出来。
需要知道这个函数它是想生成一段螺旋式的相机轨迹,相机绕着一个轴旋转,其中相机始终注视着一个焦点,相机的up轴保持不变。简单说一下上面的代码:
首先是一个for循环,每一迭代生成一个新的相机位置。c是当前迭代的相机在世界坐标系的位置,np.dot(c2w[:3,:4], np.array([0,0,-focal, 1.])是焦点在世界坐标系的位置,z是相机z轴在世界坐标系的朝向。接着使用介绍的viewmatrix(z, up, c)构造当前相机的矩阵。
下面这个图可视化了 render_path_spiral()生成的轨迹。
(中间大的坐标系是世界坐标系,每一个小的坐标系对应一个相机的局部坐标系)
这个函数也比较复杂,前半部分是在将输入的相机参数进行归一化,后半部分是生成一段相机轨迹用于合成新视角。对输入相机参数进行归一化时,思路是:
用 pt_mindist = min_line_dist(rays_o, rays_d)找到离所有相机中心射线距离之和最短的点(可以先简单理解成场景的中心位置)
将得到的场景中心位置移到世界坐标系的原点,同时将所有相机z轴的平均方向转到和世界坐标系的z轴相同
最后将相机的位置缩放到单位圆内
下面这个图可视化了spherify_poses()返回的结果。
最后我们看一下这个射线是怎么构造的。给定一张图像的一个像素点,我们的目标是构造以相机中心为起始点,经过相机中心和像素点的射线。
首先,明确两件事:
一条射线包括一个起始点和一个方向,起点的话就是相机中心。对于射线方向,我们都知道两点确定一条直线,所以除了相机中心我们还需另一个点,而这个点就是成像平面的像素点。
NeRF代码是在相机坐标系下构建射线,然后再通过camera-to-world (c2w)矩阵将射线变换到世界坐标系。
通过上述的讨论,我们第一步是要先写出相机中心和像素点在相机坐标系的3D坐标。下面我们以OpenCV/Colmap的相机坐标系为例介绍。相机中心的坐标很明显就是[0,0,0]了。像素点的坐标可能复杂一点:首先3D像素点的x和y坐标是2D的图像坐标 (i, j)减去光心坐标 (cx,cy),然后z坐标其实就是焦距f (因为图像平面距离相机中心的距离就是焦距f)。
所以我们就可以得到射线的方向向量是(i-cx,j-cy,f) - (0,0,0)。因为是向量,我们可以把整个向量除以焦距f归一化z坐标
接着只需要用c2w矩阵把相机坐标系下的相机中心和射线方向变换到世界坐标系就搞定了。
(OpenCV/Colmap相机坐标系下射线的构造示意图)
下面是NeRF的实现代码
这是因为OpenCV/Colmap的相机坐标系里相机的Up/Y朝下, 相机光心朝向+Z轴,而NeRF/OpenGL相机坐标系里相机的Up/朝上,相机光心朝向-Z轴,所以这里代码在方向向量dir的第二和第三项乘了个负号。
数据包含4个部分,即激光雷达数据velodyne、图像数据image_2、校准数据calib和标注数据label_2。
1.激光雷达数据(data_object_velodyne)
velodyne文件夹下存储了点云文件,以bin格式存储。velodyne文件是激光雷达的测量数据(绕其垂直轴(逆时针)连续旋转),激光雷达参数如下:
以“000000.bin”文件为例,点云数据以浮点二进制文件格式存储,每行包含8个数据,每个数据由四位十六进制数表示(浮点数),每个数据通过空格隔开。一个点云数据由四个浮点数数据构成,分别表示点云的x、y、z、r(强度 or 反射值)。
KITTI激光雷达文件夹下的训练点云数量有7481个,即7481个bin文件,共13.2GB大小。测试点云数量有7518个,即7518个bin文件,共13.4GB大小。
可视化
利用python中的open3d实现 点云数据可视化(.bin文件)
2.标注数据label_2.
标签存储在data_object_label_2文件夹中,存储为txt文本文件,即data_object_label_2/training/label_2/xxxxxx.txt。标签中仅不含了7481个训练场景的标注数据,而没有测试场景的标注数据。
标注文件中16个属性,即16列。但我们只能够看到前15列数据,因为第16列是针对测试场景下目标的置信度得分,也可以认为训练场景中得分全部为1但是没有专门标注出来。下图是000001.txt的标注内容和对应属性介绍。
第1列
目标类比别(type),共有8种类别,分别是Car、Van、Truck、Pedestrian、Person_sitting、Cyclist、Tram、Misc或’DontCare。DontCare表示某些区域是有目标的,但是由于一些原因没有做标注,比如距离激光雷达过远。但实际算法可能会检测到该目标,但没有标注,这样会被当作false positive (FP)。这是不合理的。用DontCare标注后,评估时将会自动忽略这个区域的预测结果,相当于没有检测到目标,这样就不会增加FP的数量了。此外,在 2D 与 3D Detection Benchmark 中只针对 Car、Pedestrain、Cyclist 这三类。
第2列
截断程度(truncated),表示处于边缘目标的截断程度,取值范围为0~1,0表示没有截断,取值越大表示截断程度越大。处于边缘的目标可能只有部分出现在视野当中,这种情况被称为截断。
第3列
遮挡程度(occlude),取值为(0,1,2,3)。0表示完全可见,1表示小部分遮挡,2表示大部分遮挡,3表示未知(遮挡过大)。
第4列
观测角度(alpha),取值范围为(-pi, pi)。是在相机坐标系下,以相机原点为中心,相机原点到物体中心的连线为半径,将物体绕相机y轴旋转至相机z轴,此时物体方向与相机x轴的夹角。这相当于将物体中心旋转到正前方后,计算其与车身方向的夹角。
第5-8列
二维检测框(bbox),目标二维矩形框坐标,分别对应left、top、right、bottom,即左上(xy)和右下的坐标(xy)。
第9-11列
三维物体的尺寸(dimensions),分别对应高度、宽度、长度,以米为单位。
第12-14列
中心坐标(location),三维物体中心在相机坐标系下的位置坐标(x,y,z),单位为米。
第15列
旋转角(rotation_y),取值范围为(-pi, pi)。表示车体朝向,绕相机坐标系y轴的弧度值,即物体前进方向与相机坐标系x轴的夹角。rolation_y与alpha的关系为alpha=rotation_y - theta,theta为物体中心与车体前进方向上的夹角。alpha的效果是从正前方看目标行驶方向与车身方向的夹角,如果物体不在正前方,那么旋转物体或者坐标系使得能从正前方看到目标,旋转的角度为theta。
第16列
置信度分数(score),仅在测试评估的时候才需要用到。置信度越高,表示目标越存在的概率越大。
3.图像数据image_2
KITTI数据集种共包含了4相机数据,2个灰度相机和2个彩色相机,其中image_2存储了左侧彩色相机采集的RGB图像数据(RGB)。
存储方式为png格式。KITTI相机的分辨率是1392x512,而image_2种存储的图像是矫正后的图像,分辨率为1242x375。训练集共7481张图片;测试集共7518张图片。
4.标定校准数据calib
标定校准文件主要作用是把 激光雷达坐标系 测得的点云坐标 转换到相机坐标中 去,相关参数存在data object calib中,共包含7481个训练标定文件和7518个测试标定文件。标定文件的存储方式为txt文本文件
以训练文件中的000000.txt标定校准文件为例,其内容如下图所示:
其中,0、1、2、3分别代表左边灰度相机、右边灰度相机、左边彩色相机和右边彩色相机。
1. 内参矩阵
P0-P3分别表示4个相机的内参矩阵,或投影矩阵, 大小为 3x4。相机内参矩阵是为了计算点云空间位置坐标在相机坐标系下的坐标,即把点云坐标投影到相机坐标系。将相机的内参矩阵乘以点云在世界坐标系中的坐标即可得到点云在相机坐标系中的坐标。
如果需要进一步将点云在相机坐标系下的坐标投影到像平面,还需要除以Z值,以及内参矩阵的推导请参考:。
2. 外参矩阵
根据上述介绍,我们知道存在三种坐标系世界坐标系、相机坐标系、激光雷达坐标系。世界坐标系反映了物体的真实位置坐标,也是作为相机坐标系和激光雷达坐标系之间相互变换的过渡坐标系。
点云位置坐标投影到相机坐标系前,需要转换到世界坐标系下,对应的矩阵为外参矩阵。外参矩阵为Tr_velo_to_cam ,大小为3x4,包含了旋转矩阵 R 和 平移向量 T。将相机的外参矩阵乘以点云坐标即可得到点云在世界坐标系中的坐标。
3.R0校准矩阵
R0_rect 为0号相机的修正矩阵,大小为3x3,目的是为了使4个相机成像达到共面的效果,保证4个相机光心在同一个xoy平面上。在进行外参矩阵变化之后,需要于R0_rect相乘得到相机坐标系下的坐标。
4.点云坐标到相机坐标
综上所述,点云坐标在相机坐标系中的坐标等于
内参矩阵 * 外参矩阵 * R0校准矩阵 * 点云坐标
例如,要将Velodyne激光雷达坐标系中的点x投影到左侧的彩色图像中y,使用公式:
当计算出z<0的时候表明该点在相机的后面 。
按照上述过程得到的结果是点云在相机坐标系中的坐标,如果需要将点云坐标投影到像平面还需要除以Z。参考2.1节。示例程序可以参考 :。
- 激光雷达坐标系 (下图中的蓝色坐标系)
- 相机坐标系 (下图中的红色坐标系)
- 图像坐标系 (下图:相机采集的图像)
先分析了主函数 test。实际跑代码,可以把主函数放最后
效果图:
常用点云格式如 ply、obj ,可直接安装软件 meshlab 直接可视化,也可实现格式转换。
其它如bin格式可见以下方法:
点云的平移函数为translate。其函数原型如下所示:
1.当relative为True时,(tx, ty, tz)表示点云平移的相对尺度,也就是平移了多少距离。
2.当relative为False时,(tx, ty, tz)表示点云中心(质心)平移到的指定位置。
3.质心可以坐标可以通过 pcd.get_center( ) 得到。
1.第一个参数R是旋转矩阵。open3d中点云的旋转仍然是通过矩阵运算来完成的,因而需要先获取旋转矩阵。旋转矩阵可以自己进行定义,也可以根据欧拉角、旋转向量和四元数计算得到。
2.第二个参数是旋转中心,即围绕哪个点进行旋转。如果不指定center的值,默认为点云质心 (pcd.get_center())。
1.1 根据欧拉角计算旋转矩阵
1.2 根据旋转向量(轴角)计算旋转矩阵
旋转向量用3行1列的列向量(x, y, z).T来表示。那么旋转轴为向量方向,旋转角度为向量模长。
1.3 根据四元数计算旋转矩阵
四元数用4行1列的列向量(w, x, y, z).T来表示。
仿射变换包含了一组线性变换和一个平移变换。其中,线性变换可以用矩阵左乘来表示。因此,仿射变换可以用矩阵和向量的方式来表达。
pen3d中的投影变换为函数为transform,参数为投影变换矩阵T。需要注意的是,open3d中的投影变换不仅仅包括仿射变换,还包括透视投影变换。仿射变换是线性的投影变换,而透视变换是非线性的。因此。open3d中的变换矩阵是4x4大小,而不是3x4。即:
矩阵T前3行对应仿射变换,最后一行对应透视变换。其中,s可以用来控制缩放系数,表示缩小的倍数。
PLY是一种电脑档案格式,全名为 多边形档案(Polygon File Format)或 斯坦福三角形档案(Stanford Triangle
Format)。
格式组成:
头:声明数据格式,规定和点和面片的数据内容
点:点的数据内容(坐标x,y,z 颜色r,g,b等)
线:线的数据内容(组成线的点序号索引,颜色等)
面片:面片的数据内容(组成面的点序号索引,颜色等)
举例:
ply
format ascii 1.0
//数据保存格式,三类
//format ascii 1.0
//format binary_little_endian 1.0
//format binary_big_endian 1.0
element vertex 8 //元素:顶点 个数为8
property float x
property float y
property float z //顶点格式:依次为x,yz坐标
element edge 6 //元素:边 6条
property int vertex1
property int vertex2
property uchar red
property uchar green
property uchar blue //边存储格式为: 顶点id 1,2,颜色r,g,b
end_header //头,以end_header结束
0.194585 0.0202505 -0.654565
0.393574 0.0181872 -0.634588
0.196413 0.220227 -0.652125
0.174584 0.0180056 -0.455581
0.811062 -0.0294865 -0.551833
0.991697 -0.0650619 -0.473697
0.845413 0.167279 -0.541659
0.73238 -0.0252545 -0.368009 //点内容,8个顶点(x,y,z)坐标
0 1 255 0 0
0 2 0 255 0
0 3 0 0 255
4 5 255 0 0
4 6 0 255 0
4 7 0 0 255 //6条边,(id1,id2,r,g,b)
ply 数据读取(open3d、plyfile、 直接打开)
法1:转换为TriangleMesh格式才能被Open3d处理,包括存储和点云处理等。
法2:利用plyfile 等数据包打开,需要提前 pip install这个包
法3:用 open 直接打开(适合纯数据,不包含头文件的ply)
保存ply文件
ply转pcd
pip install open3d 或conda 安装该库,conda install -c open3d-admin open3d
ply转bin,任意点云格式转ply
首先安装 pip install plyfile
读取ply中的点云数据为numpy矩阵,直接保存为bin格式。不一定需要plyfile库,也可以是open3d库,或直接用with open打开文件读取即可。
semantic kitti数据
点云(bin)与标注(label)处理
最远点采样FPS
点云最远点采样FPS(Farthest Point Sampling)方法的优势是可以尽可能多地覆盖到全部点云,但是需要多次计算全部距离,因而属于复杂度较高的、耗时较多的采样方法。
FPS采样步骤
体素下采样(open3d)
- open3d中体素下采样的函数为: voxel_down_sample(voxel_size=0.1)。
其参数为体素尺寸。尺寸越大,下采样的倍数越大,点云更稀疏。- voxel_down_sample_and_trace(self, voxel_size, min_bound, max_bound, approximate_class=False)
参数voxel_size体素尺寸外,还有min_bound、max_bound体素边界的最小最大坐标;
approximate_class=True时,体素采样后的点的颜色由体素中大多数点的颜色决定。当approximate_class=False时,体素采样后的点的颜色由体素中所有点的平均颜色决定。
输出包含如下两部分:
(1)稀疏后点云坐标
(2)稀疏后点云中各个点在原点云中的索引
均匀下采样(open3d)
是指每隔固定的点数采样一次。样本按点的顺序执行,始终选择从第 1 个点开始,而不是随机选择。显然点存储的顺序不同,得到的结果也会不一样(适合有序点云),适合均匀采集的点云,如果点云本身不均匀,那么有可能造成某一部分的点云没被采样到。相比于体素的采样方法,点云均匀采样后的点数是固定可控的,而体素采样后的点云数量是不可控的。
随机下采样(open3d 与 numpy )
离群点一般是指偏离大部分数据的点,可能是由于随机误差造成异常点。
离群点剔除方法: 基于统计、邻近度、密度、方差等方法。
open3d中三种剔除方法: 无效值剔除、统计方法、半径滤波法。
1. 无效值剔除
无效值包括空值和无限值。空值一般用NaN表示。
当remove_nan为True时,剔除空值。当remove_infinite为True时表示去除无限值。
2. 统计方式剔除(邻域滤波)
在一个点周围选择若干个点,计算它们距离的统计参数,如果某个点偏离平均值超过stdio_ratio倍的方差则认为是离群点,并进行删除。std_ratio实际上是指偏离标准差的倍数。
第一个参数: nb_neighbors ( int ) – 目标点周围的邻居数。
第二参数: std_ratio ( float ) – 标准偏差比率。
第三个参数: print_progress ( bool , optional , default=False ) – 设置为 True 以打印进度条。
3. 半径滤波方式剔除
在指在目标点周围指定半径内统计点的数量,如果小于某一阈值则认为目标点是离群点并进行删除。两个主要参数:半径和点云数量阈值。
1、DBSCAN点云聚类
是一种基于密度的聚类算法,大体思想是根据样本点的密度和连通性,将密度满足要求且密度可达的点设置为同一类。
函数cluster_dbscan:
第1个参数eps表示DBSCAN算法确定点密度时和邻近点的距离大小,即考虑eps距离范围内的点进行密度计算。
第2个参数min_points表示组成一类最少需要多少个点。print_progress可以用来显示运行的进度。
labels返回聚类成功的类别,-1表示没有分到任何类中的点,原始点云中每个点都会分别得到一个类别标签。
2. KMeans点云聚类
1.先确定k个分类中心,使得各个分类中的点到分类中心的距离总和最小。最直观的效果是将距离相近的点聚为同一类。Kmeans聚类的总数需要提前设置,即假定K个类别,也就是聚类后的类别是确定的。而DBSCAN方法聚类的类别是不确定的。
2.KMeans通常第一步是随机选择K个点作为初始化的类别中心,然后通过不断迭代进行中心坐标更新直到中心点更新距离变化小于阈值或者迭代次数达到上限。
3.KMeans++是在第一步上进行了改进,在初始化过程中尽可能选择距离相隔较远的点作为初始化中心。Skit-learn的Kmeans默认采用的初始化方式为KMeans++。
Skit-learn的Kmeans函数为sklearn.cluster.KMeans
(共11个参数)其中三个参数:n_clusters定义类别数量;max_iter定义最大迭代次数。
函数返回值中:返回结果用result表示,可以用 result.__dict__查看其包含的结果数据。n_iter_为算法实际迭代的次数,cluster_centers_为类别中心,labels_返回各个点的类别标签,从0开始。
此外还有OPTICS、Spectral Clustering(SC,即谱聚类)、 Hierarchical Clustering(层次聚类)、Mean-shift(即:均值迁移)、BIRCH、Affinity Propagation等聚类算法在点云聚类上的简单应用效果。具体可见:
/article/details/124519378
1 中心归一化
减去均值,并除以点距原点的最大距离。
2 打乱点云顺序
3 点云随机旋转
4 z方向点云随机旋转
5 欧拉角随机旋转
随机生成三个角度,代表x、y、z方向的旋转角。
6 指定角度旋转点云
7.随机扰动
8.随机平移
9.随机缩放
10.随机丢弃
详情请见:
变换结果为:
应用Meshroom中的aliceVision_utils_split360Images.exe将图片进行分裂
在路径Meshroom/aliceVision/bin路径cmd输入以下代码:
-i“”双引号中输入你想分裂的照片的路径,-o“”双引号中输入你想输出的分裂后照片的输出路径,–equirectangularNbSplits 8中,8是你想选择将照片分裂的数量
- 安装 NeRF-Studio
2.命令行估计照片位姿:
该命令采用 superpoint 和 superglue替代 sift提取特征