diff --git a/include/laser_scan_utils/icp.h b/include/laser_scan_utils/icp.h index c05911203fa1000a5d0e9391bd9526fd80a147f2..ec849b4ad01a660b7dda27dd0d525c65a01053d6 100644 --- a/include/laser_scan_utils/icp.h +++ b/include/laser_scan_utils/icp.h @@ -39,6 +39,7 @@ namespace laserscanutils Eigen::Matrix3s res_covar; // Covariance of the transformation int nvalid; // Number of valid correspondences in the match double error; // Total correspondence error + unsigned int attempts; // Number of ICP calls to obtain a valid result (<= params.icp_attempts) }; struct icpParams @@ -122,6 +123,10 @@ namespace laserscanutils double cov_factor; // Factor multiplying the cov output of csm double cov_max_eigv_factor; // Factor multiplying the direction of the max eigenvalue of the cov output of csm + // Attempts ------------------------------------------------------------------ + unsigned int icp_attempts; // number of icp attempts if result fails (not valid or error > restart_threshold_mean_error) + double perturbation_new_attempts; // perturbation noise amplitude applied to initial guess in new attempts + void print() const { std::cout << "verbose: " << std::to_string(verbose) << std::endl; @@ -170,7 +175,7 @@ namespace laserscanutils 1e-4, // double epsilon_xy 1e-3, // double epsilon_theta false, // bool restart - 0, // double restart_threshold_mean_error + 1e-1, // double restart_threshold_mean_error 0, // double restart_dt 0, // double restart_dtheta 0.023, // double min_reading @@ -189,7 +194,9 @@ namespace laserscanutils 0.2, // double sigma true, // bool do_compute_covariance 5, // double cov_factor - 2 // double cov_max_eigv_factor + 2, // double cov_max_eigv_factor + 1, // unsigned int attempts + 1e-1 // double perturbation_new_attempts }; class ICP diff --git a/src/icp.cpp b/src/icp.cpp index 354a905d86521fac7a72650b6cac461a32849e03..066786e41941cf06cd10ef7b237bf9c66edd8b28 100644 --- a/src/icp.cpp +++ b/src/icp.cpp @@ -26,66 +26,63 @@ using namespace laserscanutils; unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); -std::mt19937 generator (seed); +std::mt19937 generator(seed); std::uniform_real_distribution<double> dis(0.0, 1.0); class LDWrapper { - public: - LDP laser_data; +public: + LDP laser_data; - LDWrapper(const LaserScan& scan, const LaserScanParams& scan_params) + LDWrapper(const LaserScan &scan, const LaserScanParams &scan_params) + { + int num_rays = scan.ranges_raw_.size(); + laser_data = ld_alloc_new(num_rays); + int i = 0; + for (auto it : scan.ranges_raw_) { - int num_rays = scan.ranges_raw_.size(); - laser_data = ld_alloc_new(num_rays); - int i = 0; - for(auto it : scan.ranges_raw_) - { - laser_data->theta[i] = scan_params.angle_min_ + i*scan_params.angle_step_; + laser_data->theta[i] = scan_params.angle_min_ + i * scan_params.angle_step_; - if (scan_params.range_min_ <= it and - it <= scan_params.range_max_ and - 0 < it and it < 100) - { - laser_data->readings[i] = it; - laser_data->valid[i] = 1; - } - else - { - laser_data->readings[i] = NAN; - laser_data->valid[i] = 0; - } - laser_data->cluster[i] = -1; - ++i; + if (scan_params.range_min_ <= it and + it <= scan_params.range_max_ and + 0 < it and it < 100) + { + laser_data->readings[i] = it; + laser_data->valid[i] = 1; } + else + { + laser_data->readings[i] = NAN; + laser_data->valid[i] = 0; + } + laser_data->cluster[i] = -1; + ++i; + } - laser_data->min_theta = laser_data->theta[0]; - laser_data->max_theta = laser_data->theta[num_rays-1]; - - laser_data->odometry[0] = 0.0; - laser_data->odometry[1] = 0.0; - laser_data->odometry[2] = 0.0; + laser_data->min_theta = laser_data->theta[0]; + laser_data->max_theta = laser_data->theta[num_rays - 1]; - laser_data->true_pose[0] = 0.0; - laser_data->true_pose[1] = 0.0; - laser_data->true_pose[2] = 0.0; + laser_data->odometry[0] = 0.0; + laser_data->odometry[1] = 0.0; + laser_data->odometry[2] = 0.0; - } + laser_data->true_pose[0] = 0.0; + laser_data->true_pose[1] = 0.0; + laser_data->true_pose[2] = 0.0; + } - ~LDWrapper() - { - ld_free(laser_data); - } + ~LDWrapper() + { + ld_free(laser_data); + } }; ICP::ICP() { - } ICP::~ICP() { - } icpOutput ICP::align(const LaserScan &_current_ls, @@ -147,69 +144,97 @@ icpOutput ICP::align(const LaserScan &_current_ls, csm_input.use_ml_weights = _icp_params.use_ml_weights ? 1 : 0; csm_input.use_sigma_weights = _icp_params.use_sigma_weights ? 1 : 0; - try - { - sm_icp(&csm_input, &csm_output); - } - catch(...) - { - icpOutput result{}; - result.valid = false; - return result; - } - icpOutput result{}; - result.nvalid = csm_output.nvalid; - result.valid = csm_output.valid == 1; - result.error = csm_output.error; - - if (result.valid) + result.attempts = 0; + do { - result.res_transf(0) = csm_output.x[0]; - result.res_transf(1) = csm_output.x[1]; - result.res_transf(2) = csm_output.x[2]; + // perturb from 2nd attempt + if (result.attempts > 0) + { + Eigen::Vector3d initial_guess_perturbed = _initial_guess + Eigen::Vector3d::Random() * _icp_params.perturbation_new_attempts; + csm_input.first_guess[0] = initial_guess_perturbed(0); + csm_input.first_guess[1] = initial_guess_perturbed(1); + csm_input.first_guess[2] = initial_guess_perturbed(2); + } + + // call icp + result.attempts++; + if (_icp_params.verbose) + { + std::cout << "Calling ICP, attempt: " << result.attempts << std::endl; + } + try + { + sm_icp(&csm_input, &csm_output); + result.valid = csm_output.valid == 1; + } + catch (...) + { + result.valid = false; + } - if (csm_input.do_compute_covariance) + // VALID --> copy output (and modify covariance) + if (result.valid == 1) { - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - result.res_covar(i, j) = _icp_params.cov_factor * - csm_output.cov_x_m->data[i * csm_output.cov_x_m->tda + j]; - - // Grow covariance in the biggest eigenvalue direction - if (_icp_params.cov_max_eigv_factor - 1 > 1e-6) + result.nvalid = csm_output.nvalid; + result.error = csm_output.error; + result.res_transf(0) = csm_output.x[0]; + result.res_transf(1) = csm_output.x[1]; + result.res_transf(2) = csm_output.x[2]; + + if (csm_input.do_compute_covariance) { - Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> eigensolver(result.res_covar.topLeftCorner<2,2>()); + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + result.res_covar(i, j) = _icp_params.cov_factor * + csm_output.cov_x_m->data[i * csm_output.cov_x_m->tda + j]; - if (eigensolver.info() == Eigen::Success) + // Grow covariance in the biggest eigenvalue direction + if (_icp_params.cov_max_eigv_factor - 1 > 1e-6) { - Eigen::Vector2d eigvs = eigensolver.eigenvalues(); - Eigen::Index maxRow, maxCol; - float max_eigv = eigvs.maxCoeff(&maxRow, &maxCol); - - eigvs(maxRow) = _icp_params.cov_max_eigv_factor * max_eigv; - result.res_covar.topLeftCorner<2,2>() = eigensolver.eigenvectors() * - eigvs.asDiagonal() * - eigensolver.eigenvectors().transpose(); + Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> eigensolver(result.res_covar.topLeftCorner<2, 2>()); + + if (eigensolver.info() == Eigen::Success) + { + Eigen::Vector2d eigvs = eigensolver.eigenvalues(); + Eigen::Index maxRow, maxCol; + float max_eigv = eigvs.maxCoeff(&maxRow, &maxCol); + + eigvs(maxRow) = _icp_params.cov_max_eigv_factor * max_eigv; + result.res_covar.topLeftCorner<2, 2>() = eigensolver.eigenvectors() * + eigvs.asDiagonal() * + eigensolver.eigenvectors().transpose(); + } } } } - } - else - { - //std::cout << "ICP NOT VALID, providing first guess transformation and identity covariance\n"; - result.res_transf = _initial_guess; - result.res_covar = Eigen::Matrix3s::Identity(); - } + else + { + // std::cout << "ICP NOT VALID, providing first guess transformation and identity covariance\n"; + result.res_transf = _initial_guess; + result.res_covar = Eigen::Matrix3s::Identity(); + } + if (_icp_params.verbose and not result.valid and result.attempts < _icp_params.icp_attempts) + { + std::cout << "Invalid result, trying again!" << std::endl; + } + if (_icp_params.verbose and not result.valid and result.attempts < _icp_params.icp_attempts) + { + std::cout << "Error too big: " << result.error + << " ( should be < " + << _icp_params.restart_threshold_mean_error << "). Trying again!" << std::endl; + } + } while ((not result.valid or result.error > _icp_params.restart_threshold_mean_error) and + result.attempts < _icp_params.icp_attempts); return result; } -//Legacy code -icpOutput ICP::align(const LaserScan &_current_ls, - const LaserScan &_ref_ls, - const LaserScanParams& scan_params, - const icpParams &icp_params, +// Legacy code +icpOutput ICP::align(const LaserScan &_current_ls, + const LaserScan &_ref_ls, + const LaserScanParams &scan_params, + const icpParams &icp_params, const Eigen::Vector3s &_initial_guess) { return align(_current_ls, _ref_ls, scan_params, scan_params, icp_params, _initial_guess); @@ -223,11 +248,10 @@ void ICP::printLaserData(LDP &laser_data) void ICP::printTwoLaserData(sm_params ¶ms) { - for (int ii=0; ii<params.laser_ref->nrays-1; ++ii) + for (int ii = 0; ii < params.laser_ref->nrays - 1; ++ii) { std::cout << "Theta: " << params.laser_ref->theta[ii] << "; Readings: " - << params.laser_ref->readings[ii] << "; " << params.laser_sens->readings[ii] - << '\n'; + << params.laser_ref->readings[ii] << "; " << params.laser_sens->readings[ii] + << '\n'; } - } diff --git a/test/gtest_icp.cpp b/test/gtest_icp.cpp index a633fdc3575f5442e06757a2ec350461ac4748ef..991ffe0dafc10e53337296c09f97a61f9e66a895 100644 --- a/test/gtest_icp.cpp +++ b/test/gtest_icp.cpp @@ -26,7 +26,7 @@ using namespace laserscanutils; int N = 50; -int n_attempts = 5; +int n_attempts = 10; const Eigen::Vector2d A = Eigen::Vector2d::Zero(); const Eigen::Vector2d B = (Eigen::Vector2d() << 0, 40).finished(); @@ -268,6 +268,8 @@ TEST(TestIcp, TestIcpSame1) // icp auto icp_params = icp_params_default; + icp_params.icp_attempts = n_attempts; + // icp_params.verbose = true; // no perturbation std::cout << "\n//////////// TestIcpSame1: random problem " << i << " pose: " << pose.transpose() << std::endl; @@ -288,31 +290,17 @@ TEST(TestIcp, TestIcpSame1) // perturbation std::cout << "------ WITH perturbation: " << pert << std::endl; Eigen::Vector3d initial_guess = Eigen::Vector3d::Random() * pert; - int n = 1; icp_output = ICP::align(scan, scan, scan_params, icp_params, initial_guess); - while (not icp_output.valid or icp_output.error > 1e-1) - { - n++; - initial_guess = Eigen::Vector3d::Random() * pert; - icp_output = ICP::align(scan, - scan, - scan_params, - icp_params, - initial_guess); - if (n == n_attempts) - break; - } - std::cout << " initial_guess: " << initial_guess.transpose() << std::endl; std::cout << " icp_output: " << icp_output.res_transf.transpose() << std::endl; std::cout << " icp error: " << icp_output.error << std::endl; std::cout << " icp valid: " << icp_output.valid << std::endl; - std::cout << " icp attempts: " << n << std::endl; + std::cout << " icp attempts: " << icp_output.attempts << std::endl; ASSERT_TRUE(icp_output.valid); EXPECT_MATRIX_APPROX(icp_output.res_transf, Eigen::Vector3d::Zero(), 1e-1); @@ -326,46 +314,14 @@ TEST(TestIcp, TestIcpSame2) // 0,360 (1 degrees step) LaserScanParams scan_params = generateLaserScanParams(0, 1); double pert = 0.0; + auto icp_params = icp_params_default; + icp_params.icp_attempts = n_attempts; for (auto i = 0; i < N; i++) { auto pose = generateRandomPoseInsideTriangle(); auto scan = simulateScan(pose, scan_params); - // icp - auto icp_params = icp_params_default; - icp_params.verbose = false; // false, // bool verbose (prints debug messages) - icp_params.use_point_to_line_distance = false; // true, // bool use_point_to_line_distance - // 5.0, // double max_angular_correction_deg - // 1, // double max_linear_correction - // 0.5, // double max_correspondence_dist - icp_params.use_corr_tricks = false; // false, // bool use_corr_tricks - icp_params.debug_verify_tricks = false; // false, // bool debug_verify_tricks - // 50, // int max_iterations - // 1e-4, // double epsilon_xy - // 1e-3, // double epsilon_theta - icp_params.restart = true; // false, // bool restart - icp_params.restart_threshold_mean_error = 0.1; // 0, // double restart_threshold_mean_error - // 0, // double restart_dt - // 0, // double restart_dtheta - // 0.023, // double min_reading - // 60, // max_reading - // 1, // double outliers_maxPerc - // 0.8, // double outliers_adaptive_order - // 2, // double outliers_adaptive_mult - // false, // bool outliers_remove_doubles - // false, // bool do_visibility_test - // false, // bool do_alpha_test - // 10, // double do_alpha_test_thresholdDeg - // 0.5, // double clustering_threshold - // 4, // int orientation_neighbourhood - // false, // bool use_ml_weights - // false, // bool use_sigma_weights - // 0.2, // double sigma - // true, // bool do_compute_covariance - // 5, // double cov_factor - // 2 // double cov_max_eigv_factor - // no perturbation std::cout << "\n//////////// TestIcpSame2: random problem " << i << " pose: " << pose.transpose() << std::endl; std::cout << "------ NO perturbation" << std::endl; @@ -385,31 +341,17 @@ TEST(TestIcp, TestIcpSame2) // perturbation std::cout << "------ WITH perturbation: " << pert << std::endl; Eigen::Vector3d initial_guess = Eigen::Vector3d::Random() * pert; - int n = 1; icp_output = ICP::align(scan, scan, scan_params, icp_params, initial_guess); - while (not icp_output.valid or icp_output.error > 1e-1) - { - n++; - initial_guess = Eigen::Vector3d::Random() * pert; - icp_output = ICP::align(scan, - scan, - scan_params, - icp_params, - initial_guess); - if (n == n_attempts) - break; - } - std::cout << " initial_guess: " << initial_guess.transpose() << std::endl; std::cout << " icp_output: " << icp_output.res_transf.transpose() << std::endl; std::cout << " icp error: " << icp_output.error << std::endl; std::cout << " icp valid: " << icp_output.valid << std::endl; - std::cout << " icp attempts: " << n << std::endl; + std::cout << " icp attempts: " << icp_output.attempts << std::endl; ASSERT_TRUE(icp_output.valid); EXPECT_MATRIX_APPROX(icp_output.res_transf, Eigen::Vector3d::Zero(), 1e-1); @@ -426,35 +368,25 @@ TEST(TestIcp, TestIcp1) Eigen::Vector3d pose_ref, pose_tar, pose_d, initial_guess; LaserScan scan_ref, scan_tar; + auto icp_params = icp_params_default; + icp_params.icp_attempts = n_attempts; + double pert = 0.0; + for (auto i = 0; i < N; i++) { // Random problem generateRandomProblem(pose_ref, pose_tar, pose_d, scan_params, scan_ref, scan_tar); - // icp - auto icp_params = icp_params_default; - int n = 1; + initial_guess = pose_d + pert * Eigen::Vector3d::Random(); + // icp auto icp_output = ICP::align(scan_tar, scan_ref, scan_params, icp_params, initial_guess); - while (not icp_output.valid or icp_output.error > 1e-1) - { - n++; - initial_guess = pose_d + pert * Eigen::Vector3d::Random(); - icp_output = ICP::align(scan_tar, - scan_ref, - scan_params, - icp_params, - initial_guess); - if (n == n_attempts) - break; - } - std::cout << "\n//////////// TestIcp1: random problem " << i << " - perturbation: " << pert << std::endl; std::cout << " pose_ref: " << pose_ref.transpose() << std::endl; std::cout << " pose_tar: " << pose_tar.transpose() << std::endl; @@ -463,7 +395,7 @@ TEST(TestIcp, TestIcp1) std::cout << " icp_output: " << icp_output.res_transf.transpose() << std::endl; std::cout << " icp error: " << icp_output.error << std::endl; std::cout << " icp valid: " << icp_output.valid << std::endl; - std::cout << " icp attempts: " << n << std::endl; + std::cout << " icp attempts: " << icp_output.attempts << std::endl; std::cout << " d error: " << (pose_d - icp_output.res_transf).transpose() << std::endl; ASSERT_TRUE(icp_output.valid); @@ -481,7 +413,11 @@ TEST(TestIcp, TestIcp10) Eigen::Vector3d pose_ref, pose_tar, pose_d, initial_guess; LaserScan scan_ref, scan_tar; + auto icp_params = icp_params_default; + icp_params.icp_attempts = n_attempts; + double pert = 0.0; + for (auto i = 0; i < N; i++) { // Random problem @@ -490,26 +426,11 @@ TEST(TestIcp, TestIcp10) initial_guess = pose_d + pert * Eigen::Vector3d::Random(); // icp - auto icp_params = icp_params_default; - int n = 1; - auto icp_output = ICP::align(scan_tar, scan_ref, scan_params, icp_params, initial_guess); - while (not icp_output.valid or icp_output.error > 1e-1) - { - n++; - initial_guess = pose_d + pert * Eigen::Vector3d::Random(); - icp_output = ICP::align(scan_tar, - scan_ref, - scan_params, - icp_params, - initial_guess); - if (n == n_attempts) - break; - } std::cout << "\n//////////// TestIcp10: random problem " << i << " - perturbation: " << pert << std::endl; std::cout << " pose_ref: " << pose_ref.transpose() << std::endl; @@ -519,7 +440,7 @@ TEST(TestIcp, TestIcp10) std::cout << " icp_output: " << icp_output.res_transf.transpose() << std::endl; std::cout << " icp error: " << icp_output.error << std::endl; std::cout << " icp valid: " << icp_output.valid << std::endl; - std::cout << " icp attempts: " << n << std::endl; + std::cout << " icp attempts: " << icp_output.attempts << std::endl; std::cout << " d error: " << (pose_d - icp_output.res_transf).transpose() << std::endl; ASSERT_TRUE(icp_output.valid);