NormalEstimation

确定表面一点法线的问题近似于估计临近点所拟合表面的一个相切面法线的问题,因此转换过来以后就变成了一个最小二乘法平面拟合的问题。因此估计表面法线的解决方案就变成了分析一个协方差矩阵的特征向量和特征值(或者PCA-主成分分析),这个协方差矩阵从查询点的近邻元素中创建。最大主成分为投影到某方向后方差最大的方向(信息量最大的方向);而法向量为投影到某方向后,信息量最小的方向,因此需要PCA变换,将点云投影到特征值最小的方向。

上面这里有几个概念:

  • 平面的定义和点到直线的距离
  • 最小二乘法和主成分分析(PCA)
  • 协方差矩阵,及其特征向量和特征值

法向量求解步骤

目标平面可以由一个点xxx和一个法向量n⃗\vec{n}n来表示,且一个点pi∈Pkp_i \in P^kpi​∈Pk 到平面的距离为:
di=(pi−x)⋅n⃗d_i = (p_i - x) \cdot \vec{n} di​=(pi​−x)⋅n

即取平面上一点,平面外一点的到平面的距离,就是这两点组成的向量在法向量上面的投影。

如果点到平面的距离为0,即di=0d_i = 0di​=0,我们就可以求出平面的法向量了。因此通过以下步骤:

  1. 求所有点的质心pkp^kpk
    x=pˉ=1k⋅∑i=1kpix = \bar{p} = \frac{1}{k} \cdot \sum_{i=1}^k p_i x=pˉ​=k1​⋅i=1∑k​pi​

  2. 计算质心pkp^kpk的协方差矩阵C∈R3×3C \in \R^{3 \times 3}C∈R3×3,及其特征向量和特征值
    C=1k∑i=1kϵ⋅(pi−pˉ)⋅(pi−pˉ)T,C⋅vj⃗=λj⋅vj⃗,j∈{0,1,2}C = \frac{1}{k} \sum_{i=1}^k \epsilon \cdot (p_i - \bar{p}) \cdot (p_i - \bar{p})^T, C\cdot \vec{v_j} = \lambda_j \cdot \vec{v_j}, j\in \{0, 1, 2\} C=k1​i=1∑k​ϵ⋅(pi​−pˉ​)⋅(pi​−pˉ​)T,C⋅vj​​=λj​⋅vj​​,j∈{0,1,2}

    其中ϵ\epsilonϵ通常为1。CCC是对称和半正定矩阵且它的特征值是实数λj∈R\lambda_j \in \Rλj​∈R。特征向量vj⃗\vec{v_j}vj​​形成了orthogonal frame, 相对应于质心pkp^kpk的主成分(principal components)。 如果0≤λ0≤λ1≤λ20 \leq \lambda_0 \leq \lambda_1 \leq \lambda_20≤λ0​≤λ1​≤λ2​,最小特征值λ0\lambda_0λ0​的特征向量v0⃗\vec{v_0}v0​​就是法向量+n⃗={nx,ny,nz}+ \vec{n} = \{n_x, n_y, n_z\}+n={nx​,ny​,nz​}或者−n⃗- \vec{n}−n的近似。 或者,n⃗\vec{n}n可以在球坐标系下用ϕ,θ\phi, \thetaϕ,θ表示:
    ϕ=arctan⁡(nzny),θ=arctan⁡(ny2+nz2)nx\phi = \arctan (\frac{n_z}{n_y}) , \theta = \arctan \frac{\sqrt{(n_y^2+n_z^2)}}{n_x} ϕ=arctan(ny​nz​​),θ=arctannx​(ny2​+nz2​)​​

    可以看到,通过主成分分析法(PCA)来计算它的方向具有二异性,无法对整个点云数据集的法向方向进行一致性定向。 如下图所示:

  3. 计算法向与视点
    解决这个问题的办法就是使用视点VpV_pVp​。对所有法线ni⃗\vec{n_i}ni​​定向只需要使它们一致朝向视点的方向,满足下面的方程式:
    ni⃗⋅(Vp−pi)>0\vec{n_i} \cdot (V_p - p_i) > 0 ni​​⋅(Vp​−pi​)>0

PCL法向量估计

