00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036 #include <iostream>
00037
00038 #include "Eigen/SVD"
00039
00040 #include "ErrorMinimizersImpl.h"
00041 #include "PointMatcherPrivate.h"
00042 #include "Functions.h"
00043
00044 using namespace Eigen;
00045 using namespace std;
00046
00047 typedef PointMatcherSupport::Parametrizable Parametrizable;
00048 typedef PointMatcherSupport::Parametrizable P;
00049 typedef Parametrizable::Parameters Parameters;
00050 typedef Parametrizable::ParameterDoc ParameterDoc;
00051 typedef Parametrizable::ParametersDoc ParametersDoc;
00052
00053 template<typename T>
00054 PointToPlaneErrorMinimizer<T>::PointToPlaneErrorMinimizer(const Parameters& params):
00055 ErrorMinimizer(name(), availableParameters(), params),
00056 force2D(Parametrizable::get<T>("force2D"))
00057 {
00058 LOG_INFO_STREAM("PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 2D.");
00059 }
00060
00061 template<typename T>
00062 PointToPlaneErrorMinimizer<T>::PointToPlaneErrorMinimizer(const ParametersDoc paramsDoc, const Parameters& params):
00063 ErrorMinimizer(name(), paramsDoc, params),
00064 force2D(Parametrizable::get<T>("force2D"))
00065 {
00066 LOG_INFO_STREAM("PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 2D.");
00067 }
00068
00069
00070 template<typename T, typename MatrixA, typename Vector>
00071 void solvePossiblyUnderdeterminedLinearSystem(const MatrixA& A, const Vector & b, Vector & x) {
00072 assert(A.cols() == A.rows());
00073 assert(b.cols() == 1);
00074 assert(b.rows() == A.rows());
00075 assert(x.cols() == 1);
00076 assert(x.rows() == A.cols());
00077
00078 typedef typename PointMatcher<T>::Matrix Matrix;
00079
00080 BOOST_AUTO(Aqr, A.fullPivHouseholderQr());
00081 if (!Aqr.isInvertible())
00082 {
00083
00084 const int rank = Aqr.rank();
00085 const int rows = A.rows();
00086 const Matrix Q1t = Aqr.matrixQ().transpose().block(0, 0, rank, rows);
00087 const Matrix R1 = (Q1t * A * Aqr.colsPermutation()).block(0, 0, rank, rows);
00088
00089 const bool findMinimalNormSolution = true;
00090
00091
00092 if(findMinimalNormSolution){
00093
00094 x = R1.template triangularView<Eigen::Upper>().transpose() * (R1 * R1.transpose()).llt().solve(Q1t * b);
00095 } else {
00096
00097 x.block(0, 0, rank, 1) = R1.block(0, 0, rank, rank).template triangularView<Eigen::Upper>().solve(Q1t * b);
00098 x.block(rank, 0, rows - rank, 1).setZero();
00099 }
00100
00101 x = Aqr.colsPermutation() * x;
00102
00103 BOOST_AUTO(ax , (A * x).eval());
00104 if (!b.isApprox(ax, 1e-5)) {
00105 LOG_INFO_STREAM("PointMatcher::icp - encountered almost singular matrix while minimizing point to plane distance. QR solution was too inaccurate. Trying more accurate approach using double precision SVD.");
00106 x = A.template cast<double>().jacobiSvd(ComputeThinU | ComputeThinV).solve(b.template cast<double>()).template cast<T>();
00107 ax = A * x;
00108
00109 if((b - ax).norm() > 1e-5 * std::max(A.norm() * x.norm(), b.norm())){
00110 LOG_WARNING_STREAM("PointMatcher::icp - encountered numerically singular matrix while minimizing point to plane distance and the current workaround remained inaccurate."
00111 << " b=" << b.transpose()
00112 << " !~ A * x=" << (ax).transpose().eval()
00113 << ": ||b- ax||=" << (b - ax).norm()
00114 << ", ||b||=" << b.norm()
00115 << ", ||ax||=" << ax.norm());
00116 }
00117 }
00118 }
00119 else {
00120
00121 x = A.llt().solve(b);
00122 }
00123 }
00124
00125 template<typename T>
00126 typename PointMatcher<T>::TransformationParameters PointToPlaneErrorMinimizer<T>::compute(const ErrorElements& mPts_const)
00127 {
00128 ErrorElements mPts = mPts_const;
00129 return compute_in_place(mPts);
00130 }
00131
00132
00133 template<typename T>
00134 typename PointMatcher<T>::TransformationParameters PointToPlaneErrorMinimizer<T>::compute_in_place(ErrorElements& mPts)
00135 {
00136 const int dim = mPts.reading.features.rows();
00137 const int nbPts = mPts.reading.features.cols();
00138
00139
00140 int forcedDim = dim - 1;
00141 if(force2D && dim == 4)
00142 {
00143 mPts.reading.features.conservativeResize(3, Eigen::NoChange);
00144 mPts.reading.features.row(2) = Matrix::Ones(1, nbPts);
00145 mPts.reference.features.conservativeResize(3, Eigen::NoChange);
00146 mPts.reference.features.row(2) = Matrix::Ones(1, nbPts);
00147 forcedDim = dim - 2;
00148 }
00149
00150
00151 const BOOST_AUTO(normalRef, mPts.reference.getDescriptorViewByName("normals").topRows(forcedDim));
00152
00153
00154 assert(normalRef.rows() > 0);
00155
00156
00157 const Matrix cross = this->crossProduct(mPts.reading.features, normalRef);
00158
00159
00160
00161 Matrix wF(normalRef.rows()+ cross.rows(), normalRef.cols());
00162 Matrix F(normalRef.rows()+ cross.rows(), normalRef.cols());
00163
00164 for(int i=0; i < cross.rows(); i++)
00165 {
00166 wF.row(i) = mPts.weights.array() * cross.row(i).array();
00167 F.row(i) = cross.row(i);
00168 }
00169 for(int i=0; i < normalRef.rows(); i++)
00170 {
00171 wF.row(i + cross.rows()) = mPts.weights.array() * normalRef.row(i).array();
00172 F.row(i + cross.rows()) = normalRef.row(i);
00173 }
00174
00175
00176 const Matrix A = wF * F.transpose();
00177
00178 const Matrix deltas = mPts.reading.features - mPts.reference.features;
00179
00180
00181 Matrix dotProd = Matrix::Zero(1, normalRef.cols());
00182
00183 for(int i=0; i<normalRef.rows(); i++)
00184 {
00185 dotProd += (deltas.row(i).array() * normalRef.row(i).array()).matrix();
00186 }
00187
00188
00189 const Vector b = -(wF * dotProd.transpose());
00190
00191 Vector x(A.rows());
00192
00193 solvePossiblyUnderdeterminedLinearSystem<T>(A, b, x);
00194
00195
00196 Matrix mOut;
00197 if(dim == 4 && !force2D)
00198 {
00199 Eigen::Transform<T, 3, Eigen::Affine> transform;
00200
00201
00202
00203
00204
00205
00206 transform = Eigen::AngleAxis<T>(x.head(3).norm(),x.head(3).normalized());
00207
00208
00209
00210
00211
00212
00213 transform.translation() = x.segment(3, 3);
00214 mOut = transform.matrix();
00215
00216 if (mOut != mOut)
00217 {
00218
00219
00220
00221 mOut.block(0, 0, dim-1, dim-1) = Matrix::Identity(dim-1, dim-1);
00222 }
00223 }
00224 else
00225 {
00226 Eigen::Transform<T, 2, Eigen::Affine> transform;
00227 transform = Eigen::Rotation2D<T> (x(0));
00228 transform.translation() = x.segment(1, 2);
00229
00230 if(force2D)
00231 {
00232 mOut = Matrix::Identity(dim, dim);
00233 mOut.topLeftCorner(2, 2) = transform.matrix().topLeftCorner(2, 2);
00234 mOut.topRightCorner(2, 1) = transform.matrix().topRightCorner(2, 1);
00235 }
00236 else
00237 {
00238 mOut = transform.matrix();
00239 }
00240 }
00241 return mOut;
00242 }
00243
00244
00245 template<typename T>
00246 T PointToPlaneErrorMinimizer<T>::computeResidualError(ErrorElements mPts, const bool& force2D)
00247 {
00248 const int dim = mPts.reading.features.rows();
00249 const int nbPts = mPts.reading.features.cols();
00250
00251
00252 int forcedDim = dim - 1;
00253 if(force2D && dim == 4)
00254 {
00255 mPts.reading.features.conservativeResize(3, Eigen::NoChange);
00256 mPts.reading.features.row(2) = Matrix::Ones(1, nbPts);
00257 mPts.reference.features.conservativeResize(3, Eigen::NoChange);
00258 mPts.reference.features.row(2) = Matrix::Ones(1, nbPts);
00259 forcedDim = dim - 2;
00260 }
00261
00262
00263 const BOOST_AUTO(normalRef, mPts.reference.getDescriptorViewByName("normals").topRows(forcedDim));
00264
00265
00266 assert(normalRef.rows() > 0);
00267
00268 const Matrix deltas = mPts.reading.features - mPts.reference.features;
00269
00270
00271 Matrix dotProd = Matrix::Zero(1, normalRef.cols());
00272 for(int i = 0; i < normalRef.rows(); i++)
00273 {
00274 dotProd += (deltas.row(i).array() * normalRef.row(i).array()).matrix();
00275 }
00276
00277 dotProd = (mPts.weights.row(0).array() * dotProd.array().square()).matrix();
00278
00279
00280 return dotProd.sum();
00281 }
00282
00283
00284 template<typename T>
00285 T PointToPlaneErrorMinimizer<T>::getResidualError(
00286 const DataPoints& filteredReading,
00287 const DataPoints& filteredReference,
00288 const OutlierWeights& outlierWeights,
00289 const Matches& matches) const
00290 {
00291 assert(matches.ids.rows() > 0);
00292
00293
00294 typename ErrorMinimizer::ErrorElements mPts(filteredReading, filteredReference, outlierWeights, matches);
00295
00296 return PointToPlaneErrorMinimizer::computeResidualError(mPts, force2D);
00297 }
00298
00299 template<typename T>
00300 T PointToPlaneErrorMinimizer<T>::getOverlap() const
00301 {
00302
00303
00304 const bool hasReadingNoise = this->lastErrorElements.reading.descriptorExists("simpleSensorNoise");
00305 const bool hasReferenceNoise = this->lastErrorElements.reference.descriptorExists("simpleSensorNoise");
00306 const bool hasReferenceDensity = this->lastErrorElements.reference.descriptorExists("densities");
00307
00308 const int nbPoints = this->lastErrorElements.reading.features.cols();
00309 const int dim = this->lastErrorElements.reading.features.rows();
00310
00311
00312 if(nbPoints == 0)
00313 {
00314 throw std::runtime_error("Error, last error element empty. Error minimizer needs to be called at least once before using this method.");
00315 }
00316
00317 Eigen::Array<T, 1, Eigen::Dynamic> uncertainties(nbPoints);
00318
00319
00320 if (hasReadingNoise && hasReferenceNoise && hasReferenceDensity)
00321 {
00322
00323
00324 Matrix densities = this->lastErrorElements.reference.getDescriptorViewByName("densities");
00325 vector<T> values(densities.data(), densities.data() + densities.size());
00326
00327
00328 nth_element(values.begin(), values.begin() + (values.size() * 0.5), values.end());
00329
00330
00331 const T medianDensity = values[values.size() * 0.5];
00332 const T medianRadius = 1.0/pow(medianDensity, 1/3.0);
00333
00334 uncertainties = (medianRadius +
00335 this->lastErrorElements.reading.getDescriptorViewByName("simpleSensorNoise").array() +
00336 this->lastErrorElements.reference.getDescriptorViewByName("simpleSensorNoise").array());
00337 }
00338 else if(hasReadingNoise && hasReferenceNoise)
00339 {
00340 uncertainties = this->lastErrorElements.reading.getDescriptorViewByName("simpleSensorNoise") +
00341 this->lastErrorElements.reference.getDescriptorViewByName("simpleSensorNoise");
00342 }
00343 else if(hasReadingNoise)
00344 {
00345 uncertainties = this->lastErrorElements.reading.getDescriptorViewByName("simpleSensorNoise");
00346 }
00347 else if(hasReferenceNoise)
00348 {
00349 uncertainties = this->lastErrorElements.reference.getDescriptorViewByName("simpleSensorNoise");
00350 }
00351 else
00352 {
00353 LOG_INFO_STREAM("PointToPlaneErrorMinimizer - warning, no sensor noise and density. Using best estimate given outlier rejection instead.");
00354 return this->getWeightedPointUsedRatio();
00355 }
00356
00357
00358 const Vector dists = (this->lastErrorElements.reading.features.topRows(dim-1) - this->lastErrorElements.reference.features.topRows(dim-1)).colwise().norm();
00359
00360
00361
00362
00363 int count = 0;
00364 int nbUniquePoint = 1;
00365 Vector lastValidPoint = this->lastErrorElements.reading.features.col(0) * 2.;
00366 for(int i=0; i < nbPoints; i++)
00367 {
00368 const Vector point = this->lastErrorElements.reading.features.col(i);
00369
00370 if(lastValidPoint != point)
00371 {
00372
00373
00374
00375
00376
00377 if(PointMatcherSupport::anyabs(dists(i, 0)) < (uncertainties(0,i)))
00378 {
00379 lastValidPoint = point;
00380 count++;
00381 }
00382 }
00383
00384
00385 if(i > 0)
00386 {
00387 if(point != this->lastErrorElements.reading.features.col(i-1))
00388 nbUniquePoint++;
00389 }
00390
00391 }
00392
00393
00394 return (T)count/(T)(nbUniquePoint + this->lastErrorElements.nbRejectedPoints);
00395 }
00396
00397 template struct PointToPlaneErrorMinimizer<float>;
00398 template struct PointToPlaneErrorMinimizer<double>;