/* * icp6D implementation * * Copyright (C) Andreas Nuechter, Kai Lingemann * * Released under the GPL version 3. * */ /** * @file * @brief Implementation of 3D scan matching with ICP * @author Kai Lingemann. Institute of Computer Science, University of Osnabrueck, Germany. * @author Andreas Nuechter. Institute of Computer Science, University of Osnabrueck, Germany. */ #include "slam6d/icp6D.h" #include "slam6d/metaScan.h" #include "slam6d/globals.icc" #include using std::cerr; #include #ifdef _MSC_VER #if !defined _OPENMP && defined OPENMP #define _OPENMP #endif #endif #ifdef _OPENMP #include #endif /** * Constructor * * @param my_icp6Dminimizer Pointer to the ICP-minimizer * @param max_dist_match Maximum distance to which point pairs are collected * @param max_num_iterations Maximum number of iterations * @param quiet Whether to print to the standard output * @param meta Match against a meta scan? * @param rnd Randomized point selection * @param eP Extrapolate odometry? * @param anim Animate which frames? * @param epsilonICP Termination criterion * @param nns_method Selects NNS method to be used */ icp6D::icp6D(icp6Dminimizer *my_icp6Dminimizer, double max_dist_match, int max_num_iterations, bool quiet, bool meta, int rnd, bool eP, int anim, double epsilonICP, int nns_method, bool cuda_enabled, bool cad_matching) { this->my_icp6Dminimizer = my_icp6Dminimizer; this->anim = anim; this->cuda_enabled = cuda_enabled; this->nns_method = nns_method; if (!quiet) { cout << "Maximal distance match : " << max_dist_match << endl << "Maximal number of iterations: " << max_num_iterations << endl << endl; } // checks if (max_dist_match < 0.0) { cerr << "ERROR [ICP6D]: first parameter (max_dist_match) has to be >= 0," << endl; exit(1); } if (max_num_iterations < 0) { cerr << "ERROR [ICP6D]: second parameter (max_num_iterations) has to be >= 0." << endl; exit(1); } this->max_dist_match2 = sqr(max_dist_match); this->max_num_iterations = max_num_iterations; this->quiet = quiet; this->meta = meta; this->rnd = rnd; this->eP = eP; this->epsilonICP = epsilonICP; // Set initial seed (for "real" random numbers) // srand( (unsigned)time( NULL ) ); this->cad_matching = cad_matching; } /** * Matches a 3D Scan against a 3D Scan * @param PreviousScan The scan or metascan forming the model * @param CurrentScan The current scan thas is to be matched * @return The number of iterations done in this matching run */ int icp6D::match(Scan* PreviousScan, Scan* CurrentScan, PairingMode pairing_mode) { // If ICP shall not be applied, then just write // the identity matrix if (max_num_iterations == 0) { double id[16]; M4identity(id); CurrentScan->transform(id, Scan::ICP, 0); // write end pose return 0; } // icp main loop double ret = 0.0, prev_ret = 0.0, prev_prev_ret = 0.0; int iter = 0; double alignxf[16]; for (iter = 0; iter < max_num_iterations; iter++) { prev_prev_ret = prev_ret; prev_ret = ret; #ifdef _OPENMP // Implementation according to the paper // "The Parallel Iterative Closest Point Algorithm" // by Langis / Greenspan / Godin, IEEE 3DIM 2001 // // The same information are given in (ecrm2007.pdf) // Andreas Nüchter. Parallelization of Scan Matching // for Robotic 3D Mapping. In Proceedings of the 3rd // European Conference on Mobile Robots (ECMR '07), // Freiburg, Germany, September 2007 omp_set_num_threads(OPENMP_NUM_THREADS); int max = (int)CurrentScan->size("xyz reduced"); int step = max / OPENMP_NUM_THREADS; vector pairs[OPENMP_NUM_THREADS]; double sum[OPENMP_NUM_THREADS]; double centroid_m[OPENMP_NUM_THREADS][3]; double centroid_d[OPENMP_NUM_THREADS][3]; double Si[OPENMP_NUM_THREADS][9]; unsigned int n[OPENMP_NUM_THREADS]; for (int i = 0; i < OPENMP_NUM_THREADS; i++) { sum[i] = centroid_m[i][0] = centroid_m[i][1] = centroid_m[i][2] = 0.0; centroid_d[i][0] = centroid_d[i][1] = centroid_d[i][2] = 0.0; Si[i][0] = Si[i][1] = Si[i][2] = Si[i][3] = Si[i][4] = Si[i][5] = Si[i][6] = Si[i][7] = Si[i][8] = 0.0; n[i] = 0; } #pragma omp parallel { int thread_num = omp_get_thread_num(); Scan::getPtPairsParallel(pairs, PreviousScan, CurrentScan, thread_num, step, rnd, max_dist_match2, sum, centroid_m, centroid_d, pairing_mode); n[thread_num] = (unsigned int)pairs[thread_num].size(); if ((my_icp6Dminimizer->getAlgorithmID() == 1) || (my_icp6Dminimizer->getAlgorithmID() == 2)) { for (unsigned int i = 0; i < n[thread_num]; i++) { double pp[3] = {pairs[thread_num][i].p1.x - centroid_m[thread_num][0], pairs[thread_num][i].p1.y - centroid_m[thread_num][1], pairs[thread_num][i].p1.z - centroid_m[thread_num][2]}; double qq[3] = {pairs[thread_num][i].p2.x - centroid_d[thread_num][0], pairs[thread_num][i].p2.y - centroid_d[thread_num][1], pairs[thread_num][i].p2.z - centroid_d[thread_num][2]}; /* double pp[3] = {pairs[thread_num][i].p1.x - centroid_d[thread_num][0], pairs[thread_num][i].p1.y - centroid_d[thread_num][1], pairs[thread_num][i].p1.z - centroid_d[thread_num][2]}; double qq[3] = {pairs[thread_num][i].p2.x - centroid_m[thread_num][0], pairs[thread_num][i].p2.y - centroid_m[thread_num][1], pairs[thread_num][i].p2.z - centroid_m[thread_num][2]}; */ // formula (6) Si[thread_num][0] += pp[0] * qq[0]; Si[thread_num][1] += pp[0] * qq[1]; Si[thread_num][2] += pp[0] * qq[2]; Si[thread_num][3] += pp[1] * qq[0]; Si[thread_num][4] += pp[1] * qq[1]; Si[thread_num][5] += pp[1] * qq[2]; Si[thread_num][6] += pp[2] * qq[0]; Si[thread_num][7] += pp[2] * qq[1]; Si[thread_num][8] += pp[2] * qq[2]; } } } // end parallel // do we have enough point pairs? unsigned int pairssize = 0; for (int i = 0; i < OPENMP_NUM_THREADS; i++) { pairssize += n[i]; } if (pairssize > 3) { if ((my_icp6Dminimizer->getAlgorithmID() == 1) || (my_icp6Dminimizer->getAlgorithmID() == 2) ) { ret = my_icp6Dminimizer->Point_Point_Align_Parallel(OPENMP_NUM_THREADS, n, sum, centroid_m, centroid_d, Si, alignxf); } else if (my_icp6Dminimizer->getAlgorithmID() == 6) { ret = my_icp6Dminimizer->Point_Point_Align_Parallel(OPENMP_NUM_THREADS, n, sum, centroid_m, centroid_d, pairs, alignxf); } else { cout << "This parallel minimization algorithm is not implemented !!!" << endl; exit(-1); } } else { //break; } #else double centroid_m[3] = {0.0, 0.0, 0.0}; double centroid_d[3] = {0.0, 0.0, 0.0}; vector pairs; Scan::getPtPairs(&pairs, PreviousScan, CurrentScan, 0, rnd, max_dist_match2, ret, centroid_m, centroid_d, pairing_mode); // do we have enough point pairs? if (pairs.size() > 3) { if (my_icp6Dminimizer->getAlgorithmID() == 3 || my_icp6Dminimizer->getAlgorithmID() == 8 ) { memcpy(alignxf, CurrentScan->get_transMat(), sizeof(alignxf)); } ret = my_icp6Dminimizer->Point_Point_Align(pairs, alignxf, centroid_m, centroid_d); } else { break; } #endif if ((iter == 0 && anim != -2) || ((anim > 0) && (iter % anim == 0))) { CurrentScan->transform(alignxf, Scan::ICP, 0); // transform the current scan } else { CurrentScan->transform(alignxf, Scan::ICP, -1); // transform the current scan } if ((fabs(ret - prev_ret) < epsilonICP) && (fabs(ret - prev_prev_ret) < epsilonICP)) { double id[16]; M4identity(id); if(anim == -2) { CurrentScan->transform(id, Scan::ICP, -1); // write end pose } else { CurrentScan->transform(id, Scan::ICP, 0); // write end pose } break; } } return iter; } /** * Computes the point to point error between two scans * * */ double icp6D::Point_Point_Error(Scan* PreviousScan, Scan* CurrentScan, double max_dist_match, unsigned int *np) { double error = 0; unsigned int nr_ppairs = 0; #ifdef _OPENMP omp_set_num_threads(OPENMP_NUM_THREADS); int max = (int)CurrentScan->size("xyz reduced"); int step = max / OPENMP_NUM_THREADS; vector pairs[OPENMP_NUM_THREADS]; double sum[OPENMP_NUM_THREADS]; double centroid_m[OPENMP_NUM_THREADS][3]; double centroid_d[OPENMP_NUM_THREADS][3]; for (int i = 0; i < OPENMP_NUM_THREADS; i++) { sum[i] = centroid_m[i][0] = centroid_m[i][1] = centroid_m[i][2] = 0.0; centroid_d[i][0] = centroid_d[i][1] = centroid_d[i][2] = 0.0; } #pragma omp parallel { int thread_num = omp_get_thread_num(); Scan::getPtPairsParallel(pairs, PreviousScan, CurrentScan, thread_num, step, rnd, sqr(max_dist_match), sum, centroid_m, centroid_d, CLOSEST_POINT); } for (unsigned int thread_num = 0; thread_num < OPENMP_NUM_THREADS; thread_num++) { for (unsigned int i = 0; i < (unsigned int)pairs[thread_num].size(); i++) { error += sqr(pairs[thread_num][i].p1.x - pairs[thread_num][i].p2.x) + sqr(pairs[thread_num][i].p1.y - pairs[thread_num][i].p2.y) + sqr(pairs[thread_num][i].p1.z - pairs[thread_num][i].p2.z); } nr_ppairs += (unsigned int)pairs[thread_num].size(); } #else double centroid_m[3] = {0.0, 0.0, 0.0}; double centroid_d[3] = {0.0, 0.0, 0.0}; vector pairs; Scan::getPtPairs(&pairs, PreviousScan, CurrentScan, 0, rnd, sqr(max_dist_match), error, centroid_m, centroid_d, CLOSEST_POINT); // getPtPairs computes error as sum of squared distances error = 0; for (unsigned int i = 0; i < pairs.size(); i++) { error += sqrt( sqr(pairs[i].p1.x - pairs[i].p2.x) + sqr(pairs[i].p1.y - pairs[i].p2.y) + sqr(pairs[i].p1.z - pairs[i].p2.z) ); } nr_ppairs = pairs.size(); #endif if (np) *np = nr_ppairs; // return sqrt(error/nr_ppairs); return error/nr_ppairs; } /** * This function matches the scans only with ICP * * @param allScans Contains all necessary scans. */ void icp6D::doICP(vector allScans, PairingMode pairing_mode) { double id[16]; M4identity(id); vector < Scan* > meta_scans; Scan* my_MetaScan = 0; for(unsigned int i = 0; i < allScans.size(); i++) { cout << i << "*" << endl; Scan *CurrentScan = allScans[i]; Scan *PreviousScan = 0; if (i > 0) { PreviousScan = allScans[i-1]; if (eP) { // extrapolate odometry CurrentScan->mergeCoordinatesWithRoboterPosition(PreviousScan); } } if (i > 0) { if (meta) { match(my_MetaScan, CurrentScan, pairing_mode); } else if (cad_matching) { match(allScans[0], CurrentScan, pairing_mode); } else { match(PreviousScan, CurrentScan, pairing_mode); } } // push processed scan if ( meta && i != allScans.size()-1 ) { meta_scans.push_back(CurrentScan); if (my_MetaScan) { delete my_MetaScan; } my_MetaScan = new MetaScan(meta_scans, nns_method, cuda_enabled); } } }