Here, is my implementation of Iterative Closest Point algorithm in C++. The code is written using the Eigen library. I have tried to implement an efficient coding methodology best to my knowledge however, I believe there are still improvements that can be made in terms of vectorizing the code and getting rid of for loops.
For further information: GitHub
#include<iostream> #include<eigen3/Eigen/Dense> #include<eigen3/Eigen/Core> #include<eigen3/Eigen/SVD> #include<fstream> #include<vector> // Run using: g++ -std=c++17 -I/usr/include/eigen3 ICP.cpp -o ICP -O2 -DNDEBUG using namespace Eigen; struct Tyx { MatrixXd Rot; VectorXd trans; }; // Rigid Registration. Tyx ComputeOptimalRigidRegistration(MatrixXd X, MatrixXd Y, MatrixXi C) { Tyx T; // Calculate the point cloud centroids. MatrixXd x_subset = X(C.col(0), Eigen::placeholders::all); MatrixXd y_subset = Y(C.col(1), Eigen::placeholders::all); VectorXd x_centroid = x_subset.colwise().mean(); VectorXd y_centroid = y_subset.colwise().mean(); // Calculate the deviation of X and Y. MatrixXd x_deviation = x_subset.rowwise() - x_centroid.transpose(); MatrixXd y_deviation = y_subset.rowwise() - y_centroid.transpose(); // Calculate covariance. MatrixXd W = x_deviation.transpose() * y_deviation; //std::cout << "W covarieance matrix: " << W << std::endl; //Calculate the SVD. JacobiSVD<MatrixXd> svd(W, Eigen::ComputeFullU | Eigen::ComputeFullV); MatrixXd U = svd.matrixU(); MatrixXd V = svd.matrixV(); //std::cout << "U = " << U << std::endl; //std::cout << "V = " << V << std::endl; // Construct optimal rotation T.Rot = U * V.transpose(); //std::cout << "T.Rot = " << T.Rot << std::endl; // Construct Optimal translation T.trans = y_centroid - (x_centroid.transpose()*T.Rot).transpose(); //std::cout << "T.trans = " << T.trans << std::endl; return T; } // Get the correspondences. MatrixXi EstimateCorrespondences(MatrixXd X, MatrixXd Y, Vector3d t, Matrix3d R, double d_max) { //Initialize C as empty. MatrixXi C(0,2); int N = X.rows(); MatrixXd transposed_x(N,3); int index; VectorXd norm(N); for(int i = 0; i < X.rows(); i++) { transposed_x.row(i) = X.row(i) * R + t.transpose(); } // Find the indexes in the least square sense. for(int i =0; i < transposed_x.rows(); i++) { // diff = Y.rowwise() - transposed_x.row(i); // norm = diff.rowwise().norm(); norm = (Y.rowwise() - transposed_x.row(i)).rowwise().norm(); // Print the first 10 elements of the norm vector. Debugging purposes. // if (i == 0) // { // std::cout << "X = " << X.row(0) << std::endl; // std::cout << "Y = " << Y.row(0) << std::endl; // std::cout << "transposed_x = " << transposed_x.row(0) << std::endl; // std::cout << norm.head(10) << std::endl; // } norm.minCoeff(&index); if (norm.coeff(index) < d_max) { C.conservativeResize(C.rows()+1, Eigen::NoChange); C.row(C.rows()-1) << i,index; } } return C; } Tyx ICP_algo(MatrixXd X, MatrixXd Y, Vector3d t0, Matrix3d R0, double d_max, int max_iter) { int iter = 0; Tyx T; MatrixXi C; while(true) { C = EstimateCorrespondences(X , Y , t0 , R0 , d_max); T = ComputeOptimalRigidRegistration(X, Y, C); t0 = T.trans; R0 = T.Rot; iter = iter +1; if (iter == max_iter) { std::cout << "Max iterations reached " << iter << std::endl; break; } } T.Corresp = C; return T; } int main() { // Create a vector of vector std::vector<std::vector<double>> data; double value; //Open the text file. std::ifstream file("pclX.txt"); //Read the data from the file to the matrix while(file >> value) { data.push_back({value}); while (file.peek() == ' ') { file >> value; data.back().push_back(value); } } file.close(); int n_rows = data.size(); int n_cols = data[0].size(); MatrixXd pcl_X(n_rows,n_cols); for (int i = 0; i < n_rows; ++i) { for (int j =0; j < n_cols; ++j) { pcl_X(i,j) = data[i][j]; } } file.close(); // Similarly for pcl_Y std::ifstream file_y("pclY.txt"); //Read the data from the file to the matrix // Clear the data vector data.clear(); while(file_y >> value) { data.push_back({value}); while (file_y.peek() == ' ') { file_y >> value; data.back().push_back(value); } } file_y.close(); n_rows = data.size(); n_cols = data[0].size(); MatrixXd pcl_Y(n_rows,n_cols); for (int i = 0; i < n_rows; i++) { for (int j =0; j < n_cols; j++) { pcl_Y(i,j) = data[i][j]; } } // Initial translational vector and Rotational matrix Vector3d t = Vector3d::Zero(); Matrix3d R = Matrix3d::Identity(); // Create a identity matrix. double d_max = 0.25; int iter = 30; Tyx result; // std::cout << "pcl_X = " << pcl_X.row(0) << std::endl; // std::cout << "pcl_Y = " << pcl_Y.row(0) << std::endl; clock_t begin = clock(); result = ICP_algo(pcl_X, pcl_Y, t, R, d_max, iter); clock_t end = clock(); double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC; std::cout << "Time taken : " << elapsed_secs << std::endl; std::cout << "\n Translation vector: " << result.trans << std::endl; std::cout << "-----------------------------" << std::endl; std::cout << "Rotation matrix: " << result.Rot << std::endl; MatrixXd result_pt_cloud(pcl_X.rows(),3); for(int i = 0; i < pcl_X.rows(); i++) { result_pt_cloud.row(i) = pcl_X.row(i) * result.Rot + result.trans.transpose(); } // Write the result point cloud to a text file. std::ofstream file_result("result.txt"); for(int i = 0; i < result_pt_cloud.rows(); i++) { file_result << result_pt_cloud(i,0) << " " << result_pt_cloud(i,1) << " " << result_pt_cloud(i,2) << std::endl; } return 0; }