点云配准汇总
配准比较常用的套路就是法线提取,然后fpfh特征提取,粗配准,最后再来精配准,基本就能得到比较准确的旋转矩阵和模板点云配上。
特征提取
轮廓提取
1 |
|
法线提取
1 |
|
FPFH特征提取
1 |
|
粗配准
1 |
|
精配准
1 |
|
常用方法
求质心点
1 | Eigen::Vector4f pcaCentroid;//质心点(4x1) |
PCA求主方向(转正)的变换矩阵
1 | void getPCATransfrom(const pcl::PointCloud<pcl::PointXYZ> cloud_in, Eigen::Vector4f centroid, Eigen::Matrix4f &tm) { |
矩阵旋转
1 |
|
求最小包围盒
比较普通的处理是通过上面求质心点然后PCA求矩阵再转正最后直接获取对角线的两个点(也就是最大点最小点)即可。1
2pcl::PointXYZ min_p1, max_p1;
pcl::getMinMax3D(*transformedCloud, min_p1, max_p1);
但上面这种通常只是凑活能用,并不是真正的最小包围,原因就是转正的时候没法保证一个不规则物体哪个方向摆正才能正对着立方体包围起来就是最小包围。
如果要求比较高,也就是针对体积的最小包围盒,采用第三方ApproxMVBB其实比较好。
关于KDTree和OCTree
这两个用示例代码很难讲清楚只能多解释一下
KDTree
KD树,在k维空间中组织点云的数据结构,是一种二叉搜索树(对于3维点云,k=3)。可用于最近邻的搜索。简单说就是给定条件的二叉树一边大于一边小于这么往下来。KD树包括构建阶段和搜索阶段。主要用途也就通过把点云按KD树排列后加速搜索。
- 构建阶段:即对空间的切分,每轮切分按照一定原则先后切分k个维度。切分域(维度)的先后顺序可以根据方差(空间点的分布)确定,切分点选择中间点即可。
- 搜索阶段:1.寻找目标数据的近似最近点,即根据目标数据从根节点开始搜索kd树,找到对应的叶子节点作为近似最近点。2.回溯,沿着搜索路径回溯,以目标数据和近似最近点的距离作为判断依据,看看有无更近的点。
一般setSearchMethod
让传入时懒得搞用下面代码即可创建用于提取搜索方法的kdtree树对象。使用KdTree做检索,下面用上面创建的tree对象也可以只不过要用->,因为上面是指针。1
2pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud_in);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//K近邻搜索,即搜索该点周围的K个点。
void consoleKSearchPoint(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in, pcl::PointXYZ searchPoint, int K) {
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
kdtree.setInputCloud(cloud_in);
//结果为cloud_in内的K个点的索引集合
std::vector<int> pointIdxKNNSearch;
//结果为周围的K个点与搜索点的平方距离
std::vector<float> pointKNNSquaredDistance;
if (kdtree.nearestKSearch(searchPoint, K, pointIdxKNNSearch, pointKNNSquaredDistance) > 0)
{
for (std::size_t i = 0; i < pointIdxKNNSearch.size(); ++i)
std::cout << "x:" << (*cloud_in)[pointIdxKNNSearch[i]].x
<< " y:" << (*cloud_in)[pointIdxKNNSearch[i]].y
<< " z:" << (*cloud_in)[pointIdxKNNSearch[i]].z
<< " (squared distance: " << pointKNNSquaredDistance[i] << ")" << std::endl;
}
}
//半径搜索
void consoleRadiusSearchPoint(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in, pcl::PointXYZ searchPoint,double radius) {
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
kdtree.setInputCloud(cloud_in);
//结果为cloud_in内在搜索点半径radius范围内的点的索引集合
std::vector<int> pointIdxRadiusSearch;
//结果为搜索点半径radius范围内的点与搜索点的平方距离
std::vector<float> pointRadiusSquaredDistance;
if (kdtree.radiusSearch(searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0)
{
for (std::size_t i = 0; i < pointIdxRadiusSearch.size(); ++i)
std::cout << "x:" << (*cloud_in)[pointIdxRadiusSearch[i]].x
<< " y:" << (*cloud_in)[pointIdxRadiusSearch[i]].y
<< " z:" << (*cloud_in)[pointIdxRadiusSearch[i]].z
<< " (squared distance: " << pointRadiusSquaredDistance[i] << ")" << std::endl;
}
}
OCTree
八叉树是一种基于树的数据结构,每个节点有八个孩子。即每一次划分,都同时划分三个维度。主要用途包括空间划分、搜索、检测以及点云压缩。
一般setSearchMethod让传入时想用octree用下面代码即可1
2
3
4
5
//resolution为八叉树分辨率,即最小体素的边长
pcl::octree::OctreePointCloudSearch<pcl::PointXYZ>::Ptr octree(new pcl::octree::OctreePointCloudSearch<pcl::PointXYZ>(resolution));
octree->setInputCloud(cloud_in);
octree->addPointsFromInputCloud();
空间划分和搜索
三种搜索方式:体素内搜索,K近邻搜索,半径内搜索
使用与KDTree类似不过初始化用上面的pcl::octree::OctreePointCloudSearch
即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 体素内搜索:输入搜索点,返回点索引向量
std::vector<int> pointIdxVec;
if (octree.voxelSearch(searchPoint, pointIdxVec)) {
}
// K近邻搜索
std::vector<int> pointIdxNKNSearch;
std::vector<float> pointNKNSquaredDistance;
if (octree.nearestKSearch(searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0 {
}
// 半径内搜索
std::vector<int> pointIdxRadiusSearch;
std::vector<float> pointRadiusSquaredDistance;
if (octree.radiusSearch(searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0) {
}
空间变化检测
通过递归对比树结构,可以识别体素布局差异所体现的空间变化。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <summary>
/// 八叉树点云变化检测
/// </summary>
/// <param name="cloud_in1">输入点云1</param>
/// <param name="cloud_in2">输入点云2</param>
/// <param name="resolution">八叉树分辨率,体素边长</param>
/// <param name="point_out">新的点的索引</param>
void octreeChangeDetector(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in1, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in2, float resolution, std::vector<int> & indices_out) {
// 创建八叉树点云变化检测器
pcl::octree::OctreePointCloudChangeDetector<pcl::PointXYZ> octree(resolution);
// 添加CloudA的点到八叉树
octree.setInputCloud(cloud_in1);
octree.addPointsFromInputCloud();
//切换缓冲区: 重置octree,但是保存之前内存中的树结构
octree.switchBuffers();
// 添加CloudB的点到octree
octree.setInputCloud(cloud_in2);
octree.addPointsFromInputCloud();
// 获的新的点的索引
octree.getPointIndicesFromNewVoxels(indices_out);
}
点云压缩
点云压缩主要用于数据传输中,可以压缩单个点云或点云流。
压缩配置文件:
压缩配置文件为PCL点云编码器定义了参数集。并针对压缩从OpenNI采集器获取的普通点云进行了优化设置。请注意,解码对象不需要用参数表示,因为它在解码时检测并获取对应的编码参数配置。下面的压缩配置文件是可用的:
- LOW_RES_ONLINE_COMPRESSION_WITHOUT_COLOR:分辨率1cm3,无颜色,快速在线编码
- LOW_RES_ONLINE_COMPRESSION_WITH_COLOR:分辨率1cm3,有颜色,快速在线编码
- MED_RES_ONLINE_COMPRESSION_WITHOUT_COLOR:分辨率5mm3,无颜色,快速在线编码
- MED_RES_ONLINE_COMPRESSION_WITH_COLOR:分辨率5mm3,有颜色,快速在线编码
- HIGH_RES_ONLINE_COMPRESSION_WITHOUT_COLOR:分辨率1mm3,无颜色,快速在线编码
- HIGH_RES_ONLINE_COMPRESSION_WITH_COLOR:分辨率1mm3,有颜色,快速在线编码
- LOW_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR:分辨率1cm3,无颜色,高效离线编码
- LOW_RES_OFFLINE_COMPRESSION_WITH_COLOR:分辨率1cm3,有颜色,高效离线编码
- MED_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR:分辨率5mm3,无颜色,高效离线编码
- MED_RES_OFFLINE_COMPRESSION_WITH_COLOR:分辨率5mm3,有颜色,高效离线编码
- HIGH_RES_OFFLINE_COMPRESSION_WITHOUT_COLOR:分辨率5mm3,无颜色,高效离线编码
- HIGH_RES_OFFLINE_COMPRESSION_WITH_COLOR:分辨率5mm3,有颜色,高效离线编码
- MANUAL_CONFIGURATION允许为高级参数化进行手工配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/// <summary>
/// 点云解压缩
/// </summary>
/// <param name="filepath">文件路径</param>
/// <param name="cloud_out">输出点云</param>
void compressionDecode(std::string filepath, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_out) {
pcl::io::OctreePointCloudCompression<pcl::PointXYZ>* PointCloudDecoder = new pcl::io::OctreePointCloudCompression<pcl::PointXYZ>();
ifstream fin(filepath, ios::binary | ios::out);
stringstream out;
copy(istreambuf_iterator<char>(fin),
istreambuf_iterator<char>(),
ostreambuf_iterator<char>(out));
fin.close();
PointCloudDecoder->decodePointCloud(out, cloud_out);
delete (PointCloudDecoder);
}
/// <summary>
/// 点云压缩
/// </summary>
/// <param name="cloud_in">输入点云</param>
/// <param name="filepath">文件路径</param>
void compressionEncode(std::string filepath, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in) {
//设置压缩选项为:分辨率1立方厘米,有颜色,快速在线编码
pcl::io::compression_Profiles_e compressionProfile = pcl::io::LOW_RES_ONLINE_COMPRESSION_WITHOUT_COLOR;
bool showStatistics = true;
double pointResolution = 0.001;
double octreeResolution = 0.01;
bool doVoxelGridDownDownSampling = false;
unsigned int iFrameRate = 30;
bool doColorEncoding = true;
unsigned char colorBitResolution = 6;
//初始化压缩和解压缩对象 其中压缩对象需要设定压缩参数选项,解压缩按照数据源自行判断
pcl::io::OctreePointCloudCompression<pcl::PointXYZ> *PointCloudEncoder = new pcl::io::OctreePointCloudCompression<pcl::PointXYZ>(compressionProfile, showStatistics, pointResolution, octreeResolution, doVoxelGridDownDownSampling,iFrameRate,doColorEncoding,colorBitResolution);
std::stringstream compressedData;
PointCloudEncoder->encodePointCloud(cloud_in->makeShared(), compressedData);
fstream file(filepath, ios::binary | ios::out);
file << compressedData.str();
file.close();
//删除压缩与解压缩的实例
delete (PointCloudEncoder);
}