目录
- 经纬线扫描法
- 网格划分法
- 法线估计法
- alpha shapes算法
原始点云:
经纬线扫描法
经纬线扫描法的基本思想是:将经过坐标变换后的二维数据点集按照 x值的大小排序后存储在一个链表中,在二维平面建立点集的最小包围盒并分别计算出 x 和 y 的最大、最小值;令经线从 x 的最小值开始,取步长为dx,在 x 的取值范围内分别计算出每根经线的最大和最小 y 值,并将它们的索引值放在一个新建的链表中,扫描完整个 x 区间;同样的方法扫描纬线,最后将重复的索引值删掉。
该算法的运算速度受点云点数以及搜索分辨率影响。下面代码在点云形状近似为平面,且在xoy平面上投影面积最大时表现较好,其他情况需要修改。
#include <iostream>
#include <algorithm>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
void BoundaryExtraction(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_boundary, int resolution)
{
pcl::PointXYZ px_min = *std::min_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.x < pt2.x; });
pcl::PointXYZ px_max = *std::max_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.x < pt2.x; });
float delta_x = (px_max.x - px_min.x) / resolution;
float min_y = INT_MAX, max_y = -INT_MAX;
std::vector<int> indexs_x(2 * resolution + 2);
std::vector<std::pair<float, float>> minmax_x(resolution + 1, { INT_MAX,-INT_MAX });
for (size_t i = 0; i < cloud->size(); ++i)
{
int id = (cloud->points[i].x - px_min.x) / delta_x;
if (cloud->points[i].y < minmax_x[id].first)
{
minmax_x[id].first = cloud->points[i].y;
indexs_x[id] = i;
}
else if (cloud->points[i].y > minmax_x[id].second)
{
minmax_x[id].second = cloud->points[i].y;
indexs_x[id + resolution + 1] = i;
}
}
pcl::PointXYZ py_min = *std::min_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.y < pt2.y; });
pcl::PointXYZ py_max = *std::max_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.y < pt2.y; });
float delta_y = (py_max.y - py_min.y) / resolution;
float min_x = INT_MAX, max_x = -INT_MAX;
std::vector<int> indexs_y(2 * resolution + 2);
std::vector<std::pair<float, float>> minmax_y(resolution + 1, { INT_MAX,-INT_MAX });
for (size_t i = 0; i < cloud->size(); ++i)
{
int id = (cloud->points[i].y - py_min.y) / delta_y;
if (cloud->points[i].x < minmax_y[id].first)
{
minmax_y[id].first = cloud->points[i].x;
indexs_y[id] = i;
}
else if (cloud->points[i].x > minmax_y[id].second)
{
minmax_y[id].second = cloud->points[i].x;
indexs_y[id + resolution + 1] = i;
}
}
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_xboundary(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud, indexs_x, *cloud_xboundary);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_yboundary(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud, indexs_y, *cloud_yboundary);
*cloud_boundary = *cloud_xboundary + *cloud_yboundary;
}
int main(int argc, char* argv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>),;
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_boundary(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("test.pcd", *cloud);
BoundaryExtraction(cloud, cloud_boundary, 200);
pcl::io::savePCDFile("boundary.pcd", *cloud_boundary);
return EXIT_SUCCESS;
}
提取结果:
网格划分法
网格划分法分为三个步骤:(1)网格划分(2)寻找边界网格(3)提取边界线。首先建立数据点集的最小包围盒,并用给定间隔的矩形网格将其分割;然后找出边界网格,将这些边界网格按顺序连接起来形成一条由边界网格组成的“粗边界”;再对每个边界网格按照某种规则判断其内的点是否为边界点,连接初始边界,并对此边界线进一步处理使其光滑。
该算法的运算速度受点云点数以及搜索分辨率影响。下面代码在点云形状近似为平面,且在xoy平面上投影面积最大时表现较好,其他情况需要修改。
#include <iostream>
#include <vector>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/common/transforms.h>
void BoundaryExtraction( pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_boundary, int resolution)
{
pcl::PointXYZ px_min = *std::min_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.x < pt2.x; });
pcl::PointXYZ px_max = *std::max_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.x < pt2.x; });
pcl::PointXYZ py_min = *std::min_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.y < pt2.y; });
pcl::PointXYZ py_max = *std::max_element(cloud->begin(), cloud->end(), [](pcl::PointXYZ pt1, pcl::PointXYZ pt2) {return pt1.y < pt2.y; });
float x_min = px_min.x, x_max = px_max.x, y_min = py_min.y, y_max = py_max.y;
float L = sqrt((x_max - x_min)*(y_max - y_min) / resolution);
int x_num = (x_max - x_min) / L + 1;
int y_num = (y_max - y_min) / L + 1;
std::vector<std::vector< pcl::PointCloud<pcl::PointXYZ>::Ptr>> uv(x_num + 1, std::vector< pcl::PointCloud<pcl::PointXYZ>::Ptr>(y_num + 1));
for (int i = 0; i <= x_num; ++i)
{
for (int j = 0; j <= y_num; ++j)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr ptcloud(new pcl::PointCloud<pcl::PointXYZ>);
uv[i][j] = ptcloud;
}
}
for (int i = 0; i < cloud->size(); ++i)
{
int x_id = (cloud->points[i].x - x_min) / L;
int y_id = (cloud->points[i].y - y_min) / L;
uv[x_id][y_id]->push_back(cloud->points[i]);
}
std::vector<std::vector<bool>> boundary_index(x_num + 1, std::vector<bool>(y_num + 1, false));
for (int i = 0; i <= x_num; ++i)
{
if (uv[i][0]->size())
boundary_index[i][0] = true;
if (uv[i][y_num]->size())
boundary_index[i][y_num] = true;
}
for (int i = 0; i <= y_num; ++i)
{
if(uv[0][i]->size())
boundary_index[0][i] = true;
if (uv[x_num][i]->size())
boundary_index[x_num][i] = true;
}
for (int i = 1; i < x_num - 1; ++i)
{
for (int j = 1; j < y_num - 1; ++j)
{
if (uv[i][j]->size())
{
if (!uv[i - 1][j - 1]->size() || !uv[i][j - 1]->size() || !uv[i + 1][j - 1]->size() || !uv[i][j - 1]->size() ||
!uv[i + 1][j - 1]->size() || !uv[i + 1][j]->size() || !uv[i + 1][j + 1]->size() || !uv[i][j + 1]->size())
boundary_index[i][j] = true;
}
}
}
for (int i = 0; i <= x_num; ++i)
{
for (int j = 0; j <= y_num; ++j)
{
if (boundary_index[i][j])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr ptcloud(new pcl::PointCloud<pcl::PointXYZ>);
ptcloud = uv[i][j];
Eigen::Vector4f cloud_centroid;
pcl::compute3DCentroid(*ptcloud, cloud_centroid);
cloud_boundary->push_back(pcl::PointXYZ(cloud_centroid(0), cloud_centroid(1), cloud_centroid(2)));
}
}
}
}
int main(int argc, char* argv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_boundary(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("test.pcd", *cloud);
BoundaryExtraction(cloud, cloud_boundary, 200*200);
pcl::io::savePCDFile("boundary.pcd", *cloud_boundary);
return EXIT_SUCCESS;
}
提取结果:
法线估计法
可参考PCL:点云数据基于法线的边界提取(从最初的法线估计理论推导到最终的边界提取) 该算法的运算速度受点云点数以及搜索半径影响。
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/boundary.h>
int main(int argc, char* argv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("test.pcd", *cloud);
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
pcl::PointCloud<pcl::Boundary> boundaries;
pcl::BoundaryEstimation<pcl::PointXYZ, pcl::Normal, pcl::Boundary> est;
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normEst;
normEst.setInputCloud(cloud);
normEst.setSearchMethod(tree);
normEst.setRadiusSearch(3); //法向估计的半径 3
//normEst.setKSearch(9); //法向估计的点数
normEst.compute(*normals);
est.setInputCloud(cloud);
est.setInputNormals(normals);
est.setSearchMethod(tree);
est.setKSearch(500); //一般这里的数值越高,最终边界识别的精度越好
est.compute(boundaries);
pcl::PointCloud<pcl::PointXYZ>::Ptr boundPoints(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ> noBoundPoints;
int countBoundaries = 0;
for (int i = 0; i < cloud->size(); i++)
{
uint8_t x = (boundaries.points[i].boundary_point);
int a = static_cast<int>(x); //该函数的功能是强制类型转换
if (a == 1)
{
(*boundPoints).push_back(cloud->points[i]);
countBoundaries++;
}
else
noBoundPoints.push_back(cloud->points[i]);
}
pcl::io::savePCDFile("boundary.pcd", *boundPoints);
return EXIT_SUCCESS;
}
提取结果:
alpha shapes算法
原理参考:alpha shapes提取平面点云边界点 该算法的运算速度受点云点数以及搜索半径影响。
手写实现:在alpha-shape计算二维点云边界–c++实现基础上精简优化
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/common/distances.h>
#include <pcl/kdtree/kdtree_flann.h>
int main()
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("test.pcd", *cloud);
for (auto& point : *cloud)
point.z = 0.0;
pcl::PointCloud<pcl::PointXYZ>::Ptr boundary(new pcl::PointCloud<pcl::PointXYZ>);
float alpha = 10.0;
pcl::KdTreeFLANN<pcl::PointXYZ>kdtree;
kdtree.setInputCloud(cloud);
std::vector<int> indices;
std::vector<float>dist;
for (size_t i = 0; i < cloud->size(); ++i)
{
pcl::PointXYZ p = cloud->points[i];
kdtree.radiusSearch(cloud->points[i], 2*alpha, indices, dist); //搜索在点云中到p点距离小于2*alpha的点
for (size_t j = 1; j < indices.size(); ++j)
{
pcl::PointXYZ p1 = cloud->points[indices[j]];
float s_2 = std::pow((p.x - p1.x), 2) + std::pow((p.y - p1.y), 2);
float h = std::pow((alpha * alpha / s_2 - 0.25), 0.5);
float x2 = p.x + 0.5 * (p1.x - p.x) - h * (p1.y - p.y);
float y2 = p.y + 0.5 * (p1.y - p.y) - h * (p.x - p1.x);
float x3 = p.x + 0.5 * (p1.x - p.x) + h * (p1.y - p.y);
float y3 = p.y + 0.5 * (p1.y - p.y) + h * (p.x - p1.x);
pcl::PointXYZ p2(x2, y2, 0.0);
pcl::PointXYZ p3(x3, y3, 0.0);
//计算邻域内除了p1之外的点到p2、p3的距离
std::vector<bool> distp2_bool, distp3_bool;
int count = 0;
for (size_t k = 1; k < indices.size(); ++k)
{
pcl::PointXYZ p_other = cloud->points[indices[k]];
if (k != j)
{
++count;
//比较剩余的点到p2、p3距离与r的大小
if (pcl::euclideanDistance(p_other, p2) > alpha)
distp2_bool.push_back(true);
if (pcl::euclideanDistance(p_other, p3) > alpha)
distp3_bool.push_back(true);
}
}
//如果邻域内所有点到p2,或者p3的距离均大于alpha,则表明p是边界点,退出循环
if (count == distp2_bool.size() || count == distp3_bool.size())
{
boundary->push_back(cloud->points[i]);
break;
}
}
}
pcl::io::savePCDFile("boundary.pcd", *boundary);
return EXIT_SUCCESS;
}
调用api:
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/surface/concave_hull.h>
int main(int argc, char* argv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("test.pcd", *cloud);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_hull(new pcl::PointCloud<pcl::PointXYZ>);
pcl::ConcaveHull<pcl::PointXYZ> chull;
chull.setInputCloud(cloud); //输入点云为投影后的点云
chull.setAlpha(10); //设置aLpha值
chull.reconstruct(*cloud_hull);
pcl::io::savePCDFile("boundary.pcd", *cloud_hull);
return EXIT_SUCCESS;
}
提取结果:
参考文献:[1]杨雪娇. 点云的边界提取及角点检测算法研究[D].哈尔滨工程大学,2010.