17 #include <Eigen/Dense>
23 const Eigen::Vector3d &n1,
24 const Eigen::Vector3d &p2,
25 const Eigen::Vector3d &n2) {
27 Eigen::Vector3d dp2p1 = p2 - p1;
30 return Eigen::Vector4d::Zero();
34 double angle1 = n1_copy.dot(dp2p1) /
result(3);
35 double angle2 = n2_copy.dot(dp2p1) /
result(3);
36 if (acos(fabs(angle1)) > acos(fabs(angle2))) {
44 auto v = dp2p1.cross(n1_copy);
45 double v_norm = v.norm();
47 return Eigen::Vector4d::Zero();
50 auto w = n1_copy.cross(v);
51 result(1) = v.dot(n2_copy);
52 result(0) = atan2(w.dot(n2_copy), n1_copy.dot(n2_copy));
62 const bool filter_spfh = indices.has_value();
63 const auto spfh_indices = indices.value_or(std::vector<size_t>());
65 const size_t n_spfh = filter_spfh ? spfh_indices.size() : input.
size();
66 auto feature = std::make_shared<Feature>();
67 feature->Resize(33, (
int)n_spfh);
69 #pragma omp parallel for schedule(static) \
70 num_threads(utility::EstimateMaxThreads())
71 for (
int i = 0; i < (int)n_spfh; i++) {
72 const int point_idx = filter_spfh ? spfh_indices[i] : i;
75 std::vector<int> indices;
76 std::vector<double> distance2;
77 if (kdtree.
Search(point, search_param, indices, distance2) > 1) {
79 double hist_incr = 100.0 / (double)(indices.size() - 1);
80 for (
size_t k = 1; k < indices.size(); k++) {
85 int h_index = (int)(
floor(11 * (pf(0) +
M_PI) / (2.0 *
M_PI)));
86 if (h_index < 0) h_index = 0;
87 if (h_index >= 11) h_index = 10;
88 feature->data_(h_index, i) += hist_incr;
89 h_index = (int)(
floor(11 * (pf(1) + 1.0) * 0.5));
90 if (h_index < 0) h_index = 0;
91 if (h_index >= 11) h_index = 10;
92 feature->data_(h_index + 11, i) += hist_incr;
93 h_index = (int)(
floor(11 * (pf(2) + 1.0) * 0.5));
94 if (h_index < 0) h_index = 0;
95 if (h_index >= 11) h_index = 10;
96 feature->data_(h_index + 22, i) += hist_incr;
104 const std::vector<size_t> &indices,
bool invert )
const {
105 auto output = std::make_shared<Feature>();
107 std::vector<bool> mask = std::vector<bool>(
data_.cols(), invert);
108 size_t n_output_features = 0;
109 for (
size_t i : indices) {
110 if (i < mask.size()) {
111 if (mask[i] == invert) {
117 "[SelectByIndex] contains index {} that is "
118 "not within the bounds",
123 n_output_features =
data_.cols() - n_output_features;
126 output->Resize(
data_.rows(), n_output_features);
128 for (
size_t i = 0, current_col_feature = 0;
129 i < static_cast<size_t>(
data_.cols()); i++) {
131 output->data_.col(current_col_feature++) =
data_.col(i);
136 "[SelectByIndex] Feature group down sampled from {:d} features to "
138 (
int)
data_.cols(), (
int)output->data_.cols());
151 "[ComputeFPFHFeature] Failed because input point cloud has no "
154 const bool filter_fpfh = indices.has_value();
155 std::vector<int> fpfh_indices;
157 std::vector<bool> mask_fpfh(input.
size(),
false);
158 for (
auto idx : indices.value()) {
159 if (idx < mask_fpfh.size()) {
160 if (!mask_fpfh[idx]) {
161 mask_fpfh[idx] =
true;
165 "[ComputeFPFHFeature] contains index {} that is "
166 "not within the bounds",
170 fpfh_indices.reserve(indices.value().size());
171 for (
size_t i = 0; i < mask_fpfh.size(); i++) {
173 fpfh_indices.push_back(i);
178 const size_t n_fpfh = filter_fpfh ? fpfh_indices.size() : input.
size();
182 std::vector<size_t> spfh_indices;
183 std::vector<int> map_point_idx_to_spfh_idx;
184 std::vector<std::vector<int>> map_fpfh_idx_to_indices;
185 std::vector<std::vector<double>> map_fpfh_idx_to_distance2;
190 std::vector<uint8_t> mask_spfh(input.
size(), 0);
191 map_fpfh_idx_to_indices = std::vector<std::vector<int>>(n_fpfh);
192 map_fpfh_idx_to_distance2 = std::vector<std::vector<double>>(n_fpfh);
193 #pragma omp parallel for schedule(static) \
194 num_threads(utility::EstimateMaxThreads())
195 for (
int i = 0; i < (int)n_fpfh; i++) {
197 std::vector<int> p_indices;
198 std::vector<double> p_distance2;
199 kdtree.
Search(point, search_param, p_indices, p_distance2);
200 for (
size_t k = 0; k < p_indices.size(); k++) {
201 if (!mask_spfh[p_indices[k]]) {
202 mask_spfh[p_indices[k]] = 1;
205 map_fpfh_idx_to_indices[i] = std::move(p_indices);
206 map_fpfh_idx_to_distance2[i] = std::move(p_distance2);
208 size_t spfh_indices_reserve_factor;
211 spfh_indices_reserve_factor =
216 spfh_indices_reserve_factor =
222 spfh_indices_reserve_factor = 30;
224 spfh_indices.reserve(spfh_indices_reserve_factor * fpfh_indices.size());
225 map_point_idx_to_spfh_idx = std::vector<int>(input.
size(), -1);
226 for (
size_t i = 0; i < mask_spfh.size(); i++) {
228 map_point_idx_to_spfh_idx[i] = spfh_indices.size();
229 spfh_indices.push_back(i);
234 auto feature = std::make_shared<Feature>();
235 feature->Resize(33, (
int)n_fpfh);
240 if (spfh ==
nullptr) {
243 #pragma omp parallel for schedule(static) \
244 num_threads(utility::EstimateMaxThreads())
245 for (
int i = 0; i < (int)n_fpfh; i++) {
247 std::vector<int> p_indices;
248 std::vector<double> p_distance2;
250 i_spfh = map_point_idx_to_spfh_idx[fpfh_indices[i]];
251 p_indices = std::move(map_fpfh_idx_to_indices[i]);
252 p_distance2 = std::move(map_fpfh_idx_to_distance2[i]);
258 if (p_indices.size() > 1) {
259 double sum[3] = {0.0, 0.0, 0.0};
260 for (
size_t k = 1; k < p_indices.size(); k++) {
262 double dist = p_distance2[k];
263 if (dist == 0.0)
continue;
265 filter_fpfh ? map_point_idx_to_spfh_idx[p_indices[k]]
267 for (
int j = 0; j < 33; j++) {
268 double val = spfh->data_(j, p_index_k) / dist;
270 feature->data_(j, i) += val;
273 for (
int j = 0; j < 3; j++)
274 if (sum[j] != 0.0) sum[j] = 100.0 / sum[j];
275 for (
int j = 0; j < 33; j++) {
276 feature->data_(j, i) *= sum[j / 11];
282 feature->data_(j, i) += spfh->data_(j, i_spfh);
288 "[ComputeFPFHFeature] Computed {:d} features from "
289 "input point cloud with {:d} points.",
290 (
int)feature->data_.cols(), (
int)input.
size());
296 const Feature &target_features,
298 float mutual_consistent_ratio) {
299 const int num_searches = mutual_filter ? 2 : 1;
302 std::array<std::reference_wrapper<const Feature>, 2> features{
303 std::reference_wrapper<const Feature>(source_features),
304 std::reference_wrapper<const Feature>(target_features)};
305 std::array<int, 2> num_pts{int(source_features.
data_.cols()),
306 int(target_features.
data_.cols())};
307 std::vector<CorrespondenceSet> corres(num_searches);
310 const int kOuterThreads = std::min(kMaxThreads, num_searches);
311 const int kInnerThreads = std::max(kMaxThreads / num_searches, 1);
314 #pragma omp parallel for num_threads(kOuterThreads)
315 for (
int k = 0; k < num_searches; ++k) {
318 int num_pts_k = num_pts[k];
320 #pragma omp parallel for num_threads(kInnerThreads)
321 for (
int i = 0; i < num_pts_k; i++) {
322 std::vector<int> corres_tmp(1);
323 std::vector<double> dist_tmp(1);
325 kdtree.
SearchKNN(Eigen::VectorXd(features[k].get().data_.col(i)), 1,
326 corres_tmp, dist_tmp);
327 int j = corres_tmp[0];
333 if (!mutual_filter)
return corres[0];
337 int num_src_pts = num_pts[0];
338 for (
int i = 0; i < num_src_pts; ++i) {
339 int j = corres[0][i](1);
340 if (corres[1][j](1) == i) {
341 corres_mutual.emplace_back(i, j);
346 if (
int(corres_mutual.size()) >=
347 int(mutual_consistent_ratio * num_src_pts)) {
349 corres_mutual.size());
350 return corres_mutual;
353 "Too few correspondences ({:d}) after mutual filter, fall back to "
354 "original correspondences.",
355 corres_mutual.size());
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
Eigen::Vector3d getEigenNormal(size_t index) const
bool hasNormals() const override
Returns whether normals are enabled or not.
unsigned size() const override
Eigen::Vector3d getEigenPoint(size_t index) const
KDTree with FLANN for nearest neighbor search.
int SearchKNN(const T &query, int knn, std::vector< int > &indices, std::vector< double > &distance2) const
int Search(const T &query, const KDTreeSearchParam ¶m, std::vector< int > &indices, std::vector< double > &distance2) const
KDTree search parameters for hybrid KNN and radius search.
KDTree search parameters for pure KNN search.
Base class for KDTree search parameters.
SearchType GetSearchType() const
Get the search type (KNN, Radius, Hybrid) for the search parameter.
Class to store featrues for registration.
std::shared_ptr< Feature > SelectByIndex(const std::vector< size_t > &indices, bool invert=false) const
Selects features from input Feature group, with indices in indices, and returns a new Feature group w...
Eigen::MatrixXd data_
Data buffer storing features.
static std::shared_ptr< Feature > ComputeSPFHFeature(const ccPointCloud &input, const geometry::KDTreeFlann &kdtree, const geometry::KDTreeSearchParam &search_param, const utility::optional< std::vector< size_t >> &indices=utility::nullopt)
static Eigen::Vector4d ComputePairFeatures(const Eigen::Vector3d &p1, const Eigen::Vector3d &n1, const Eigen::Vector3d &p2, const Eigen::Vector3d &n2)
int EstimateMaxThreads()
Estimate the maximum number of threads to be used in a parallel region.
std::vector< Eigen::Vector2i > CorrespondenceSet
MiniVec< float, N > floor(const MiniVec< float, N > &a)
constexpr nullopt_t nullopt
std::shared_ptr< Feature > ComputeFPFHFeature(const ccPointCloud &input, const geometry::KDTreeSearchParam &search_param=geometry::KDTreeSearchParamKNN(), const utility::optional< std::vector< size_t >> &indices=utility::nullopt)
CorrespondenceSet CorrespondencesFromFeatures(const Feature &source_features, const Feature &target_features, bool mutual_filter=false, float mutual_consistency_ratio=0.1)
Function to find correspondences via 1-nearest neighbor feature matching. Target is used to construct...
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 2, 1 > Vector2i