threePointAlgorithm.h

Go to the documentation of this file.
00001 
00015 #ifndef DLR_COMPUTERVISION_THREEPOINTALGORITHM_H
00016 #define DLR_COMPUTERVISION_THREEPOINTALGORITHM_H
00017 
00018 #include <dlrComputerVision/cameraIntrinsicsPinhole.h>
00019 #include <dlrNumeric/transform3D.h>
00020 #include <dlrNumeric/vector2D.h>
00021 #include <dlrNumeric/vector3D.h>
00022 #include <dlrRandom/pseudoRandom.h>
00023 
00024 namespace dlr {
00025 
00026   namespace computerVision {
00027 
00095     template <class IterType>
00096     unsigned int
00097     threePointAlgorithm(dlr::numeric::Vector3D const& w0,
00098                         dlr::numeric::Vector3D const& w1,
00099                         dlr::numeric::Vector3D const& w2,
00100                         dlr::numeric::Vector2D const& u0,
00101                         dlr::numeric::Vector2D const& u1,
00102                         dlr::numeric::Vector2D const& u2,
00103                         CameraIntrinsicsPinhole const& intrinsics,
00104                         IterType p0OutputIter,
00105                         IterType p1OutputIter,
00106                         IterType p2OutputIter,
00107                         double epsilon = 1.0E-8);
00108 
00152     template<class InIter3D, class InIter2D, class OutIter3D>
00153     dlr::numeric::Transform3D
00154     threePointAlgorithmRobust(InIter3D worldPointsBegin,
00155                               InIter3D worldPointsEnd,
00156                               InIter2D imagePointsBegin,
00157                               CameraIntrinsicsPinhole const& intrinsics,
00158                               size_t iterations,
00159                               double inlierProportion,
00160                               double& score,
00161                               dlr::random::PseudoRandom& pRandom
00162                               = dlr::random::PseudoRandom());
00163 
00164 
00165     template <class OutIter>
00166     unsigned int
00167     solveThreePointAlgorithmQuarticSystem(
00168       double cosAlpha, double cosBeta, double cosGamma,
00169       double a2, double b2, double c2, double epsilon,
00170       OutIter s0Iter, OutIter s1Iter, OutIter s2Iter,
00171       double& condition);
00172     
00173   } // namespace computerVision
00174     
00175 } // namespace dlr
00176 
00177 
00178 /* ============ Definitions of inline & template functions ============ */
00179 
00180 
00181 #include <cmath>
00182 #include <complex>
00183 #include <limits>
00184 #include <dlrComputerVision/registerPoints3D.h>
00185 #include <dlrNumeric/solveQuartic.h>
00186 #include <dlrNumeric/utilities.h>
00187 
00188 namespace dlr {
00189 
00190   namespace computerVision {
00191 
00192     // This function implements the "three point perspective pose
00193     // estimation algorithm" of Grunert[1][2] for recovering the
00194     // camera-frame coordinates of the corners of a triangle of known
00195     // size, given the projections of those corners in the camera
00196     // image.
00197     template <class IterType>
00198     unsigned int
00199     threePointAlgorithm(dlr::numeric::Vector3D const& w0,
00200                         dlr::numeric::Vector3D const& w1,
00201                         dlr::numeric::Vector3D const& w2,
00202                         dlr::numeric::Vector2D const& u0,
00203                         dlr::numeric::Vector2D const& u1,
00204                         dlr::numeric::Vector2D const& u2,
00205                         CameraIntrinsicsPinhole const& intrinsics,
00206                         IterType p0OutputIter,
00207                         IterType p1OutputIter,
00208                         IterType p2OutputIter,
00209                         double epsilon)
00210     {
00211       unsigned int numberOfSolutions = 0;
00212 
00213       // Following the conventions of the paper, we define j0, j1, j2
00214       // to be unit vectors in the camera coordinate frame that point
00215       // toward the three world points w0, w1, and w2, respectively.
00216       dlr::numeric::Vector3D j0 =
00217         intrinsics.reverseProject(u0).getDirectionVector();
00218       dlr::numeric::Vector3D j1 =
00219         intrinsics.reverseProject(u1).getDirectionVector();
00220       dlr::numeric::Vector3D j2 =
00221         intrinsics.reverseProject(u2).getDirectionVector();
00222 
00223       // Define alpha to be the angle between j1 and j2, beta to be
00224       // the angle between j0 and j2, and gamma to be the angle
00225       // between j0 and j1.
00226       double cosAlpha = dlr::numeric::dot(j1, j2);
00227       double cosBeta = dlr::numeric::dot(j0, j2);
00228       double cosGamma = dlr::numeric::dot(j0, j1);
00229 
00230       // Similarly, define a, b, and c to be the distance between the
00231       // 3D points that define alpha, beta, and gamma, respectively.
00232       // We only need the squares of these distances, so we save a
00233       // sqrt() call for each by computing only the square.
00234       double a2 = dlr::numeric::magnitudeSquared(w1 - w2);
00235       double b2 = dlr::numeric::magnitudeSquared(w0 - w2);
00236       double c2 = dlr::numeric::magnitudeSquared(w0 - w1);
00237       
00238       // If we define s0, s1, and s2 to be the distances from the
00239       // camera focus to each of the three points, then we have:
00240       //
00241       //   p0 = s0 * j0
00242       //   p1 = s1 * j1
00243       //   p2 = s2 * j2
00244       //
00245       // Where p0, p1, and p2 are the positions of the three 3D points
00246       // in camera coordinates.  The law of cosines gives us:
00247       //
00248       //   s1^2 + s2^2 - 2*s1*s2*cos(alpha) = a^2
00249       //   s0^2 + s2^2 - 2*s0*s2*cos(beta) = b^2
00250       //   s0^2 + s1^2 - 2*s0*s1*cos(gamma) = c^2
00251 
00252       // If we choose k1, k2 so that s1 = k1*s0, and s2 = k2*s0 and
00253       // substitute into (and rearrange) each of the law of cosines
00254       // equations, we get:
00255       //
00256       //   s0^2 = a^2 / (k1^2 + k2^2 + 2*k1*k2*cos(alpha))
00257       //   s0^2 = b^2 / (1 + k2^2 + 2*k2*cos(beta))
00258       //   s0^2 = c^2 / (1 + k1^2 + 2*k1*cos(gamma))
00259       //
00260       // Combining these to eliminate k2, we obtain an expression for
00261       // k1 in terms of k2, and a quartic equation in k2.  These are
00262       // equations 8 and 9 in [1], and are not reproduced in this
00263       // comment (although the code below implements first the quartic
00264       // equation, and then the expression for k1).
00265       double s0Array0[4];
00266       double s1Array0[4];
00267       double s2Array0[4];
00268       double condition0;
00269       unsigned int newSolutions0 = solveThreePointAlgorithmQuarticSystem(
00270         cosAlpha, cosBeta, cosGamma, a2, b2, c2, epsilon,
00271         &(s0Array0[0]), &(s1Array0[0]), &(s2Array0[0]),
00272         condition0);
00273       double s0Array1[4];
00274       double s1Array1[4];
00275       double s2Array1[4];
00276       double condition1;
00277       unsigned int newSolutions1 = solveThreePointAlgorithmQuarticSystem(
00278         cosBeta, cosAlpha, cosGamma, b2, a2, c2, epsilon,
00279         &(s1Array1[0]), &(s0Array1[0]), &(s2Array1[0]),
00280         condition1);
00281       double s0Array2[4];
00282       double s1Array2[4];
00283       double s2Array2[4];
00284       double condition2;
00285       unsigned int newSolutions2 = solveThreePointAlgorithmQuarticSystem(
00286         cosBeta, cosGamma, cosAlpha, b2, c2, a2, epsilon,
00287         &(s1Array2[0]), &(s2Array2[0]), &(s0Array2[0]),
00288         condition2);
00289 
00290       double* s0Array;
00291       double* s1Array;
00292       double* s2Array;
00293       unsigned int newSolutions;
00294       if((condition0 >= condition1) && (condition0 >= condition2) ) {
00295         s0Array = s0Array0;
00296         s1Array = s1Array0;
00297         s2Array = s2Array0;
00298         newSolutions = newSolutions0;
00299       } else if((condition1 >= condition0) && (condition1 >= condition2) ) {
00300         s0Array = s0Array1;
00301         s1Array = s1Array1;
00302         s2Array = s2Array1;
00303         newSolutions = newSolutions1;
00304       } else {
00305         s0Array = s0Array2;
00306         s1Array = s1Array2;
00307         s2Array = s2Array2;
00308         newSolutions = newSolutions2;
00309       }
00310 
00311       for(unsigned int ii = 0; ii < newSolutions; ++ii) {
00312         *p0OutputIter = s0Array[ii] * j0;
00313         *p1OutputIter = s1Array[ii] * j1;
00314         *p2OutputIter = s2Array[ii] * j2;
00315         
00316         ++p0OutputIter;
00317         ++p1OutputIter;
00318         ++p2OutputIter;
00319         ++numberOfSolutions;
00320       }
00321       return numberOfSolutions;
00322     }
00323 
00324 
00325     template<class InIter3D, class InIter2D>
00326     dlr::numeric::Transform3D
00327     threePointAlgorithmRobust(InIter3D worldPointsBegin,
00328                               InIter3D worldPointsEnd,
00329                               InIter2D imagePointsBegin,
00330                               CameraIntrinsicsPinhole const& intrinsics,
00331                               size_t iterations,
00332                               double inlierProportion,
00333                               double& score,
00334                               dlr::random::PseudoRandom& pRandom)
00335     {
00336       // State variables so we'll remember the correct essential
00337       // matrix once we find it.
00338       double bestErrorSoFar = std::numeric_limits<double>::max();
00339       dlr::numeric::Transform3D selectedCandidate;
00340 
00341       // Sanity check arguments.
00342       size_t numberOfPoints = worldPointsEnd - worldPointsBegin;
00343       if(numberOfPoints < 4) {
00344         DLR_THROW(ValueException, "threePointAlgorithmRobust()",
00345                   "Input sequence must have at least four elements.");
00346       }
00347       
00348       // Copy input points into local buffers.
00349       std::vector<dlr::numeric::Vector3D> worldPoints(numberOfPoints);
00350       std::vector<dlr::numeric::Vector2D> imagePoints(numberOfPoints);
00351       std::copy(worldPointsBegin, worldPointsEnd, worldPoints.begin());
00352       std::copy(imagePointsBegin, imagePointsBegin + numberOfPoints,
00353                 imagePoints.begin());
00354 
00355       // Make a buffer to hold points in camera space (and from which
00356       // to compute residual errors.
00357       std::vector<dlr::numeric::Vector3D> cameraPoints(numberOfPoints);
00358 
00359       // Start the algorithm!
00360       for(size_t ii = 0; ii < iterations; ++ii) {
00361 
00362         // Select three points.
00363         for(size_t jj = 0; jj < 3; ++jj) {
00364           int selectedIndex = pRandom.uniformInt(jj, numberOfPoints);
00365           if(selectedIndex != static_cast<int>(jj)) {
00366             std::swap(worldPoints[jj], worldPoints[selectedIndex]);
00367             std::swap(imagePoints[jj], imagePoints[selectedIndex]);
00368           }
00369         }
00370 
00371         // Get candidate cameraPoints.
00372         dlr::numeric::Vector3D testPoints0_cam[4];
00373         dlr::numeric::Vector3D testPoints1_cam[4];
00374         dlr::numeric::Vector3D testPoints2_cam[4];
00375         unsigned int numberOfSolutions = threePointAlgorithm(
00376           worldPoints[0], worldPoints[1], worldPoints[2],
00377           imagePoints[0], imagePoints[1], imagePoints[2], intrinsics,
00378           testPoints0_cam, testPoints1_cam, testPoints2_cam);
00379 
00380         // Test each candidate solution.
00381         for(size_t jj = 0; jj < numberOfSolutions; ++jj) {
00382 
00383           // Recover the camTworld transform corresponding to this
00384           // solution.
00385           cameraPoints[0] = testPoints0_cam[jj];
00386           cameraPoints[1] = testPoints1_cam[jj];
00387           cameraPoints[2] = testPoints2_cam[jj];
00388           dlr::numeric::Transform3D camTworld = registerPoints3D(
00389             worldPoints.begin(), worldPoints.begin() + 3, cameraPoints.begin());
00390 
00391           // Transform all world points into camera coordinates.
00392           std::transform(worldPoints.begin(), worldPoints.end(),
00393                          cameraPoints.begin(), camTworld.getFunctor());
00394 
00395           // Project all camera points into image coordinates and
00396           // compute residuals..
00397           std::vector<double> residualVector(numberOfPoints);
00398           for(size_t kk = 0; kk < cameraPoints.size(); ++kk) {
00399             dlr::numeric::Vector2D testPoint_image =
00400               intrinsics.project(cameraPoints[kk]);
00401             residualVector[kk] = dlr::numeric::magnitudeSquared(
00402               testPoint_image - imagePoints[kk]);
00403           }
00404 
00405           // Compute robust error statistic.
00406           //
00407           // Note(xxx): Better not to sort here, since it changes the
00408           // algorithm to O(NlogN).
00409           std::sort(residualVector.begin(), residualVector.end());
00410           int testIndex = static_cast<int>(
00411             inlierProportion * (residualVector.size() - 1) + 0.5);
00412           double errorValue = residualVector[testIndex];
00413 
00414           // Remember candidate if it's the best so far.
00415           if(errorValue < bestErrorSoFar) {
00416             selectedCandidate = camTworld;
00417             bestErrorSoFar = errorValue;
00418           }
00419         }
00420       }
00421       score = bestErrorSoFar;
00422       return selectedCandidate;
00423     }
00424 
00425 
00426     template <class OutIter>
00427     unsigned int
00428     solveThreePointAlgorithmQuarticSystem(
00429       double cosAlpha, double cosBeta, double cosGamma,
00430       double a2, double b2, double c2,
00431       double epsilon,
00432       OutIter s0Iter, OutIter s1Iter, OutIter s2Iter,
00433       double& condition)
00434     {
00435       unsigned int numberOfSolutions = 0;
00436       condition = std::numeric_limits<double>::max();
00437       
00438       double cos2Alpha = cosAlpha * cosAlpha;
00439       double cos2Beta = cosBeta * cosBeta;
00440       double cos2Gamma = cosGamma * cosGamma;
00441       double a2OverB2 = a2 / b2;
00442       double a2MinusC2OverB2 = (a2 - c2) / b2;
00443       double a2MinusC2OverB2Sq = a2MinusC2OverB2 * a2MinusC2OverB2;
00444       double a2MinusC2OverB2Minus1 = a2MinusC2OverB2 - 1.0;
00445       double a2PlusC2OverB2 = (a2 + c2) / b2;
00446       double b2MinusA2OverB2 = (b2 - a2) / b2;
00447       double b2MinusC2OverB2 = (b2 - c2) / b2;
00448       double c2OverB2 = c2 / b2;
00449       double oneMinusA2PlusC2OverB2 = (1.0 - a2PlusC2OverB2);
00450       double oneMinusA2MinusC2OverB2 = (1.0 - a2MinusC2OverB2);
00451       double onePlusA2MinusC2OverB2 = (1.0 + a2MinusC2OverB2);
00452       
00453       double A0 = (onePlusA2MinusC2OverB2 * onePlusA2MinusC2OverB2
00454                    - 4.0 * a2OverB2 * cos2Gamma);
00455 
00456       double A1 = 4.0 * ((-a2MinusC2OverB2 * onePlusA2MinusC2OverB2 * cosBeta)
00457                          + (2.0 * a2OverB2 * cos2Gamma * cosBeta)
00458                          - (oneMinusA2PlusC2OverB2 * cosAlpha * cosGamma));
00459 
00460       double A2 = 2.0 * (a2MinusC2OverB2Sq - 1.0
00461                          + 2.0 * a2MinusC2OverB2Sq * cos2Beta
00462                          + 2.0 * b2MinusC2OverB2 * cos2Alpha
00463                          - 4.0 * a2PlusC2OverB2 * cosAlpha * cosBeta * cosGamma
00464                          + 2.0 * b2MinusA2OverB2 * cos2Gamma);
00465 
00466       double A3 = 4.0 * (a2MinusC2OverB2 * oneMinusA2MinusC2OverB2 * cosBeta
00467                          - oneMinusA2PlusC2OverB2 * cosAlpha * cosGamma
00468                          + 2.0 * c2OverB2 * cos2Alpha * cosBeta);
00469 
00470       double A4 = (a2MinusC2OverB2Minus1 * a2MinusC2OverB2Minus1
00471                    - 4.0 * c2OverB2 * cos2Alpha);
00472 
00473       // Now we solve for the roots of the quartic, which tell us
00474       // valid values of k2.  Each root corresponds to a scale factor
00475       // that's consistent with the observed data.
00476       std::complex<double> k2Roots[4];
00477       dlr::numeric::solveQuartic(
00478         A3 / A4, A2 / A4, A1 / A4, A0 / A4,
00479         k2Roots[0], k2Roots[1], k2Roots[2], k2Roots[3]);
00480 
00481       // For real value of k1, there's a corresponding value of k2 (see
00482       // Eq. 8 in [1]).
00483       for(unsigned int ii = 0; ii < 4; ++ii) {
00484         bool isReal = std::fabs(k2Roots[ii].imag()) <= epsilon;
00485         if(isReal) {
00486           double k2 = k2Roots[ii].real();
00487           double numerator = (a2MinusC2OverB2Minus1 * k2 * k2
00488                               - 2.0 * a2MinusC2OverB2 * cosBeta * k2
00489                               + onePlusA2MinusC2OverB2);
00490           double denominator = 2.0 * (cosGamma - k2 * cosAlpha);
00491           double magDenominator = std::fabs(denominator);
00492           if(magDenominator < condition) {
00493             condition = magDenominator;
00494           }
00495           if(magDenominator < epsilon) {
00496             continue;
00497           }
00498           double k1 = numerator / denominator;
00499 
00500           // Now that we have k1 and k2, recover the distance from the
00501           // focus to each of the observed points.
00502           double s0Sq = c2 / (1.0 + k1 * k1 - 2.0 * k1 * cosGamma);
00503           if(s0Sq < 0.0) {
00504             continue;
00505           }
00506           *s0Iter = std::sqrt(s0Sq);
00507           *s1Iter = k1 * (*s0Iter);
00508           *s2Iter = k2 * (*s0Iter);
00509 
00510           ++s0Iter;
00511           ++s1Iter;
00512           ++s2Iter;
00513           ++numberOfSolutions;
00514         }
00515       }
00516       if(numberOfSolutions == 0) {
00517         condition = 0.0;
00518       }
00519       return numberOfSolutions;
00520     }
00521 
00522   } // namespace computerVision
00523     
00524 } // namespace dlr
00525 
00526 #endif /* #ifndef DLR_COMPUTERVISION_THREEPOINTALGORITHM_H */

Generated on Wed Nov 25 12:15:06 2009 for dlrComputerVision Utility Library by  doxygen 1.5.8