3、点云特征提取:法向量估计、FPFH特征描述子、ISS关键点提取、SHOT描述子
点云特征提取,说白了就是给激光雷达的数据做「降维打击」。你想想看,一帧点云动辄十几万个点,直接拿去匹配,计算量大到离谱。所以我们要提取出那些最能代表点云结构的特征——就像你描述一个人,不会去数他有多少根头发,而是看脸型、五官这些关键信息。
我个人习惯把特征提取分成两步走:关键点检测和特征描述。关键点就是那些「特别」的点,比如墙角、边缘;特征描述则是给这些关键点算一个「身份证号」,让匹配算法能认出它们。今天我们就来聊聊这四个核心工具:法向量、FPFH、ISS、SHOT。
核心思路:先算法向量(基础),再找关键点(ISS),最后算描述子(FPFH或SHOT)。这套流程我在多个项目中验证过,稳定可靠。
3.1 法向量估计:点云的「皮肤」
法向量是什么?就是点云表面每个点的朝向。比如地面点的法向量朝上,墙面点的法向量朝水平方向。没有法向量,后面的特征描述子根本没法算。
估计方法其实很直观——用PCA(主成分分析)。对每个点,找它周围的K个近邻点,然后算这些点的协方差矩阵。最小特征值对应的特征向量,就是法向量方向。
// 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.setKSearch(20); // K近邻搜索,我一般设15-30
ne.compute(*normals);
我的经验:K值的选择很关键。K太小,法向量对噪声敏感;K太大,会模糊掉边缘特征。在城区场景,我通常设20;在隧道或室内,会降到10-15。
嗯,这里要注意一点:法向量有方向歧义。同一个平面,法向量可能朝上也可能朝下。PCL默认会做方向一致性调整,但如果你发现法向量忽上忽下,记得检查一下视点方向设置。
3.2 FPFH特征描述子:快速又靠谱
FPFH全称是Fast Point Feature Histogram,快速点特征直方图。它是PFH的加速版,计算量从O(nk²)降到了O(nk)。
FPFH的核心思想是:对每个点,统计它和邻域点之间的角度关系。具体来说,它会算三个角度:
- 法向量之间的夹角
- 法向量与点对连线之间的夹角
- 点对连线与法向量投影之间的夹角
然后把这些角度统计成33维的直方图。为什么是33维?因为每个角度分11个bin,3个角度就是33维。
// FPFH特征计算
pcl::FPFHEstimation<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fpfh;
fpfh.setInputCloud(cloud);
fpfh.setInputNormals(normals);
fpfh.setSearchMethod(tree);
fpfh.setRadiusSearch(0.5); // 搜索半径,单位米
fpfh.compute(*fpfh_features);
避坑指南:我曾经在高速场景用过FPFH,发现特征区分度不够。后来排查发现,是因为搜索半径设得太小(0.2米),导致邻域点太少。对于稀疏点云,建议把半径放大到0.5-1.0米。
3.3 ISS关键点提取:找「最特别」的点
ISS(Intrinsic Shape Signatures)是一种基于特征值分析的关键点检测方法。它的逻辑很简单:如果一个点周围的点云在各个方向上的分布差异很大,那它很可能是个关键点。
具体做法是:对每个点算协方差矩阵的三个特征值λ1≥λ2≥λ3。然后看两个比值:
- λ2/λ1:越小说明点云越像一条线(边缘点)
- λ3/λ2:越小说明点云越像一个面(平面点)
ISS会保留那些λ2/λ1和λ3/λ2都小于某个阈值的点——这些点就是所谓的「角点」或「显著点」。
// ISS关键点提取
pcl::ISSKeypoint3D<pcl::PointXYZ, pcl::PointXYZ> iss;
iss.setInputCloud(cloud);
iss.setSearchMethod(tree);
iss.setSalientRadius(0.6f); // 显著半径
iss.setNonMaxRadius(0.4f); // 非极大值抑制半径
iss.setThreshold21(0.975f); // λ2/λ1阈值
iss.setThreshold32(0.975f); // λ3/λ2阈值
iss.compute(*keypoints);
我的建议:ISS的参数调优是个体力活。我一般先设SalientRadius为0.6米,NonMaxRadius为0.4米,然后看提取的关键点数量。如果太多(超过点云总数的5%),就增大阈值;太少(低于1%),就减小阈值。
3.4 SHOT描述子:更鲁棒的选择
SHOT(Signature of Histograms of OrienTations)是另一种特征描述子,比FPFH更鲁棒,但计算量也更大。它把关键点周围的球体空间分成32个区域(8个方位角 × 2个仰角 × 2个半径),然后在每个区域内统计法向量方向的直方图。
SHOT的维度是352维(32个区域 × 11个bin)。为什么这么高?因为每个区域都要统计法向量与参考方向之间的夹角,而且还要考虑法向量与径向方向的夹角。
// SHOT特征计算
pcl::SHOTEstimation<pcl::PointXYZ, pcl::Normal, pcl::SHOT352> shot;
shot.setInputCloud(keypoints); // 注意:输入是关键点
shot.setInputNormals(normals);
shot.setSearchMethod(tree);
shot.setRadiusSearch(0.8f); // SHOT的搜索半径通常比FPFH大
shot.compute(*shot_features);
对比总结:
| 特征 | 维度 | 计算速度 | 鲁棒性 | 适用场景 |
|---|---|---|---|---|
| FPFH | 33 | 快 | 中等 | 实时性要求高的场景 |
| SHOT | 352 | 慢 | 高 | 精度要求高的场景 |
我个人在实际项目中,如果做实时匹配(比如10Hz的激光雷达),会用FPFH;如果是离线建图或者回环检测,会用SHOT。说白了就是速度和精度的trade-off。
3.5 本章知识体系
下面这张图展示了本章的核心逻辑:从原始点云到特征提取的完整流程。
从图中可以看到,法向量估计是基础,它同时服务于ISS关键点提取和FPFH/SHOT特征描述。ISS负责筛选出「值得描述」的点,而FPFH和SHOT则负责给这些点生成特征向量。这两条路径最终汇合,输出可用于匹配的特征数据。
实战建议:如果你刚开始接触点云特征提取,我建议先跑通FPFH的流程,因为它快、参数少、容易调试。等对特征匹配有了直观感受,再尝试SHOT和ISS的组合。别一上来就搞最复杂的,容易劝退。
好了,关于点云特征提取的核心内容就这些。法向量是基础,ISS帮你找关键点,FPFH和SHOT帮你算描述子。记住:没有最好的特征,只有最合适的特征。根据你的场景和性能要求,灵活选择就好。