cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
shot_extractor_impl.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <numeric>
6#include <Eigen/Dense>
7#include <Eigen/Eigenvalues>
8
9namespace toolbox::pcl
10{
11
12template<typename DataType, typename KNN>
14{
15 m_cloud = std::make_shared<point_cloud>(cloud);
16 return m_cloud->size();
17}
18
19template<typename DataType, typename KNN>
21{
22 m_cloud = cloud;
23 return m_cloud ? m_cloud->size() : 0;
24}
25
26template<typename DataType, typename KNN>
28{
29 m_knn = const_cast<knn_type*>(&knn);
30 if (m_cloud && m_knn)
31 {
32 // Convert point cloud to vector of points for new KNN API
33 std::vector<point_t<DataType>> points(m_cloud->points.begin(), m_cloud->points.end());
34 m_knn->set_input(points);
35 }
36 return m_cloud ? m_cloud->size() : 0;
37}
38
39template<typename DataType, typename KNN>
41{
42 m_search_radius = radius;
43 return m_cloud ? m_cloud->size() : 0;
44}
45
46template<typename DataType, typename KNN>
47std::size_t shot_extractor_t<DataType, KNN>::set_num_neighbors(std::size_t num_neighbors)
48{
49 m_num_neighbors = num_neighbors;
50 return m_cloud ? m_cloud->size() : 0;
51}
52
53template<typename DataType, typename KNN>
55{
56 m_normals = normals;
57}
58
59template<typename DataType, typename KNN>
61{
62 m_enable_parallel = enable;
63}
64
65template<typename DataType, typename KNN>
67 const point_cloud& cloud,
68 const std::vector<std::size_t>& keypoint_indices,
69 std::vector<signature_type>& descriptors) const
70{
71 if (!m_knn || keypoint_indices.empty())
72 {
73 descriptors.clear();
74 return;
75 }
76
77 // Compute normals if not provided
78 point_cloud_ptr normals = m_normals;
79 if (!normals)
80 {
81 normals = std::make_shared<point_cloud>();
82 normals->points.resize(cloud.size());
84 norm_extractor.set_input(cloud);
85 norm_extractor.set_knn(*m_knn);
86 norm_extractor.set_num_neighbors(m_num_neighbors);
87 norm_extractor.enable_parallel(m_enable_parallel);
88 norm_extractor.extract(normals);
89 }
90
91 descriptors.resize(keypoint_indices.size());
92
93 auto compute_shot_range = [&](std::size_t start, std::size_t end) {
94 for (std::size_t i = start; i < end; ++i)
95 {
96 std::size_t keypoint_idx = keypoint_indices[i];
97
98 // Get neighbors
99 std::vector<std::size_t> neighbor_indices;
100 std::vector<data_type> neighbor_distances;
101 m_knn->radius_neighbors(cloud.points[keypoint_idx], m_search_radius, neighbor_indices, neighbor_distances);
102
103 // Limit to m_num_neighbors
104 if (neighbor_indices.size() > m_num_neighbors)
105 {
106 neighbor_indices.resize(m_num_neighbors);
107 neighbor_distances.resize(m_num_neighbors);
108 }
109
110 if (neighbor_indices.size() < 5) // Need enough neighbors for robust LRF
111 {
112 // Set empty descriptor
113 std::fill(descriptors[i].histogram.begin(), descriptors[i].histogram.end(), DataType(0));
114 continue;
115 }
116
117 // Compute local reference frame
118 local_rf_t lrf;
119 compute_local_reference_frame(cloud, *normals, keypoint_idx, neighbor_indices, lrf);
120
121 // Compute SHOT descriptor
122 compute_shot_feature(cloud, *normals, keypoint_idx, neighbor_indices, lrf, descriptors[i]);
123 }
124 };
125
126 if (m_enable_parallel)
127 {
128 // Create index range for keypoints
129 std::vector<std::size_t> indices(keypoint_indices.size());
130 std::iota(indices.begin(), indices.end(), 0);
131
132 toolbox::concurrent::parallel_for_each(indices.begin(), indices.end(),
133 [&](std::size_t i) {
134 std::size_t keypoint_idx = keypoint_indices[i];
135
136 // Get neighbors
137 std::vector<std::size_t> neighbor_indices;
138 std::vector<data_type> neighbor_distances;
139 m_knn->radius_neighbors(cloud.points[keypoint_idx], m_search_radius, neighbor_indices, neighbor_distances);
140
141 // Limit to m_num_neighbors
142 if (neighbor_indices.size() > m_num_neighbors)
143 {
144 neighbor_indices.resize(m_num_neighbors);
145 neighbor_distances.resize(m_num_neighbors);
146 }
147
148 if (neighbor_indices.size() < 5) // Need enough neighbors for robust LRF
149 {
150 // Set empty descriptor
151 std::fill(descriptors[i].histogram.begin(), descriptors[i].histogram.end(), DataType(0));
152 return;
153 }
154
155 // Compute local reference frame
156 local_rf_t lrf;
157 compute_local_reference_frame(cloud, *normals, keypoint_idx, neighbor_indices, lrf);
158
159 // Compute SHOT descriptor
160 compute_shot_feature(cloud, *normals, keypoint_idx, neighbor_indices, lrf, descriptors[i]);
161 });
162 }
163 else
164 {
165 compute_shot_range(0, keypoint_indices.size());
166 }
167}
168
169template<typename DataType, typename KNN>
171 const point_cloud& cloud,
172 const std::vector<std::size_t>& keypoint_indices,
173 std::unique_ptr<std::vector<signature_type>>& descriptors) const
174{
175 descriptors = std::make_unique<std::vector<signature_type>>();
176 compute_impl(cloud, keypoint_indices, *descriptors);
177}
178
179template<typename DataType, typename KNN>
181 const point_cloud& cloud,
182 const point_cloud& normals,
183 std::size_t index,
184 const std::vector<std::size_t>& neighbor_indices,
185 local_rf_t& lrf) const
186{
187 const auto& center = cloud.points[index];
188 const auto& normal = normals.points[index];
189
190 // Compute weighted covariance matrix
191 std::vector<data_type> weights(neighbor_indices.size());
192 data_type total_weight = 0;
193
194 for (std::size_t i = 0; i < neighbor_indices.size(); ++i)
195 {
197 diff.x = cloud.points[neighbor_indices[i]].x - center.x;
198 diff.y = cloud.points[neighbor_indices[i]].y - center.y;
199 diff.z = cloud.points[neighbor_indices[i]].z - center.z;
200 data_type dist = diff.norm();
201 weights[i] = DataType(1) / (dist + DataType(1e-6));
202 total_weight += weights[i];
203 }
204
205 // Normalize weights
206 for (auto& w : weights)
207 {
208 w /= total_weight;
209 }
210
211 // Compute weighted covariance
212 data_type cov[9];
213 compute_weighted_covariance(cloud, index, neighbor_indices, weights, cov);
214
215 // Eigen decomposition
216 Eigen::Matrix<DataType, 3, 3> cov_matrix;
217 cov_matrix << cov[0], cov[1], cov[2],
218 cov[3], cov[4], cov[5],
219 cov[6], cov[7], cov[8];
220
221 Eigen::SelfAdjointEigenSolver<Eigen::Matrix<DataType, 3, 3>> solver(cov_matrix);
222 auto eigenvectors = solver.eigenvectors();
223
224 // Set z-axis as the normal
225 lrf.z_axis = normal;
226
227 // x-axis is the eigenvector with largest eigenvalue orthogonal to z
228 lrf.x_axis = point_t<data_type>(eigenvectors(0, 2), eigenvectors(1, 2), eigenvectors(2, 2));
229
230 // Make x-axis orthogonal to z-axis
231 data_type dot_product = lrf.x_axis.dot(lrf.z_axis);
232 lrf.x_axis.x = lrf.x_axis.x - lrf.z_axis.x * dot_product;
233 lrf.x_axis.y = lrf.x_axis.y - lrf.z_axis.y * dot_product;
234 lrf.x_axis.z = lrf.x_axis.z - lrf.z_axis.z * dot_product;
235 auto x_normalized = lrf.x_axis.normalize();
236 lrf.x_axis.x = static_cast<data_type>(x_normalized.x);
237 lrf.x_axis.y = static_cast<data_type>(x_normalized.y);
238 lrf.x_axis.z = static_cast<data_type>(x_normalized.z);
239
240 // y-axis = z × x
241 lrf.y_axis = lrf.z_axis.cross(lrf.x_axis);
242 auto y_normalized = lrf.y_axis.normalize();
243 lrf.y_axis.x = static_cast<data_type>(y_normalized.x);
244 lrf.y_axis.y = static_cast<data_type>(y_normalized.y);
245 lrf.y_axis.z = static_cast<data_type>(y_normalized.z);
246
247 // Disambiguate the sign of axes
248 data_type positive_count = 0;
249 for (std::size_t neighbor_idx : neighbor_indices)
250 {
251 point_t<data_type> diff;
252 diff.x = cloud.points[neighbor_idx].x - center.x;
253 diff.y = cloud.points[neighbor_idx].y - center.y;
254 diff.z = cloud.points[neighbor_idx].z - center.z;
255 if (diff.dot(lrf.x_axis) > 0)
256 positive_count++;
257 }
258
259 if (positive_count < neighbor_indices.size() / 2)
260 {
261 lrf.x_axis.x = -lrf.x_axis.x;
262 lrf.x_axis.y = -lrf.x_axis.y;
263 lrf.x_axis.z = -lrf.x_axis.z;
264 lrf.y_axis.x = -lrf.y_axis.x;
265 lrf.y_axis.y = -lrf.y_axis.y;
266 lrf.y_axis.z = -lrf.y_axis.z;
267 }
268}
269
270template<typename DataType, typename KNN>
271void shot_extractor_t<DataType, KNN>::compute_shot_feature(
272 const point_cloud& cloud,
273 const point_cloud& normals,
274 std::size_t index,
275 const std::vector<std::size_t>& neighbor_indices,
276 const local_rf_t& lrf,
277 signature_type& shot) const
278{
279 // Initialize histogram
280 std::fill(shot.histogram.begin(), shot.histogram.end(), DataType(0));
281
282 const auto& center = cloud.points[index];
283 constexpr std::size_t num_spatial_bins = 32;
284 constexpr std::size_t num_angular_bins = 11;
285
286 // Process each neighbor
287 for (std::size_t neighbor_idx : neighbor_indices)
288 {
289 if (neighbor_idx == index) continue;
290
291 const auto& neighbor_point = cloud.points[neighbor_idx];
292 const auto& neighbor_normal = normals.points[neighbor_idx];
293
294 // Compute spatial bin
295 std::size_t spatial_bin = compute_spatial_bin(neighbor_point, center, lrf, m_search_radius);
296 if (spatial_bin >= num_spatial_bins) continue;
297
298 // Compute angular bin
299 std::size_t angular_bin = compute_angular_bin(neighbor_normal, lrf);
300 if (angular_bin >= num_angular_bins) continue;
301
302 // Add to histogram
303 std::size_t hist_idx = spatial_bin * num_angular_bins + angular_bin;
304 point_t<data_type> diff;
305 diff.x = neighbor_point.x - center.x;
306 diff.y = neighbor_point.y - center.y;
307 diff.z = neighbor_point.z - center.z;
308 data_type distance = diff.norm();
309 data_type weight = DataType(1) - (distance / m_search_radius);
310 shot.histogram[hist_idx] += weight;
311 }
312
313 // Normalize histogram
314 data_type norm = 0;
315 for (std::size_t i = 0; i < signature_type::HISTOGRAM_SIZE; ++i)
316 {
317 norm += shot.histogram[i] * shot.histogram[i];
318 }
319
320 if (norm > DataType(1e-6))
321 {
322 norm = DataType(1) / std::sqrt(norm);
323 for (std::size_t i = 0; i < signature_type::HISTOGRAM_SIZE; ++i)
324 {
325 shot.histogram[i] *= norm;
326 }
327 }
328}
329
330template<typename DataType, typename KNN>
331void shot_extractor_t<DataType, KNN>::compute_weighted_covariance(
332 const point_cloud& cloud,
333 std::size_t center_idx,
334 const std::vector<std::size_t>& indices,
335 const std::vector<data_type>& weights,
336 data_type cov[9]) const
337{
338 const auto& center = cloud.points[center_idx];
339
340 // Initialize covariance matrix
341 std::fill(cov, cov + 9, DataType(0));
342
343 // Compute weighted mean
344 point_t<data_type> mean(0, 0, 0);
345 for (std::size_t i = 0; i < indices.size(); ++i)
346 {
347 point_t<data_type> diff;
348 diff.x = cloud.points[indices[i]].x - center.x;
349 diff.y = cloud.points[indices[i]].y - center.y;
350 diff.z = cloud.points[indices[i]].z - center.z;
351 mean.x += weights[i] * diff.x;
352 mean.y += weights[i] * diff.y;
353 mean.z += weights[i] * diff.z;
354 }
355
356 // Compute covariance
357 for (std::size_t i = 0; i < indices.size(); ++i)
358 {
359 point_t<data_type> diff;
360 diff.x = cloud.points[indices[i]].x - center.x - mean.x;
361 diff.y = cloud.points[indices[i]].y - center.y - mean.y;
362 diff.z = cloud.points[indices[i]].z - center.z - mean.z;
363 data_type w = weights[i];
364
365 cov[0] += w * diff.x * diff.x;
366 cov[1] += w * diff.x * diff.y;
367 cov[2] += w * diff.x * diff.z;
368 cov[3] += w * diff.y * diff.x;
369 cov[4] += w * diff.y * diff.y;
370 cov[5] += w * diff.y * diff.z;
371 cov[6] += w * diff.z * diff.x;
372 cov[7] += w * diff.z * diff.y;
373 cov[8] += w * diff.z * diff.z;
374 }
375}
376
377template<typename DataType, typename KNN>
378std::size_t shot_extractor_t<DataType, KNN>::compute_spatial_bin(
379 const point_t<data_type>& point,
380 const point_t<data_type>& center,
381 const local_rf_t& lrf,
382 data_type radius) const
383{
384 point_t<data_type> local_point;
385 local_point.x = point.x - center.x;
386 local_point.y = point.y - center.y;
387 local_point.z = point.z - center.z;
388
389 // Transform to local reference frame
390 data_type x = local_point.dot(lrf.x_axis);
391 data_type y = local_point.dot(lrf.y_axis);
392 data_type z = local_point.dot(lrf.z_axis);
393
394 // Compute spherical coordinates
395 data_type r = std::sqrt(x*x + y*y + z*z);
396 data_type theta = std::atan2(y, x); // Azimuth angle
397 data_type phi = std::acos(z / (r + DataType(1e-6))); // Elevation angle
398
399 // Normalize coordinates
400 r = r / radius;
401 theta = (theta + DataType(M_PI)) / (DataType(2) * DataType(M_PI));
402 phi = phi / DataType(M_PI);
403
404 // Determine spatial bin (2 radial × 4 azimuth × 4 elevation = 32 bins)
405 std::size_t r_bin = (r < DataType(0.5)) ? 0 : 1;
406 std::size_t theta_bin = static_cast<std::size_t>(theta * 4);
407 std::size_t phi_bin = static_cast<std::size_t>(phi * 4);
408
409 // Clamp bins
410 theta_bin = std::min(theta_bin, std::size_t(3));
411 phi_bin = std::min(phi_bin, std::size_t(3));
412
413 return r_bin * 16 + theta_bin * 4 + phi_bin;
414}
415
416template<typename DataType, typename KNN>
417std::size_t shot_extractor_t<DataType, KNN>::compute_angular_bin(
418 const point_t<data_type>& normal,
419 const local_rf_t& lrf) const
420{
421 // Project normal onto local reference frame
422 data_type cos_angle = normal.dot(lrf.z_axis);
423
424 // Clamp to [-1, 1]
425 cos_angle = std::max(DataType(-1), std::min(DataType(1), cos_angle));
426
427 // Convert to bin index (11 bins from -1 to 1)
428 data_type normalized = (cos_angle + DataType(1)) / DataType(2);
429 std::size_t bin = static_cast<std::size_t>(normalized * 11);
430
431 return std::min(bin, std::size_t(10));
432}
433
434} // namespace toolbox::pcl
基于PCA的法向量提取器 / PCA-based normal extractor
Definition pca_norm.hpp:56
void enable_parallel(bool enable)
启用或禁用并行计算 / Enable or disable parallel computation
Definition pca_norm.hpp:120
SHOT (Signature of Histograms of Orientations) descriptor extractor.
Definition shot_extractor.hpp:90
std::size_t set_input(const point_cloud &cloud)
Set the input point cloud.
Definition shot_extractor_impl.hpp:13
std::size_t set_knn(const knn_type &knn)
Set the KNN search algorithm.
Definition shot_extractor_impl.hpp:27
std::shared_ptr< point_cloud > point_cloud_ptr
Definition shot_extractor.hpp:99
std::size_t set_search_radius(data_type radius)
Set the search radius for neighbor search.
Definition shot_extractor_impl.hpp:40
std::size_t set_num_neighbors(std::size_t num_neighbors)
Set the maximum number of neighbors to consider.
Definition shot_extractor_impl.hpp:47
DataType data_type
Definition shot_extractor.hpp:95
void compute_impl(const point_cloud &cloud, const std::vector< std::size_t > &keypoint_indices, std::vector< signature_type > &descriptors) const
Compute descriptors for given keypoints.
Definition shot_extractor_impl.hpp:66
void set_normals(const point_cloud_ptr &normals)
Set the point cloud normals (optional, will be computed if not provided)
Definition shot_extractor_impl.hpp:54
KNN knn_type
Definition shot_extractor.hpp:97
void enable_parallel_impl(bool enable)
Enable or disable parallel processing.
Definition shot_extractor_impl.hpp:60
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
void parallel_for_each(Iterator begin, Iterator end, Function func)
使用TBB并行对范围[begin, end)中的每个元素应用函数
Definition parallel_raw.hpp:21
double mean(const TContainer &data)
计算容器中元素的平均值/Compute the mean (average) of elements in a container
Definition statistics.hpp:64
Definition base_correspondence_generator.hpp:18
3D点/向量模板类 / A 3D point/vector template class
Definition point.hpp:48
T x
X坐标 / X coordinate.
Definition point.hpp:51
T z
Z坐标 / Z coordinate.
Definition point.hpp:53
auto norm() const -> double
计算向量的欧几里得范数(长度) / Calculate Euclidean norm (length) of vector
Definition point_impl.hpp:96
T y
Y坐标 / Y coordinate.
Definition point.hpp:52