cameraIntrinsicsPlumbBob.cpp

Go to the documentation of this file.
00001 
00016 #include <iomanip>
00017 #include <dlrCommon/inputStream.h>
00018 #include <dlrComputerVision/cameraIntrinsicsPlumbBob.h>
00019 #include <dlrOptimization/optimizerBFGS.h>
00020 #include <dlrOptimization/optimizerNelderMead.h>
00021 
00022 using namespace dlr::numeric;
00023 using namespace dlr::geometry;
00024 
00025 namespace {
00026 
00027   // We require reverse projection accuracy of better than 1/100th of
00028   // a pixel.
00029   const double l_maximumReverseProjectionResidual = 0.01 * 0.01;
00030   
00031 }
00032 
00033 
00034 namespace dlr {
00035 
00036   namespace computerVision {
00037 
00038 
00039     // The default constructor initializes the CameraIntrinsicsPlumbBob
00040     // instance to a consistent (but not terribly useful) state.
00041     CameraIntrinsicsPlumbBob::
00042     CameraIntrinsicsPlumbBob()
00043       : CameraIntrinsics(),
00044         m_centerU(50),
00045         m_centerV(50),
00046         m_kX(1.0),
00047         m_kY(1.0),
00048         m_numPixelsX(100),
00049         m_numPixelsY(100),
00050         m_radialCoefficient0(0.0),
00051         m_radialCoefficient1(0.0),
00052         m_radialCoefficient2(0.0),
00053         m_skewCoefficient(0.0),
00054         m_tangentialCoefficient0(0.0),
00055         m_tangentialCoefficient1(0.0)
00056     {
00057       // Empty.
00058     }
00059       
00060 
00061     // This constructor allows the caller to explicitly set the
00062     // camera intrinsic parameters.
00063     CameraIntrinsicsPlumbBob::
00064     CameraIntrinsicsPlumbBob(size_t numPixelsX,
00065                              size_t numPixelsY,
00066                              double focalLengthX,
00067                              double focalLengthY,
00068                              double centerU,
00069                              double centerV,
00070                              double skewCoefficient,
00071                              double radialCoefficient0,
00072                              double radialCoefficient1,
00073                              double radialCoefficient2,
00074                              double tangentialCoefficient0,
00075                              double tangentialCoefficient1)
00076       : CameraIntrinsics(),
00077         m_centerU(centerU),
00078         m_centerV(centerV),
00079         m_kX(focalLengthX),
00080         m_kY(focalLengthY),
00081         m_numPixelsX(numPixelsX),
00082         m_numPixelsY(numPixelsY),
00083         m_radialCoefficient0(radialCoefficient0),
00084         m_radialCoefficient1(radialCoefficient1),
00085         m_radialCoefficient2(radialCoefficient2),
00086         m_skewCoefficient(skewCoefficient),
00087         m_tangentialCoefficient0(tangentialCoefficient0),
00088         m_tangentialCoefficient1(tangentialCoefficient1)
00089     {
00090       // Empty.
00091     }
00092     
00093 
00094 
00095     // This member function takes a point in 3D camera coordinates
00096     // and projects it into pixel coordinates.
00097     Vector2D
00098     CameraIntrinsicsPlumbBob::
00099     project(const Vector3D& point) const
00100     {
00101       // The Vector2D constructor will divide by point.z().
00102       Vector2D normalizedPoint(point.x(), point.y(), point.z());
00103 
00104       double xSquared = normalizedPoint.x() * normalizedPoint.x();
00105       double ySquared = normalizedPoint.y() * normalizedPoint.y();
00106       double rSquared = xSquared + ySquared;
00107       double rFourth = rSquared * rSquared;
00108       double rSixth = rSquared * rFourth;
00109 
00110       // Compute distortion terms according to plumb bob model.
00111       double radialDistortion = (1.0 + m_radialCoefficient0 * rSquared
00112                                  + m_radialCoefficient1 * rFourth
00113                                  + m_radialCoefficient2 * rSixth);
00114 
00115       double crossTerm = normalizedPoint.x() * normalizedPoint.y();
00116       Vector2D tangentialDistortion(
00117         (2.0 * m_tangentialCoefficient0 * crossTerm
00118          + m_tangentialCoefficient1 * (rSquared + 2.0 * xSquared)),
00119         (m_tangentialCoefficient0 * (rSquared + 2.0 * ySquared)
00120          + 2.0 * m_tangentialCoefficient1 * crossTerm));
00121 
00122       // Apply distortion and skew, then project into pixel coordinates.
00123       Vector2D distortedPoint(
00124         radialDistortion * normalizedPoint + tangentialDistortion);
00125       Vector2D skewedPoint(
00126         distortedPoint.x() + m_skewCoefficient * distortedPoint.y(),
00127         distortedPoint.y());
00128       
00129       return Vector2D(m_kX * skewedPoint.x() + m_centerU,
00130                       m_kY * skewedPoint.y() + m_centerV);
00131     }
00132     
00133 
00134     // This member function sets the calibration from an input
00135     // stream.
00136     std::istream&
00137     CameraIntrinsicsPlumbBob::
00138     readFromStream(std::istream& stream)
00139     {
00140       // If stream is in a bad state, we can't read from it.
00141       if (!stream){
00142         return stream;
00143       }
00144     
00145       // Construct an InputStream instance so we can use our
00146       // convenience functions.
00147       InputStream inputStream(stream, InputStream::SKIP_WHITESPACE);
00148 
00149       double centerU;
00150       double centerV;
00151       double kX;
00152       double kY;
00153       size_t numpixelsX;
00154       size_t numpixelsY;
00155       double radialCoefficient0;
00156       double radialCoefficient1;
00157       double radialCoefficient2;
00158       double skewCoefficient;
00159       double tangentialCoefficient0;
00160       double tangentialCoefficient1;
00161       
00162       inputStream.expect("CameraIntrinsicsPlumbBob");
00163       inputStream.expect("{");
00164       stream >> numpixelsX;
00165       inputStream.expect(",");
00166       stream >> numpixelsY;
00167       inputStream.expect(",");
00168       stream >> kX;
00169       inputStream.expect(",");
00170       stream >> kY;
00171       inputStream.expect(",");
00172       stream >> centerU;
00173       inputStream.expect(",");
00174       stream >> centerV;
00175       inputStream.expect(",");
00176       stream >> skewCoefficient;
00177       inputStream.expect(",");
00178       stream >> radialCoefficient0;
00179       inputStream.expect(",");
00180       stream >> radialCoefficient1;
00181       inputStream.expect(",");
00182       stream >> radialCoefficient2;
00183       inputStream.expect(",");
00184       stream >> tangentialCoefficient0;
00185       inputStream.expect(",");
00186       stream >> tangentialCoefficient1;
00187       inputStream.expect("}");
00188 
00189       if(stream) {
00190         m_centerU = centerU;
00191         m_centerV = centerV;
00192         m_kX = kX;
00193         m_kY = kY;
00194         m_numPixelsX = numpixelsX;
00195         m_numPixelsY = numpixelsY;
00196         m_radialCoefficient0 = radialCoefficient0;
00197         m_radialCoefficient1 = radialCoefficient1;
00198         m_radialCoefficient2 = radialCoefficient2;
00199         m_skewCoefficient = skewCoefficient;
00200         m_tangentialCoefficient0 = tangentialCoefficient0;
00201         m_tangentialCoefficient1 = tangentialCoefficient1;
00202       }
00203       return stream;
00204     }
00205 
00206 
00207     // This member function takes a point in 2D pixel coordinates
00208     // and returns a ray in 3D camera coordinates passing through
00209     // all of the 3D points that project to the specified 2D
00210     // position.
00211     geometry::Ray3D
00212     CameraIntrinsicsPlumbBob::
00213     reverseProject(const Vector2D& pixelPosition,
00214                    bool normalize) const
00215     {
00216       // TODO(xxx): Collapse this optimization to 1D when tangential
00217       // distortion is zero.
00218 
00219       // Ignoring distortion, we calculate an initial guess using the
00220       // pinhole camera model.  See
00221       // CameraIntrinsicsPinhole::reverseProject() for more detailed
00222       // comments.
00223       //
00224       // Array1D<double> startPoint(2);
00225       // startPoint[0] = (pixelPosition.x() - m_centerU) / m_kX;
00226       // startPoint[1] = (pixelPosition.y() - m_centerV) / m_kY;
00227 
00228       // Actually, for extreme distortions, the estimate above can be
00229       // a point that projects quite far outside the image.  This can
00230       // cause numerical problems because we have an eighth power
00231       // out-of-bounds error term that blows up quickly.  Instead of
00232       // using the pinhole projection above, we choose the safest
00233       // start point we can think of... the center of projection.
00234       Array1D<double> startPoint(2);
00235       startPoint = 0.0;
00236       
00237       // Now optimize to find a better answer.
00238       privateCode::PlumbBobObjective objective(*this, pixelPosition);
00239       OptimizerBFGS<privateCode::PlumbBobObjective> optimizer(objective);
00240       optimizer.setStartPoint(startPoint);
00241       Array1D<double> endPoint = optimizer.optimum();
00242       double residual = optimizer.optimalValue();
00243 
00244       // This would be the place to check convergence and try a
00245       // different start point, if we were so inclined.
00246       
00247       if(residual
00248          > l_maximumReverseProjectionResidual + objective.getOffset()) {
00249         DLR_THROW(ValueException, "CameraIntrinsicsPlumbBob::reverseProject()",
00250                   "Reverse projection failed to converge.");
00251       }
00252       
00253       return Ray3D(Vector3D(0.0, 0.0, 0.0),
00254                    Vector3D(endPoint[0], endPoint[1], 1.0, normalize));
00255     }
00256 
00257 
00258     // This member function writes the calibration to an
00259     // outputstream in a format which is compatible with member
00260     // function readFromStream().
00261     std::ostream&
00262     CameraIntrinsicsPlumbBob::
00263     writeToStream(std::ostream& stream) const
00264     {
00265       std::ios::fmtflags flags = stream.flags();
00266       std::streamsize precision = stream.precision();
00267       stream.precision(15);
00268       stream << "CameraIntrinsicsPlumbBob {"
00269              << std::fixed << std::setw(15)
00270              << m_numPixelsX << ", "
00271              << m_numPixelsY << ", "
00272              << m_kX << ", "
00273              << m_kY << ", "
00274              << m_centerU << ", "
00275              << m_centerV << ", "
00276              << m_skewCoefficient << ", "
00277              << m_radialCoefficient0 << ", "
00278              << m_radialCoefficient1 << ", "
00279              << m_radialCoefficient2 << ", "
00280              << m_tangentialCoefficient0 << ", "
00281              << m_tangentialCoefficient1 << "}";
00282       stream.precision(precision);
00283       stream.flags(flags);
00284       return stream;
00285     }
00286 
00287 
00288     void
00289     CameraIntrinsicsPlumbBob::
00290     projectWithPartialDerivatives(double xNorm,
00291                                   double yNorm,
00292                                   double& uValue,
00293                                   double& vValue,
00294                                   double& dUdX,
00295                                   double& dUdY,
00296                                   double& dVdX,
00297                                   double& dVdY) const
00298     {
00299       double xSquared = xNorm * xNorm;
00300       double ySquared = yNorm * yNorm;
00301       double rSquared = xSquared + ySquared;
00302       double rFourth = rSquared * rSquared;
00303       double rSixth = rSquared * rFourth;
00304 
00305       double dRSquaredDX = 2.0 * xNorm;
00306       double dRSquaredDY = 2.0 * yNorm;
00307       double dRFourthDX = 2.0 * rSquared * dRSquaredDX;
00308       double dRFourthDY = 2.0 * rSquared * dRSquaredDY;
00309       double dRSixthDX = 3.0 * rFourth * dRSquaredDX;
00310       double dRSixthDY = 3.0 * rFourth * dRSquaredDY;
00311 
00312       // Compute distortion terms according to plumb bob model.
00313       double radialDistortion = (1.0 + m_radialCoefficient0 * rSquared
00314                                  + m_radialCoefficient1 * rFourth
00315                                  + m_radialCoefficient2 * rSixth);
00316       double dRadialDistortionDX =
00317         (m_radialCoefficient0 * dRSquaredDX
00318          + m_radialCoefficient1 * dRFourthDX
00319          + m_radialCoefficient2 * dRSixthDX);
00320       double dRadialDistortionDY =
00321         (m_radialCoefficient0 * dRSquaredDY
00322          + m_radialCoefficient1 * dRFourthDY
00323          + m_radialCoefficient2 * dRSixthDY);
00324 
00325       double crossTerm = xNorm * yNorm;
00326       double dCrossTermDX = yNorm;
00327       double dCrossTermDY = xNorm;
00328       
00329       Vector2D tangentialDistortion(
00330         (2.0 * m_tangentialCoefficient0 * crossTerm
00331          + m_tangentialCoefficient1 * (rSquared + 2.0 * xSquared)),
00332         (m_tangentialCoefficient0 * (rSquared + 2.0 * ySquared)
00333          + 2.0 * m_tangentialCoefficient1 * crossTerm));
00334       double dTangXDX =
00335         (2.0 * m_tangentialCoefficient0 * dCrossTermDX
00336          + m_tangentialCoefficient1 * (dRSquaredDX + 4.0 * xNorm));
00337       double dTangXDY =
00338         (2.0 * m_tangentialCoefficient0 * dCrossTermDY
00339          + m_tangentialCoefficient1 * dRSquaredDY);
00340       double dTangYDX =
00341         (m_tangentialCoefficient0 * dRSquaredDX
00342          + 2.0 * m_tangentialCoefficient1 * dCrossTermDX);
00343       double dTangYDY =
00344         (m_tangentialCoefficient0 * (dRSquaredDY + 4.0 * yNorm)
00345          + 2.0 * m_tangentialCoefficient1 * dCrossTermDY);
00346 
00347       // Apply distortion and skew, then project into pixel coordinates.
00348       Vector2D distortedPoint(
00349         radialDistortion * xNorm + tangentialDistortion.x(),
00350         radialDistortion * yNorm + tangentialDistortion.y());
00351 
00352       double dDistortedXDX =
00353         dRadialDistortionDX * xNorm + radialDistortion + dTangXDX;
00354       double dDistortedXDY = dRadialDistortionDY * xNorm + dTangXDY;
00355       double dDistortedYDX = dRadialDistortionDX * yNorm + dTangYDX;
00356       double dDistortedYDY =
00357         dRadialDistortionDY * yNorm + radialDistortion + dTangYDY;
00358       
00359       Vector2D skewedPoint(
00360         distortedPoint.x() + m_skewCoefficient * distortedPoint.y(),
00361         distortedPoint.y());
00362       double dSkewedXDX = dDistortedXDX + m_skewCoefficient * dDistortedYDX;
00363       double dSkewedXDY = dDistortedXDY + m_skewCoefficient * dDistortedYDY;
00364       double dSkewedYDX = dDistortedYDX;
00365       double dSkewedYDY = dDistortedYDY;
00366       
00367       uValue = m_kX * skewedPoint.x() + m_centerU;
00368       vValue = m_kY * skewedPoint.y() + m_centerV;
00369       dUdX = m_kX * dSkewedXDX;
00370       dUdY = m_kX * dSkewedXDY;
00371       dVdX = m_kY * dSkewedYDX;
00372       dVdY = m_kY * dSkewedYDY;
00373     }
00374     
00375 
00376 
00377     namespace privateCode {
00378 
00379       PlumbBobObjective::
00380       PlumbBobObjective(const CameraIntrinsicsPlumbBob& intrinsics,
00381                         const Vector2D& uvTarget)
00382         : m_intrinsics(intrinsics),
00383           m_offset(0.0001),
00384           m_uvTarget(uvTarget)
00385       {
00386         // Empty.
00387       }
00388 
00389         
00390       double
00391       PlumbBobObjective::
00392       operator()(const Array1D<double>& theta)
00393       {
00394         Vector3D candidate(theta[0], theta[1], 1.0);
00395         Vector2D projection = m_intrinsics.project(candidate);
00396 
00397         // The high order terms of the distortion model can lead to
00398         // false minima outside the image boundaries.  We add an even
00399         // higher order penalty for projecting outside the image,
00400         // hopefully reducing this problem.
00401         double boundsPenalty = this->computeBoundsPenalty(projection);
00402 
00403         return (magnitudeSquared(projection - m_uvTarget) + boundsPenalty
00404                 + m_offset);
00405       }
00406 
00407 
00408       double
00409       PlumbBobObjective::
00410       getOffset()
00411       {
00412         return m_offset;
00413       }
00414 
00415       
00416       Array1D<double>
00417       PlumbBobObjective::
00418       gradient(const Array1D<double>& theta)
00419       {
00420         double uValue;
00421         double vValue;
00422         double dUdX;
00423         double dUdY;
00424         double dVdX;
00425         double dVdY;
00426         m_intrinsics.projectWithPartialDerivatives(
00427           theta[0], theta[1], uValue, vValue, dUdX, dUdY, dVdX, dVdY);
00428 
00429         double twoTimesDeltaU = 2.0 * (uValue - m_uvTarget.x());
00430         double twoTimesDeltaV = 2.0 * (vValue - m_uvTarget.y());
00431 
00432         Array1D<double> gradient(2);
00433         gradient[0] = twoTimesDeltaU * dUdX + twoTimesDeltaV * dVdX;
00434         gradient[1] = twoTimesDeltaU * dUdY + twoTimesDeltaV * dVdY;
00435 
00436         double dPdX;
00437         double dPdY;
00438         this->computeBoundsPenaltyGradient(
00439           uValue, vValue, dUdX, dUdY, dVdX, dVdY, dPdX, dPdY);
00440         gradient[0] += dPdX;
00441         gradient[1] += dPdY;
00442         
00443         return gradient;
00444       }
00445 
00446 
00447       double
00448       PlumbBobObjective::
00449       computeBoundsPenalty(const Vector2D& uvPosition)
00450       {
00451         double uViolation = 0.0;
00452         double scaleU = m_intrinsics.getNumPixelsX() / 10.0;
00453         if(uvPosition.x() < 0) {
00454           uViolation = -uvPosition.x() / scaleU;
00455         } else if(uvPosition.x() >= m_intrinsics.getNumPixelsX()) {
00456           uViolation = ((uvPosition.x() - m_intrinsics.getNumPixelsX())
00457                         / scaleU);
00458         }
00459         double vViolation = 0.0;
00460         double scaleV = m_intrinsics.getNumPixelsY() / 10.0;
00461         if(uvPosition.y() < 0) {
00462           vViolation = -uvPosition.y() / scaleV;
00463         } else if(uvPosition.y() >= m_intrinsics.getNumPixelsY()) {
00464           vViolation = ((uvPosition.y() - m_intrinsics.getNumPixelsY())
00465                         / scaleV);
00466         }
00467 
00468         // Bounds penaly is violation**8 so as to dominate the r**6
00469         // term in the distortion model.
00470         uViolation *= uViolation;
00471         uViolation *= uViolation;
00472         uViolation *= uViolation;
00473         vViolation *= vViolation;
00474         vViolation *= vViolation;
00475         vViolation *= vViolation;
00476         return uViolation + vViolation;
00477       }
00478         
00479 
00480       void
00481       PlumbBobObjective::
00482       computeBoundsPenaltyGradient(double uValue, double vValue,
00483                                    double dUdX, double dUdY,
00484                                    double dVdX, double dVdY,
00485                                    double& dPdX, double& dPdY)
00486       {
00487         double uViolation = 0.0;
00488         double scaleU = m_intrinsics.getNumPixelsX() / 10.0;
00489         double dUVdX = 0.0;
00490         double dUVdY = 0.0;
00491         if(uValue < 0) {
00492           uViolation = -uValue / scaleU;
00493           dUVdX = -dUdX / scaleU;
00494           dUVdY = -dUdY / scaleU;
00495         } else if(uValue >= m_intrinsics.getNumPixelsX()) {
00496           uViolation = (uValue - m_intrinsics.getNumPixelsX()) / scaleU;
00497           dUVdX = dUdX / scaleU;
00498           dUVdY = dUdY / scaleU;
00499         }
00500         double vViolation = 0.0;
00501         double scaleV = m_intrinsics.getNumPixelsY() / 10.0;
00502         double dVVdX = 0.0;
00503         double dVVdY = 0.0;
00504         if(vValue < 0) {
00505           vViolation = -vValue / scaleV;
00506           dVVdX = -dVdX / scaleV;
00507           dVVdY = -dVdY / scaleV;
00508         } else if(vValue >= m_intrinsics.getNumPixelsY()) {
00509           vViolation = (vValue - m_intrinsics.getNumPixelsY()) / scaleV;
00510           dVVdX = dVdX / scaleV;
00511           dVVdY = dVdY / scaleV;
00512         }
00513         // Bounds penaly is violation**8 so as to dominate the r**6
00514         // term in the distortion model.
00515         double uViolationTo2 = uViolation * uViolation;
00516         double uViolationTo4 = uViolationTo2 * uViolationTo2;
00517         double uViolationTo7 = uViolationTo4 * uViolationTo2 * uViolation;
00518         double vViolationTo2 = vViolation * vViolation;
00519         double vViolationTo4 = vViolationTo2 * vViolationTo2;
00520         double vViolationTo7 = vViolationTo4 * vViolationTo2 * vViolation;
00521 
00522         dPdX = 8.0 * uViolationTo7 * dUVdX + 8.0 * vViolationTo7 * dVVdX;
00523         dPdY = 8.0 * uViolationTo7 * dUVdY + 8.0 * vViolationTo7 * dVVdY;
00524       }
00525         
00526 
00527     } // namespace privateCode;
00528     
00529   } // namespace computerVision
00530   
00531 } // namespace dlr

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