00001 /* 00002 Estimates the parameters of a line in 3D space 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.4.1 (2013-08-20) 00014 */ 00015 00021 #ifndef FIT_LINE_3D_HPP_9482948571 00022 #define FIT_LINE_3D_HPP_9482948571 00023 00024 00025 #include <cmath> 00026 #include <vector> 00027 00028 #include <Eigen/Core> 00029 #include <Eigen/Dense> 00030 #include <Eigen/StdVector> 00031 #include <Eigen/SVD> 00032 00033 #include "ErrorObj.hpp" 00034 #include "Coordinate.hpp" 00035 #include "Fit3D.hpp" 00036 00037 00038 namespace TRTK 00039 { 00040 00041 00114 template <class T> 00115 class FitLine3D : public Fit3D<T> 00116 { 00117 private: 00118 typedef Fit3D<T> super; 00119 00120 public: 00121 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 00122 00123 typedef T value_type; 00124 typedef typename super::Vector3T Vector3T; 00125 typedef typename super::Vector4T Vector4T; 00126 typedef typename super::MatrixXT MatrixXT; 00127 00128 FitLine3D(); 00129 FitLine3D(const std::vector<Coordinate<T> > & points); 00130 FitLine3D(const std::vector<Vector3T> & points); 00131 FitLine3D(const std::vector<Vector4T, Eigen::aligned_allocator<Vector4T> > & points); 00132 00133 virtual ~FitLine3D(); 00134 00135 unsigned getNumberPointsRequired() const; 00136 void compute(); 00137 T getRMS() const; 00138 00139 const Coordinate<T> & getDirectionVector() const; 00140 Coordinate<T> getNormalVector() const; 00141 const Coordinate<T> & getPointOnLineSegment() const; 00142 T getDistanceFromOrigin() const; 00143 T getDistanceTo(const Coordinate<T> & point) const; 00144 00145 enum Error { 00146 NOT_ENOUGH_POINTS, 00147 UNKNOWN_ERROR, 00148 WRONG_POINT_SIZE 00149 }; 00150 00151 private: 00152 Coordinate<T> m_direction_vector; 00153 Coordinate<T> m_point_on_line; 00154 }; 00155 00156 00162 template <class T> 00163 FitLine3D<T>::FitLine3D() : 00164 m_direction_vector(0, 0, 0), 00165 m_point_on_line(0, 0, 0) 00166 { 00167 } 00168 00169 00178 template <class T> 00179 FitLine3D<T>::FitLine3D(const std::vector<Coordinate<T> > & points) : 00180 m_direction_vector(0, 0, 0), 00181 m_point_on_line(0, 0, 0) 00182 { 00183 super::setPoints(points); 00184 } 00185 00186 00194 template <class T> 00195 FitLine3D<T>::FitLine3D(const std::vector<Vector3T> & points) : 00196 m_direction_vector(0, 0, 0), 00197 m_point_on_line(0, 0, 0) 00198 { 00199 super::setPoints(points); 00200 } 00201 00202 00212 template <class T> 00213 FitLine3D<T>::FitLine3D(const std::vector<Vector4T, Eigen::aligned_allocator<Vector4T> > & points) : 00214 m_direction_vector(0, 0, 0), 00215 m_point_on_line(0, 0, 0) 00216 { 00217 super::setPoints(points); 00218 } 00219 00220 00226 template <class T> 00227 FitLine3D<T>::~FitLine3D() 00228 { 00229 } 00230 00231 00245 template <class T> 00246 void FitLine3D<T>::compute() 00247 { 00248 /* 00249 00250 Explanation of the algorithm: 00251 00252 A reasonable assumption is that the line propagates along the direction of largest 00253 variance of all known points on the line. Doing a principal component analysis (PCA) 00254 of the given data the orientation of the first principal component corresponds to 00255 the sought direction of the line. 00256 00257 The PCA can be computed with a SVD. If we compute the SVD of a matrix M whose columns 00258 are the given data points 00259 00260 M = U * S * V^T, 00261 00262 then the columns of the unitary matrix V are the principal components of M. See 00263 http://www.snl.salk.edu/~shlens/pca.pdf for an extreme good and detailed description. 00264 00265 */ 00266 00267 using namespace std; 00268 using namespace Eigen; 00269 00270 const int num_points = super::m_points.cols(); 00271 00272 if (num_points < 2) 00273 { 00274 ErrorObj error; 00275 error.setClassName("FitLine3D<T>"); 00276 error.setFunctionName("compute"); 00277 error.setErrorMessage("Not enough points to fit the line."); 00278 error.setErrorCode(NOT_ENOUGH_POINTS); 00279 throw error; 00280 } 00281 00282 00283 MatrixXT X = super::m_points; 00284 00285 // Subtract the population mean and store it. This is inevitable for doing a PCA. 00286 00287 Vector3T mean = X.rowwise().mean(); 00288 X.colwise() -= mean; 00289 00290 m_point_on_line = Coordinate<T>(mean); 00291 00292 // Construct the matrix Y (see scriptum). 00293 00294 using std::sqrt; 00295 MatrixXT Y = X.transpose() / sqrt(num_points - 1.0); 00296 00297 // Compute the SVD. 00298 // Note: Eigen sorts the singular values in decreasing order. 00299 00300 JacobiSVD<MatrixXT> svd(Y, ComputeThinU | ComputeThinV); 00301 svd.computeV(); 00302 00303 // Extract the principal components. 00304 00305 MatrixXT P = svd.matrixV(); 00306 00307 // The examined line propagates along the first principal component. 00308 00309 m_direction_vector = Coordinate<T>(P.col(0)).normalize(); 00310 } 00311 00312 00318 template <class T> 00319 const Coordinate<T> & FitLine3D<T>::getDirectionVector() const 00320 { 00321 return m_direction_vector; 00322 } 00323 00324 00333 template <class T> 00334 Coordinate<T> FitLine3D<T>::getNormalVector() const 00335 { 00336 /* 00337 00338 The shortest distance between a line and the origin is found along a 00339 line segment perpendicular to that line (i.e. a normal running through 00340 the origin). 00341 00342 Given a point P on the line, we might decompose it into two vectors a 00343 and b, one (a) being collinear with direction vector v and the other 00344 one (b) being collinear with a normal n (with this respect, n and v 00345 form a new basis) 00346 00347 P = a + b = alpha * v + beta * n. 00348 00349 Then it holds, that 00350 00351 P * v = alpha * 1 + beta * 0 = alpha 00352 00353 where alpha is the length of the line segment between P and P', and 00354 where P' is an interception point between the normal and the line. 00355 beta = |P'| is the sought distance between the line and the origin. 00356 Since P, P' and the origin form a right angle triangle, we are able 00357 to compute the absolute value of beta by 00358 00359 beta = sqrt{ |P|^2 - alpha^2 } = sqrt{ |P|^2 - (P * v)^2 } 00360 00361 Then the normal can be easily computed by rearranging the first equation 00362 00363 n = ( P - alpha * v) / beta 00364 00365 Since we only have the absolute value of beta, we might still need to 00366 negate the normal n. 00367 00368 */ 00369 00370 using std::sqrt; 00371 00372 T alpha = m_point_on_line.dot(m_direction_vector); 00373 T beta = sqrt(m_point_on_line.norm() * m_point_on_line.norm() - alpha * alpha); 00374 Coordinate<T> normal = (m_point_on_line - alpha * m_direction_vector).normalize(); 00375 // Coordinate<T> normal = (m_point_on_line - alpha * m_direction_vector) / beta; 00376 00377 T residual = (m_point_on_line - alpha * m_direction_vector - beta * normal).norm(); 00378 00379 if (Tools::isZero(residual)) 00380 { 00381 return normal; 00382 } 00383 else 00384 { 00385 return -1.0 * normal; 00386 } 00387 } 00388 00389 00395 template <class T> 00396 T FitLine3D<T>::getDistanceFromOrigin() const 00397 { 00398 /* 00399 00400 The shortest distance between a line and the origin is found along a 00401 line segment perpendicular to that line (i.e. a normal running through 00402 the origin). 00403 00404 Given a point P on the line, we might decompose it into two vectors a 00405 and b, one (a) being collinear with direction vector v and the other 00406 one (b) being collinear with a normal n (with this respect, n and v 00407 form a new basis) 00408 00409 P = a + b = alpha * v + beta * n. 00410 00411 Then it holds, that 00412 00413 P * v = alpha * 1 + beta * 0 = alpha 00414 00415 where alpha is the length of the line segment between P and P', and 00416 where P' is an interception point between the normal and the line. 00417 beta = |P'| is the sought distance between the line and the origin. 00418 Since P, P' and the origin form a right angle triangle, we are able 00419 to compute beta by 00420 00421 beta = sqrt{ |P|^2 - alpha^2 } = sqrt{ |P|^2 - (P * v)^2 } 00422 00423 */ 00424 00425 using std::sqrt; 00426 00427 T alpha = m_point_on_line.dot(m_direction_vector); 00428 T beta = sqrt(m_point_on_line.norm() * m_point_on_line.norm() - alpha * alpha); 00429 00430 return beta; 00431 } 00432 00433 00441 template <class T> 00442 T FitLine3D<T>::getDistanceTo(const Coordinate<T> & point) const 00443 { 00444 assert(point.size() == 3); 00445 00446 // To get the distance between a point and the line, we can create 00447 // a difference vector between the given point and a point on the 00448 // line. Then this difference vector can be projected to the line 00449 // forming a right angle triangle (similar as described above). 00450 // Eventually, using the Pythagorean theorem, the distance can be 00451 // computed. 00452 00453 using std::abs; 00454 using std::sqrt; 00455 00456 Coordinate<T> difference_vector = point - m_point_on_line; 00457 00458 T projection = difference_vector.dot(m_direction_vector); 00459 00460 T distance = sqrt(abs(difference_vector.norm() * difference_vector.norm() - 00461 projection * projection)); 00462 00463 return distance; 00464 } 00465 00466 00467 template <class T> 00468 inline unsigned FitLine3D<T>::getNumberPointsRequired() const 00469 { 00470 return 2; 00471 } 00472 00473 00479 template <class T> 00480 const Coordinate<T> & FitLine3D<T>::getPointOnLineSegment() const 00481 { 00482 return m_point_on_line; 00483 } 00484 00485 00491 template <class T> 00492 T FitLine3D<T>::getRMS() const 00493 { 00494 using std::abs; 00495 00496 const int number_of_points = super::m_points.cols(); 00497 00498 T sum_of_squared_errors = 0; 00499 00500 for (int i = 0; i < number_of_points; ++i) 00501 { 00502 // To get the distance between a point and the line, we can create 00503 // a difference vector between the given point and a point on the 00504 // line. Then this difference vector can be projected to the line 00505 // forming a right angle triangle (similar as described above). 00506 // Eventually, using the Pythagorean theorem, the distance can be 00507 // computed. 00508 00509 Coordinate<T> point(super::m_points.col(i)); 00510 Coordinate<T> difference_vector = point - m_point_on_line; 00511 00512 T projection = difference_vector.dot(m_direction_vector); 00513 00514 T squared_distance = abs(difference_vector.norm() * difference_vector.norm() - 00515 projection * projection); 00516 00517 sum_of_squared_errors += squared_distance; 00518 } 00519 00520 using std::sqrt; 00521 00522 return sqrt(sum_of_squared_errors / number_of_points); 00523 } 00524 00525 00526 } // namespace TRTK 00527 00528 00529 #endif // FIT_LINE_3D_HPP_9482948571
Documentation generated by Doxygen