cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
point_to_plane_icp_impl.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <numeric>
8#include <mutex>
9
10namespace toolbox::pcl
11{
12
13namespace detail_p2pl {
14// 辅助函数:创建反对称矩阵 / Helper function: create skew-symmetric matrix
15template<typename DataType>
16inline Eigen::Matrix<DataType, 3, 3> skew_symmetric(const Eigen::Matrix<DataType, 3, 1>& v)
17{
18 Eigen::Matrix<DataType, 3, 3> S;
19 S << 0, -v[2], v[1],
20 v[2], 0, -v[0],
21 -v[1], v[0], 0;
22 return S;
23}
24} // namespace detail_p2pl
25
26template<typename DataType, typename KNNSearcher>
28 const transformation_t& initial_guess, result_type& result)
29{
30 // 检查目标点云是否有法线
31 if (this->m_target_cloud->normals.empty()) {
32 LOG_ERROR_S << "目标点云缺少法线数据,Point-to-Plane ICP需要法线信息 / "
33 "Target cloud missing normals, Point-to-Plane ICP requires normal information";
34 result.termination_reason = "missing normals";
35 return false;
36 }
37
38 if (this->m_target_cloud->normals.size() != this->m_target_cloud->points.size()) {
39 LOG_ERROR_S << "目标点云的法线数量与点数量不匹配 / "
40 "Number of normals doesn't match number of points in target cloud";
41 result.termination_reason = "normal count mismatch";
42 return false;
43 }
44
45 // 初始化
46 transformation_t current_transform = initial_guess;
47 transformation_t previous_transform = current_transform;
48
49 DataType previous_error = std::numeric_limits<DataType>::max();
50 bool converged = false;
51
52 // 创建源点云的副本用于变换
53 point_cloud transformed_source = *this->m_source_cloud;
54
55 // 迭代优化
56 for (std::size_t iter = 0; iter < this->m_max_iterations; ++iter) {
57 // 变换源点云
58 for (std::size_t i = 0; i < transformed_source.size(); ++i) {
59 const auto& p = this->m_source_cloud->points[i];
60 Eigen::Vector4<DataType> pt(p.x, p.y, p.z, 1.0);
61 Eigen::Vector4<DataType> transformed_pt = current_transform * pt;
62 transformed_source.points[i].x = transformed_pt[0];
63 transformed_source.points[i].y = transformed_pt[1];
64 transformed_source.points[i].z = transformed_pt[2];
65 }
66
67 // 查找对应关系
68 std::vector<std::pair<std::size_t, std::size_t>> correspondences;
69 std::vector<DataType> distances;
70 find_correspondences(transformed_source, correspondences, distances);
71
72 if (correspondences.empty()) {
73 LOG_ERROR_S << "未找到有效的对应关系 / No valid correspondences found";
74 result.termination_reason = "no correspondences";
75 return false;
76 }
77
78 // 剔除异常值
79 if (m_outlier_rejection_ratio > 0) {
80 reject_outliers(correspondences, distances);
81 }
82
83 // 计算当前误差
84 DataType current_error = compute_error(transformed_source, *this->m_target_cloud,
85 correspondences, transformation_t::Identity());
86 DataType error_change = std::abs(current_error - previous_error);
87
88 // 记录迭代状态
89 this->record_iteration(result, iter, current_transform, current_error,
90 error_change, correspondences.size());
91
92 // 检查收敛
93 std::string termination_reason;
94 if (this->has_converged(iter, current_transform, previous_transform,
95 current_error, previous_error, termination_reason)) {
96 converged = true;
97 result.termination_reason = termination_reason;
98 break;
99 }
100
101 // 计算新的变换(相对于已变换的源点云)
102 transformation_t delta_transform = compute_transformation(
103 transformed_source, *this->m_target_cloud, correspondences);
104
105 // 更新变换
106 previous_transform = current_transform;
107 current_transform = delta_transform * current_transform;
108 previous_error = current_error;
109 }
110
111 // 设置结果
112 result.transformation = current_transform;
113 result.converged = converged;
114 result.iterations_performed = result.history.size();
115 result.final_error = previous_error;
116
117 if (!converged && result.termination_reason.empty()) {
118 result.termination_reason = "maximum iterations reached";
119 }
120
121 return true;
122}
123
124template<typename DataType, typename KNNSearcher>
126 const point_cloud& transformed_source,
127 std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
128 std::vector<DataType>& distances)
129{
130 correspondences.clear();
131 distances.clear();
132 correspondences.reserve(transformed_source.size());
133 distances.reserve(transformed_source.size());
134
135 // 串行查找对应关系(暂时禁用并行,避免编译错误)
136 std::vector<std::size_t> indices;
137 std::vector<DataType> dists;
138
139 for (std::size_t i = 0; i < transformed_source.size(); ++i) {
140 const auto& point = transformed_source.points[i];
141 m_knn_searcher->kneighbors(point, 1, indices, dists);
142
143 if (!indices.empty() && dists[0] <= this->m_max_correspondence_distance * this->m_max_correspondence_distance) {
144 correspondences.emplace_back(i, indices[0]);
145 distances.push_back(std::sqrt(dists[0]));
146 }
147 }
148}
149
150template<typename DataType, typename KNNSearcher>
153 const point_cloud& source,
154 const point_cloud& target,
155 const std::vector<std::pair<std::size_t, std::size_t>>& correspondences)
156{
157 using Vector3 = Eigen::Matrix<DataType, 3, 1>;
158 using Vector6 = Eigen::Matrix<DataType, 6, 1>;
159 using Matrix6 = Eigen::Matrix<DataType, 6, 6>;
160 using Matrix3x6 = Eigen::Matrix<DataType, 3, 6>;
161
162 // 构建线性系统 A * x = b
163 Matrix6 A = Matrix6::Zero();
164 Vector6 b = Vector6::Zero();
165
166 // 添加正则化项
167 A.diagonal() = Vector6::Constant(m_regularization);
168
169 for (const auto& [src_idx, tgt_idx] : correspondences) {
170 const auto& src_pt = source.points[src_idx];
171 const auto& tgt_pt = target.points[tgt_idx];
172
173 // 获取目标点的法线
174 if (tgt_idx >= this->m_target_cloud->normals.size()) {
175 LOG_ERROR_S << "目标点云缺少法线数据 / Target cloud missing normal data";
176 continue;
177 }
178 const auto& normal_pt = this->m_target_cloud->normals[tgt_idx];
179
180 Vector3 p_src(src_pt.x, src_pt.y, src_pt.z);
181 Vector3 p_tgt(tgt_pt.x, tgt_pt.y, tgt_pt.z);
182 Vector3 n(normal_pt.x, normal_pt.y, normal_pt.z);
183
184 // 确保法线归一化
185 n.normalize();
186
187 // 计算残差:点到平面的距离
188 DataType residual = n.dot(p_src - p_tgt);
189
190 // 构建Jacobian矩阵的一行
191 // J = [n^T, (p_src x n)^T]
192 Matrix3x6 J = Matrix3x6::Zero();
193 J.template block<3, 3>(0, 0) = Eigen::Matrix<DataType, 3, 3>::Identity();
194 J.template block<3, 3>(0, 3) = -detail_p2pl::skew_symmetric(p_src);
195
196 Eigen::Matrix<DataType, 1, 6> J_row = n.transpose() * J;
197
198 // 累加到线性系统
199 A += J_row.transpose() * J_row;
200 b -= J_row.transpose() * residual;
201 }
202
203 // 求解线性系统
204 Vector6 x = A.ldlt().solve(b);
205
206 // 将解转换为变换矩阵
207 return vector_to_transformation(x);
208}
209
210template<typename DataType, typename KNNSearcher>
212 const point_cloud& source,
213 const point_cloud& target,
214 const std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
215 const transformation_t& transform) const
216{
217 if (correspondences.empty()) {
218 return std::numeric_limits<DataType>::max();
219 }
220
221 DataType sum_error = 0;
222
223 for (const auto& [src_idx, tgt_idx] : correspondences) {
224 const auto& src_pt = source.points[src_idx];
225 const auto& tgt_pt = target.points[tgt_idx];
226
227 // 获取目标点的法线
228 if (tgt_idx >= this->m_target_cloud->normals.size()) {
229 LOG_ERROR_S << "目标点云缺少法线数据 / Target cloud missing normal data";
230 continue;
231 }
232 const auto& normal_pt = this->m_target_cloud->normals[tgt_idx];
233
234 // 变换源点
235 Eigen::Vector4<DataType> p_src(src_pt.x, src_pt.y, src_pt.z, 1.0);
236 Eigen::Vector4<DataType> p_src_transformed = transform * p_src;
237
238 Eigen::Vector3<DataType> p_src_3d(p_src_transformed[0], p_src_transformed[1], p_src_transformed[2]);
239 Eigen::Vector3<DataType> p_tgt_3d(tgt_pt.x, tgt_pt.y, tgt_pt.z);
240 Eigen::Vector3<DataType> n(normal_pt.x, normal_pt.y, normal_pt.z);
241
242 // 点到平面的距离
243 DataType distance = std::abs(n.dot(p_src_3d - p_tgt_3d));
244 sum_error += distance * distance;
245 }
246
247 return sum_error / static_cast<DataType>(correspondences.size());
248}
249
250template<typename DataType, typename KNNSearcher>
252 std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
253 std::vector<DataType>& distances)
254{
255 if (correspondences.empty() || m_outlier_rejection_ratio <= 0) {
256 return;
257 }
258
259 // 计算点到平面的距离用于异常值剔除
260 std::vector<DataType> plane_distances;
261 plane_distances.reserve(correspondences.size());
262
263 for (const auto& [src_idx, tgt_idx] : correspondences) {
264 const auto& src_pt = this->m_source_cloud->points[src_idx];
265 const auto& tgt_pt = this->m_target_cloud->points[tgt_idx];
266 // 假设法线存储为额外的点云数据,这里需要用户提供
267 // TODO: 实现更好的法线访问机制
268 normal_type normal;
269 normal.normal_x = 0;
270 normal.normal_y = 0;
271 normal.normal_z = 1;
272 normal.curvature = 0;
273
274 Eigen::Vector3<DataType> p_src(src_pt.x, src_pt.y, src_pt.z);
275 Eigen::Vector3<DataType> p_tgt(tgt_pt.x, tgt_pt.y, tgt_pt.z);
276 Eigen::Vector3<DataType> n(normal.normal_x, normal.normal_y, normal.normal_z);
277
278 DataType distance = std::abs(n.dot(p_src - p_tgt));
279 plane_distances.push_back(distance);
280 }
281
282 // 创建索引数组用于排序
283 std::vector<std::size_t> indices(plane_distances.size());
284 std::iota(indices.begin(), indices.end(), 0);
285
286 // 按距离排序索引
287 std::sort(indices.begin(), indices.end(),
288 [&plane_distances](std::size_t a, std::size_t b) {
289 return plane_distances[a] < plane_distances[b];
290 });
291
292 // 计算保留的对应关系数量
293 std::size_t num_to_keep = static_cast<std::size_t>(
294 correspondences.size() * (1.0 - m_outlier_rejection_ratio));
295 num_to_keep = std::max(num_to_keep, std::size_t(1));
296
297 // 创建新的对应关系和距离
298 std::vector<std::pair<std::size_t, std::size_t>> new_correspondences;
299 std::vector<DataType> new_distances;
300 new_correspondences.reserve(num_to_keep);
301 new_distances.reserve(num_to_keep);
302
303 for (std::size_t i = 0; i < num_to_keep; ++i) {
304 std::size_t idx = indices[i];
305 new_correspondences.push_back(correspondences[idx]);
306 new_distances.push_back(distances[idx]);
307 }
308
309 correspondences = std::move(new_correspondences);
310 distances = std::move(new_distances);
311}
312
313template<typename DataType, typename KNNSearcher>
315 const transformation_t& transform) const
316{
317 Eigen::Matrix<DataType, 6, 1> vec;
318
319 // 提取平移部分
320 vec.template head<3>() = transform.template block<3, 1>(0, 3);
321
322 // 提取旋转部分(使用旋转矩阵的对数映射)
323 Eigen::Matrix<DataType, 3, 3> R = transform.template block<3, 3>(0, 0);
324 DataType trace = R.trace();
325
326 if (trace > 3.0 - 1e-6) {
327 // 接近单位矩阵
328 vec.template tail<3>() = Eigen::Matrix<DataType, 3, 1>::Zero();
329 } else if (trace < -1.0 + 1e-6) {
330 // 接近180度旋转
331 Eigen::Matrix<DataType, 3, 1> axis;
332 int i = 0;
333 if (R(1, 1) > R(0, 0)) i = 1;
334 if (R(2, 2) > R(i, i)) i = 2;
335
336 axis[i] = std::sqrt((R(i, i) - R((i+1)%3, (i+1)%3) - R((i+2)%3, (i+2)%3) + 1.0) / 2.0);
337 axis[(i+1)%3] = R(i, (i+1)%3) / (2.0 * axis[i]);
338 axis[(i+2)%3] = R(i, (i+2)%3) / (2.0 * axis[i]);
339
340 vec.template tail<3>() = M_PI * axis;
341 } else {
342 // 一般情况
343 DataType theta = std::acos((trace - 1.0) / 2.0);
344 DataType factor = theta / (2.0 * std::sin(theta));
345
346 vec[3] = factor * (R(2, 1) - R(1, 2));
347 vec[4] = factor * (R(0, 2) - R(2, 0));
348 vec[5] = factor * (R(1, 0) - R(0, 1));
349 }
350
351 return vec;
352}
353
354template<typename DataType, typename KNNSearcher>
357 const Eigen::Matrix<DataType, 6, 1>& vec) const
358{
359 transformation_t transform = transformation_t::Identity();
360
361 // 设置平移部分
362 transform.template block<3, 1>(0, 3) = vec.template head<3>();
363
364 // 设置旋转部分(使用罗德里格斯公式)
365 Eigen::Matrix<DataType, 3, 1> omega = vec.template tail<3>();
366 DataType theta = omega.norm();
367
368 if (theta < 1e-6) {
369 // 小角度近似
370 transform.template block<3, 3>(0, 0) = Eigen::Matrix<DataType, 3, 3>::Identity() + detail_p2pl::skew_symmetric(omega);
371 } else {
372 // 罗德里格斯公式
373 Eigen::Matrix<DataType, 3, 1> axis = omega / theta;
374 Eigen::Matrix<DataType, 3, 3> K = detail_p2pl::skew_symmetric(axis);
375
376 transform.template block<3, 3>(0, 0) =
377 Eigen::Matrix<DataType, 3, 3>::Identity() +
378 std::sin(theta) * K +
379 (1 - std::cos(theta)) * K * K;
380 }
381
382 return transform;
383}
384
385
386
387} // namespace toolbox::pcl
Eigen::Matrix< DataType, 4, 4 > transformation_t
Definition base_fine_registration.hpp:43
bool align_impl(const transformation_t &initial_guess, result_type &result)
执行配准 / Perform registration
Definition point_to_plane_icp_impl.hpp:27
transformation_t compute_transformation(const point_cloud &source, const point_cloud &target, const std::vector< std::pair< std::size_t, std::size_t > > &correspondences)
计算变换矩阵(使用Gauss-Newton方法) / Compute transformation matrix (using Gauss-Newton method)
Definition point_to_plane_icp_impl.hpp:152
transformation_t vector_to_transformation(const Eigen::Matrix< DataType, 6, 1 > &vec) const
将李代数表示转换为变换矩阵 / Convert Lie algebra representation to transformation matrix
Definition point_to_plane_icp_impl.hpp:356
Eigen::Matrix< DataType, 4, 4 > transformation_t
Definition base_fine_registration.hpp:43
void find_correspondences(const point_cloud &transformed_source, std::vector< std::pair< std::size_t, std::size_t > > &correspondences, std::vector< DataType > &distances)
查找对应关系 / Find correspondences
Definition point_to_plane_icp_impl.hpp:125
DataType compute_error(const point_cloud &source, const point_cloud &target, const std::vector< std::pair< std::size_t, std::size_t > > &correspondences, const transformation_t &transform) const
计算配准误差 / Compute registration error
Definition point_to_plane_icp_impl.hpp:211
Eigen::Matrix< DataType, 6, 1 > transformation_to_vector(const transformation_t &transform) const
将变换矩阵转换为李代数表示 / Convert transformation matrix to Lie algebra representation
Definition point_to_plane_icp_impl.hpp:314
void reject_outliers(std::vector< std::pair< std::size_t, std::size_t > > &correspondences, std::vector< DataType > &distances)
剔除异常值 / Reject outliers
Definition point_to_plane_icp_impl.hpp:251
std::vector< point_t< T > > points
点坐标 / Point coordinates
Definition point.hpp:270
auto size() const -> std::size_t
获取点云中的点数 / Get number of points in cloud
Definition point_impl.hpp:293
#define LOG_ERROR_S
ERROR级别流式日志的宏 / Macro for ERROR level stream logging.
Definition thread_logger.hpp:1332
Eigen::Matrix< DataType, 3, 3 > skew_symmetric(const Eigen::Matrix< DataType, 3, 1 > &v)
Definition point_to_plane_icp_impl.hpp:16
Definition base_correspondence_generator.hpp:18
细配准结果 / Fine registration result
Definition registration_result.hpp:46
std::vector< iteration_state_t< DataType > > history
迭代历史(可选) / Iteration history (optional)
Definition registration_result.hpp:61
std::string termination_reason
终止原因 / Termination reason
Definition registration_result.hpp:58
transformation_t transformation
最终变换 / Final transformation
Definition registration_result.hpp:49
bool converged
是否收敛 / Whether converged
Definition registration_result.hpp:57
DataType final_error
最终误差 / Final error
Definition registration_result.hpp:53
std::size_t iterations_performed
执行的迭代次数 / Iterations performed
Definition registration_result.hpp:55
Definition point_to_plane_icp.hpp:56
DataType normal_y
Definition point_to_plane_icp.hpp:58
DataType normal_x
Definition point_to_plane_icp.hpp:57
DataType normal_z
Definition point_to_plane_icp.hpp:59
DataType curvature
Definition point_to_plane_icp.hpp:60