先来看看PCL法向量估计的模板。通常包含以下步骤:

  • 设置输入点云,及其索引方法
  • 设置临近点的半径
  • 计算法向量
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>{pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);... read, pass in or create a point cloud ...// Create the normal estimation class, and pass the input dataset to itpcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;ne.setInputCloud (cloud);// Create an empty kdtree representation, and pass it to the normal estimation object.// Its content will be filled inside the object, based on the given input dataset (as no other search surface is given).pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());ne.setSearchMethod (tree);// Output datasetspcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);// Use all neighbors in a sphere of radius 3cmne.setRadiusSearch (0.03);// Compute the featuresne.compute (*cloud_normals);// cloud_normals->size () should have the same size as the input cloud->size ()*
}

上面的主要函数在于compute模板函数。compute模板函数关键在于computeFeature函数,我们深入看看,在PCL源码 normal_3d.hpp文件内。

1. computeFeature 函数

template <typename PointInT, typename PointOutT> void
pcl::NormalEstimation<PointInT, PointOutT>::computeFeature (PointCloudOut &output)
{// Allocate enough space to hold the results// \note This resize is irrelevant for a radiusSearch ().pcl::Indices nn_indices (k_);std::vector<float> nn_dists (k_);output.is_dense = true;// Iterating over the entire index vectorfor (std::size_t idx = 0; idx < indices_->size (); ++idx){if (this->searchForNeighbors ((*indices_)[idx], search_parameter_, nn_indices, nn_dists) == 0 ||!computePointNormal (*surface_, nn_indices, output[idx].normal[0], output[idx].normal[1], output[idx].normal[2], output[idx].curvature)){output[idx].normal[0] = output[idx].normal[1] = output[idx].normal[2] = output[idx].curvature = std::numeric_limits<float>::quiet_NaN ();output.is_dense = false;continue;}flipNormalTowardsViewpoint ((*input_)[(*indices_)[idx]], vpx_, vpy_, vpz_,output[idx].normal[0], output[idx].normal[1], output[idx].normal[2]);}
}

computeFeature模板函数就有两个对应的函数API是我们上面理论部分,computePointNormal和flipNormalTowardsViewpoint,也就是对应于上面的第二第三步骤。 接着深入看看。

2. computePointNormal模板函数

template <typename PointT> inline boolcomputePointNormal (const pcl::PointCloud<PointT> &cloud, const pcl::Indices &indices,Eigen::Vector4f &plane_parameters, float &curvature){// Placeholder for the 3x3 covariance matrix at each surface patchEIGEN_ALIGN16 Eigen::Matrix3f covariance_matrix;// 16-bytes aligned placeholder for the XYZ centroid of a surface patchEigen::Vector4f xyz_centroid;if (indices.size () < 3 ||computeMeanAndCovarianceMatrix (cloud, indices, covariance_matrix, xyz_centroid) == 0){plane_parameters.setConstant (std::numeric_limits<float>::quiet_NaN ());curvature = std::numeric_limits<float>::quiet_NaN ();return false;}// Get the plane normal and surface curvaturesolvePlaneParameters (covariance_matrix, xyz_centroid, plane_parameters, curvature);return true;}

这个模板函数,首先计算点云的协方差矩阵,通过computeMeanAndCovarianceMatrix模板函数,然后计算平面参数。

3. computeMeanAndCovarianceMatrix模板函数

template <typename PointT, typename Scalar> inline unsigned int
computeMeanAndCovarianceMatrix (const pcl::PointCloud<PointT> &cloud,const Indices &indices,Eigen::Matrix<Scalar, 3, 3> &covariance_matrix,Eigen::Matrix<Scalar, 4, 1> &centroid)
{// Shifted data/with estimate of mean. This gives very good accuracy and good performance.// create the buffer on the stack which is much faster than using cloud[indices[i]] and centroid as a bufferEigen::Matrix<Scalar, 1, 9, Eigen::RowMajor> accu = Eigen::Matrix<Scalar, 1, 9, Eigen::RowMajor>::Zero ();Eigen::Matrix<Scalar, 3, 1> K(0.0, 0.0, 0.0);for(const auto& index : indices)if(isFinite(cloud[index])) {K.x() = cloud[index].x; K.y() = cloud[index].y; K.z() = cloud[index].z; break;}std::size_t point_count;point_count = indices.size ();for (const auto &index : indices){Scalar x = cloud[index].x - K.x(), y = cloud[index].y - K.y(), z = cloud[index].z - K.z();accu [0] += x * x;accu [1] += x * y;accu [2] += x * z;accu [3] += y * y;accu [4] += y * z;accu [5] += z * z;accu [6] += x;accu [7] += y;accu [8] += z;}if (point_count != 0){accu /= static_cast<Scalar> (point_count);centroid[0] = accu[6] + K.x(); centroid[1] = accu[7] + K.y(); centroid[2] = accu[8] + K.z();centroid[3] = 1;covariance_matrix.coeffRef (0) = accu [0] - accu [6] * accu [6];covariance_matrix.coeffRef (1) = accu [1] - accu [6] * accu [7];covariance_matrix.coeffRef (2) = accu [2] - accu [6] * accu [8];covariance_matrix.coeffRef (4) = accu [3] - accu [7] * accu [7];covariance_matrix.coeffRef (5) = accu [4] - accu [7] * accu [8];covariance_matrix.coeffRef (8) = accu [5] - accu [8] * accu [8];covariance_matrix.coeffRef (3) = covariance_matrix.coeff (1);covariance_matrix.coeffRef (6) = covariance_matrix.coeff (2);covariance_matrix.coeffRef (7) = covariance_matrix.coeff (5);}return (static_cast<unsigned int> (point_count));
}

这里有 Shifted data/with estimate of mean. This gives very good accuracy and good performance. 在参考2中作出了详细的解释。

4. solvePlaneParameters模板函数


inline void
solvePlaneParameters (const Eigen::Matrix3f &covariance_matrix,const Eigen::Vector4f &point,Eigen::Vector4f &plane_parameters, float &curvature)
{solvePlaneParameters (covariance_matrix, plane_parameters [0], plane_parameters [1], plane_parameters [2], curvature);plane_parameters[3] = 0;// Hessian form (D = nc . p_plane (centroid here) + p)plane_parameters[3] = -1 * plane_parameters.dot (point);
}inline void
solvePlaneParameters (const Eigen::Matrix3f &covariance_matrix,float &nx, float &ny, float &nz, float &curvature)
{// Avoid getting hung on Eigen's optimizers
//  for (int i = 0; i < 9; ++i)
//    if (!std::isfinite (covariance_matrix.coeff (i)))
//    {//      //PCL_WARN ("[pcl::solvePlaneParameters] Covariance matrix has NaN/Inf values!\n");
//      nx = ny = nz = curvature = std::numeric_limits<float>::quiet_NaN ();
//      return;
//    }// Extract the smallest eigenvalue and its eigenvectorEIGEN_ALIGN16 Eigen::Vector3f::Scalar eigen_value;EIGEN_ALIGN16 Eigen::Vector3f eigen_vector;pcl::eigen33 (covariance_matrix, eigen_value, eigen_vector);nx = eigen_vector [0];ny = eigen_vector [1];nz = eigen_vector [2];// Compute the curvature surface changefloat eig_sum = covariance_matrix.coeff (0) + covariance_matrix.coeff (4) + covariance_matrix.coeff (8);if (eig_sum != 0)curvature = std::abs (eigen_value / eig_sum);elsecurvature = 0;
}

求出特征向量和特征值。

5. flipNormalTowardsViewpoint模板函数

template <typename PointT> inline voidflipNormalTowardsViewpoint (const PointT &point, float vp_x, float vp_y, float vp_z,float &nx, float &ny, float &nz){// See if we need to flip any plane normalsvp_x -= point.x;vp_y -= point.y;vp_z -= point.z;// Dot product between the (viewpoint - point) and the plane normalfloat cos_theta = (vp_x * nx + vp_y * ny + vp_z * nz);// Flip the plane normalif (cos_theta < 0){nx *= -1;ny *= -1;nz *= -1;}}

Reference

  1. pcl::NormalRobustEstimation - computing robust normals at the origin, https://github.com/PointCloudLibrary/pcl/issues/4965
  2. Algorithms for calculating variancek, https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
  3. Regression asymetry and PCA (Principal Component Analysis), http://zoonek2.free.fr/UNIX/48_R/09.html#7

NormalEstimation法向量估计理论和代码---PCL源码笔记相关推荐

  1. PCL源码剖析之MarchingCubes算法

    MarchingCubes算法简介 MarchingCubes(移动立方体)算法是目前三围数据场等值面生成中最常用的方法.它实际上是一个分而治之的方法,把等值面的抽取分布于每个体素中进行.对于每个被处 ...

  2. python12306源码_Python动刷新抢12306火车票的代码(附源码)

    Python动刷新抢12306火车票的代码(附源码) 用python另一个抢票神器,你get到了吗? 2017年时间飞逝,转眼间距离2018年春节还有不到1个月的时间,还在为抢不到火车票发愁吗?作为程 ...

  3. vue如何让一句代码只执行一次_lt;Vue 源码笔记系列4gt;异步更新队列与$nextTick...

    1. 前言 原文发布在语雀: <Vue 源码笔记系列4>异步更新队列与$nextTick · 语雀​www.yuque.com 上一章我们讲到了修改数据是如何触发渲染函数的观察者,最终调用 ...

  4. html直播动画,HTML5 直播疯狂点赞动画实现代码 附源码

    直播有一个很重要的互动: 为了烘托直播间的氛围,直播相对于普通视频或者文本内容,点赞通常有两个特殊需求: 点赞动作无限次,引导用户疯狂点赞 直播间的所有疯狂点赞,都需要在所有用户界面都 我们先来看效果 ...

  5. Node后端模板代码(附源码)

    前言 之前也写过简单的node服务器代码,但不算做事一个工程,这一次因为也需求,就写了这套后端模板. 从git上找的一些node模板,功能是可以实现,但工作流程理解起来有点困难,可能是小弟我水平有限, ...

  6. python抢购火车票源代码_Python动刷新抢12306火车票的代码(附源码)

    摘要:这篇Python开发技术栏目下的"Python动刷新抢12306火车票的代码(附源码)",介绍的技术点是"12306火车票.Python.12306.附源码.火车票 ...

  7. 疯狂html附源码,科技常识:HTML5 直播疯狂点赞动画实现代码 附源码

    今天小编跟大家讲解下有关HTML5 直播疯狂点赞动画实现代码 附源码 ,相信小伙伴们对这个话题应该有所关注吧,小编也收集到了有关HTML5 直播疯狂点赞动画实现代码 附源码 的相关资料,希望小伙伴们看 ...

  8. 易语言翻译php_易语言PHP代码翻译器源码

    易语言PHP代码翻译器源码 系统结构:取表单变量,取表单变量_,取链接变量,取链接变量_,取服务器变量,取服务器变量_返回,取客户IP地址,取客户IP地址_,输出,会话_置值,会话_取值,会话_取值_ ...

  9. 基于ssh的航空订票系统-飞机订票系统javaweb-机票订购课程设计java代码(源码+数据库文件+文档)

    基于ssh的航空订票系统-飞机订票系统javaweb-机票订购java代码(源码+数据库文件+文档) 注意:该项目只展示部分功能,如需了解,评论区咨询即可. 作者:IT跃迁谷 1.开发环境 开发语言: ...

最新文章

  1. Jmeter之函数助手操作
  2. 设备树与驱动的关系_Linux CommonClock Framework分析之四 gpio clk gate驱动实现
  3. WSFC2008R2跨群集迁移WSFC2012R2
  4. Ubuntu 12.10 安装官方JDK
  5. python kotlin_用Java和Python模仿Kotlin构建器
  6. EventUtil.addHandler方法
  7. COCO数据集提取自己需要的类,转VOC
  8. csdn markdown 的使用 (二)
  9. 厉害了,Apache架构师们遵循的 30 条设计原则
  10. c语言运动员成绩查询,运动会成绩管理系统
  11. 原版windows下载地址
  12. 2022-2028年中国环保减速机行业运行动态及投资机会分析报告
  13. 示波器的阻抗1M欧和50欧、探头
  14. Excel文本如何间隔指定字符插入分隔符
  15. 遥感数据相关资源获取
  16. 分糖果(candy)
  17. vue-element-admin入坑之切换中文版
  18. 科技论文的图题翻译,图例,图坐标轴
  19. mark制图软件_初学者使用Mac绘图软件推荐
  20. springboot项目整合阿里云oss的内容审核

热门文章

  1. 动态规划法(二)——弗洛伊德算法
  2. Primo Ramdisk配置教程
  3. 计算机组成原理课程设计-基本模型机的设计与实现
  4. Word2007/2003常用快捷键
  5. 力扣解法汇总2043-简易银行系统
  6. 没有基础怎么自学好淘宝美工设计?自学有哪些方法?
  7. 张孝祥正在整理Java就业面试题大全20100602版本(一)
  8. python爬虫中文不能正常显示问题的解决
  9. 计算机专业专硕,2018厦门大学计算机专硕考研经验贴(专业课干货!!)
  10. 04.第五章、范围管理