00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040 #include <gtest/gtest.h>
00041
00042 #include <pcl/point_types.h>
00043 #include <pcl/io/pcd_io.h>
00044 #include <pcl/features/normal_3d.h>
00045 #include <pcl/features/fpfh.h>
00046 #include <pcl/registration/registration.h>
00047 #include <pcl/registration/icp.h>
00048 #include <pcl/registration/icp_nl.h>
00049 #include <pcl/registration/transformation_estimation_point_to_plane.h>
00050 #include <pcl/registration/transformation_validation_euclidean.h>
00051 #include <pcl/registration/transformation_estimation_point_to_plane_lls.h>
00052 #include <pcl/registration/ia_ransac.h>
00053 #include <pcl/registration/pyramid_feature_matching.h>
00054 #include <pcl/features/ppf.h>
00055 #include <pcl/registration/ppf_registration.h>
00056
00057 #include <pcl/kdtree/impl/kdtree_flann.hpp>
00058
00059
00060 using namespace pcl;
00061 using namespace pcl::io;
00062 using namespace std;
00063
00064 PointCloud<PointXYZ> cloud_source, cloud_target, cloud_reg;
00065
00066 template <typename PointSource, typename PointTarget>
00067 class RegistrationWrapper : public Registration<PointSource, PointTarget>
00068 {
00069 public:
00070 void computeTransformation (pcl::PointCloud<PointSource> &, const Eigen::Matrix4f& ) { }
00071
00072 bool hasValidFeaturesTest ()
00073 {
00074 return (this->hasValidFeatures ());
00075 }
00076 void findFeatureCorrespondencesTest (int index, std::vector<int> &correspondence_indices)
00077 {
00078 this->findFeatureCorrespondences (index, correspondence_indices);
00079 }
00080 };
00081
00083 TEST (PCL, findFeatureCorrespondences)
00084 {
00085 typedef Histogram<2> FeatureT;
00086 typedef PointCloud<FeatureT> FeatureCloud;
00087 typedef FeatureCloud::ConstPtr FeatureCloudConstPtr;
00088
00089 RegistrationWrapper <PointXYZ, PointXYZ> reg;
00090
00091 FeatureCloud feature0, feature1, feature2, feature3;
00092 feature0.height = feature1.height = feature2.height = feature3.height = 1;
00093 feature0.is_dense = feature1.is_dense = feature2.is_dense = feature3.is_dense = true;
00094
00095 for (float x = -5.0f; x <= 5.0f; x += 0.2f)
00096 {
00097 for (float y = -5.0f; y <= 5.0f; y += 0.2f)
00098 {
00099 FeatureT f;
00100 f.histogram[0] = x;
00101 f.histogram[1] = y;
00102 feature0.points.push_back (f);
00103
00104 f.histogram[0] = x;
00105 f.histogram[1] = y - 2.5f;
00106 feature1.points.push_back (f);
00107
00108 f.histogram[0] = x - 2.0f;
00109 f.histogram[1] = y + 1.5f;
00110 feature2.points.push_back (f);
00111
00112 f.histogram[0] = x + 2.0f;
00113 f.histogram[1] = y + 1.5f;
00114 feature3.points.push_back (f);
00115 }
00116 }
00117 feature0.width = static_cast<uint32_t> (feature0.points.size ());
00118 feature1.width = static_cast<uint32_t> (feature1.points.size ());
00119 feature2.width = static_cast<uint32_t> (feature2.points.size ());
00120 feature3.width = static_cast<uint32_t> (feature3.points.size ());
00121
00122 KdTreeFLANN<FeatureT> tree;
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150 }
00151
00153 TEST (PCL, IterativeClosestPoint)
00154 {
00155 IterativeClosestPoint<PointXYZ, PointXYZ> reg;
00156 reg.setInputCloud (cloud_source.makeShared ());
00157 reg.setInputTarget (cloud_target.makeShared ());
00158 reg.setMaximumIterations (50);
00159 reg.setTransformationEpsilon (1e-8);
00160 reg.setMaxCorrespondenceDistance (0.05);
00161
00162
00163 reg.align (cloud_reg);
00164 EXPECT_EQ (int (cloud_reg.points.size ()), int (cloud_source.points.size ()));
00165
00166 Eigen::Matrix4f transformation = reg.getFinalTransformation ();
00167
00168 EXPECT_NEAR (transformation (0, 0), 0.8806, 1e-3);
00169 EXPECT_NEAR (transformation (0, 1), 0.036481287330389023, 1e-2);
00170 EXPECT_NEAR (transformation (0, 2), -0.4724, 1e-3);
00171 EXPECT_NEAR (transformation (0, 3), 0.03453, 1e-3);
00172
00173 EXPECT_NEAR (transformation (1, 0), -0.02354, 1e-3);
00174 EXPECT_NEAR (transformation (1, 1), 0.9992, 1e-3);
00175 EXPECT_NEAR (transformation (1, 2), 0.03326, 1e-3);
00176 EXPECT_NEAR (transformation (1, 3), -0.001519, 1e-3);
00177
00178 EXPECT_NEAR (transformation (2, 0), 0.4732, 1e-3);
00179 EXPECT_NEAR (transformation (2, 1), -0.01817, 1e-3);
00180 EXPECT_NEAR (transformation (2, 2), 0.8808, 1e-3);
00181 EXPECT_NEAR (transformation (2, 3), 0.04116, 1e-3);
00182
00183 EXPECT_EQ (transformation (3, 0), 0);
00184 EXPECT_EQ (transformation (3, 1), 0);
00185 EXPECT_EQ (transformation (3, 2), 0);
00186 EXPECT_EQ (transformation (3, 3), 1);
00187 }
00188
00190 TEST (PCL, IterativeClosestPointNonLinear)
00191 {
00192 typedef PointXYZRGB PointT;
00193 PointCloud<PointT>::Ptr temp_src (new PointCloud<PointT>);
00194 copyPointCloud (cloud_source, *temp_src);
00195 PointCloud<PointT>::Ptr temp_tgt (new PointCloud<PointT>);
00196 copyPointCloud (cloud_target, *temp_tgt);
00197 PointCloud<PointT> output;
00198
00199 IterativeClosestPointNonLinear<PointT, PointT> reg;
00200 reg.setInputCloud (temp_src);
00201 reg.setInputTarget (temp_tgt);
00202 reg.setMaximumIterations (50);
00203 reg.setTransformationEpsilon (1e-8);
00204
00205
00206 reg.align (output);
00207 EXPECT_EQ (int (output.points.size ()), int (cloud_source.points.size ()));
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235 EXPECT_LT (reg.getFitnessScore (), 0.001);
00236 }
00237
00239 TEST (PCL, IterativeClosestPoint_PointToPlane)
00240 {
00241 typedef PointNormal PointT;
00242 PointCloud<PointT>::Ptr src (new PointCloud<PointT>);
00243 copyPointCloud (cloud_source, *src);
00244 PointCloud<PointT>::Ptr tgt (new PointCloud<PointT>);
00245 copyPointCloud (cloud_target, *tgt);
00246 PointCloud<PointT> output;
00247
00248 NormalEstimation<PointNormal, PointNormal> norm_est;
00249 norm_est.setSearchMethod (search::KdTree<PointNormal>::Ptr (new search::KdTree<PointNormal>));
00250 norm_est.setKSearch (10);
00251 norm_est.setInputCloud (tgt);
00252 norm_est.compute (*tgt);
00253
00254 IterativeClosestPoint<PointT, PointT> reg;
00255 typedef registration::TransformationEstimationPointToPlane<PointT, PointT> PointToPlane;
00256 boost::shared_ptr<PointToPlane> point_to_plane (new PointToPlane);
00257 reg.setTransformationEstimation (point_to_plane);
00258 reg.setInputCloud (src);
00259 reg.setInputTarget (tgt);
00260 reg.setMaximumIterations (50);
00261 reg.setTransformationEpsilon (1e-8);
00262
00263
00264 reg.align (output);
00265 EXPECT_EQ (int (output.points.size ()), int (cloud_source.points.size ()));
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293 EXPECT_LT (reg.getFitnessScore (), 0.001);
00294 }
00295
00297 TEST (PCL, TransformationEstimationPointToPlaneLLS)
00298 {
00299 registration::TransformationEstimationPointToPlaneLLS<PointNormal, PointNormal> tform_est;
00300
00301
00302 PointCloud<PointNormal>::Ptr src (new PointCloud<PointNormal>);
00303 src->height = 1;
00304 src->is_dense = true;
00305 for (float x = -5.0f; x <= 5.0f; x += 0.5f)
00306 {
00307 for (float y = -5.0f; y <= 5.0f; y += 0.5f)
00308 {
00309 PointNormal p;
00310 p.x = x;
00311 p.y = y;
00312 p.z = 0.1f * powf (x, 2.0f) + 0.2f * p.x * p.y - 0.3f * y + 1.0f;
00313 float & nx = p.normal[0];
00314 float & ny = p.normal[1];
00315 float & nz = p.normal[2];
00316 nx = -0.2f * p.x - 0.2f;
00317 ny = 0.6f * p.y - 0.2f;
00318 nz = 1.0f;
00319
00320 float magnitude = sqrtf (nx * nx + ny * ny + nz * nz);
00321 nx /= magnitude;
00322 ny /= magnitude;
00323 nz /= magnitude;
00324
00325 src->points.push_back (p);
00326 }
00327 }
00328 src->width = static_cast<uint32_t> (src->points.size ());
00329
00330
00331 Eigen::Matrix4f ground_truth_tform = Eigen::Matrix4f::Identity ();
00332 ground_truth_tform.row (0) << 0.9938f, 0.0988f, 0.0517f, 0.1000f;
00333 ground_truth_tform.row (1) << -0.0997f, 0.9949f, 0.0149f, -0.2000f;
00334 ground_truth_tform.row (2) << -0.0500f, -0.0200f, 0.9986f, 0.3000f;
00335 ground_truth_tform.row (3) << 0.0000f, 0.0000f, 0.0000f, 1.0000f;
00336
00337 PointCloud<PointNormal>::Ptr tgt (new PointCloud<PointNormal>);
00338
00339 transformPointCloudWithNormals (*src, *tgt, ground_truth_tform);
00340
00341 Eigen::Matrix4f estimated_tform;
00342 tform_est.estimateRigidTransformation (*src, *tgt, estimated_tform);
00343
00344 for (int i = 0; i < 4; ++i)
00345 for (int j = 0; j < 4; ++j)
00346 EXPECT_NEAR (estimated_tform (i, j), ground_truth_tform (i, j), 1e-2);
00347 }
00348
00350 TEST (PCL, SampleConsensusInitialAlignment)
00351 {
00352
00353 Eigen::Vector3f initial_offset (100, 0, 0);
00354 float angle = static_cast<float> (M_PI) / 2.0f;
00355 Eigen::Quaternionf initial_rotation (cos (angle / 2), 0, 0, sin (angle / 2));
00356 PointCloud<PointXYZ> cloud_source_transformed;
00357 transformPointCloud (cloud_source, cloud_source_transformed, initial_offset, initial_rotation);
00358
00359
00360 PointCloud<PointXYZ>::Ptr cloud_source_ptr, cloud_target_ptr;
00361 cloud_source_ptr = cloud_source_transformed.makeShared ();
00362 cloud_target_ptr = cloud_target.makeShared ();
00363
00364
00365 search::KdTree<PointXYZ>::Ptr tree (new search::KdTree<PointXYZ>);
00366
00367 NormalEstimation<PointXYZ, Normal> norm_est;
00368 norm_est.setSearchMethod (tree);
00369 norm_est.setRadiusSearch (0.05);
00370 PointCloud<Normal> normals;
00371
00372 FPFHEstimation<PointXYZ, Normal, FPFHSignature33> fpfh_est;
00373 fpfh_est.setSearchMethod (tree);
00374 fpfh_est.setRadiusSearch (0.05);
00375 PointCloud<FPFHSignature33> features_source, features_target;
00376
00377
00378 norm_est.setInputCloud (cloud_source_ptr);
00379 norm_est.compute (normals);
00380 fpfh_est.setInputCloud (cloud_source_ptr);
00381 fpfh_est.setInputNormals (normals.makeShared ());
00382 fpfh_est.compute (features_source);
00383
00384
00385 norm_est.setInputCloud (cloud_target_ptr);
00386 norm_est.compute (normals);
00387 fpfh_est.setInputCloud (cloud_target_ptr);
00388 fpfh_est.setInputNormals (normals.makeShared ());
00389 fpfh_est.compute (features_target);
00390
00391
00392 SampleConsensusInitialAlignment<PointXYZ, PointXYZ, FPFHSignature33> reg;
00393 reg.setMinSampleDistance (0.05f);
00394 reg.setMaxCorrespondenceDistance (0.2);
00395 reg.setMaximumIterations (1000);
00396
00397 reg.setInputCloud (cloud_source_ptr);
00398 reg.setInputTarget (cloud_target_ptr);
00399 reg.setSourceFeatures (features_source.makeShared ());
00400 reg.setTargetFeatures (features_target.makeShared ());
00401
00402
00403 reg.align (cloud_reg);
00404 EXPECT_EQ (int (cloud_reg.points.size ()), int (cloud_source.points.size ()));
00405 EXPECT_EQ (reg.getFitnessScore () < 0.0005, true);
00406 }
00407
00409 TEST (PCL, PyramidFeatureHistogram)
00410 {
00411
00412 PointCloud<PointXYZ>::Ptr cloud_source_ptr = cloud_source.makeShared (),
00413 cloud_target_ptr = cloud_target.makeShared ();
00414
00415 PointCloud<Normal>::Ptr cloud_source_normals (new PointCloud<Normal> ()),
00416 cloud_target_normals (new PointCloud<Normal> ());
00417 search::KdTree<PointXYZ>::Ptr tree (new search::KdTree<PointXYZ>);
00418 NormalEstimation<PointXYZ, Normal> normal_estimator;
00419 normal_estimator.setSearchMethod (tree);
00420 normal_estimator.setRadiusSearch (0.05);
00421 normal_estimator.setInputCloud (cloud_source_ptr);
00422 normal_estimator.compute (*cloud_source_normals);
00423
00424 normal_estimator.setInputCloud (cloud_target_ptr);
00425 normal_estimator.compute (*cloud_target_normals);
00426
00427
00428 PointCloud<PPFSignature>::Ptr ppf_signature_source (new PointCloud<PPFSignature> ()),
00429 ppf_signature_target (new PointCloud<PPFSignature> ());
00430 PPFEstimation<PointXYZ, Normal, PPFSignature> ppf_estimator;
00431 ppf_estimator.setInputCloud (cloud_source_ptr);
00432 ppf_estimator.setInputNormals (cloud_source_normals);
00433 ppf_estimator.compute (*ppf_signature_source);
00434
00435 ppf_estimator.setInputCloud (cloud_target_ptr);
00436 ppf_estimator.setInputNormals (cloud_target_normals);
00437 ppf_estimator.compute (*ppf_signature_target);
00438
00439
00440 vector<pair<float, float> > dim_range_input, dim_range_target;
00441 for (size_t i = 0; i < 3; ++i) dim_range_input.push_back (pair<float, float> (static_cast<float> (-M_PI), static_cast<float> (M_PI)));
00442 dim_range_input.push_back (pair<float, float> (0.0f, 1.0f));
00443 for (size_t i = 0; i < 3; ++i) dim_range_target.push_back (pair<float, float> (static_cast<float> (-M_PI) * 10.0f, static_cast<float> (M_PI) * 10.0f));
00444 dim_range_target.push_back (pair<float, float> (0.0f, 50.0f));
00445
00446
00447 PyramidFeatureHistogram<PPFSignature>::Ptr pyramid_source (new PyramidFeatureHistogram<PPFSignature> ()),
00448 pyramid_target (new PyramidFeatureHistogram<PPFSignature> ());
00449 pyramid_source->setInputCloud (ppf_signature_source);
00450 pyramid_source->setInputDimensionRange (dim_range_input);
00451 pyramid_source->setTargetDimensionRange (dim_range_target);
00452 pyramid_source->compute ();
00453
00454 pyramid_target->setInputCloud (ppf_signature_target);
00455 pyramid_target->setInputDimensionRange (dim_range_input);
00456 pyramid_target->setTargetDimensionRange (dim_range_target);
00457 pyramid_target->compute ();
00458
00459 float similarity_value = PyramidFeatureHistogram<PPFSignature>::comparePyramidFeatureHistograms (pyramid_source, pyramid_target);
00460 EXPECT_NEAR (similarity_value, 0.74101555347442627, 1e-4);
00461
00462 vector<pair<float, float> > dim_range_target2;
00463 for (size_t i = 0; i < 3; ++i) dim_range_target2.push_back (pair<float, float> (static_cast<float> (-M_PI) * 5.0f, static_cast<float> (M_PI) * 5.0f));
00464 dim_range_target2.push_back (pair<float, float> (0.0f, 20.0f));
00465
00466 pyramid_source->setTargetDimensionRange (dim_range_target2);
00467 pyramid_source->compute ();
00468
00469 pyramid_target->setTargetDimensionRange (dim_range_target2);
00470 pyramid_target->compute ();
00471
00472 float similarity_value2 = PyramidFeatureHistogram<PPFSignature>::comparePyramidFeatureHistograms (pyramid_source, pyramid_target);
00473 EXPECT_NEAR (similarity_value2, 0.80097091197967529, 1e-4);
00474
00475
00476 vector<pair<float, float> > dim_range_target3;
00477 for (size_t i = 0; i < 3; ++i) dim_range_target3.push_back (pair<float, float> (static_cast<float> (-M_PI) * 2.0f, static_cast<float> (M_PI) * 2.0f));
00478 dim_range_target3.push_back (pair<float, float> (0.0f, 10.0f));
00479
00480 pyramid_source->setTargetDimensionRange (dim_range_target3);
00481 pyramid_source->compute ();
00482
00483 pyramid_target->setTargetDimensionRange (dim_range_target3);
00484 pyramid_target->compute ();
00485
00486 float similarity_value3 = PyramidFeatureHistogram<PPFSignature>::comparePyramidFeatureHistograms (pyramid_source, pyramid_target);
00487 EXPECT_NEAR (similarity_value3, 0.87623238563537598, 1e-3);
00488 }
00489
00490
00491
00492 #if 0
00493
00494 TEST (PCL, PPFRegistration)
00495 {
00496
00497 Eigen::Vector3f initial_offset (100, 0, 0);
00498 float angle = M_PI/6;
00499 Eigen::Quaternionf initial_rotation (cos (angle / 2), 0, 0, sin (angle / 2));
00500 PointCloud<PointXYZ> cloud_source_transformed;
00501 transformPointCloud (cloud_source, cloud_source_transformed, initial_offset, initial_rotation);
00502
00503
00504
00505 PointCloud<PointXYZ>::Ptr cloud_source_ptr, cloud_target_ptr, cloud_source_transformed_ptr;
00506 cloud_source_transformed_ptr = cloud_source_transformed.makeShared ();
00507 cloud_target_ptr = cloud_target.makeShared ();
00508
00509
00510 NormalEstimation<PointXYZ, Normal> normal_estimation;
00511 search::KdTree<PointXYZ>::Ptr search_tree (new search::KdTree<PointXYZ> ());
00512 normal_estimation.setSearchMethod (search_tree);
00513 normal_estimation.setRadiusSearch (0.05);
00514 PointCloud<Normal>::Ptr normals_target (new PointCloud<Normal> ()),
00515 normals_source_transformed (new PointCloud<Normal> ());
00516 normal_estimation.setInputCloud (cloud_target_ptr);
00517 normal_estimation.compute (*normals_target);
00518 normal_estimation.setInputCloud (cloud_source_transformed_ptr);
00519 normal_estimation.compute (*normals_source_transformed);
00520
00521 PointCloud<PointNormal>::Ptr cloud_target_with_normals (new PointCloud<PointNormal> ()),
00522 cloud_source_transformed_with_normals (new PointCloud<PointNormal> ());
00523 concatenateFields (*cloud_target_ptr, *normals_target, *cloud_target_with_normals);
00524 concatenateFields (*cloud_source_transformed_ptr, *normals_source_transformed, *cloud_source_transformed_with_normals);
00525
00526
00527 PPFEstimation<PointXYZ, Normal, PPFSignature> ppf_estimator;
00528 PointCloud<PPFSignature>::Ptr features_source_transformed (new PointCloud<PPFSignature> ());
00529 ppf_estimator.setInputCloud (cloud_source_transformed_ptr);
00530 ppf_estimator.setInputNormals (normals_source_transformed);
00531 ppf_estimator.compute (*features_source_transformed);
00532
00533
00534
00535 PPFHashMapSearch::Ptr hash_map_search (new PPFHashMapSearch (15.0 / 180 * M_PI,
00536 0.05));
00537 hash_map_search->setInputFeatureCloud (features_source_transformed);
00538
00539
00540 PPFRegistration<PointNormal, PointNormal> ppf_registration;
00541 ppf_registration.setSceneReferencePointSamplingRate (20);
00542 ppf_registration.setPositionClusteringThreshold (0.15);
00543 ppf_registration.setRotationClusteringThreshold (45.0 / 180 * M_PI);
00544 ppf_registration.setSearchMethod (hash_map_search);
00545 ppf_registration.setInputCloud (cloud_source_transformed_with_normals);
00546 ppf_registration.setInputTarget (cloud_target_with_normals);
00547
00548 PointCloud<PointNormal> cloud_output;
00549 ppf_registration.align (cloud_output);
00550 Eigen::Matrix4f transformation = ppf_registration.getFinalTransformation ();
00551
00552 EXPECT_NEAR (transformation(0, 0), -0.153768, 1e-4);
00553 EXPECT_NEAR (transformation(0, 1), -0.628136, 1e-4);
00554 EXPECT_NEAR (transformation(0, 2), 0.762759, 1e-4);
00555 EXPECT_NEAR (transformation(0, 3), 15.472, 1e-4);
00556 EXPECT_NEAR (transformation(1, 0), 0.967397, 1e-4);
00557 EXPECT_NEAR (transformation(1, 1), -0.252918, 1e-4);
00558 EXPECT_NEAR (transformation(1, 2), -0.0132578, 1e-4);
00559 EXPECT_NEAR (transformation(1, 3), -96.6221, 1e-4);
00560 EXPECT_NEAR (transformation(2, 0), 0.201243, 1e-4);
00561 EXPECT_NEAR (transformation(2, 1), 0.735852, 1e-4);
00562 EXPECT_NEAR (transformation(2, 2), 0.646547, 1e-4);
00563 EXPECT_NEAR (transformation(2, 3), -20.134, 1e-4);
00564 EXPECT_NEAR (transformation(3, 0), 0.000000, 1e-4);
00565 EXPECT_NEAR (transformation(3, 1), 0.000000, 1e-4);
00566 EXPECT_NEAR (transformation(3, 2), 0.000000, 1e-4);
00567 EXPECT_NEAR (transformation(3, 3), 1.000000, 1e-4);
00568 }
00569 #endif
00570
00571
00572 int
00573 main (int argc, char** argv)
00574 {
00575 if (argc < 3)
00576 {
00577 std::cerr << "No test files given. Please download `bun0.pcd` and `bun4.pcd` and pass their path to the test." << std::endl;
00578 return (-1);
00579 }
00580
00581
00582 if (loadPCDFile (argv[1], cloud_source) < 0)
00583 {
00584 std::cerr << "Failed to read test file. Please download `bun0.pcd` and pass its path to the test." << std::endl;
00585 return (-1);
00586 }
00587 if (loadPCDFile (argv[2], cloud_target) < 0)
00588 {
00589 std::cerr << "Failed to read test file. Please download `bun4.pcd` and pass its path to the test." << std::endl;
00590 return (-1);
00591 }
00592
00593 testing::InitGoogleTest (&argc, argv);
00594 return (RUN_ALL_TESTS ());
00595
00596
00597
00598
00599
00600
00601 }
00602