PCL 法向量夹角剔除错误匹配点对【2024最新版】

264 篇文章 ¥19.90 ¥99.00
本文介绍了PCL库中的CorrespondenceRejectorSurfaceNormal算法,该算法通过比较对应点的法线角度来剔除错误匹配点对。主要内容包括算法简介,主要函数的使用,以及相关参考文献。在实际应用中,该算法可以用于点云配准过程中的错误匹配过滤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
本文由CSDN点云侠原创,原文链接,首发于:2021年03月23日。抄袭狗把自己当个狗加油抄,抄袭狗抄哪一篇进自己的付费专栏,我就哪一篇永久免费开放。

博客长期更新,本文最新更新时间为:2024年10月28日。代码在PCL1.14.1中测试通过

一、 算法简介

 pcl::registration::CorrespondenceRejectorSurfaceNormal::ConstPtr = shared_ptr<const
<think>我们要求实现一个结合法向量夹角约束和加权到平面距离的ICP算法。具体思路如下: 1. 误差函数定义为加权到平面距离:对于每个源p_i,找到目标点云中对应的q_i(注意:这里我们使用到平面的距离,所以实际上需要目标点云中的及其法向量到平面的距离公式为:$d_i = |(R \cdot p_i + t - q_i) \cdot n_i|$,其中n_i是目标点云q_i的法向量。 但这里我们要求加权,因此引入权重w_i,则误差项为:$e_i = w_i \cdot ( (R \cdot p_i + t - q_i) \cdot n_i )$。 2. 同时,我们还要考虑法向量夹角约束:即源点云法向量经过旋转后,与目标点云中对应法向量夹角不能太大。设源p_i的法向量为n_{s_i},目标q_i的法向量为n_{t_i},则约束条件为:$n_{t_i} \cdot (R \cdot n_{s_i}) > \cos(\theta_{\text{max}})$,其中$\theta_{\text{max}}$是最大允许角度。 3. 因此,在寻找对应时,除了距离阈值外,还要检查法向量夹角是否满足条件。 4. 权重w_i的设计:可以基于法向量夹角的相似性来设计权重,例如:$w_i = \exp(-\alpha \cdot (1 - \cos(\theta_i)))$,其中$\theta_i$是当前对的法向量夹角,$\alpha$是控制权重衰减的参数。也可以使用其他权重策略,如距离权重等。 5. 使用Sophus库进行李代数优化:我们将变换矩阵用李代数表示,然后通过高斯-牛顿法或列文伯格-马夸尔特法求解最优变换。 具体步骤: a. 初始化变换矩阵T(单位矩阵) b. 迭代直到收敛: - 对于源点云中的每个,应用当前变换T,然后在目标点云中寻找最近(使用KDTree) - 检查距离阈值和法向量夹角阈值,剔除不满足条件的对 - 计算权重w_i(基于法向量夹角和/或距离) - 构建误差函数,利用所有有效对,形成线性方程组(雅可比矩阵和残差向量) - 使用最小二乘求解增量变换(李代数表示) - 更新变换:T = exp(δξ) * T 6. 在PCL中,我们可以自定义一个变换估计类,继承自pcl::registration::TransformationEstimation,并重写estimateRigidTransformation方法。但是,这里我们想要使用Sophus进行优化,因此需要自己实现整个优化过程。 7. 另一种做法是:使用PCL提供的非线性优化ICP框架(如pcl::Registration)并自定义误差函数。但PCL并没有直接提供这种接口,因此我们选择自己实现迭代过程。 我们将按照以下步骤实现: 步骤1:定义类型(使用pcl::PointNormal,包含位置和法向量) 步骤2:计算源点云和目标点云法向量 步骤3:实现迭代优化过程 由于PCL的ICP类扩展性有限,我们将自行实现整个ICP循环,并使用Sophus进行李代数优化。 下面给出具体代码框架: 注意:我们将使用Eigen和Sophus进行数学运算,使用PCL进行点云操作和KDTree搜索。 代码结构如下: 1. 参数设置:最大距离、最大法向量夹角、最大迭代次数、权重参数α等。 2. 初始化变换(单位矩阵) 3. 迭代: a. 建立对应关系(考虑法向量约束) b. 计算每个对的权重(基于法向量夹角) c. 构建最小二乘问题(每个对对应一个误差项) d. 求解增量变换(使用高斯-牛顿法) e. 更新当前位姿 4. 输出最终变换 具体实现: 由于代码较长,我们分块实现: 首先,包含必要的头文件:</think>我将为您实现一个结合法向量夹角约束和加权到平面距离的ICP配准算法,使用PCL进行点云处理,Sophus库进行李代数优化。以下是完整实现方案: ### 1. 核心算法实现 ```cpp #include <pcl/point_types.h> #include <pcl/features/normal_3d.h> #include <pcl/kdtree/kdtree_flann.h> #include <Sophus/se3.hpp> #include <Eigen/Dense> class WeightedPointToPlaneICP { public: void setParams(float max_dist, float max_normal_angle, float weight_factor, int max_iter) { max_distance_ = max_dist; max_normal_angle_ = max_normal_angle; weight_factor_ = weight_factor; max_iterations_ = max_iter; } void align(pcl::PointCloud<pcl::PointNormal>::Ptr source, pcl::PointCloud<pcl::PointNormal>::Ptr target) { Sophus::SE3f current_pose; // 使用Sophus表示当前位姿 for (int iter = 0; iter < max_iterations_; ++iter) { pcl::KdTreeFLANN<pcl::PointNormal> kdtree; kdtree.setInputCloud(target); // 存储优化变量 Eigen::MatrixXf J(6, 1); Eigen::VectorXf residuals; std::vector<float> weights; int valid_pairs = 0; // 1. 寻找对应对并计算加权误差 for (size_t i = 0; i < source->size(); ++i) { // 应用当前变换 Eigen::Vector3f src_pt = source->points[i].getVector3fMap(); Eigen::Vector3f src_pt_trans = current_pose * src_pt; // 搜索最近邻 std::vector<int> indices(1); std::vector<float> dists(1); pcl::PointNormal search_point; search_point.x = src_pt_trans[0]; search_point.y = src_pt_trans[1]; search_point.z = src_pt_trans[2]; if (kdtree.nearestKSearch(search_point, 1, indices, dists) > 0) { int j = indices[0]; // 检查距离约束 if (dists[0] > max_distance_) continue; // 检查法向量夹角约束 Eigen::Vector3f src_normal = source->points[i].getNormalVector3fMap(); Eigen::Vector3f tgt_normal = target->points[j].getNormalVector3fMap(); // 计算法向量夹角余弦 float cos_angle = src_normal.normalized().dot(tgt_normal.normalized()); float angle = std::acos(cos_angle); if (angle > max_normal_angle_ * M_PI/180.0f) continue; // 计算加权到平面距离 Eigen::Vector3f diff = src_pt_trans - target->points[j].getVector3fMap(); float point_to_plane_dist = std::abs(diff.dot(tgt_normal)); // 计算权重:基于法向量夹角 float weight = std::exp(-weight_factor_ * (1 - cos_angle)); // 构建误差项 Eigen::Matrix<float, 1, 6> Ji; Eigen::Vector3f cross = src_pt_trans.cross(tgt_normal); Ji << tgt_normal.x(), tgt_normal.y(), tgt_normal.z(), cross.x(), cross.y(), cross.z(); // 保存雅可比和残差 J.conservativeResize(6, valid_pairs+1); J.col(valid_pairs) = Ji.transpose(); residuals.conservativeResize(valid_pairs+1); residuals(valid_pairs) = -point_to_plane_dist; weights.push_back(weight); valid_pairs++; } } // 2. 使用加权最小二乘求解位姿增量 if (valid_pairs > 6) { // 构建权重矩阵 Eigen::MatrixXf W = Eigen::MatrixXf::Zero(valid_pairs, valid_pairs); for (int i = 0; i < valid_pairs; ++i) { W(i, i) = weights[i]; } // 求解:J^T W J δξ = -J^T W r Eigen::MatrixXf JT = J.transpose(); Eigen::MatrixXf H = JT * W * J; Eigen::VectorXf b = -JT * W * residuals; // 使用SVD求解 Eigen::VectorXf delta_xi = H.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); // 3. 更新位姿 current_pose = Sophus::SE3f::exp(delta_xi) * current_pose; } // 4. 收敛检查 if (delta_xi.norm() < 1e-6) break; } final_transformation_ = current_pose.matrix(); } Eigen::Matrix4f getFinalTransformation() { return final_transformation_; } private: float max_distance_ = 0.05f; // 最大对应距离 float max_normal_angle_ = 30.0f; // 最大法向量夹角(度) float weight_factor_ = 5.0f; // 权重因子 int max_iterations_ = 100; // 最大迭代次数 Eigen::Matrix4f final_transformation_; }; ``` ### 2. 误差函数解析 算法中的误差函数定义为加权到平面距离之和: $$ E(\mathbf{T}) = \sum_i w_i \cdot | (\mathbf{T} \cdot \mathbf{p}_i - \mathbf{q}_i) \cdot \mathbf{n}_i | $$ 其中: - $\mathbf{T}$ 是位姿变换矩阵(李群表示) - $\mathbf{p}_i$ 是源点云中的 - $\mathbf{q}_i$ 是目标点云中的对应 - $\mathbf{n}_i$ 是目标点云中对应法向量 - $w_i$ 是基于法向量夹角的权重: $$ w_i = \exp(-\beta(1 - \cos\theta_i)) $$ $\theta_i$ 是源和目标法向量之间的夹角,$\beta$ 是权重因子 ### 3. 完整使用流程 ```cpp int main() { // 1. 加载点云数据 pcl::PointCloud<pcl::PointXYZ>::Ptr source_cloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr target_cloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::io::loadPCDFile("source.pcd", *source_cloud); pcl::io::loadPCDFile("target.pcd", *target_cloud); // 2. 计算法向量 pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne; pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>()); ne.setSearchMethod(tree); ne.setKSearch(20); pcl::PointCloud<pcl::Normal>::Ptr source_normals(new pcl::PointCloud<pcl::Normal>); ne.setInputCloud(source_cloud); ne.compute(*source_normals); pcl::PointCloud<pcl::Normal>::Ptr target_normals(new pcl::PointCloud<pcl::Normal>); ne.setInputCloud(target_cloud); ne.compute(*target_normals); // 3. 合并法向量 pcl::PointCloud<pcl::PointNormal>::Ptr source(new pcl::PointCloud<pcl::PointNormal>); pcl::PointCloud<pcl::PointNormal>::Ptr target(new pcl::PointCloud<pcl::PointNormal>); pcl::concatenateFields(*source_cloud, *source_normals, *source); pcl::concatenateFields(*target_cloud, *target_normals, *target); // 4. 执行配准 WeightedPointToPlaneICP icp; icp.setParams(0.05f, 30.0f, 5.0f, 100); // 距离阈值0.05m,法向量夹角30度,权重因子5.0 icp.align(source, target); // 5. 应用最终变换 pcl::transformPointCloud(*source_cloud, *source_cloud, icp.getFinalTransformation()); // 6. 保存结果 pcl::io::savePCDFile("result.pcd", *source_cloud); return 0; } ``` ### 4. 算法关键说明 1. **李代数优化**: ```cpp // 位姿增量求解 Eigen::VectorXf delta_xi = H.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); // 更新位姿 current_pose = Sophus::SE3f::exp(delta_xi) * current_pose; ``` 2. **加权到平面距离计算**: ```cpp float point_to_plane_dist = std::abs(diff.dot(tgt_normal)); float weight = std::exp(-weight_factor_ * (1 - cos_angle)); ``` 3. **雅可比矩阵构造**: ```cpp Eigen::Matrix<float, 1, 6> Ji; Eigen::Vector3f cross = src_pt_trans.cross(tgt_normal); Ji << tgt_normal.x(), tgt_normal.y(), tgt_normal.z(), cross.x(), cross.y(), cross.z(); ``` ### 5. 参数调优建议 1. **权重因子选择**: - 小角度差异:权重因子 $\beta = 2.0 \sim 5.0$ - 大角度差异:权重因子 $\beta = 0.5 \sim 2.0$ - 可通过网格搜索确定最优值: ```python # Python示例 for beta in np.linspace(0.1, 10.0, 20): icp.set_weight_factor(beta) error = icp.evaluate() ``` 2. **法向量计算优化**: ```cpp // 使用积分图像加速法向量计算 pcl::IntegralImageNormalEstimation<pcl::PointXYZ, pcl::Normal> ne; ne.setNormalEstimationMethod(ne.AVERAGE_3D_GRADIENT); ``` 3. **多尺度配准策略**: ```cpp // 粗配准 icp.setParams(0.10f, 45.0f, 2.0f, 30); icp.align(source_downsampled, target_downsampled); // 精配准 icp.setParams(0.02f, 20.0f, 5.0f, 50); icp.align(source, target); ``` ### 6. 性能评估方法 ```cpp // 计算配准后的均方根误差 float computeRMSE(pcl::PointCloud<pcl::PointNormal>::Ptr source, pcl::PointCloud<pcl::PointNormal>::Ptr target, const Eigen::Matrix4f& transformation) { pcl::KdTreeFLANN<pcl::PointNormal> kdtree; kdtree.setInputCloud(target); float sum_squared_error = 0.0f; int valid_points = 0; for (size_t i = 0; i < source->size(); ++i) { Eigen::Vector3f pt = source->points[i].getVector3fMap(); Eigen::Vector3f pt_trans = transformation.block<3,3>(0,0) * pt + transformation.block<3,1>(0,3); // 搜索最近邻 std::vector<int> indices(1); std::vector<float> dists(1); pcl::PointNormal search_point; search_point.x = pt_trans[0]; search_point.y = pt_trans[1]; search_point.z = pt_trans[2]; if (kdtree.nearestKSearch(search_point, 1, indices, dists) > 0) { Eigen::Vector3f normal = target->points[indices[0]].getNormalVector3fMap(); Eigen::Vector3f diff = pt_trans - target->points[indices[0]].getVector3fMap(); float error = diff.dot(normal); sum_squared_error += error * error; valid_points++; } } return std::sqrt(sum_squared_error / valid_points); } ``` 本实现结合了PCL点云处理能力和Sophus的李代数优化,通过法向量夹角约束和加权到平面距离,显著提高了配准精度。实际应用中可根据具体场景调整参数,结合点云预处理技术获得最佳效果。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点云侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值