44 using namespace Eigen;
67 LOG_INFO_STREAM(
"PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 2D.");
72 LOG_INFO_STREAM(
"PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 4-DOF (yaw,x,y,z).");
76 LOG_INFO_STREAM(
"PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 3D.");
94 LOG_INFO_STREAM(
"PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 2D.");
99 LOG_INFO_STREAM(
"PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 4-DOF (yaw,x,y,z).");
103 LOG_INFO_STREAM(
"PointMatcher::PointToPlaneErrorMinimizer - minimization will be in 3D.");
108 template<
typename T,
typename MatrixA,
typename Vector>
110 assert(A.cols() == A.rows());
111 assert(b.cols() == 1);
112 assert(b.rows() == A.rows());
113 assert(
x.cols() == 1);
114 assert(
x.rows() == A.cols());
118 BOOST_AUTO(Aqr, A.fullPivHouseholderQr());
119 if (!Aqr.isInvertible())
122 const int rank = Aqr.rank();
123 const int rows = A.rows();
124 const Matrix Q1t = Aqr.matrixQ().transpose().block(0, 0, rank, rows);
125 const Matrix R1 = (Q1t * A * Aqr.colsPermutation()).block(0, 0, rank, rows);
127 const bool findMinimalNormSolution =
true;
130 if(findMinimalNormSolution){
132 x = R1.template triangularView<Eigen::Upper>().transpose() * (R1 * R1.transpose()).llt().solve(Q1t * b);
135 x.block(0, 0, rank, 1) = R1.block(0, 0, rank, rank).template triangularView<Eigen::Upper>().solve(Q1t * b);
136 x.block(rank, 0, rows - rank, 1).setZero();
139 x = Aqr.colsPermutation() *
x;
141 BOOST_AUTO(ax , (A *
x).eval());
142 if (!b.isApprox(ax, 1e-5)) {
143 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.");
144 x = A.template cast<double>().jacobiSvd(ComputeThinU | ComputeThinV).solve(b.template cast<double>()).template cast<T>();
147 if((b - ax).norm() > 1e-5 * std::max(A.norm() *
x.norm(), b.norm())){
148 LOG_WARNING_STREAM(
"PointMatcher::icp - encountered numerically singular matrix while minimizing point to plane distance and the current workaround remained inaccurate."
149 <<
" b=" << b.transpose()
150 <<
" !~ A * x=" << (ax).transpose().eval()
151 <<
": ||b- ax||=" << (b - ax).norm()
152 <<
", ||b||=" << b.norm()
153 <<
", ||ax||=" << ax.norm());
159 x = A.llt().solve(b);
167 return compute_in_place(mPts);
178 int forcedDim =
dim - 1;
179 if(force2D &&
dim == 4)
192 assert(normalRef.rows() > 0);
206 matrixGamma << 0,-1, 0,
209 cross = ((matrixGamma*mPts.
reading.
features.block(0, 0, 3, nbPts)).transpose()*normalRef).diagonal().transpose();
215 Matrix wF(normalRef.rows()+ cross.rows(), normalRef.cols());
216 Matrix F(normalRef.rows()+ cross.rows(), normalRef.cols());
218 for(
int i=0; i < cross.rows(); i++)
220 wF.row(i) = mPts.
weights.array() * cross.row(i).array();
221 F.row(i) = cross.row(i);
223 for(
int i=0; i < normalRef.rows(); i++)
225 wF.row(i + cross.rows()) = mPts.
weights.array() * normalRef.row(i).array();
226 F.row(i + cross.rows()) = normalRef.row(i);
230 const Matrix A = wF * F.transpose();
235 Matrix dotProd = Matrix::Zero(1, normalRef.cols());
237 for(
int i=0; i<normalRef.rows(); i++)
239 dotProd += (deltas.row(i).array() * normalRef.row(i).array()).matrix();
243 const Vector b = -(wF * dotProd.transpose());
247 solvePossiblyUnderdeterminedLinearSystem<T>(A, b,
x);
251 if(
dim == 4 && !force2D)
253 Eigen::Transform<T, 3, Eigen::Affine> transform;
263 transform = Eigen::AngleAxis<T>(
x.head(3).norm(),
x.head(3).normalized());
268 transform = Eigen::AngleAxis<T>(
x(0), unitZ);
278 transform.translation() =
x.segment(3, 3);
281 transform.translation() =
x.segment(1, 3);
284 mOut = transform.matrix();
291 mOut.block(0, 0,
dim-1,
dim-1) = Matrix::Identity(
dim-1,
dim-1);
296 Eigen::Transform<T, 2, Eigen::Affine> transform;
297 transform = Eigen::Rotation2D<T> (
x(0));
298 transform.translation() =
x.segment(1, 2);
302 mOut = Matrix::Identity(
dim,
dim);
303 mOut.topLeftCorner(2, 2) = transform.matrix().topLeftCorner(2, 2);
304 mOut.topRightCorner(2, 1) = transform.matrix().topRightCorner(2, 1);
308 mOut = transform.matrix();
322 int forcedDim =
dim - 1;
323 if(force2D &&
dim == 4)
336 assert(normalRef.rows() > 0);
341 Matrix dotProd = Matrix::Zero(1, normalRef.cols());
342 for(
int i = 0; i < normalRef.rows(); i++)
344 dotProd += (deltas.row(i).array() * normalRef.row(i).array()).matrix();
347 dotProd = (mPts.
weights.row(0).array() * dotProd.array().square()).matrix();
350 return dotProd.sum();
361 assert(
matches.ids.rows() > 0);
374 const bool hasReadingNoise = this->lastErrorElements.reading.descriptorExists(
"simpleSensorNoise");
375 const bool hasReferenceNoise = this->lastErrorElements.reference.descriptorExists(
"simpleSensorNoise");
376 const bool hasReferenceDensity = this->lastErrorElements.reference.descriptorExists(
"densities");
378 const int nbPoints = this->lastErrorElements.reading.features.cols();
379 const int dim = this->lastErrorElements.reading.features.rows();
384 throw std::runtime_error(
"Error, last error element empty. Error minimizer needs to be called at least once before using this method.");
387 Eigen::Array<T, 1, Eigen::Dynamic> uncertainties(nbPoints);
390 if (hasReadingNoise && hasReferenceNoise && hasReferenceDensity)
394 Matrix densities = this->lastErrorElements.reference.getDescriptorViewByName(
"densities");
395 vector<T> values(densities.data(), densities.data() + densities.size());
398 nth_element(values.begin(), values.begin() + (values.size() * 0.5), values.end());
401 const T medianDensity = values[values.size() * 0.5];
402 const T medianRadius = 1.0/
pow(medianDensity, 1/3.0);
404 uncertainties = (medianRadius +
405 this->lastErrorElements.reading.getDescriptorViewByName(
"simpleSensorNoise").array() +
406 this->lastErrorElements.reference.getDescriptorViewByName(
"simpleSensorNoise").array());
408 else if(hasReadingNoise && hasReferenceNoise)
410 uncertainties = this->lastErrorElements.reading.getDescriptorViewByName(
"simpleSensorNoise") +
411 this->lastErrorElements.reference.getDescriptorViewByName(
"simpleSensorNoise");
413 else if(hasReadingNoise)
415 uncertainties = this->lastErrorElements.reading.getDescriptorViewByName(
"simpleSensorNoise");
417 else if(hasReferenceNoise)
419 uncertainties = this->lastErrorElements.reference.getDescriptorViewByName(
"simpleSensorNoise");
423 LOG_INFO_STREAM(
"PointToPlaneErrorMinimizer - warning, no sensor noise and density. Using best estimate given outlier rejection instead.");
424 return this->getWeightedPointUsedRatio();
428 const Vector dists = (this->lastErrorElements.reading.features.topRows(
dim-1) - this->lastErrorElements.reference.features.topRows(
dim-1)).colwise().norm();
434 int nbUniquePoint = 1;
435 Vector lastValidPoint = this->lastErrorElements.reading.features.col(0) * 2.;
436 for(
int i=0; i < nbPoints; i++)
438 const Vector point = this->lastErrorElements.reading.features.col(i);
440 if(lastValidPoint != point)
449 lastValidPoint = point;
457 if(point != this->lastErrorElements.reading.features.col(i-1))
464 return (
T)count/(
T)(nbUniquePoint + this->lastErrorElements.nbRejectedPoints);