extendedKalmanFilter.cpp

Go to the documentation of this file.
00001 
00015 #include <dlrComputerVision/extendedKalmanFilter.h>
00016 #include <dlrLinearAlgebra/linearAlgebra.h>
00017 #include <dlrNumeric/utilities.h>
00018 
00019 namespace dlr {
00020 
00021   namespace computerVision {
00022 
00023     // Default constructor.
00024     ExtendedKalmanFilter::
00025     ExtendedKalmanFilter(double startTime)
00026       : m_covariance(),
00027         m_state(),
00028         m_previousTimestamp(startTime),
00029         m_timestamp(startTime)
00030     {
00031       // Empty.
00032     }
00033 
00034     
00035     // Use this member function tell the filter about a new
00036     // measurement, and to request that the state estimate be
00037     // updated to reflect this new measurement.
00038     void
00039     ExtendedKalmanFilter::
00040     addMeasurement(unsigned int measurementID,
00041                    double timestamp,
00042                    numeric::Array1D<double> const& measurement,
00043                    numeric::Array1D<double> const& controlInput)
00044     {
00045       this->doPredictionStep(timestamp, controlInput);
00046       this->doMeasurementUpdate(measurementID, measurement);
00047     }
00048 
00049 
00050     void
00051     ExtendedKalmanFilter::
00052     checkMeasurementJacobians(unsigned int measurementID,
00053                               numeric::Array1D<double> const& epsilonArray,
00054                               numeric::Array2D<double>& residualArray0)
00055     {
00056       // Check arguments.
00057       if(epsilonArray.size() != m_state.size()) {
00058         DLR_THROW(common::ValueException,
00059                   "ExtendedKalmanFilter::checkProcessJacobians()",
00060                   "Argument epsilon has incorrect size.");
00061       }
00062       
00063       // Get the symbolically computed Jacobians and check their sizes.
00064       numeric::Array2D<double> measurementJacobian0;
00065       numeric::Array2D<double> measurementJacobian1;
00066       this->getMeasurementJacobians(
00067         measurementID, m_timestamp, m_previousTimestamp, m_state,
00068         measurementJacobian0, measurementJacobian1);
00069 
00070 
00071       numeric::Array1D<double> measurement =
00072         this->applyMeasurementModel(
00073           measurementID, m_timestamp, m_previousTimestamp, m_state);
00074       numeric::Array2D<double> measurementNoiseCovariance = 
00075         this->getMeasurementNoiseCovariance(
00076           measurementID, m_timestamp, m_previousTimestamp);
00077       if(measurementJacobian0.rows() != measurement.size()
00078          || measurementJacobian0.columns() != m_state.size()
00079          || measurementJacobian1.rows() != measurement.size()
00080          || (measurementJacobian1.columns()
00081              != measurementNoiseCovariance.rows())) {
00082         DLR_THROW(common::LogicException,
00083                   "ExtendedKalmanFilter::checkMeasurementJacobians()",
00084                   "At least one of the returned Jacobians has incorrect size.");
00085       }
00086 
00087       // Generate finite-difference approximation to measurementJacobian0, the 
00088       // Jacobian wrt state.
00089       numeric::Array2D<double> approximateJacobian0(
00090         measurementJacobian0.rows(), measurementJacobian0.columns());
00091       for(unsigned int columnIndex = 0; columnIndex < m_state.size();
00092           ++columnIndex) {
00093         numeric::Array1D<double> state = m_state.copy();
00094 
00095         state[columnIndex] -= epsilonArray[columnIndex];
00096         numeric::Array1D<double> resultMeasurement0 =
00097           this->applyMeasurementModel(
00098             measurementID, m_timestamp, m_previousTimestamp, state);
00099 
00100         state[columnIndex] += 2.0 * epsilonArray[columnIndex];
00101         numeric::Array1D<double> resultMeasurement1 = 
00102           this->applyMeasurementModel(
00103             measurementID, m_timestamp, m_previousTimestamp, state);
00104 
00105         numeric::Array1D<double> approximateGradient =
00106           ((resultMeasurement1 - resultMeasurement0)
00107            / (2.0 * epsilonArray[columnIndex]));
00108         for(unsigned int rowIndex = 0; rowIndex < resultMeasurement0.size();
00109             ++rowIndex) {
00110           approximateJacobian0(rowIndex, columnIndex) =
00111             approximateGradient[rowIndex];
00112         }
00113       }
00114 
00115       // Unfortunately, we don't have a good way to check the Jacobian
00116       // wrt noise right now.  On the bright side, it's often the
00117       // identity matrix, which is hard to get wrong.  In cases where
00118       // it's not the identity matrix, we'll trust the user to write
00119       // her own test.
00120       
00121       residualArray0 = approximateJacobian0 - measurementJacobian0;
00122     }
00123       
00124       
00125     void
00126     ExtendedKalmanFilter::
00127     checkProcessJacobians(numeric::Array1D<double> const& epsilonArray,
00128                           numeric::Array1D<double> const& controlInput,
00129                           numeric::Array2D<double>& residualArray0)
00130     {
00131       // Check arguments.
00132       if(epsilonArray.size() != m_state.size()) {
00133         DLR_THROW(common::ValueException,
00134                   "ExtendedKalmanFilter::checkProcessJacobians()",
00135                   "Argument epsilon has incorrect size.");
00136       }
00137 
00138       // Get the symbolically computed Jacobians and check their sizes.
00139       numeric::Array2D<double> processJacobian0;
00140       numeric::Array2D<double> processJacobian1;
00141       this->getProcessJacobians(
00142         m_timestamp, m_previousTimestamp, m_state,
00143         processJacobian0, processJacobian1);
00144 
00145       numeric::Array2D<double> processNoiseCovariance = 
00146         this->getProcessNoiseCovariance(m_timestamp, m_previousTimestamp);
00147       if(processJacobian0.rows() != m_state.size()
00148          || processJacobian0.columns() != m_state.size()
00149          || processJacobian1.rows() != m_state.size()
00150          || processJacobian1.columns() != processNoiseCovariance.rows()) {
00151         DLR_THROW(common::LogicException,
00152                   "ExtendedKalmanFilter::checkProcessJacobians()",
00153                   "At least one of the returned Jacobians has incorrect size.");
00154       }
00155 
00156       // Generate finite-difference approximation to processJacobian0, the 
00157       // Jacobian wrt state.
00158       numeric::Array2D<double> approximateJacobian0(
00159         processJacobian0.rows(), processJacobian0.columns());
00160       for(unsigned int columnIndex = 0; columnIndex < m_state.size();
00161           ++columnIndex) {
00162         numeric::Array1D<double> state = m_state.copy();
00163 
00164         state[columnIndex] -= epsilonArray[columnIndex];
00165         numeric::Array1D<double> resultState0 = 
00166           this->applyProcessModel(m_timestamp, m_previousTimestamp, state,
00167                                   controlInput);
00168 
00169         state[columnIndex] += 2.0 * epsilonArray[columnIndex];
00170         numeric::Array1D<double> resultState1 = 
00171           this->applyProcessModel(m_timestamp, m_previousTimestamp, state,
00172                                   controlInput);
00173 
00174         numeric::Array1D<double> approximateGradient =
00175           (resultState1 - resultState0) / (2.0 * epsilonArray[columnIndex]);
00176         for(unsigned int rowIndex = 0; rowIndex < m_state.size(); ++rowIndex) {
00177           approximateJacobian0(rowIndex, columnIndex) =
00178             approximateGradient[rowIndex];
00179         }
00180       }
00181 
00182       // Unfortunately, we don't have a good way to check the Jacobian
00183       // wrt noise right now.  On the bright side, it's often the
00184       // identity matrix, which is hard to get wrong.  In cases where
00185       // it's not the identity matrix, we'll trust the user to write
00186       // her own test.
00187       
00188       residualArray0 = approximateJacobian0 - processJacobian0;
00189     }
00190 
00191     
00192     // This member function may optionally be called to disable
00193     // updates of Kalman gain and estimation error covariance.
00194     void
00195     ExtendedKalmanFilter::
00196     freezeKalmanGain()
00197     {
00198       DLR_THROW(common::NotImplementedException,
00199                 "ExtendedKalmanFilter::freezeKalmanGain()",
00200                 "Sorry. Should be easy to implement, though.");
00201     }
00202 
00203     
00204     // This member function returns the current state estimate for
00205     // the filter, as well as the estimated covariance of the state
00206     // estimate.
00207     void
00208     ExtendedKalmanFilter::
00209     getStateEstimate(double& timestamp,
00210                      numeric::Array1D<double>& state,
00211                      numeric::Array2D<double>& covariance)
00212     {
00213       timestamp = m_timestamp;
00214       state = m_state.copy();
00215       covariance = m_covariance.copy();
00216     }
00217 
00218       
00219     // This member function sets the initial state estimate for the
00220     // filter, as well as the covariance of any Gaussian noise
00221     // reflected in the initial state estimate.
00222     void
00223     ExtendedKalmanFilter::
00224     setStateEstimate(double timestamp,
00225                      numeric::Array1D<double> const& state,
00226                      numeric::Array2D<double> const& covariance)
00227     {
00228       m_timestamp = timestamp;
00229       m_state = state.copy();
00230       m_covariance = covariance.copy();
00231     }
00232 
00233       
00234     // This member function may optionally be called to reverse the
00235     // effect of freezeKalmanGain().
00236     void
00237     ExtendedKalmanFilter::
00238     unfreezeKalmanGain()
00239     {
00240       DLR_THROW(common::NotImplementedException,
00241                 "ExtendedKalmanFilter::unfreezeKalmanGain()",
00242                 "Sorry. Should be easy to implement, though.");
00243     }
00244       
00245 
00246     void
00247     ExtendedKalmanFilter::
00248     doPredictionStep(double currentTime,
00249                      Array1D<double> const& controlInput)
00250     {
00251       // Prediction step is as follows:
00252       //
00253       //   xpr_k = f(xpo_(k-1) + u_(k-1), 0)
00254       //
00255       // where xpr_k is the a priori (before adding in measurements
00256       // taken at time k) estimate of x at time k.  It will be updated
00257       // by doMeasurementUpdate() to generate the a posteriori
00258       // estimate at time k, which reflects any new sensor data.
00259       // xpo_(k-1) is the a posteriori estimate of x at time (k - 1),
00260       // u_(k-1) is the control input at time (k - 1), f() is the user
00261       // supplied processmodel (which can be nonlinear), and the final
00262       // argument of zero reflects that the prediction step assumes no
00263       // noise.
00264       //
00265       // Prediction step updates state covariance estimate as follows:
00266       //
00267       //   Ppr_k = A_k * Ppo_(k-1) * (A_k)^T + W_k * Q_(k-1) * (W_k)^T
00268       //
00269       // Where Ppr_k is the a priori estimate of the covariance of our
00270       // state estimate at time k, Ppo_(k-1) is the a posteriori
00271       // estimate of the covariance at time (k-1), Q_(k-1) is the
00272       // covariance of our process noise at time (k - 1), A_k and W_k
00273       // are the process Jacobians at time k, and the right
00274       // superscript ^T indicates matrix transpose.
00275       m_previousTimestamp = m_timestamp;
00276       m_timestamp = currentTime;
00277       m_state = this->applyProcessModel(
00278         m_timestamp, m_previousTimestamp, m_state, controlInput);
00279       
00280       // Note(xxx): Inefficient to transpose every time.
00281       Array2D<double> processJacobian0;
00282       Array2D<double> processJacobian1;
00283       this->getProcessJacobians(
00284         m_timestamp, m_previousTimestamp, m_state,
00285         processJacobian0, processJacobian1);
00286       m_covariance = 
00287         matrixMultiply(
00288           matrixMultiply(processJacobian0, m_covariance),
00289           processJacobian0.transpose());
00290       m_covariance += 
00291         matrixMultiply(
00292           matrixMultiply(
00293             processJacobian1,
00294             this->getProcessNoiseCovariance(m_timestamp, m_previousTimestamp)),
00295           processJacobian1.transpose());
00296     }
00297 
00298 
00299     void
00300     ExtendedKalmanFilter::
00301     doMeasurementUpdate(unsigned int measurementID,
00302                         numeric::Array1D<double> const& measurement)
00303     {
00304       // Measurement step is as follows (assuming prediction step has
00305       // already brought us to the time of the sensor observation):
00306       //
00307       //   K_k = Ppr_k * (H_k)^T * (H_k * Ppr_k * (H_k)^T
00308       //                            + V_k * R * (V_k)^T)^(-1)
00309       //
00310       //   xpo_k = xpr_k + K_k * (z_k - H * xpr_k)
00311       //
00312       //   Ppo_k = (1 - K_k * H)Ppr_k
00313       //
00314       // Where K_k is the "Kalman gain" at time k, Ppr_k is the a
00315       // priori estimate of the covariance of our state estimate at
00316       // time k, H^T is the transpose of measurement matrix H, R is
00317       // the covariance of the measurement noise, xpr_k is the a
00318       // priori (before adding in measurements taken at time k)
00319       // estimate of x at time k, xpo_k is the a posteriori (after
00320       // accounting for measurements) estimate of x at time k, z_k is
00321       // the measurement at time k, and Ppo_k is the a posteriori
00322       // estimate of the covariance of our state estimate at time k.
00323 
00324       Array2D<double> measurementJacobian0;
00325       Array2D<double> measurementJacobian1;
00326       this->getMeasurementJacobians(
00327         measurementID, m_timestamp, m_previousTimestamp, m_state,
00328         measurementJacobian0, measurementJacobian1);
00329 
00330       // Convenient matrices for constructing Kalman gain, etc.
00331       Array2D<double> const& HMatrix = measurementJacobian0;
00332       Array2D<double> HTranspose = HMatrix.transpose();
00333       Array2D<double> const& VMatrix = measurementJacobian1;
00334       Array2D<double> VTranspose = VMatrix.transpose();
00335 
00336       // "Denominator" should be (H_k * Ppr_k * (H_k)^T) + (V_k * R_k * (V_k)^T)
00337       numeric::Array2D<double> denominator = 
00338         matrixMultiply(matrixMultiply(HMatrix, m_covariance), HTranspose);
00339       denominator +=
00340         (matrixMultiply(
00341           matrixMultiply(
00342             VMatrix,
00343             this->getMeasurementNoiseCovariance(
00344               measurementID, m_timestamp, m_previousTimestamp)),
00345           VTranspose));
00346 
00347       numeric::Array1D<double> innovation =
00348         measurement - this->applyMeasurementModel(
00349           measurementID, m_timestamp, m_previousTimestamp, m_state);
00350       
00351       numeric::Array2D<double> kalmanGain = 
00352         matrixMultiply(matrixMultiply(m_covariance, HTranspose),
00353                        linearAlgebra::inverse(denominator));
00354 
00355       m_state += matrixMultiply(kalmanGain, innovation);
00356 
00357       // Compute P_k = (I - K * H) * P_(k-1)
00358       numeric::Array2D<double> kTimesHMinusI =
00359         matrixMultiply(kalmanGain, HMatrix);
00360       if(kTimesHMinusI.rows() != kTimesHMinusI.columns()) {
00361         DLR_THROW(common::LogicException,
00362                   "ExtendedKalmanFilter::doMeasurementUpdate()",
00363                   "Programming error. K * H must be square.");
00364       }
00365       for(unsigned int ii = 0; ii < kTimesHMinusI.rows(); ++ii) {
00366         kTimesHMinusI(ii, ii) -= 1.0;
00367       }
00368       m_covariance = matrixMultiply(kTimesHMinusI, m_covariance);
00369       m_covariance *= -1.0;
00370     }
00371     
00372   } // namespace computerVision
00373   
00374 } // namespace dlr

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