00001 /* 00002 Estimates the parameters of a 2D line from a set of points lying on 00003 the line. 00004 00005 Copyright (C) 2010 - 2014 Christoph Haenisch, Fabian Killus 00006 00007 Chair of Medical Engineering (mediTEC) 00008 RWTH Aachen University 00009 Pauwelsstr. 20 00010 52074 Aachen 00011 Germany 00012 00013 Version 0.6.0 (2012-03-29) 00014 */ 00015 00021 #ifndef FIT_LINE_HPP_8904538197 00022 #define FIT_LINE_HPP_8904538197 00023 00024 00025 #include <cmath> 00026 #include <vector> 00027 00028 #include <Eigen/Core> 00029 #include <Eigen/Dense> 00030 #include <Eigen/Eigenvalues> 00031 #include <Eigen/StdVector> 00032 00033 #include "ErrorObj.hpp" 00034 #include "Coordinate.hpp" 00035 #include "Fit2D.hpp" 00036 00037 00038 namespace TRTK 00039 { 00040 00041 00119 template <class T> 00120 class FitLine : public Fit2D<T> 00121 { 00122 private: 00123 typedef Fit2D<T> super; 00124 00125 public: 00126 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 00127 00128 typedef T value_type; 00129 typedef typename super::Vector2T Vector2T; 00130 typedef typename super::Vector3T Vector3T; 00131 typedef typename super::VectorXT VectorXT; 00132 typedef typename super::Matrix2T Matrix2T; 00133 typedef typename super::MatrixXT MatrixXT; 00134 00135 FitLine(); 00136 FitLine(const std::vector<Coordinate<T> > & points); 00137 FitLine(const std::vector<Vector2T, Eigen::aligned_allocator<Vector2T> > & points); 00138 FitLine(const std::vector<Vector3T> & points); 00139 00140 virtual ~FitLine(); 00141 00142 void compute(); 00143 T getRMS() const; 00144 00145 unsigned getNumberPointsRequired() const; 00146 00147 const Coordinate<T> & getDirectionVector() const; 00148 const Coordinate<T> & getNormalVector() const; 00149 const Coordinate<T> & getPointOnLineSegment() const; 00150 00151 T getSlope() const; 00152 T getYIntercept() const; 00153 T getXIntercept() const; 00154 T getDistanceFromOrigin() const; 00155 T getDistanceTo(const Coordinate<T> & point) const; 00156 00157 using super::DATA_POINTS_TOO_SIMILAR; 00158 using super::INFINITY_NOT_AVAILABLE; 00159 using super::NAN_NOT_AVAILABLE; 00160 using super::NOT_ENOUGH_POINTS; 00161 using super::UNKNOWN_ERROR; 00162 using super::WRONG_POINT_SIZE; 00163 00164 private: 00165 T m_rms; 00166 T m_slope; 00167 T m_y_intercept; 00168 T m_x_intercept; 00169 Coordinate<T> m_direction_vector; 00170 Coordinate<T> m_normal_vector; 00171 Coordinate<T> m_point_on_line_segment; 00172 }; 00173 00174 00180 template <class T> 00181 FitLine<T>::FitLine() : 00182 m_rms(0), 00183 m_slope(0), 00184 m_y_intercept(0), 00185 m_x_intercept(0), 00186 m_direction_vector(0, 0), 00187 m_normal_vector(0, 0), 00188 m_point_on_line_segment(0, 0) 00189 { 00190 } 00191 00192 00201 template <class T> 00202 FitLine<T>::FitLine(const std::vector<Coordinate<T> > & points) : 00203 m_rms(0), 00204 m_slope(0), 00205 m_y_intercept(0), 00206 m_x_intercept(0), 00207 m_direction_vector(0, 0), 00208 m_normal_vector(0, 0), 00209 m_point_on_line_segment(0, 0) 00210 { 00211 super::setPoints(points); 00212 } 00213 00214 00222 template <class T> 00223 FitLine<T>::FitLine(const std::vector<Vector2T, Eigen::aligned_allocator<Vector2T> > & points) : 00224 m_rms(0), 00225 m_slope(0), 00226 m_y_intercept(0), 00227 m_x_intercept(0), 00228 m_direction_vector(0, 0), 00229 m_normal_vector(0, 0), 00230 m_point_on_line_segment(0, 0) 00231 { 00232 super::setPoints(points); 00233 } 00234 00235 00245 template <class T> 00246 FitLine<T>::FitLine(const std::vector<Vector3T> & points) : 00247 m_rms(0), 00248 m_slope(0), 00249 m_y_intercept(0), 00250 m_x_intercept(0), 00251 m_direction_vector(0, 0), 00252 m_normal_vector(0, 0), 00253 m_point_on_line_segment(0, 0) 00254 { 00255 super::setPoints(points); 00256 } 00257 00258 00264 template <class T> 00265 FitLine<T>::~FitLine() 00266 { 00267 } 00268 00269 00283 template <class T> 00284 void FitLine<T>::compute() 00285 { 00286 /* 00287 00288 Explanation of the algorithm: 00289 00290 First we compute a point lying on the line segment (the mean works) and 00291 move the line such that it pass through the origin 00292 00293 x_i := x_i - E[x] (E[x] = expected value = population mean) 00294 00295 Then the difference vector between a point on the line (x_i, y_i) and the 00296 point of origin (0, 0) is collinear to the direction vector of the line. 00297 The direction vector in turn is orthogonal to the normal n of the line. 00298 Thus, for every point x_i on the line it holds that 00299 00300 x_i * n = 0 00301 00302 This can be used to define an error which is to be minimized yielding the 00303 sought normal n 00304 00305 Error = sum_i (x_i^T * n)^2 00306 = sum_i [ (x_i^T * n)^T * (x_i^T * n) ] 00307 = sum_i [ n^T * (x_i * x_i^T) * n ] 00308 = n^T * [ sum_i (x_i * x_i^T) ] * n (note the outer product!) 00309 = n^T * A * n 00310 00311 The above quadratic form can be solved using Lagrange multipliers 00312 00313 L := n^T * A * n - lambda * (n^T * n - 1) 00314 00315 grad L = 0 00316 00317 where n^T * n = 1 is an auxiliary condition avoiding the trival solution 00318 n = (0, 0). As we will see, this is equivalent to an eigenvalue problem 00319 00320 grad L = 2 * A * n - 2 * lambda * I * n = 0 00321 00322 ==> A n = lambda n 00323 00324 And since 00325 00326 Error = n^T * A * n 00327 = n^T * lambda * n 00328 = lambda 00329 00330 is to be minimized, we search for an eigenvector of A corresponding to the 00331 absolute smallest eigenvalue. 00332 00333 Finally, the sought direction vector of the line is an orthogonal vector 00334 to the normal n. 00335 00336 */ 00337 00338 using namespace std; 00339 using namespace Eigen; 00340 00341 const int number_of_points = super::m_points.cols(); 00342 00343 if (number_of_points < 2) 00344 { 00345 ErrorObj error; 00346 error.setClassName("FitLine<T>"); 00347 error.setFunctionName("compute"); 00348 error.setErrorMessage("Not enough points to fit the line."); 00349 error.setErrorCode(NOT_ENOUGH_POINTS); 00350 throw error; 00351 } 00352 00353 MatrixXT X = super::m_points; 00354 00355 // subtract the population mean which is a point on the line segment 00356 00357 VectorXT mean = X.rowwise().mean(); 00358 00359 X.colwise() -= mean; 00360 00361 m_point_on_line_segment = Coordinate<T>(mean); 00362 00363 // Construct the matrix A = sum ( x_i^T * x_i ) 00364 00365 Matrix<T, 2, 2> A = X * X.transpose(); 00366 00367 if (A.isZero()) 00368 { 00369 ErrorObj error; 00370 error.setClassName("FitLine<T>"); 00371 error.setFunctionName("compute"); 00372 error.setErrorMessage("Data points are too similar."); 00373 error.setErrorCode(DATA_POINTS_TOO_SIMILAR); 00374 throw error; 00375 } 00376 00377 // Compute the eigenvalues and the eigenvectors of A. 00378 00379 // Note: - Since A is symmetric, there are only real eigenvalues. 00380 // ==> Use SelfAdjointEigenSolver. 00381 // - SelfAdjointEigenSolver normalizes the eigenvectors. 00382 00383 SelfAdjointEigenSolver<Matrix2T> eigen_solver(A); 00384 00385 VectorXT eigen_values = eigen_solver.eigenvalues(); 00386 MatrixXT eigen_vectors = eigen_solver.eigenvectors(); 00387 00388 // Find the absolute minimum eigenvalue and its corresponding eigenvector. 00389 // Do also compute the RMS from the smallest eigenvalue. 00390 00391 using std::sqrt; 00392 00393 if (abs(eigen_values(0)) < abs(eigen_values(1))) 00394 { 00395 m_normal_vector = Coordinate<T>(eigen_vectors.col(0)); 00396 00397 T sum_of_squared_errors = abs(eigen_values(0)); 00398 m_rms = sqrt(sum_of_squared_errors / number_of_points); 00399 } 00400 else 00401 { 00402 m_normal_vector = Coordinate<T>(eigen_vectors.col(1)); 00403 00404 T sum_of_squared_errors = abs(eigen_values(1)); 00405 m_rms = sqrt(sum_of_squared_errors / number_of_points); 00406 } 00407 00408 // Now, we can easily compute the direction vector and the intercepts. 00409 00410 m_direction_vector = Coordinate<T>(-m_normal_vector.y(), m_normal_vector.x()); 00411 00412 // There are three cases: 00413 // (i) the line is parallel to the x-intercept 00414 // (ii) the line is parallel to the y-intercept 00415 // (iii) the line is not parallel to any axis 00416 00417 T epsilon = numeric_limits<T>::is_specialized ? 10 * numeric_limits<T>::epsilon() : 1e-8; 00418 00419 if (abs(m_direction_vector.y()) < epsilon) // case (i) 00420 { 00421 // y = mx + b = b 00422 00423 m_slope = 0.0; 00424 00425 if (numeric_limits<T>::has_quiet_NaN) 00426 { 00427 m_x_intercept = numeric_limits<T>::quiet_NaN(); 00428 } 00429 else 00430 { 00431 ErrorObj error; 00432 error.setClassName("FitLine<T>"); 00433 error.setFunctionName("compute"); 00434 error.setErrorMessage("Slope is zero but the x-intercept cannot be set to NaN."); 00435 error.setErrorCode(NAN_NOT_AVAILABLE); 00436 throw error; 00437 } 00438 00439 m_y_intercept = m_point_on_line_segment.y(); 00440 } 00441 else if (abs(m_direction_vector.x()) < epsilon) // case (ii) 00442 { 00443 // x = a 00444 00445 if (numeric_limits<T>::has_infinity) 00446 { 00447 m_slope = numeric_limits<T>::infinity(); 00448 } 00449 else 00450 { 00451 ErrorObj error; 00452 error.setClassName("FitLine<T>"); 00453 error.setFunctionName("compute"); 00454 error.setErrorMessage("Slope is infinity but T cannot represent this number."); 00455 error.setErrorCode(INFINITY_NOT_AVAILABLE); 00456 throw error; 00457 } 00458 00459 m_x_intercept = m_point_on_line_segment.x(); 00460 00461 if (numeric_limits<T>::has_quiet_NaN) 00462 { 00463 m_y_intercept = numeric_limits<T>::quiet_NaN(); 00464 } 00465 else 00466 { 00467 ErrorObj error; 00468 error.setClassName("FitLine<T>"); 00469 error.setFunctionName("compute"); 00470 error.setErrorMessage("Slope is zero but the y-intercept cannot be set to NaN."); 00471 error.setErrorCode(NAN_NOT_AVAILABLE); 00472 throw error; 00473 } 00474 } 00475 else // case (iii) 00476 { 00477 // direction vector = (1, slope)^T 00478 00479 m_slope = m_direction_vector.y() / m_direction_vector.x(); 00480 00481 // y = mx + b ==> b = y - mx 00482 00483 m_y_intercept = m_point_on_line_segment.y() - m_slope * m_point_on_line_segment.x(); 00484 00485 // y = mx + b = 0 ==> x = -b/m 00486 00487 m_x_intercept = - m_y_intercept / m_slope; 00488 } 00489 } 00490 00491 00497 template <class T> 00498 const Coordinate<T> & FitLine<T>::getPointOnLineSegment() const 00499 { 00500 return m_point_on_line_segment; 00501 } 00502 00503 00509 template <class T> 00510 const Coordinate<T> & FitLine<T>::getDirectionVector() const 00511 { 00512 return m_direction_vector; 00513 } 00514 00515 00521 template <class T> 00522 T FitLine<T>::getDistanceFromOrigin() const 00523 { 00524 using std::abs; 00525 00526 // The shortest distance can be found by projecting a vector pointing 00527 // to an arbitrary point on the line (we just use the population mean 00528 // of all points) onto the normal vector of the line. 00529 00530 return abs(m_point_on_line_segment.dot(m_normal_vector)); 00531 } 00532 00533 00541 template <class T> 00542 T FitLine<T>::getDistanceTo(const Coordinate<T> & point) const 00543 { 00544 assert(point.size() == 2); 00545 00546 // If d is the difference vector between an arbitrary point on the line 00547 // (we simply use the center of the line segment) and the currently processed 00548 // point, then the shortest distance to the line equals d * n where n is 00549 // then normal of the line. 00550 00551 using std::abs; 00552 return abs((point - m_point_on_line_segment).dot(m_normal_vector)); // = d * n 00553 } 00554 00555 00556 template <class T> 00557 inline unsigned FitLine<T>::getNumberPointsRequired() const 00558 { 00559 return 2; 00560 } 00561 00562 00568 template <class T> 00569 const Coordinate<T> & FitLine<T>::getNormalVector() const 00570 { 00571 return m_normal_vector; 00572 } 00573 00574 00580 template <class T> 00581 T FitLine<T>::getRMS() const 00582 { 00583 return m_rms; 00584 00585 /* 00586 00587 const int num_points = super::m_points.cols(); 00588 00589 T sum_of_squared_errors = 0.0; 00590 00591 for (int i = 0; i < num_points; ++i) 00592 { 00593 Coordinate<T> point(super::m_points.col(i)); 00594 00595 // If d is the difference vector between an arbitrary point on the line 00596 // (we simply use the center of the line segment) and the currently processed 00597 // point, then the shortest distance to the line equals d * n where n is 00598 // then normal of the line. Ideally the distance is zero, so every deviation 00599 // constitutes an error. 00600 00601 T error = (point - m_point_on_line_segment).dot(m_normal_vector); // = d * n 00602 sum_of_squared_errors += error * error; 00603 } 00604 00605 return sqrt(sum_of_squared_errors / num_points); 00606 00607 */ 00608 } 00609 00610 00619 template <class T> 00620 T FitLine<T>::getSlope() const 00621 { 00622 return m_slope; 00623 } 00624 00625 00634 template <class T> 00635 T FitLine<T>::getXIntercept() const 00636 { 00637 return m_x_intercept; 00638 } 00639 00640 00649 template <class T> 00650 T FitLine<T>::getYIntercept() const 00651 { 00652 return m_y_intercept; 00653 } 00654 00655 00656 } // namespace TRTK 00657 00658 00659 #endif // FIT_LINE_HPP_8904538197
Documentation generated by Doxygen