4、点云特征提取:法向量估计、FPFH特征描述子、SHOT特征描述子、ISS关键点提取

好,咱们进入正题。点云配准这事儿,说白了就是让两片点云“对齐”。但怎么对齐?你得先找到点云里那些有代表性的“特征”。就像你认人,得先看脸、看轮廓、看标志性的痣。点云特征提取,就是干这个的。

我个人习惯把特征提取分成两步:关键点检测特征描述。关键点就是那些“显眼包”,比如墙角、桌角、雕塑的鼻子尖。特征描述呢,就是把关键点周围的局部形状,编码成一个数学向量,方便计算机去匹配。

今天咱们就掰开揉碎,聊聊这四个核心内容:法向量估计、FPFH、SHOT和ISS。嗯,这里要注意,顺序是有讲究的——法向量是很多特征描述子的基础,所以咱们先搞定它。

核心逻辑:法向量 → 局部坐标系 → 特征描述子(FPFH/SHOT) + 关键点检测(ISS) → 配准

原始点云 法向量估计 FPFH 特征描述 SHOT 特征描述 ISS 关键点提取 配准输入

4.1 法向量估计:点云的“皮肤触感”

法向量是什么?简单说,就是点云表面某一点处的“朝向”。你摸一个球,手指感受到的方向就是法向量方向。在3D视觉里,法向量是后续一切特征的基础。

估计法向量的方法很多,最经典的是PCA(主成分分析)。思路很简单:对目标点周围的邻域点集做PCA,最小特征值对应的特征向量,就是法向量方向。

我在项目中遇到过一个问题:ToF传感器在物体边缘测得的点云,噪声特别大。这时候法向量估计就很容易跑偏。我的经验是,先做一遍体素滤波降噪,再算法向量,效果会好很多。

小技巧:PCL里用 pcl::NormalEstimation 类,核心参数就一个——搜索半径。半径太小,法向量对噪声敏感;半径太大,会丢失细节。我一般从0.02米开始试,根据点云密度调整。

// PCL 法向量估计示例
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud(cloud);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
ne.setSearchMethod(tree);
ne.setRadiusSearch(0.03);  // 搜索半径 3cm
ne.compute(*cloud_normals);

4.2 FPFH 特征描述子:快速又实用的“指纹”

FPFH,全称是快速点特征直方图。你想想看,它为什么叫“快速”?因为它是对早期PFH的简化。PFH计算量太大,实时性差。FPFH在保证描述能力的同时,把计算复杂度从O(nk²)降到了O(nk)。

FPFH的核心思想是:统计查询点与其邻域点之间的角度关系。具体来说,它计算三个角度特征(α, φ, θ),然后统计成直方图。最终得到一个33维的特征向量。

我记得有一次做室内场景拼接,点云数据量很大。用SHOT跑不动,换成FPFH,速度直接快了5倍,配准精度也没差多少。所以,如果你的场景对实时性有要求,FPFH是首选。

避坑指南:我曾经在低分辨率点云上直接用FPFH,结果匹配一塌糊涂。后来发现,FPFH对点云密度敏感。建议先做均匀采样,让点云密度大致均匀,再提取FPFH特征。

// PCL FPFH 特征提取
pcl::FPFHEstimation<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fpfh;
fpfh.setInputCloud(cloud);
fpfh.setInputNormals(normals);
fpfh.setSearchMethod(tree);
fpfh.setRadiusSearch(0.05);  // 搜索半径 5cm
fpfh.compute(*fpfh_features);

4.3 SHOT 特征描述子:稳健但“重”的选手

SHOT,全称是Signature of Histograms of OrienTations。名字很长,但思路很清晰:它先建立一个局部参考坐标系(LRF),然后把邻域空间划分成多个子空间,在每个子空间里统计法向量角度分布。

SHOT的维度是352维(32个空间区域 × 11个角度区间)。相比FPFH的33维,它“重”得多。但好处是,它对噪声和遮挡更鲁棒。我在做工业零件抓取时,零件表面反光严重,ToF数据噪声大。FPFH匹配经常失败,换成SHOT,成功率从60%提升到了90%。

为什么会这样?因为SHOT的局部坐标系提供了旋转不变性,而FPFH是近似旋转不变的。说白了,SHOT更“较真”,对几何细节的描述更精细。

经验之谈:如果你的点云质量好、密度高,用FPFH就够了。如果点云噪声大、有遮挡,或者需要高精度配准,咬咬牙上SHOT。计算慢一点,但结果靠谱。

// PCL SHOT 特征提取
pcl::SHOTEstimation<pcl::PointXYZ, pcl::Normal, pcl::SHOT352> shot;
shot.setInputCloud(cloud);
shot.setInputNormals(normals);
shot.setSearchMethod(tree);
shot.setRadiusSearch(0.08);  // SHOT 通常需要更大的搜索半径
shot.compute(*shot_features);

4.4 ISS 关键点提取:找“显眼包”的利器

ISS,全称是Intrinsic Shape Signatures。它干的事很简单:在点云里找出那些“与众不同”的点。怎么判断“与众不同”?看这个点邻域的特征值分布

具体来说,ISS对每个点做PCA,得到三个特征值 λ1 ≥ λ2 ≥ λ3。如果 λ2/λ1 和 λ3/λ2 都小于某个阈值,说明这个点邻域的几何变化剧烈,是个关键点。比如桌角、墙角、雕塑的凸起部位。

我个人习惯在配准前先用ISS提取关键点,再在这些关键点上计算FPFH或SHOT。这样既减少了计算量,又提高了匹配的鲁棒性。你想想看,如果全点云都算特征,很多平坦区域的特征是相似的,容易误匹配。

参数调优:ISS有两个关键参数:salient_radius(显著性半径)和non_max_radius(非极大值抑制半径)。前者决定“看多远”,后者决定关键点的密度。我一般设 salient_radius = 2 × non_max_radius。

// PCL ISS 关键点提取
pcl::ISSKeypoint3D<pcl::PointXYZ, pcl::PointXYZ> iss;
iss.setInputCloud(cloud);
iss.setSalientRadius(0.06f);    // 显著性半径 6cm
iss.setNonMaxRadius(0.03f);     // 非极大值抑制半径 3cm
iss.setThreshold21(0.975f);     // λ2/λ1 阈值
iss.setThreshold32(0.975f);     // λ3/λ2 阈值
iss.compute(*keypoints);

4.5 四种方法的对比与选择

说了这么多,到底该用哪个?我整理了一个表格,方便你对照选择。

方法 类型 维度 计算速度 鲁棒性 适用场景
法向量估计 基础特征 3 所有点云处理的预处理步骤
FPFH 特征描述子 33 实时配准、大场景、低噪声
SHOT 特征描述子 352 高精度配准、噪声大、有遮挡
ISS 关键点检测 减少计算量、提高匹配精度

嗯,总结一下我的个人经验:先ISS提取关键点,再在关键点上计算FPFH或SHOT。如果追求速度,用FPFH;如果追求精度,用SHOT。法向量估计是必选项,所有特征描述子都依赖它。

最后提醒一句:ToF点云有个特点——边缘噪声大、中心精度高。做特征提取时,可以适当裁剪边缘区域,或者对不同区域设置不同的搜索半径。我在做项目时吃过这个亏,后来加了边缘检测,效果立竿见影。


专注资料整理