cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
generalized_icp_impl.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <numeric>
8#include <deque>
9#include <mutex>
10
11namespace toolbox::pcl
12{
13
14namespace detail {
15// 辅助函数:创建反对称矩阵 / Helper function: create skew-symmetric matrix
16template<typename DataType>
17inline Eigen::Matrix<DataType, 3, 3> skew_symmetric(const Eigen::Matrix<DataType, 3, 1>& v)
18{
19 Eigen::Matrix<DataType, 3, 3> S;
20 S << 0, -v[2], v[1],
21 v[2], 0, -v[0],
22 -v[1], v[0], 0;
23 return S;
24}
25} // namespace detail
26
27template<typename DataType, typename KNNSearcher>
29{
30 // 构建源点云和目标点云的KD树
31 if (this->m_source_cloud) {
32 LOG_INFO_S << "构建源点云KD树 / Building source cloud KD-tree";
33 m_source_knn_searcher->set_input(*this->m_source_cloud);
34
35 LOG_INFO_S << "计算源点云协方差矩阵 / Computing source cloud covariances";
36 compute_covariances(*this->m_source_cloud, *m_source_knn_searcher, m_source_covariances);
37 }
38
39 if (this->m_target_cloud) {
40 LOG_INFO_S << "构建目标点云KD树 / Building target cloud KD-tree";
41 m_target_knn_searcher->set_input(*this->m_target_cloud);
42
43 LOG_INFO_S << "计算目标点云协方差矩阵 / Computing target cloud covariances";
44 compute_covariances(*this->m_target_cloud, *m_target_knn_searcher, m_target_covariances);
45 }
46}
47
48template<typename DataType, typename KNNSearcher>
50 const transformation_t& initial_guess, result_type& result)
51{
52 // 初始化
53 transformation_t current_transform = initial_guess;
54 transformation_t previous_transform = current_transform;
55
56 DataType previous_error = std::numeric_limits<DataType>::max();
57 bool converged = false;
58
59 // 创建源点云的副本用于变换
60 point_cloud transformed_source = *this->m_source_cloud;
61
62 // 迭代优化
63 for (std::size_t iter = 0; iter < this->m_max_iterations; ++iter) {
64 // 变换源点云
65 for (std::size_t i = 0; i < transformed_source.size(); ++i) {
66 const auto& p = this->m_source_cloud->points[i];
67 Eigen::Vector4<DataType> pt(p.x, p.y, p.z, 1.0);
68 Eigen::Vector4<DataType> transformed_pt = current_transform * pt;
69 transformed_source.points[i].x = transformed_pt[0];
70 transformed_source.points[i].y = transformed_pt[1];
71 transformed_source.points[i].z = transformed_pt[2];
72 }
73
74 // 查找对应关系
75 std::vector<std::pair<std::size_t, std::size_t>> correspondences;
76 std::vector<DataType> distances;
77 find_correspondences(transformed_source, correspondences, distances);
78
79 if (correspondences.empty()) {
80 LOG_ERROR_S << "未找到有效的对应关系 / No valid correspondences found";
81 result.termination_reason = "no correspondences";
82 return false;
83 }
84
85 // 剔除异常值
86 if (m_outlier_rejection_ratio > 0) {
87 reject_outliers(correspondences, distances);
88 }
89
90 // 计算当前误差
91 DataType current_error = compute_error(*this->m_source_cloud, *this->m_target_cloud,
92 correspondences, current_transform);
93 DataType error_change = std::abs(current_error - previous_error);
94
95 // 记录迭代状态
96 this->record_iteration(result, iter, current_transform, current_error,
97 error_change, correspondences.size());
98
99 // 检查收敛
100 std::string termination_reason;
101 if (this->has_converged(iter, current_transform, previous_transform,
102 current_error, previous_error, termination_reason)) {
103 converged = true;
104 result.termination_reason = termination_reason;
105 break;
106 }
107
108 // 计算新的变换
109 transformation_t new_transform = compute_transformation(
110 *this->m_source_cloud, *this->m_target_cloud, correspondences, current_transform);
111
112 // 更新变换
113 previous_transform = current_transform;
114 current_transform = new_transform;
115 previous_error = current_error;
116 }
117
118 // 设置结果
119 result.transformation = current_transform;
120 result.converged = converged;
121 result.iterations_performed = result.history.size();
122 result.final_error = previous_error;
123
124 if (!converged && result.termination_reason.empty()) {
125 result.termination_reason = "maximum iterations reached";
126 }
127
128 return true;
129}
130
131template<typename DataType, typename KNNSearcher>
133 const point_cloud& cloud,
134 knn_searcher_type& searcher,
135 std::vector<Matrix3>& covariances)
136{
137 covariances.resize(cloud.size());
138
139 // 正则化矩阵
140 Matrix3 regularization = Matrix3::Identity();
141 regularization(0, 0) = m_covariance_epsilon;
142 regularization(1, 1) = 1.0;
143 regularization(2, 2) = 1.0;
144
145 // 串行计算协方差(暂时禁用并行,避免编译错误)
146 for (std::size_t i = 0; i < cloud.size(); ++i) {
147 std::vector<std::size_t> indices;
148 std::vector<DataType> distances;
149
150 // 查找k个最近邻
151 searcher.kneighbors(cloud.points[i], m_k_correspondences + 1, indices, distances);
152
153 if (indices.size() <= 1) {
154 // 如果没有足够的邻居,使用单位矩阵
155 covariances[i] = regularization;
156 continue;
157 }
158
159 // 计算邻域点的质心
160 Vector3 centroid = Vector3::Zero();
161 for (std::size_t j = 1; j < indices.size(); ++j) { // 跳过自己
162 const auto& p = cloud.points[indices[j]];
163 centroid += Vector3(p.x, p.y, p.z);
164 }
165 centroid /= static_cast<DataType>(indices.size() - 1);
166
167 // 计算协方差矩阵
168 Matrix3 cov = Matrix3::Zero();
169 for (std::size_t j = 1; j < indices.size(); ++j) {
170 const auto& p = cloud.points[indices[j]];
171 Vector3 diff(p.x - centroid[0], p.y - centroid[1], p.z - centroid[2]);
172 cov += diff * diff.transpose();
173 }
174 cov /= static_cast<DataType>(indices.size() - 1);
175
176 // 应用正则化
177 covariances[i] = cov + regularization;
178 }
179}
180
181template<typename DataType, typename KNNSearcher>
183 const point_cloud& transformed_source,
184 std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
185 std::vector<DataType>& distances)
186{
187 correspondences.clear();
188 distances.clear();
189 correspondences.reserve(transformed_source.size());
190 distances.reserve(transformed_source.size());
191
192 // 串行查找对应关系(暂时禁用并行,避免编译错误)
193 std::vector<std::size_t> indices;
194 std::vector<DataType> dists;
195
196 for (std::size_t i = 0; i < transformed_source.size(); ++i) {
197 const auto& point = transformed_source.points[i];
198 m_target_knn_searcher->kneighbors(point, 1, indices, dists);
199
200 if (!indices.empty() && dists[0] <= this->m_max_correspondence_distance * this->m_max_correspondence_distance) {
201 correspondences.emplace_back(i, indices[0]);
202 distances.push_back(std::sqrt(dists[0]));
203 }
204 }
205}
206
207template<typename DataType, typename KNNSearcher>
210 const point_cloud& source,
211 const point_cloud& target,
212 const std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
213 const transformation_t& current_transform)
214{
215 // 使用L-BFGS优化求解变换
216 Vector6 x0 = Vector6::Zero(); // 从零开始优化增量
217 Vector6 x_opt = lbfgs_optimize(x0, source, target, correspondences, current_transform);
218
219 // 将优化结果转换为变换矩阵
220 transformation_t delta_transform = vector_to_transformation(x_opt);
221 return delta_transform * current_transform;
222}
223
224template<typename DataType, typename KNNSearcher>
226 const Vector3& p_src,
227 const Vector3& p_tgt,
228 const Matrix3& C_src,
229 const Matrix3& C_tgt,
230 const transformation_t& transform,
231 Vector6* gradient) const
232{
233 // 变换源点
234 Eigen::Vector4<DataType> p_src_h(p_src[0], p_src[1], p_src[2], 1.0);
235 Eigen::Vector4<DataType> p_src_transformed_h = transform * p_src_h;
236 Vector3 p_src_transformed = p_src_transformed_h.template head<3>();
237
238 // 变换源点的协方差
239 Matrix3 R = transform.template block<3, 3>(0, 0);
240 Matrix3 C_src_transformed = R * C_src * R.transpose();
241
242 // 计算组合协方差的逆
243 Matrix3 C_combined = C_src_transformed + C_tgt;
244 Matrix3 C_inv = C_combined.inverse();
245
246 // 计算残差
247 Vector3 residual = p_src_transformed - p_tgt;
248
249 // 计算Mahalanobis距离
250 DataType distance = residual.transpose() * C_inv * residual;
251
252 // 如果需要计算梯度
253 if (gradient) {
254 // 计算对平移的梯度
255 gradient->template head<3>() = 2.0 * C_inv * residual;
256
257 // 计算对旋转的梯度(使用李代数)
258 Matrix3 dR_dx = -detail::skew_symmetric(p_src_transformed);
259 gradient->template tail<3>() = 2.0 * dR_dx.transpose() * C_inv * residual;
260 }
261
262 return distance;
263}
264
265template<typename DataType, typename KNNSearcher>
267 const point_cloud& source,
268 const point_cloud& target,
269 const std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
270 const transformation_t& transform) const
271{
272 if (correspondences.empty()) {
273 return std::numeric_limits<DataType>::max();
274 }
275
276 DataType sum_error = 0;
277
278 for (const auto& [src_idx, tgt_idx] : correspondences) {
279 const auto& p_src = source.points[src_idx];
280 const auto& p_tgt = target.points[tgt_idx];
281
282 Vector3 src_vec(p_src.x, p_src.y, p_src.z);
283 Vector3 tgt_vec(p_tgt.x, p_tgt.y, p_tgt.z);
284
285 DataType distance = compute_mahalanobis_distance(
286 src_vec, tgt_vec, m_source_covariances[src_idx],
287 m_target_covariances[tgt_idx], transform);
288
289 sum_error += distance;
290 }
291
292 return sum_error / static_cast<DataType>(correspondences.size());
293}
294
295template<typename DataType, typename KNNSearcher>
297 std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
298 std::vector<DataType>& distances)
299{
300 if (correspondences.empty() || m_outlier_rejection_ratio <= 0) {
301 return;
302 }
303
304 // 计算Mahalanobis距离用于异常值剔除
305 std::vector<DataType> mahalanobis_distances;
306 mahalanobis_distances.reserve(correspondences.size());
307
308 transformation_t identity = transformation_t::Identity();
309
310 for (const auto& [src_idx, tgt_idx] : correspondences) {
311 const auto& p_src = this->m_source_cloud->points[src_idx];
312 const auto& p_tgt = this->m_target_cloud->points[tgt_idx];
313
314 Vector3 src_vec(p_src.x, p_src.y, p_src.z);
315 Vector3 tgt_vec(p_tgt.x, p_tgt.y, p_tgt.z);
316
317 DataType distance = compute_mahalanobis_distance(
318 src_vec, tgt_vec, m_source_covariances[src_idx],
319 m_target_covariances[tgt_idx], identity);
320
321 mahalanobis_distances.push_back(distance);
322 }
323
324 // 创建索引数组用于排序
325 std::vector<std::size_t> indices(mahalanobis_distances.size());
326 std::iota(indices.begin(), indices.end(), 0);
327
328 // 按距离排序索引
329 std::sort(indices.begin(), indices.end(),
330 [&mahalanobis_distances](std::size_t a, std::size_t b) {
331 return mahalanobis_distances[a] < mahalanobis_distances[b];
332 });
333
334 // 计算保留的对应关系数量
335 std::size_t num_to_keep = static_cast<std::size_t>(
336 correspondences.size() * (1.0 - m_outlier_rejection_ratio));
337 num_to_keep = std::max(num_to_keep, std::size_t(1));
338
339 // 创建新的对应关系和距离
340 std::vector<std::pair<std::size_t, std::size_t>> new_correspondences;
341 std::vector<DataType> new_distances;
342 new_correspondences.reserve(num_to_keep);
343 new_distances.reserve(num_to_keep);
344
345 for (std::size_t i = 0; i < num_to_keep; ++i) {
346 std::size_t idx = indices[i];
347 new_correspondences.push_back(correspondences[idx]);
348 new_distances.push_back(distances[idx]);
349 }
350
351 correspondences = std::move(new_correspondences);
352 distances = std::move(new_distances);
353}
354
355template<typename DataType, typename KNNSearcher>
357 const Vector6& x,
358 const point_cloud& source,
359 const point_cloud& target,
360 const std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
361 const transformation_t& base_transform,
362 Vector6* gradient) const
363{
364 // 将优化向量转换为变换矩阵
365 transformation_t delta_transform = vector_to_transformation(x);
366 transformation_t transform = delta_transform * base_transform;
367
368 DataType total_cost = 0;
369 Vector6 total_gradient = Vector6::Zero();
370
371 for (const auto& [src_idx, tgt_idx] : correspondences) {
372 const auto& p_src = source.points[src_idx];
373 const auto& p_tgt = target.points[tgt_idx];
374
375 Vector3 src_vec(p_src.x, p_src.y, p_src.z);
376 Vector3 tgt_vec(p_tgt.x, p_tgt.y, p_tgt.z);
377
378 Vector6 point_gradient;
379 DataType distance = compute_mahalanobis_distance(
380 src_vec, tgt_vec, m_source_covariances[src_idx],
381 m_target_covariances[tgt_idx], transform,
382 gradient ? &point_gradient : nullptr);
383
384 total_cost += distance;
385 if (gradient) {
386 total_gradient += point_gradient;
387 }
388 }
389
390 if (gradient) {
391 *gradient = total_gradient / static_cast<DataType>(correspondences.size());
392 }
393
394 return total_cost / static_cast<DataType>(correspondences.size());
395}
396
397template<typename DataType, typename KNNSearcher>
400 const Vector6& x0,
401 const point_cloud& source,
402 const point_cloud& target,
403 const std::vector<std::pair<std::size_t, std::size_t>>& correspondences,
404 const transformation_t& base_transform)
405{
406 // 简化的L-BFGS实现
407 const std::size_t m = 5; // 历史记录数量
408 const DataType alpha_init = 1.0;
409 const DataType c1 = 1e-4; // Wolfe条件参数
410 const DataType c2 = 0.9;
411
412 Vector6 x = x0;
413 Vector6 g;
414 DataType f = objective_function(x, source, target, correspondences, base_transform, &g);
415
416 std::deque<Vector6> s_history;
417 std::deque<Vector6> y_history;
418 std::deque<DataType> rho_history;
419
420 for (std::size_t iter = 0; iter < m_optimizer_max_iterations; ++iter) {
421 // 计算搜索方向(L-BFGS two-loop recursion)
422 Vector6 q = g;
423 std::vector<DataType> alphas(s_history.size());
424
425 // 第一个循环
426 for (int i = static_cast<int>(s_history.size()) - 1; i >= 0; --i) {
427 alphas[i] = rho_history[i] * s_history[i].dot(q);
428 q -= alphas[i] * y_history[i];
429 }
430
431 // 初始Hessian近似
432 Vector6 r = q;
433 if (!s_history.empty()) {
434 DataType gamma = s_history.back().dot(y_history.back()) /
435 y_history.back().dot(y_history.back());
436 r *= gamma;
437 }
438
439 // 第二个循环
440 for (std::size_t i = 0; i < s_history.size(); ++i) {
441 DataType beta = rho_history[i] * y_history[i].dot(r);
442 r += s_history[i] * (alphas[i] - beta);
443 }
444
445 Vector6 p = -r; // 搜索方向
446
447 // 线搜索
448 DataType alpha = alpha_init;
449 Vector6 x_new = x + alpha * p;
450 Vector6 g_new;
451 DataType f_new = objective_function(x_new, source, target, correspondences, base_transform, &g_new);
452
453 // 简单的回溯线搜索
454 while (f_new > f + c1 * alpha * g.dot(p)) {
455 alpha *= 0.5;
456 if (alpha < 1e-10) break;
457 x_new = x + alpha * p;
458 f_new = objective_function(x_new, source, target, correspondences, base_transform, &g_new);
459 }
460
461 // 更新历史
462 Vector6 s = x_new - x;
463 Vector6 y = g_new - g;
464 DataType rho = 1.0 / y.dot(s);
465
466 if (std::isfinite(rho) && rho > 0) {
467 s_history.push_back(s);
468 y_history.push_back(y);
469 rho_history.push_back(rho);
470
471 if (s_history.size() > m) {
472 s_history.pop_front();
473 y_history.pop_front();
474 rho_history.pop_front();
475 }
476 }
477
478 // 检查收敛
479 if (g_new.norm() < 1e-6 || std::abs(f_new - f) < 1e-8) {
480 break;
481 }
482
483 // 更新状态
484 x = x_new;
485 f = f_new;
486 g = g_new;
487 }
488
489 return x;
490}
491
492template<typename DataType, typename KNNSearcher>
495 const transformation_t& transform) const
496{
497 Vector6 vec;
498
499 // 提取平移部分
500 vec.template head<3>() = transform.template block<3, 1>(0, 3);
501
502 // 提取旋转部分(使用旋转矩阵的对数映射)
503 Matrix3 R = transform.template block<3, 3>(0, 0);
504 DataType trace = R.trace();
505
506 if (trace > 3.0 - 1e-6) {
507 // 接近单位矩阵
508 vec.template tail<3>() = Vector3::Zero();
509 } else if (trace < -1.0 + 1e-6) {
510 // 接近180度旋转
511 Vector3 axis;
512 int i = 0;
513 if (R(1, 1) > R(0, 0)) i = 1;
514 if (R(2, 2) > R(i, i)) i = 2;
515
516 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);
517 axis[(i+1)%3] = R(i, (i+1)%3) / (2.0 * axis[i]);
518 axis[(i+2)%3] = R(i, (i+2)%3) / (2.0 * axis[i]);
519
520 vec.template tail<3>() = M_PI * axis;
521 } else {
522 // 一般情况
523 DataType theta = std::acos((trace - 1.0) / 2.0);
524 DataType factor = theta / (2.0 * std::sin(theta));
525
526 vec[3] = factor * (R(2, 1) - R(1, 2));
527 vec[4] = factor * (R(0, 2) - R(2, 0));
528 vec[5] = factor * (R(1, 0) - R(0, 1));
529 }
530
531 return vec;
532}
533
534template<typename DataType, typename KNNSearcher>
537 const Vector6& vec) const
538{
539 transformation_t transform = transformation_t::Identity();
540
541 // 设置平移部分
542 transform.template block<3, 1>(0, 3) = vec.template head<3>();
543
544 // 设置旋转部分(使用罗德里格斯公式)
545 Vector3 omega = vec.template tail<3>();
546 DataType theta = omega.norm();
547
548 if (theta < 1e-6) {
549 // 小角度近似
550 transform.template block<3, 3>(0, 0) = Matrix3::Identity() + detail::skew_symmetric(omega);
551 } else {
552 // 罗德里格斯公式
553 Vector3 axis = omega / theta;
555
556 transform.template block<3, 3>(0, 0) =
557 Matrix3::Identity() +
558 std::sin(theta) * K +
559 (1 - std::cos(theta)) * K * K;
560 }
561
562 return transform;
563}
564
565
566} // namespace toolbox::pcl
Eigen::Matrix< DataType, 4, 4 > transformation_t
Definition base_fine_registration.hpp:43
Eigen::Matrix< DataType, 6, 1 > Vector6
Definition generalized_icp.hpp:56
transformation_t compute_transformation(const point_cloud &source, const point_cloud &target, const std::vector< std::pair< std::size_t, std::size_t > > &correspondences, const transformation_t &current_transform)
计算变换矩阵(使用L-BFGS优化) / Compute transformation matrix (using L-BFGS optimization)
Definition generalized_icp_impl.hpp:209
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 generalized_icp_impl.hpp:266
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 generalized_icp_impl.hpp:182
void reject_outliers(std::vector< std::pair< std::size_t, std::size_t > > &correspondences, std::vector< DataType > &distances)
剔除异常值 / Reject outliers
Definition generalized_icp_impl.hpp:296
Vector6 lbfgs_optimize(const Vector6 &x0, const point_cloud &source, const point_cloud &target, const std::vector< std::pair< std::size_t, std::size_t > > &correspondences, const transformation_t &base_transform)
L-BFGS优化器 / L-BFGS optimizer.
Definition generalized_icp_impl.hpp:399
KNNSearcher knn_searcher_type
Definition generalized_icp.hpp:53
DataType objective_function(const Vector6 &x, const point_cloud &source, const point_cloud &target, const std::vector< std::pair< std::size_t, std::size_t > > &correspondences, const transformation_t &base_transform, Vector6 *gradient=nullptr) const
优化目标函数 / Optimization objective function
Definition generalized_icp_impl.hpp:356
Vector6 transformation_to_vector(const transformation_t &transform) const
将变换矩阵转换为优化向量 / Convert transformation matrix to optimization vector
Definition generalized_icp_impl.hpp:494
void compute_covariances(const point_cloud &cloud, knn_searcher_type &searcher, std::vector< Matrix3 > &covariances)
计算点云的协方差矩阵 / Compute covariance matrices for point cloud
Definition generalized_icp_impl.hpp:132
void preprocess_impl()
预处理(构建KD树和计算协方差) / Preprocessing (build KD-trees and compute covariances)
Definition generalized_icp_impl.hpp:28
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 generalized_icp_impl.hpp:49
transformation_t vector_to_transformation(const Vector6 &vec) const
将优化向量转换为变换矩阵 / Convert optimization vector to transformation matrix
Definition generalized_icp_impl.hpp:536
Eigen::Matrix< DataType, 3, 1 > Vector3
Definition generalized_icp.hpp:55
DataType compute_mahalanobis_distance(const Vector3 &p_src, const Vector3 &p_tgt, const Matrix3 &C_src, const Matrix3 &C_tgt, const transformation_t &transform, Vector6 *gradient=nullptr) const
计算Mahalanobis距离和梯度 / Compute Mahalanobis distance and gradient
Definition generalized_icp_impl.hpp:225
Eigen::Matrix< DataType, 3, 3 > Matrix3
Definition generalized_icp.hpp:54
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_INFO_S
INFO级别流式日志的宏 / Macro for INFO level stream logging.
Definition thread_logger.hpp:1330
#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 generalized_icp_impl.hpp:17
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