一,点的法向量
点云法线
法向量的概念是很小的时候我们就已经说的,法向量是我们点云中一个非常重要的属性,诸如饿哦们常说的三维重建、点云分割,点云去噪 以及特种描述算法等。
特性:
点云中每一点的法向量夹角和曲率值均不随着物体的运动而改变,具有刚体运动不变性
点云法向量得的应用:
1、点云渲染:
法向量可以用于光照渲染,漫反射光照符合Lambert余弦定律,即漫反射光强与N * L成正比,N为法线方向,L为点到光源的向量。所以,在模型边缘处,N与L近似垂直,着色会比较暗。
点云渲染可以检查法向量的正确性。
2、点云重建:
对与一个封闭的曲面,我们可以在空间中定义一个函数形状Indicator := 曲面内部为1,曲面外部为0。则这个形状函数的梯度只有在曲面上不为0,这个梯度方向就是曲面的法线方向。
3、区分薄板正反面:
4、 法线贴图
下面两个图是一个人脸网格的UV展开,属于平面网格。它们的区别在于法线信息,右图的法线继承了原始网格的法线,它使得平面网格也可以渲染出凹凸感。这个技巧常用于游戏场景的渲染,用低面片数的网格加上高质量的法线贴图信息,来增强模型的几何凹凸感。
二、法向量估计方法
一般可用低阶多项式曲面进行局部拟合。如果点云均匀分布,希望计算速度快,也可以用平面进行局部拟合,平面法线即为点云法线。
1.基于Delaunay三角分割法
基于Delaunay三角分割法不适合有噪声的点云,无法很好的用于现场采集数据集中。
2.基于鲁棒统计学方法
基于鲁棒统计学的方法从原理到计算都过于复杂,因此无法直接用在大规模的点云场景中。
3.基于局部表面拟合法
该方法使用范围最为广泛,且适用于大规模的点云场景中,计算原理较为简单了,效率快。
原理介绍:
1、 对点云中的每个点找半径A规定领域范围或者直接选择最近的K个点组合成一个平面,法然后使用最小二乘法拟合一个局部的平面。平面方程ax+by+cz+d=0;
Matlab 最小二乘法 拟合平面_Σίσυφος1900的博客-CSDN博客_matlab最小二乘法拟合平面
因此我们对点云中的某个点拟合的过程表示为:
平面局部拟合我们在pcl 中采的是PCA( 主要成分分析):
2、PCA分析:
因为曲面局部是平坦的,法线所在的方向是主成分最低的方向,也就是PCA里面最小特征值对应的特征方向
寻求一个方向n使得所有邻域点在方向n上的投影点的分布最为集中,也是就意味着点在该方向上的投影的方差最小
具体操作如下:
1)给每个点计算K邻近邻域(也可以用半径邻域);
2)计算PCA的协方差矩阵 Cov = ∑ (Ni – C) X (Ni – C),其中Ni为邻域点,C为中心点。这个协方差矩阵的最小特征值对应的特征向量即为这个点的法线方向。
在特征比较尖锐的地方,如左图所示,法线计算容易被光滑掉。右图直线是物体真实的几何,可以比较红色法线方向的差别。特征点的法线,可以用迭代权重的方法来修正,如先用平面局部拟合,然后给局部的点计算权重,离平面越远的点权重越小,然后再用带权重的点局部拟合平面,如此迭代即可。
pca : 主成分分析(PCA)原理详解 - 知乎
点云法向量_o180o的博客-CSDN博客_点云法向量
法向量的定向:
PCL 计算点云法向量并显示_点云侠的博客-CSDN博客_pcl 法向量
若两个相邻的连个点p1 p2(假设这连个平面上的点平滑的切共面的),那么我们从上一步中计算得到的两个向量你n1 n2 也几乎是平行的,也就是说n1 n2 之间的夹角可能就是0,,若夹角为180度,那么这两个点种的器重一个向量需要翻转一下。
表面的曲率:
曲率的意义:
曲率越小表示这个有K个点组成的平面越平坦,也就是说k-1 个领域的起伏情况越小,反之,曲率越大,则表示领域的起伏越大
计算:
我们在用PCA的时候用协方差矩阵M对齐进行特征值的分解,计算得出M的每个特征值r1
曲率S=r1/(r1+r2+r3+....+r(k-2))
三、PCL中的法向量
1、pcl 中法向量的格式:
/*brief A point structure representing normal coordinates and the surface curvature estimate. (SSE friendly)ingroup common*/
struct Normal : public _Normal
{
inline Normal (const _Normal &p)
{
normal_x = p.normal_x;
normal_y = p.normal_y;
normal_z = p.normal_z;
data_n[3] = 0.0f;
curvature = p.curvature;
}
inline Normal ()
{
normal_x = normal_y = normal_z = data_n[3] = 0.0f;
curvature = 0;
}
inline Normal (float n_x, float n_y, float n_z)
{
normal_x = n_x; normal_y = n_y; normal_z = n_z;
curvature = 0;
data_n[3] = 0.0f;
}
friend std::ostream& operator << (std::ostream& os, const Normal& p);
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
pcl::NormalEstimation n;
pcl::PointCloud::Ptr normals(new pcl::PointCloud);
pcl::search::KdTree::Ptr tree(new pcl::search::KdTree);
tree->setInputCloud(m_currentCloud->makeShared());
n.setInputCloud(m_currentCloud->makeShared());
n.setSearchMethod(tree);
n.setKSearch(p1);
// 输出结果是 法向量的x y z坐标和表面的曲率,
n.compute(*normals);
四、PCL计算点云并显示
#if 1
int main()
{
pcl::PointCloud::Ptr m_current_cloud(new pcl::PointCloud);
if (-1 == pcl::io::loadPCDFile("bunny.pcd", *m_current_cloud))//放到与工程中的主.cpp同一位置的文件夹下
{
cout << "加载文件失败!" << endl;
return -1;
}
cout << m_current_cloud->points.size() << endl;
// 就算这个点云的法向量
pcl::NormalEstimationOMP n; // OMP
pcl::PointCloud::Ptr normals(new pcl::PointCloud); // 法向量
// 建立kdtree 来进行临近点的搜集 用来拟合平面
pcl::search::KdTree::Ptr tree(new pcl::search::KdTree());
n.setNumberOfThreads(10);// 设置openOMP的线程数
n.setInputCloud(m_current_cloud);
n.setSearchMethod(tree); // 使用kd tree 来计算
n.setKSearch(20); // 20 个点的近邻进行拟合平面 我们也可以使用搜索半径来找到点
// n.setRadiusSearch(0.4); // 0.4 的半径来找临近点
n.compute(*normals); // 开始计算法向量
// 可视化
boost::shared_ptrviewer(new pcl::visualization::PCLVisualizer("Normal viewer"));
// 设置点云的背景色
viewer->setBackgroundColor(0.3,0.3,0.3);
viewer->addText("Normal_Show",10,10,"text");
// 设置点云的颜色
pcl::visualization::PointCloudColorHandlerCustom single_color(m_current_cloud, 0, 225, 0);
//添加坐标系
viewer->addCoordinateSystem(0.1);
viewer->addPointCloud(m_current_cloud, single_color, "sample cloud");
//添加需要显示的点云法向。cloud为原始点云模型,normal为法向信息,20表示需要显示法向的点云间隔,即每20个点显示一次法向,0.02表示法向长度。
viewer->addPointCloudNormals(m_current_cloud, normals, 20, 0.02, "normals");
//设置点云大小
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample cloud");
while (!viewer->wasStopped())
{
viewer->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
return 0;
}
#endif
//
2、显示点云中的子集的法线 显示了10 %的点云的法向量
#if 1 // 显示部分法向量
int main()
{
pcl::PointCloud::Ptr m_current_cloud(new pcl::PointCloud);
if (-1 == pcl::io::loadPCDFile("bunny.pcd", *m_current_cloud))//放到与工程中的主.cpp同一位置的文件夹下
{
cout << "加载文件失败!" << endl;
return -1;
}
cout << m_current_cloud->points.size() << endl;
vector point_indices(floor(m_current_cloud->points.size() / 10)); // 拿到点云中 10% 的点
for (size_t i = 0; i < point_indices.size(); i++)
{
point_indices[i] = i;
}
// 传递索引
pcl::IndicesPtr in(new vector(point_indices));
// 就算这个点云的法向量
pcl::NormalEstimationOMP n; // OMP
pcl::PointCloud::Ptr normals(new pcl::PointCloud); // 法向量
// 建立kdtree 来进行临近点的搜集 用来拟合平面
pcl::search::KdTree::Ptr tree(new pcl::search::KdTree());
n.setNumberOfThreads(10);// 设置openOMP的线程数
n.setInputCloud(m_current_cloud);
// 设置我这里要显示的部分点云
n.setIndices(in);
n.setSearchMethod(tree); // 使用kd tree 来计算
// n.setKSearch(20); // 20 个点的近邻进行拟合平面 我们也可以使用搜索半径来找到点
n.setRadiusSearch(0.4); // 0.4 的半径来找临近点
n.compute(*normals); // 开始计算法向量
// 将我们需要得那10% 的点云提出出来
pcl::PointCloud::Ptr cloud1(new pcl::PointCloud);
pcl::copyPointCloud(*m_current_cloud, point_indices, *cloud1);
// 可视化
boost::shared_ptrviewer(new pcl::visualization::PCLVisualizer("Normal viewer"));
// 设置点云的背景色
viewer->setBackgroundColor(0.3, 0.3, 0.3);
viewer->addText("Normal_Show", 10, 10, "text");
// 设置点云的颜色 设置两个
pcl::visualization::PointCloudColorHandlerCustom single_color(m_current_cloud, 0, 225, 0);
pcl::visualization::PointCloudColorHandlerCustom single_color2(cloud1, 255, 0, 0);
//添加坐标系
viewer->addCoordinateSystem(0.1);
viewer->addPointCloud(m_current_cloud, single_color, "sample cloud");
viewer->addPointCloud(cloud1, single_color2, "sample_cloud_2");
//添加需要显示的点云法向。cloud为原始点云模型,normal为法向信息,20表示需要显示法向的点云间隔,即每20个点显示一次法向,0.02表示法向长度。
viewer->addPointCloudNormals(cloud1, normals, 20, 0.02, "normals");
//设置点云大小
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample_cloud_2");
while (!viewer->wasStopped())
{
viewer->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
return 0;
}
#endif
计算一个点的法向量:
void computePointNormal (const pcl::PointCloud &cloud, const std::vector &indices,
Eigen::Vector4f &plane_parameters, float &curvature);
cloud 是输入的点云
indices 是要给估计法向量的点的索引
plane_parameters 是输出的法向量
curvature 是输出的曲率
五、PCL中法向量的定向
PCA可以很好的实现降维,找到切平面,但是并不能确定切平面的法线方向。
解决这一问题的方法是引入一个视点约束,假设引入一个视点vp,为了使所有法线vector ni始终朝向视点的一面,它们需要满足如下不等式约束:
若要在PCL中手动重新定向给定法线,可以用如下代码:
flipNormalTowardsViewpoint (const PointT &point, float vp_x, float vp_y, float vp_z, Eigen::Vector4f &normal);
默认的视点坐标是(0,0,0),可以通过如下代码指定:
setViewPoint (float vpx, float vpy, float vpz);