Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Here is the function that finds translation and rotation. Generalization to scaling, weighted points, and RANSAC are straight forward. I used openCV library for visualization and SVD. The function below combines data generation, Unit Test , and actual solution.</p> <pre><code> // rotation and translation in 2D from point correspondences void rigidTransform2D(const int N) { // Algorithm: http://igl.ethz.ch/projects/ARAP/svd_rot.pdf const bool debug = false; // print more debug info const bool add_noise = true; // add noise to imput and output srand(time(NULL)); // randomize each time /********************************* * Creat data with some noise **********************************/ // Simulated transformation Point2f T(1.0f, -2.0f); float a = 30.0; // [-180, 180], see atan2(y, x) float noise_level = 0.1f; cout&lt;&lt;"True parameters: rot = "&lt;&lt;a&lt;&lt;"deg., T = "&lt;&lt;T&lt;&lt; "; noise level = "&lt;&lt;noise_level&lt;&lt;endl; // noise vector&lt;Point2f&gt; noise_src(N), noise_dst(N); for (int i=0; i&lt;N; i++) { noise_src[i] = Point2f(randf(noise_level), randf(noise_level)); noise_dst[i] = Point2f(randf(noise_level), randf(noise_level)); } // create data with noise vector&lt;Point2f&gt; src(N), dst(N); float Rdata = 10.0f; // radius of data float cosa = cos(a*DEG2RAD); float sina = sin(a*DEG2RAD); for (int i=0; i&lt;N; i++) { // src float x1 = randf(Rdata); float y1 = randf(Rdata); src[i] = Point2f(x1,y1); if (add_noise) src[i] += noise_src[i]; // dst float x2 = x1*cosa - y1*sina; float y2 = x1*sina + y1*cosa; dst[i] = Point2f(x2,y2) + T; if (add_noise) dst[i] += noise_dst[i]; if (debug) cout&lt;&lt;i&lt;&lt;": "&lt;&lt;src[i]&lt;&lt;"---"&lt;&lt;dst[i]&lt;&lt;endl; } // Calculate data centroids Scalar centroid_src = mean(src); Scalar centroid_dst = mean(dst); Point2f center_src(centroid_src[0], centroid_src[1]); Point2f center_dst(centroid_dst[0], centroid_dst[1]); if (debug) cout&lt;&lt;"Centers: "&lt;&lt;center_src&lt;&lt;", "&lt;&lt;center_dst&lt;&lt;endl; /********************************* * Visualize data **********************************/ // Visualization namedWindow("data", 1); float w = 400, h = 400; Mat Mdata(w, h, CV_8UC3); Mdata = Scalar(0); Point2f center_img(w/2, h/2); float scl = 0.4*min(w/Rdata, h/Rdata); // compensate for noise scl/=sqrt(2); // compensate for rotation effect Point2f dT = (center_src+center_dst)*0.5; // compensate for translation for (int i=0; i&lt;N; i++) { Point2f p1(scl*(src[i] - dT)); Point2f p2(scl*(dst[i] - dT)); // invert Y axis p1.y = -p1.y; p2.y = -p2.y; // add image center p1+=center_img; p2+=center_img; circle(Mdata, p1, 1, Scalar(0, 255, 0)); circle(Mdata, p2, 1, Scalar(0, 0, 255)); line(Mdata, p1, p2, Scalar(100, 100, 100)); } /********************************* * Get 2D rotation and translation **********************************/ markTime(); // subtract centroids from data for (int i=0; i&lt;N; i++) { src[i] -= center_src; dst[i] -= center_dst; } // compute a covariance matrix float Cxx = 0.0, Cxy = 0.0, Cyx = 0.0, Cyy = 0.0; for (int i=0; i&lt;N; i++) { Cxx += src[i].x*dst[i].x; Cxy += src[i].x*dst[i].y; Cyx += src[i].y*dst[i].x; Cyy += src[i].y*dst[i].y; } Mat Mcov = (Mat_&lt;float&gt;(2, 2)&lt;&lt;Cxx, Cxy, Cyx, Cyy); if (debug) cout&lt;&lt;"Covariance Matrix "&lt;&lt;Mcov&lt;&lt;endl; // SVD cv::SVD svd; svd = SVD(Mcov, SVD::FULL_UV); if (debug) { cout&lt;&lt;"U = "&lt;&lt;svd.u&lt;&lt;endl; cout&lt;&lt;"W = "&lt;&lt;svd.w&lt;&lt;endl; cout&lt;&lt;"V transposed = "&lt;&lt;svd.vt&lt;&lt;endl; } // rotation = V*Ut Mat V = svd.vt.t(); Mat Ut = svd.u.t(); float det_VUt = determinant(V*Ut); Mat W = (Mat_&lt;float&gt;(2, 2)&lt;&lt;1.0, 0.0, 0.0, det_VUt); float rot[4]; Mat R_est(2, 2, CV_32F, rot); R_est = V*W*Ut; if (debug) cout&lt;&lt;"Rotation matrix: "&lt;&lt;R_est&lt;&lt;endl; float cos_est = rot[0]; float sin_est = rot[2]; float ang = atan2(sin_est, cos_est); // translation = mean_dst - R*mean_src Point2f center_srcRot = Point2f( cos_est*center_src.x - sin_est*center_src.y, sin_est*center_src.x + cos_est*center_src.y); Point2f T_est = center_dst - center_srcRot; // RMSE double RMSE = 0.0; for (int i=0; i&lt;N; i++) { Point2f dst_est( cos_est*src[i].x - sin_est*src[i].y, sin_est*src[i].x + cos_est*src[i].y); RMSE += SQR(dst[i].x - dst_est.x) + SQR(dst[i].y - dst_est.y); } if (N&gt;0) RMSE = sqrt(RMSE/N); // Final estimate msg cout&lt;&lt;"Estimate = "&lt;&lt;ang*RAD2DEG&lt;&lt;"deg., T = "&lt;&lt;T_est&lt;&lt;"; RMSE = "&lt;&lt;RMSE&lt;&lt;endl; // show image printTime(1); imshow("data", Mdata); waitKey(-1); return; } // rigidTransform2D() // --------------------------- 3DOF // calculates squared error from two point mapping; assumes rotation around Origin. inline float sqErr_3Dof(Point2f p1, Point2f p2, float cos_alpha, float sin_alpha, Point2f T) { float x2_est = T.x + cos_alpha * p1.x - sin_alpha * p1.y; float y2_est = T.y + sin_alpha * p1.x + cos_alpha * p1.y; Point2f p2_est(x2_est, y2_est); Point2f dp = p2_est-p2; float sq_er = dp.dot(dp); // squared distance //cout&lt;&lt;dp&lt;&lt;endl; return sq_er; } // calculate RMSE for point-to-point metrics float RMSE_3Dof(const vector&lt;Point2f&gt;&amp; src, const vector&lt;Point2f&gt;&amp; dst, const float* param, const bool* inliers, const Point2f center) { const bool all_inliers = (inliers==NULL); // handy when we run QUADRTATIC will all inliers unsigned int n = src.size(); assert(n&gt;0 &amp;&amp; n==dst.size()); float ang_rad = param[0]; Point2f T(param[1], param[2]); float cos_alpha = cos(ang_rad); float sin_alpha = sin(ang_rad); double RMSE = 0.0; int ninliers = 0; for (unsigned int i=0; i&lt;n; i++) { if (all_inliers || inliers[i]) { RMSE += sqErr_3Dof(src[i]-center, dst[i]-center, cos_alpha, sin_alpha, T); ninliers++; } } //cout&lt;&lt;"RMSE = "&lt;&lt;RMSE&lt;&lt;endl; if (ninliers&gt;0) return sqrt(RMSE/ninliers); else return LARGE_NUMBER; } // Sets inliers and returns their count inline int setInliers3Dof(const vector&lt;Point2f&gt;&amp; src, const vector &lt;Point2f&gt;&amp; dst, bool* inliers, const float* param, const float max_er, const Point2f center) { float ang_rad = param[0]; Point2f T(param[1], param[2]); // set inliers unsigned int ninliers = 0; unsigned int n = src.size(); assert(n&gt;0 &amp;&amp; n==dst.size()); float cos_ang = cos(ang_rad); float sin_ang = sin(ang_rad); float max_sqErr = max_er*max_er; // comparing squared values if (inliers==NULL) { // just get the number of inliers (e.g. after QUADRATIC fit only) for (unsigned int i=0; i&lt;n; i++) { float sqErr = sqErr_3Dof(src[i]-center, dst[i]-center, cos_ang, sin_ang, T); if ( sqErr &lt; max_sqErr) ninliers++; } } else { // get the number of inliers and set them (e.g. for RANSAC) for (unsigned int i=0; i&lt;n; i++) { float sqErr = sqErr_3Dof(src[i]-center, dst[i]-center, cos_ang, sin_ang, T); if ( sqErr &lt; max_sqErr) { inliers[i] = 1; ninliers++; } else { inliers[i] = 0; } } } return ninliers; } // fits 3DOF (rotation and translation in 2D) with least squares. float fit3DofQUADRATICold(const vector&lt;Point2f&gt;&amp; src, const vector&lt;Point2f&gt;&amp; dst, float* param, const bool* inliers, const Point2f center) { const bool all_inliers = (inliers==NULL); // handy when we run QUADRTATIC will all inliers unsigned int n = src.size(); assert(dst.size() == n); // count inliers int ninliers; if (all_inliers) { ninliers = n; } else { ninliers = 0; for (unsigned int i=0; i&lt;n; i++){ if (inliers[i]) ninliers++; } } // under-dermined system if (ninliers&lt;2) { // param[0] = 0.0f; // ? // param[1] = 0.0f; // param[2] = 0.0f; return LARGE_NUMBER; } /* * x1*cosx(a)-y1*sin(a) + Tx = X1 * x1*sin(a)+y1*cos(a) + Ty = Y1 * * approximation for small angle a (radians) sin(a)=a, cos(a)=1; * * x1*1 - y1*a + Tx = X1 * x1*a + y1*1 + Ty = Y1 * * in matrix form M1*h=M2 * * 2n x 4 4 x 1 2n x 1 * * -y1 1 0 x1 * a = X1 * x1 0 1 y1 Tx Y1 * Ty * 1=Z * ---------------------------- * src1 res src2 */ // 4 x 1 float res_ar[4]; // alpha, Tx, Ty, 1 Mat res(4, 1, CV_32F, res_ar); // 4 x 1 // 2n x 4 Mat src1(2*ninliers, 4, CV_32F); // 2n x 4 // 2n x 1 Mat src2(2*ninliers, 1, CV_32F); // 2n x 1: [X1, Y1, X2, Y2, X3, Y3]' for (unsigned int i=0, row_cnt = 0; i&lt;n; i++) { // use inliers only if (all_inliers || inliers[i]) { float x = src[i].x - center.x; float y = src[i].y - center.y; // first row // src1 float* rowPtr = src1.ptr&lt;float&gt;(row_cnt); rowPtr[0] = -y; rowPtr[1] = 1.0f; rowPtr[2] = 0.0f; rowPtr[3] = x; // src2 src2.at&lt;float&gt; (0, row_cnt) = dst[i].x - center.x; // second row row_cnt++; // src1 rowPtr = src1.ptr&lt;float&gt;(row_cnt); rowPtr[0] = x; rowPtr[1] = 0.0f; rowPtr[2] = 1.0f; rowPtr[3] = y; // src2 src2.at&lt;float&gt; (0, row_cnt) = dst[i].y - center.y; } } cv::solve(src1, src2, res, DECOMP_SVD); // estimators float alpha_est; Point2f T_est; // original alpha_est = res.at&lt;float&gt;(0, 0); T_est = Point2f(res.at&lt;float&gt;(1, 0), res.at&lt;float&gt;(2, 0)); float Z = res.at&lt;float&gt;(3, 0); if (abs(Z-1.0) &gt; 0.1) { //cout&lt;&lt;"Bad Z in fit3DOF(), Z should be close to 1.0 = "&lt;&lt;Z&lt;&lt;endl; //return LARGE_NUMBER; } param[0] = alpha_est; // rad param[1] = T_est.x; param[2] = T_est.y; // calculate RMSE float RMSE = RMSE_3Dof(src, dst, param, inliers, center); return RMSE; } // fit3DofQUADRATICOLd() // fits 3DOF (rotation and translation in 2D) with least squares. float fit3DofQUADRATIC(const vector&lt;Point2f&gt;&amp; src_, const vector&lt;Point2f&gt;&amp; dst_, float* param, const bool* inliers, const Point2f center) { const bool debug = false; // print more debug info const bool all_inliers = (inliers==NULL); // handy when we run QUADRTATIC will all inliers assert(dst_.size() == src_.size()); int N = src_.size(); // collect inliers vector&lt;Point2f&gt; src, dst; int ninliers; if (all_inliers) { ninliers = N; src = src_; // copy constructor dst = dst_; } else { ninliers = 0; for (int i=0; i&lt;N; i++){ if (inliers[i]) { ninliers++; src.push_back(src_[i]); dst.push_back(dst_[i]); } } } if (ninliers&lt;2) { param[0] = 0.0f; // default return when there is not enough points param[1] = 0.0f; param[2] = 0.0f; return LARGE_NUMBER; } /* Algorithm: Least-Square Rigid Motion Using SVD by Olga Sorkine * http://igl.ethz.ch/projects/ARAP/svd_rot.pdf * * Subtract centroids, calculate SVD(cov), * R = V[1, det(VU')]'U', T = mean_q-R*mean_p */ // Calculate data centroids Scalar centroid_src = mean(src); Scalar centroid_dst = mean(dst); Point2f center_src(centroid_src[0], centroid_src[1]); Point2f center_dst(centroid_dst[0], centroid_dst[1]); if (debug) cout&lt;&lt;"Centers: "&lt;&lt;center_src&lt;&lt;", "&lt;&lt;center_dst&lt;&lt;endl; // subtract centroids from data for (int i=0; i&lt;ninliers; i++) { src[i] -= center_src; dst[i] -= center_dst; } // compute a covariance matrix float Cxx = 0.0, Cxy = 0.0, Cyx = 0.0, Cyy = 0.0; for (int i=0; i&lt;ninliers; i++) { Cxx += src[i].x*dst[i].x; Cxy += src[i].x*dst[i].y; Cyx += src[i].y*dst[i].x; Cyy += src[i].y*dst[i].y; } Mat Mcov = (Mat_&lt;float&gt;(2, 2)&lt;&lt;Cxx, Cxy, Cyx, Cyy); Mcov /= (ninliers-1); if (debug) cout&lt;&lt;"Covariance-like Matrix "&lt;&lt;Mcov&lt;&lt;endl; // SVD of covariance cv::SVD svd; svd = SVD(Mcov, SVD::FULL_UV); if (debug) { cout&lt;&lt;"U = "&lt;&lt;svd.u&lt;&lt;endl; cout&lt;&lt;"W = "&lt;&lt;svd.w&lt;&lt;endl; cout&lt;&lt;"V transposed = "&lt;&lt;svd.vt&lt;&lt;endl; } // rotation (V*Ut) Mat V = svd.vt.t(); Mat Ut = svd.u.t(); float det_VUt = determinant(V*Ut); Mat W = (Mat_&lt;float&gt;(2, 2)&lt;&lt;1.0, 0.0, 0.0, det_VUt); float rot[4]; Mat R_est(2, 2, CV_32F, rot); R_est = V*W*Ut; if (debug) cout&lt;&lt;"Rotation matrix: "&lt;&lt;R_est&lt;&lt;endl; float cos_est = rot[0]; float sin_est = rot[2]; float ang = atan2(sin_est, cos_est); // translation (mean_dst - R*mean_src) Point2f center_srcRot = Point2f( cos_est*center_src.x - sin_est*center_src.y, sin_est*center_src.x + cos_est*center_src.y); Point2f T_est = center_dst - center_srcRot; // Final estimate msg if (debug) cout&lt;&lt;"Estimate = "&lt;&lt;ang*RAD2DEG&lt;&lt;"deg., T = "&lt;&lt;T_est&lt;&lt;endl; param[0] = ang; // rad param[1] = T_est.x; param[2] = T_est.y; // calculate RMSE float RMSE = RMSE_3Dof(src_, dst_, param, inliers, center); return RMSE; } // fit3DofQUADRATIC() // RANSAC fit in 3DOF: 1D rot and 2D translation (maximizes the number of inliers) // NOTE: no data normalization is currently performed float fit3DofRANSAC(const vector&lt;Point2f&gt;&amp; src, const vector&lt;Point2f&gt;&amp; dst, float* best_param, bool* inliers, const Point2f center , const float inlierMaxEr, const int niter) { const int ITERATION_TO_SETTLE = 2; // iterations to settle inliers and param const float INLIERS_RATIO_OK = 0.95f; // stopping criterion // size of data vector unsigned int N = src.size(); assert(N==dst.size()); // unrealistic case if(N&lt;2) { best_param[0] = 0.0f; // ? best_param[1] = 0.0f; best_param[2] = 0.0f; return LARGE_NUMBER; } unsigned int ninliers; // current number of inliers unsigned int best_ninliers = 0; // number of inliers float best_rmse = LARGE_NUMBER; // error float cur_rmse; // current distance error float param[3]; // rad, Tx, Ty vector &lt;Point2f&gt; src_2pt(2), dst_2pt(2);// min set of 2 points (1 correspondence generates 2 equations) srand (time(NULL)); // iterations for (int iter = 0; iter&lt;niter; iter++) { #ifdef DEBUG_RANSAC cout&lt;&lt;"iteration "&lt;&lt;iter&lt;&lt;": "; #endif // 1. Select a random set of 2 points (not obligatory inliers but valid) int i1, i2; i1 = rand() % N; // [0, N[ i2 = i1; while (i2==i1) { i2 = rand() % N; } src_2pt[0] = src[i1]; // corresponding points src_2pt[1] = src[i2]; dst_2pt[0] = dst[i1]; dst_2pt[1] = dst[i2]; bool two_inliers[] = {true, true}; // 2. Quadratic fit for 2 points cur_rmse = fit3DofQUADRATIC(src_2pt, dst_2pt, param, two_inliers, center); // 3. Recalculate to settle params and inliers using a larger set for (int iter2=0; iter2&lt;ITERATION_TO_SETTLE; iter2++) { ninliers = setInliers3Dof(src, dst, inliers, param, inlierMaxEr, center); // changes inliers cur_rmse = fit3DofQUADRATIC(src, dst, param, inliers, center); // changes cur_param } // potential ill-condition or large error if (ninliers&lt;2) { #ifdef DEBUG_RANSAC cout&lt;&lt;" !!! less than 2 inliers "&lt;&lt;endl; #endif continue; } else { #ifdef DEBUG_RANSAC cout&lt;&lt;" "&lt;&lt;ninliers&lt;&lt;" inliers; "; #endif } #ifdef DEBUG_RANSAC cout&lt;&lt;"; recalculate: RMSE = "&lt;&lt;cur_rmse&lt;&lt;", "&lt;&lt;ninliers &lt;&lt;" inliers"; #endif // 4. found a better solution? if (ninliers &gt; best_ninliers) { best_ninliers = ninliers; best_param[0] = param[0]; best_param[1] = param[1]; best_param[2] = param[2]; best_rmse = cur_rmse; #ifdef DEBUG_RANSAC cout&lt;&lt;" --- Solution improved: "&lt;&lt; best_param[0]&lt;&lt;", "&lt;&lt;best_param[1]&lt;&lt;", "&lt;&lt;param[2]&lt;&lt;endl; #endif // exit condition float inlier_ratio = (float)best_ninliers/N; if (inlier_ratio &gt; INLIERS_RATIO_OK) { #ifdef DEBUG_RANSAC cout&lt;&lt;"Breaking early after "&lt;&lt; iter+1&lt;&lt; " iterations; inlier ratio = "&lt;&lt;inlier_ratio&lt;&lt;endl; #endif break; } } else { #ifdef DEBUG_RANSAC cout&lt;&lt;endl; #endif } } // iterations // 5. recreate inliers for the best parameters ninliers = setInliers3Dof(src, dst, inliers, best_param, inlierMaxEr, center); return best_rmse; } // fit3DofRANSAC() </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload