00001 /* 00002 Copyright (C) 2010 - 2014 Christoph Haenisch 00003 00004 Chair of Medical Engineering (mediTEC) 00005 RWTH Aachen University 00006 Pauwelsstr. 20 00007 52074 Aachen 00008 Germany 00009 00010 Version 0.2.4 (2013-08-20) 00011 */ 00012 00019 #ifndef ESTIMATE_PROJECTIVE_TRANSFORMATION_2D_HPP_7460134016 00020 #define ESTIMATE_PROJECTIVE_TRANSFORMATION_2D_HPP_7460134016 00021 00022 00023 #include <cmath> 00024 #include <vector> 00025 00026 #include <Eigen/Core> 00027 #include <Eigen/Dense> 00028 #include <Eigen/StdVector> 00029 00030 #include "ErrorObj.hpp" 00031 #include "Coordinate.hpp" 00032 #include "EstimateTransformation2D.hpp" 00033 00034 00035 namespace TRTK 00036 { 00037 00038 00221 template <class T> 00222 class EstimateProjectiveTransformation2D : public EstimateTransformation2D<T> 00223 { 00224 private: 00225 00226 typedef EstimateTransformation2D<T> super; 00227 00228 public: 00229 00230 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 00231 00232 typedef T value_type; 00233 00234 typedef typename super::ArrayXT ArrayXT; 00235 typedef typename super::MatrixXT MatrixXT; 00236 typedef typename super::VectorXT VectorXT; 00237 typedef typename super::Vector2T Vector2T; 00238 typedef typename super::Vector3T Vector3T; 00239 typedef typename super::Vector4T Vector4T; 00240 typedef typename super::RowVector2T RowVector2T; 00241 typedef typename super::RowVector3T RowVector3T; 00242 typedef typename super::Matrix2T Matrix2T; 00243 typedef typename super::Matrix3T Matrix3T; 00244 typedef typename super::Matrix4T Matrix4T; 00245 00246 using super::NOT_ENOUGH_POINTS; 00247 using super::UNEQUAL_NUMBER_OF_POINTS; 00248 using super::UNKNOWN_ERROR; 00249 using super::WRONG_POINT_SIZE; 00250 00251 EstimateProjectiveTransformation2D(); 00252 00253 EstimateProjectiveTransformation2D(const std::vector<Coordinate<T> > & source_points, 00254 const std::vector<Coordinate<T> > & target_points); 00255 00256 EstimateProjectiveTransformation2D(const std::vector<Vector2T, Eigen::aligned_allocator<Vector2T> > & source_points, 00257 const std::vector<Vector2T, Eigen::aligned_allocator<Vector2T> > & target_points); 00258 00259 EstimateProjectiveTransformation2D(const std::vector<Vector3T> & source_points, 00260 const std::vector<Vector3T> & target_points); 00261 00262 virtual ~EstimateProjectiveTransformation2D(); 00263 00264 void compute(); 00265 00266 void setMaxIterations(int value); 00267 void setMaxRMS(value_type value); 00268 00269 private: 00270 00271 int m_max_iterations; 00272 static const int MAX_ITERATIONS = 10; 00273 00274 value_type m_max_RMS; 00275 static const value_type MAX_RMS; 00276 }; 00277 00278 00279 template <class T> 00280 const T EstimateProjectiveTransformation2D<T>::MAX_RMS = 0.1; 00281 00282 00288 template <class T> 00289 EstimateProjectiveTransformation2D<T>::EstimateProjectiveTransformation2D() : 00290 m_max_iterations(MAX_ITERATIONS), 00291 m_max_RMS(MAX_RMS) 00292 { 00293 } 00294 00295 00317 template <class T> 00318 EstimateProjectiveTransformation2D<T>::EstimateProjectiveTransformation2D(const std::vector<Coordinate<T> > & source_points, 00319 const std::vector<Coordinate<T> > & target_points) : 00320 m_max_iterations(MAX_ITERATIONS), 00321 m_max_RMS(MAX_RMS) 00322 { 00323 super::setSourcePoints(source_points); 00324 super::setTargetPoints(target_points); 00325 } 00326 00327 00341 template <class T> 00342 EstimateProjectiveTransformation2D<T>::EstimateProjectiveTransformation2D(const std::vector<Vector2T, Eigen::aligned_allocator<Vector2T> > & source_points, 00343 const std::vector<Vector2T, Eigen::aligned_allocator<Vector2T> > & target_points) : 00344 m_max_iterations(MAX_ITERATIONS), 00345 m_max_RMS(MAX_RMS) 00346 { 00347 super::setSourcePoints(source_points); 00348 super::setTargetPoints(target_points); 00349 } 00350 00351 00365 template <class T> 00366 EstimateProjectiveTransformation2D<T>::EstimateProjectiveTransformation2D(const std::vector<Vector3T> & source_points, 00367 const std::vector<Vector3T> & target_points) : 00368 m_max_iterations(MAX_ITERATIONS), 00369 m_max_RMS(MAX_RMS) 00370 { 00371 super::setSourcePoints(source_points); 00372 super::setTargetPoints(target_points); 00373 } 00374 00375 00381 template <class T> 00382 EstimateProjectiveTransformation2D<T>::~EstimateProjectiveTransformation2D() 00383 { 00384 } 00385 00386 00407 template <class T> 00408 void EstimateProjectiveTransformation2D<T>::compute() 00409 { 00410 if (super::m_source_points.size() != super::m_target_points.size()) 00411 { 00412 ErrorObj error; 00413 error.setClassName("EstimateProjectiveTransformation2D<T>"); 00414 error.setFunctionName("compute"); 00415 error.setErrorMessage("Point sets must have the same cardinality."); 00416 error.setErrorCode(UNEQUAL_NUMBER_OF_POINTS); 00417 throw error; 00418 } 00419 00420 if (super::m_source_points.cols() < 4 || super::m_target_points.cols() < 4) 00421 { 00422 ErrorObj error; 00423 error.setClassName("EstimateProjectiveTransformation2D<T>"); 00424 error.setFunctionName("compute"); 00425 error.setErrorMessage("Not enough points to compute transformation matrix."); 00426 error.setErrorCode(NOT_ENOUGH_POINTS); 00427 throw error; 00428 } 00429 00430 /* 00431 00432 Explanation of the algorithm: 00433 00434 By using homogeneous coordinates an projective transformation can be described as follows: 00435 00436 | a_11 a_12 a_13 | | source_x | | target_x' | 00437 | a_21 a_22 a_23 | * | source_y | = | target_y' | 00438 | a_31 a_32 1 | | 1 | | target_z' |. <-- note: target_z' is *not* known 00439 00440 Then the sought target coordinates are: 00441 00442 | target_x | | target_x' / target_z' | 00443 | target_y | = | target_y' / target_z' | 00444 | target_z | | 1 |. 00445 00446 The matrix A can be normalized such that component a_33 is equal to one, i.e. A := A' / a'_33, because of the 00447 subsequent normalisation step. Hence A has only 8 DoF. Since the normalisation step is nonlinear, we use 00448 Newton's method (after Simpson) to solve for the entries a_11, ..., a_32. 00449 00450 The equation can be rewritten as a function f of the sought variables a_11, ..., a_32: 00451 00452 | f_1 | 1 | a_11 * source_x + a_12 * source_y + a_13 | 00453 f = | | = -------------------------------------- * | | 00454 | f_2 | a_31 * source_x + a_32 * source_y + 1 | a_21 * source_x + a_22 * source_y + a_23 |. 00455 00456 Then we try to find the zero-crossing of the residual (and we are done): 00457 00458 residual(a_11, ..., a_32) := f(a_11, ..., a_32) - |target_x, target_y|^T. 00459 00460 This can be done iteratively by computing 00461 00462 A_new = A_old - Jacobian(residual)^(-1) * residual 00463 00464 where 00465 00466 | c_3*source_x c_3*source_y c_3 0 0 0 -c_1*c_4*source_x -c_1*c_4*source_y | 00467 Jacobian = | | 00468 | 0 0 0 c_3*source_x c_3*source_y c_3 -c_2*c_4*source_x -c_2*c_4*source_y | 00469 00470 and 00471 00472 c_1 = a_11 * source_x + a_12 * source_y + a_13 00473 c_2 = a_21 * source_x + a_22 * source_y + a_23 00474 c_3 = 1 / (a_31 * source_x + a_31 * source_y + 1) 00475 c_4 = c_3^2. 00476 00477 Instead of inverting the Jacobian directly we rewrite the above equation: 00478 00479 delta_A := A_new - A_old = - Jacobian(residual)^(-1) * residual 00480 00481 and solve the system of linear equations with respect to delta_A: 00482 00483 delta_A * Jacobian = -residual. 00484 00485 By using the method of least squares (or the Moore-Penrose pseudoinverse) we get 00486 00487 delta_A = - (Jacobian^T * Jacobian)^(-1) * (Jacobian^T * residual). 00488 00489 Then (in each iteration step) the new solution vector becomes: 00490 00491 A_new = A_old + delta_A. 00492 00493 */ 00494 00495 const int number_points = super::m_source_points.cols(); 00496 00497 // initialize solution matrix 00498 00499 Matrix3T A; 00500 A.setIdentity(); 00501 00502 const ArrayXT & source_x = super::m_source_points.block(0, 0, 1, number_points).transpose(); 00503 const ArrayXT & source_y = super::m_source_points.block(1, 0, 1, number_points).transpose(); 00504 00505 const ArrayXT & target_x = super::m_target_points.block(0, 0, 1, number_points).transpose(); 00506 const ArrayXT & target_y = super::m_target_points.block(1, 0, 1, number_points).transpose(); 00507 00508 for (int i = 0; i < m_max_iterations; ++i) 00509 { 00510 00511 // construct Jacobian matrix 00512 00513 ArrayXT ones = ArrayXT::Ones(number_points); 00514 00515 ArrayXT c_1(number_points, 1); 00516 ArrayXT c_2(number_points, 1); 00517 ArrayXT c_3(number_points, 1); 00518 ArrayXT c_4(number_points, 1); 00519 00520 c_1 = A(0, 0) * source_x + A(0, 1) * source_y + A(0, 2) * ones; 00521 c_2 = A(1, 0) * source_x + A(1, 1) * source_y + A(1, 2) * ones; 00522 c_3 = (A(2, 0) * source_x + A(2, 1) * source_y + ones).inverse(); 00523 c_4 = c_3 * c_3; 00524 00525 MatrixXT Jacobian(2 * number_points, 8); 00526 00527 Jacobian.setZero(); 00528 00529 Jacobian.block(0 * number_points, 0, number_points, 1) = c_3 * source_x; 00530 Jacobian.block(0 * number_points, 1, number_points, 1) = c_3 * source_y; 00531 Jacobian.block(0 * number_points, 2, number_points, 1) = c_3; 00532 Jacobian.block(0 * number_points, 6, number_points, 1) = -c_1 * c_4 * source_x; 00533 Jacobian.block(0 * number_points, 7, number_points, 1) = -c_1 * c_4 * source_y; 00534 00535 Jacobian.block(1 * number_points, 3, number_points, 1) = c_3 * source_x; 00536 Jacobian.block(1 * number_points, 4, number_points, 1) = c_3 * source_y; 00537 Jacobian.block(1 * number_points, 5, number_points, 1) = c_3; 00538 Jacobian.block(1 * number_points, 6, number_points, 1) = -c_2 * c_4 * source_x; 00539 Jacobian.block(1 * number_points, 7, number_points, 1) = -c_2 * c_4 * source_y; 00540 00541 // compute residual vector 00542 00543 VectorXT residual(2 * number_points); 00544 00545 residual.block(0 * number_points, 0, number_points, 1) = c_1 * c_3 - target_x; 00546 residual.block(1 * number_points, 0, number_points, 1) = c_2 * c_3 - target_y; 00547 00548 // compute difference vector 00549 00550 VectorXT delta_A = - ((Jacobian.transpose() * Jacobian).inverse() * (Jacobian.transpose() * residual)); 00551 00552 // compute estimate of transformation matrix 00553 00554 A(0, 0) += delta_A(0); 00555 A(0, 1) += delta_A(1); 00556 A(0, 2) += delta_A(2); 00557 A(1, 0) += delta_A(3); 00558 A(1, 1) += delta_A(4); 00559 A(1, 2) += delta_A(5); 00560 A(2, 0) += delta_A(6); 00561 A(2, 1) += delta_A(7); 00562 00563 // compute MSE as stop criterion 00564 00565 Transform2D<T> transform(A); 00566 00567 value_type RMS = 0; 00568 00569 for (int n = 0; n < number_points; ++n) 00570 { 00571 Vector2T source = Vector2T(source_x(n), source_y(n)); 00572 Vector2T target = Vector2T(target_x(n), target_y(n)); 00573 00574 RMS += (transform * source - target).squaredNorm(); 00575 } 00576 00577 using std::sqrt; 00578 00579 RMS = sqrt(RMS / number_points); 00580 00581 if (RMS < m_max_RMS) break; 00582 } 00583 00584 super::m_transformation_matrix = A; 00585 } 00586 00587 00600 template <class T> 00601 void EstimateProjectiveTransformation2D<T>::setMaxIterations(int value) 00602 { 00603 m_max_iterations = value; 00604 } 00605 00606 00620 template <class T> 00621 void EstimateProjectiveTransformation2D<T>::setMaxRMS(value_type value) 00622 { 00623 m_max_RMS = value; 00624 } 00625 00626 00627 } // namespace TRTK 00628 00629 00630 #endif // ESTIMATE_PROJECTIVE_TRANSFORMATION_2D_HPP_7460134016
Documentation generated by Doxygen