cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
mls_keypoints_impl.hpp
Go to the documentation of this file.
1#pragma once
2
5#include <Eigen/Dense>
6#include <Eigen/SVD>
7#include <algorithm>
8#include <cmath>
9#include <future>
10#include <vector>
11#include <iostream>
12
13namespace toolbox::pcl
14{
15
16template<typename DataType, typename KNN>
18{
19 m_cloud = std::make_shared<point_cloud>(cloud);
20 return m_cloud->size();
21}
22
23template<typename DataType, typename KNN>
25{
26 m_cloud = cloud;
27 return m_cloud->size();
28}
29
30template<typename DataType, typename KNN>
32{
33 m_knn = const_cast<knn_type*>(&knn);
34 if (m_cloud && m_knn) {
35 m_knn->set_input(*m_cloud);
36 }
37 return m_cloud ? m_cloud->size() : 0;
38}
39
40template<typename DataType, typename KNN>
42{
43 m_search_radius = radius;
44 return 0;
45}
46
47template<typename DataType, typename KNN>
49{
50 m_enable_parallel = enable;
51}
52
53template<typename DataType, typename KNN>
55{
56 switch (m_polynomial_order) {
57 case polynomial_order_t::NONE:
58 return 3; // a0 + a1*x + a2*y (plane z = f(x,y))
59 case polynomial_order_t::LINEAR:
60 return 4; // Above + a3*x*y
61 case polynomial_order_t::QUADRATIC:
62 return 6; // Above + a4*x^2 + a5*y^2
63 default:
64 return 3;
65 }
66}
67
68template<typename DataType, typename KNN>
69void mls_keypoint_extractor_t<DataType, KNN>::compute_polynomial_coefficients(
70 const std::vector<Eigen::Vector3f>& points,
71 const std::vector<data_type>& weights,
72 const Eigen::Vector3f& mean_point,
73 Eigen::VectorXf& coefficients)
74{
75 const int n_coeffs = get_polynomial_coefficients_size();
76 const int n_points = static_cast<int>(points.size());
77
78 // Build the design matrix A and weight matrix W
79 Eigen::MatrixXf A(n_points, n_coeffs);
80 Eigen::VectorXf b(n_points);
81 Eigen::VectorXf W(n_points);
82
83 // Fill matrices
84 for (int i = 0; i < n_points; ++i) {
85 const Eigen::Vector3f p = points[i] - mean_point;
86 const float x = p.x();
87 const float y = p.y();
88 const float z = p.z();
89
90 // Basic terms (plane z = f(x,y))
91 A(i, 0) = 1.0f;
92 A(i, 1) = x;
93 A(i, 2) = y;
94
95 // First order terms
96 if (m_polynomial_order >= polynomial_order_t::LINEAR && n_coeffs > 3) {
97 A(i, 3) = x * y;
98 }
99
100 // Second order terms
101 if (m_polynomial_order >= polynomial_order_t::QUADRATIC && n_coeffs > 4) {
102 A(i, 4) = x * x;
103 A(i, 5) = y * y;
104 }
105
106 b(i) = z; // Fit to the actual z values
107 W(i) = weights[i];
108 }
109
110 // Solve weighted least squares: (A^T * W * A) * coeffs = A^T * W * b
111 Eigen::MatrixXf AtW = A.transpose() * W.asDiagonal();
112 Eigen::MatrixXf AtWA = AtW * A;
113 Eigen::VectorXf AtWb = AtW * b;
114
115 // Use SVD for numerical stability
116 Eigen::JacobiSVD<Eigen::MatrixXf> svd(AtWA, Eigen::ComputeFullU | Eigen::ComputeFullV);
117 coefficients = svd.solve(AtWb);
118}
119
120template<typename DataType, typename KNN>
122mls_keypoint_extractor_t<DataType, KNN>::compute_surface_variation(
123 const std::vector<Eigen::Vector3f>& points,
124 const Eigen::Vector3f& mean_point,
125 const Eigen::VectorXf& coefficients)
126{
127 data_type total_variation = 0;
128 data_type total_weight = 0;
129
130 const int n_coeffs = get_polynomial_coefficients_size();
131
132 for (const auto& point : points) {
133 const Eigen::Vector3f p = point - mean_point;
134 const float x = p.x();
135 const float y = p.y();
136 const float z = p.z();
137
138 // Evaluate polynomial at this point (only x and y terms, as we're fitting z = f(x,y))
139 float poly_value = coefficients(0);
140 poly_value += coefficients(1) * x + coefficients(2) * y;
141
142 if (m_polynomial_order >= polynomial_order_t::LINEAR && n_coeffs > 3) {
143 poly_value += coefficients(3) * x * y;
144 }
145
146 if (m_polynomial_order >= polynomial_order_t::QUADRATIC && n_coeffs > 4) {
147 poly_value += coefficients(4) * x * x;
148 poly_value += coefficients(5) * y * y;
149 }
150
151 // Compute residual (difference between actual z and predicted z)
152 float residual = z - poly_value;
153
154 // Accumulate squared residual
155 total_variation += residual * residual;
156 total_weight += 1.0;
157 }
158
159 return total_weight > 0 ? std::sqrt(total_variation / total_weight) : 0;
160}
161
162template<typename DataType, typename KNN>
163typename mls_keypoint_extractor_t<DataType, KNN>::MLSResult
164mls_keypoint_extractor_t<DataType, KNN>::compute_mls_surface(std::size_t point_idx)
165{
166 MLSResult result;
167
168 if (!m_cloud || !m_knn || point_idx >= m_cloud->size()) {
169 return result;
170 }
171
172 const auto& query_point = m_cloud->points[point_idx];
173 std::vector<std::size_t> neighbor_indices;
174 std::vector<data_type> neighbor_distances;
175
176 // Find neighbors within search radius
177 m_knn->radius_neighbors(query_point, m_search_radius, neighbor_indices, neighbor_distances);
178
179 if (neighbor_indices.size() < m_min_neighbors) {
180 return result;
181 }
182
183 // Convert points to Eigen format and compute mean
184 std::vector<Eigen::Vector3f> eigen_points;
185 eigen_points.reserve(neighbor_indices.size());
186 Eigen::Vector3f mean_point = Eigen::Vector3f::Zero();
187
188 for (const auto& idx : neighbor_indices) {
189 const auto& pt = m_cloud->points[idx];
190 Eigen::Vector3f eigen_pt(pt.x, pt.y, pt.z);
191 eigen_points.push_back(eigen_pt);
192 mean_point += eigen_pt;
193 }
194 mean_point /= static_cast<float>(neighbor_indices.size());
195
196 // Compute weights using Gaussian kernel
197 std::vector<data_type> weights;
198 weights.reserve(neighbor_indices.size());
199
200 const data_type sqr_gauss = (m_sqr_gauss_param > 0) ? m_sqr_gauss_param : (m_search_radius * m_search_radius);
201
202 for (std::size_t i = 0; i < neighbor_distances.size(); ++i) {
203 const data_type sqr_dist = neighbor_distances[i] * neighbor_distances[i];
204 const data_type weight = std::exp(-sqr_dist / sqr_gauss);
205 weights.push_back(weight);
206 }
207
208 // Check if we have a normal for the query point
209 Eigen::Vector3f normal(0, 0, 1); // Default to Z-up
210 if (point_idx < m_cloud->normals.size()) {
211 const auto& n = m_cloud->normals[point_idx];
212 normal = Eigen::Vector3f(n.x, n.y, n.z);
213 if (normal.norm() > 0.1f) {
214 normal.normalize();
215 } else {
216 // Estimate normal using PCA if not provided or invalid
217 Eigen::Matrix3f covariance = Eigen::Matrix3f::Zero();
218 for (const auto& pt : eigen_points) {
219 Eigen::Vector3f diff = pt - mean_point;
220 covariance += diff * diff.transpose();
221 }
222 covariance /= static_cast<float>(eigen_points.size());
223
224 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3f> eigen_solver(covariance);
225 normal = eigen_solver.eigenvectors().col(0); // Smallest eigenvalue
226 }
227 }
228
229 // Project points to local coordinate system
230 // Create local coordinate frame with normal as Z axis
231 Eigen::Vector3f v1, v2;
232 if (std::abs(normal.dot(Eigen::Vector3f::UnitX())) < 0.9f) {
233 v1 = normal.cross(Eigen::Vector3f::UnitX()).normalized();
234 } else {
235 v1 = normal.cross(Eigen::Vector3f::UnitY()).normalized();
236 }
237 v2 = normal.cross(v1).normalized();
238
239 // Transform points to local coordinates
240 std::vector<Eigen::Vector3f> local_points;
241 local_points.reserve(eigen_points.size());
242
243 for (const auto& pt : eigen_points) {
244 Eigen::Vector3f diff = pt - mean_point;
245 Eigen::Vector3f local_pt;
246 local_pt.x() = diff.dot(v1);
247 local_pt.y() = diff.dot(v2);
248 local_pt.z() = diff.dot(normal);
249 local_points.push_back(local_pt);
250 }
251
252 // Fit polynomial
253 Eigen::VectorXf coefficients;
254 compute_polynomial_coefficients(local_points, weights, Eigen::Vector3f::Zero(), coefficients);
255
256 // Compute surface variation
257 result.variation = compute_surface_variation(local_points, Eigen::Vector3f::Zero(), coefficients);
258
259 // Compute curvature if requested
260 if (m_compute_curvatures && m_polynomial_order >= polynomial_order_t::QUADRATIC) {
261 // Mean curvature from second order coefficients
262 // H = (fxx + fyy) / 2 for z = f(x,y)
263 if (coefficients.size() >= 6) {
264 const float fxx = 2.0f * coefficients(4);
265 const float fyy = 2.0f * coefficients(5);
266 result.curvature = std::abs((fxx + fyy) / 2.0f);
267 }
268 }
269
270 // Store refined normal (gradient of the polynomial)
271 if (coefficients.size() >= 3) {
272 Eigen::Vector3f grad_local(coefficients(1), coefficients(2), -1.0f);
273 grad_local.normalize();
274
275 // Transform back to global coordinates
276 result.normal = v1 * grad_local.x() + v2 * grad_local.y() + normal * grad_local.z();
277 result.normal.normalize();
278 } else {
279 result.normal = normal;
280 }
281
282 result.valid = true;
283 return result;
284}
285
286template<typename DataType, typename KNN>
287void mls_keypoint_extractor_t<DataType, KNN>::compute_mls_range(
288 std::vector<MLSResult>& mls_results,
289 std::size_t start_idx,
290 std::size_t end_idx)
291{
292 for (std::size_t i = start_idx; i < end_idx; ++i) {
293 mls_results[i] = compute_mls_surface(i);
294 }
295}
296
297template<typename DataType, typename KNN>
298std::vector<typename mls_keypoint_extractor_t<DataType, KNN>::MLSResult>
299mls_keypoint_extractor_t<DataType, KNN>::compute_all_mls_surfaces()
300{
301 if (!m_cloud) {
302 return {};
303 }
304
305 const std::size_t num_points = m_cloud->size();
306 std::vector<MLSResult> mls_results(num_points);
307
308 if (m_enable_parallel && num_points > k_parallel_threshold) {
309 // Parallel computation
310 const std::size_t num_threads = toolbox::base::thread_pool_singleton_t::instance().get_thread_count();
311 const std::size_t chunk_size = (num_points + num_threads - 1) / num_threads;
312
313 std::vector<std::future<void>> futures;
314 for (std::size_t t = 0; t < num_threads; ++t) {
315 const std::size_t start_idx = t * chunk_size;
316 const std::size_t end_idx = std::min(start_idx + chunk_size, num_points);
317
318 if (start_idx < end_idx) {
319 futures.emplace_back(
321 [this, &mls_results, start_idx, end_idx]() {
322 compute_mls_range(mls_results, start_idx, end_idx);
323 }
324 )
325 );
326 }
327 }
328
329 // Wait for all threads to complete
330 for (auto& future : futures) {
331 future.wait();
332 }
333 } else {
334 // Sequential computation
335 compute_mls_range(mls_results, 0, num_points);
336 }
337
338 return mls_results;
339}
340
341template<typename DataType, typename KNN>
343mls_keypoint_extractor_t<DataType, KNN>::apply_non_maxima_suppression(
344 const std::vector<MLSResult>& mls_results)
345{
346 if (!m_cloud || mls_results.empty()) {
347 return {};
348 }
349
350 indices_vector keypoints;
351 const std::size_t num_points = m_cloud->size();
352
353 // Debug: Count valid results
354 std::size_t valid_count = 0;
355 data_type max_variation = 0;
356 data_type max_curvature = 0;
357
358 for (const auto& result : mls_results) {
359 if (result.valid) {
360 valid_count++;
361 max_variation = std::max(max_variation, result.variation);
362 max_curvature = std::max(max_curvature, result.curvature);
363 }
364 }
365
366 // Debug output (uncomment for debugging)
367 // std::cout << "MLS Debug: valid=" << valid_count << "/" << num_points
368 // << " max_var=" << max_variation << " max_curv=" << max_curvature
369 // << " thresh_var=" << m_variation_threshold << " thresh_curv=" << m_curvature_threshold << std::endl;
370
371 for (std::size_t i = 0; i < num_points; ++i) {
372 const auto& current_result = mls_results[i];
373
374 // Skip invalid points
375 if (!current_result.valid) {
376 continue;
377 }
378
379 // Check thresholds
380 bool is_keypoint = false;
381
382 if (current_result.variation > m_variation_threshold) {
383 is_keypoint = true;
384 }
385
386 if (m_compute_curvatures && current_result.curvature > m_curvature_threshold) {
387 is_keypoint = true;
388 }
389
390 if (!is_keypoint) {
391 continue;
392 }
393
394 // Find neighbors within non-maxima suppression radius
395 const auto& query_point = m_cloud->points[i];
396 std::vector<std::size_t> neighbor_indices;
397 std::vector<data_type> neighbor_distances;
398
399 m_knn->radius_neighbors(query_point, m_non_maxima_radius, neighbor_indices, neighbor_distances);
400
401 // Check if current point is local maximum
402 bool is_local_maximum = true;
403
404 // Use combined score (variation + curvature)
405 const data_type current_score = current_result.variation +
406 (m_compute_curvatures ? current_result.curvature : 0);
407
408 for (const auto& neighbor_idx : neighbor_indices) {
409 if (neighbor_idx != i && neighbor_idx < mls_results.size()) {
410 const auto& neighbor_result = mls_results[neighbor_idx];
411 if (neighbor_result.valid) {
412 const data_type neighbor_score = neighbor_result.variation +
413 (m_compute_curvatures ? neighbor_result.curvature : 0);
414 if (neighbor_score > current_score) {
415 is_local_maximum = false;
416 break;
417 }
418 }
419 }
420 }
421
422 if (is_local_maximum) {
423 keypoints.push_back(i);
424 }
425 }
426
427 return keypoints;
428}
429
430template<typename DataType, typename KNN>
433{
434 if (!m_cloud || !m_knn) {
435 return {};
436 }
437
438 // Compute MLS surfaces for all points
439 auto mls_results = compute_all_mls_surfaces();
440
441 // Apply non-maxima suppression to find keypoints
442 return apply_non_maxima_suppression(mls_results);
443}
444
445template<typename DataType, typename KNN>
447{
448 keypoint_indices = extract_impl();
449}
450
451template<typename DataType, typename KNN>
454{
455 auto keypoint_indices = extract_impl();
456
457 point_cloud keypoints;
458 keypoints.points.reserve(keypoint_indices.size());
459
460 for (const auto& idx : keypoint_indices) {
461 keypoints.points.push_back(m_cloud->points[idx]);
462 }
463
464 return keypoints;
465}
466
467template<typename DataType, typename KNN>
469{
470 auto keypoint_indices = extract_impl();
471
472 output->points.clear();
473 output->points.reserve(keypoint_indices.size());
474
475 for (const auto& idx : keypoint_indices) {
476 output->points.push_back(m_cloud->points[idx]);
477 }
478}
479
480} // namespace toolbox::pcl
static thread_pool_singleton_t & instance()
获取单例实例/Get the singleton instance
Definition thread_pool_singleton.hpp:23
MLS (Moving Least Squares) 关键点提取器 / MLS (Moving Least Squares) keypoint extractor.
Definition mls_keypoints.hpp:28
typename base_type::data_type data_type
Definition mls_keypoints.hpp:33
point_cloud extract_keypoints_impl()
Definition mls_keypoints_impl.hpp:453
std::size_t set_search_radius_impl(data_type radius)
Definition mls_keypoints_impl.hpp:41
typename base_type::indices_vector indices_vector
Definition mls_keypoints.hpp:37
indices_vector extract_impl()
Definition mls_keypoints_impl.hpp:432
typename base_type::point_cloud point_cloud
Definition mls_keypoints.hpp:35
typename base_type::point_cloud_ptr point_cloud_ptr
Definition mls_keypoints.hpp:36
std::size_t set_input_impl(const point_cloud &cloud)
Definition mls_keypoints_impl.hpp:17
std::size_t set_knn_impl(const knn_type &knn)
Definition mls_keypoints_impl.hpp:31
void enable_parallel_impl(bool enable)
Definition mls_keypoints_impl.hpp:48
typename base_type::knn_type knn_type
Definition mls_keypoints.hpp:34
Definition base_correspondence_generator.hpp:18