481 lines
13 KiB
Text
481 lines
13 KiB
Text
|
/*
|
||
|
* lum6Dquat implementation
|
||
|
*
|
||
|
* Copyright (C) Andreas Nuechter, Jan Elseberg
|
||
|
*
|
||
|
* Released under the GPL version 3.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @file
|
||
|
* @brief The implementation of globally consistent scan matching algorithm
|
||
|
* @author Jan Elseberg. Institute of Computer Science, University of Osnabrueck, Germany.
|
||
|
* @author Andreas Nuechter. Institute of Computer Science, University of Osnabrueck, Germany.
|
||
|
*/
|
||
|
|
||
|
#ifdef _MSC_VER
|
||
|
#if !defined _OPENMP && defined OPENMP
|
||
|
#define _OPENMP
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#include "slam6d/lum6Dquat.h"
|
||
|
#include "sparse/csparse.h"
|
||
|
|
||
|
#include <cfloat>
|
||
|
#include <fstream>
|
||
|
using std::ofstream;
|
||
|
using std::cerr;
|
||
|
#include "slam6d/globals.icc"
|
||
|
|
||
|
using namespace NEWMAT;
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* @param my_icp6Dminimizer Pointer to ICP minimization functor
|
||
|
* @param mdm Maximum PtoP distance to which point pairs are collected for ICP
|
||
|
* @param max_dist_match Maximum PtoP distance to which point pairs are collected for LUM
|
||
|
* @param max_num_iterations Maximal number of iterations for ICP
|
||
|
* @param quiet Suspesses all output to std out
|
||
|
* @param meta Indicates if metascan matching has to be used
|
||
|
* @param rnd Indicates if randomization has to be used
|
||
|
* @param eP Extrapolate odometry?
|
||
|
* @param anim Animate which frames?
|
||
|
* @param epsilonICP Termination criterion for ICP
|
||
|
* @param nns_method Specifies which NNS method is used
|
||
|
* @param epsilonLUM Termination criterion for LUM
|
||
|
*/
|
||
|
lum6DQuat::lum6DQuat(icp6Dminimizer *my_icp6Dminimizer,
|
||
|
double mdm, double max_dist_match,
|
||
|
int max_num_iterations, bool quiet, bool meta, int rnd,
|
||
|
bool eP, int anim, double epsilonICP, int nns_method, double epsilonLUM)
|
||
|
: graphSlam6D(my_icp6Dminimizer,
|
||
|
mdm, max_dist_match,
|
||
|
max_num_iterations, quiet, meta, rnd,
|
||
|
eP, anim, epsilonICP, nns_method, epsilonLUM)
|
||
|
{ }
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Destructor
|
||
|
*/
|
||
|
lum6DQuat::~lum6DQuat()
|
||
|
{
|
||
|
delete my_icp;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This function calculates the inverse covariances Cij and the Vector Cij*Dij for
|
||
|
* two scans by finding pointpairs.
|
||
|
*
|
||
|
* @param first pointer to the first scan of the link
|
||
|
* @param second pointer to the second scan of the link
|
||
|
* @param nns_method Specifies which NNS method is used
|
||
|
* @param rnd shall we use randomization for computing the point pairs?
|
||
|
* @param max_dist_match2 maximal distance allowed for point pairs
|
||
|
* @param C pointer to the inverse of the covariance matrix Cij
|
||
|
* @param CD pointer to the vector Cij*Dij
|
||
|
*/
|
||
|
void lum6DQuat::covarianceQuat(Scan *first, Scan *second,
|
||
|
int nns_method, int rnd, double max_dist_match2,
|
||
|
Matrix *C, ColumnVector *CD)
|
||
|
{
|
||
|
// x,y,z denote the coordinates of uk (Here averaged over ak and bk)
|
||
|
// sx,sy,sz are the sums of their respective coordinates of uk over each paint pair
|
||
|
// xpy,xpz,ypz are the sums over x*x + y*y ,x*x + z*z and y*y + z*z respectively over each point pair
|
||
|
// xy,yz,xz are the sums over each respective multiplication
|
||
|
// dx,dy,dz are the deltas in each coordinate of a point pair
|
||
|
// ss is the estimation of the covariance of sensing error
|
||
|
double x, y, z, sx, sy, sz, xy, yz, xz, ypz, xpz, xpy, dx, dy, dz, ss, xpypz;
|
||
|
|
||
|
// D is needed to calculate the estimation of the covariance s
|
||
|
ColumnVector D(7);
|
||
|
// Almost Cij*Dij
|
||
|
ColumnVector MZ(7);
|
||
|
// Almost the covarianve
|
||
|
Matrix MM(7,7);
|
||
|
// A set of point pairs
|
||
|
vector <PtPair> uk;
|
||
|
// A point pair
|
||
|
Point ak, bk;
|
||
|
// number of pairs in a set
|
||
|
int m;
|
||
|
|
||
|
#ifdef _OPENMP
|
||
|
int thread_num = omp_get_thread_num();
|
||
|
#else
|
||
|
int thread_num = 0;
|
||
|
#endif
|
||
|
|
||
|
double dummy_centroid_m[3];
|
||
|
double dummy_centroid_d[3];
|
||
|
double dummy_sum;
|
||
|
|
||
|
Scan::getPtPairs(&uk, first, second, thread_num,
|
||
|
rnd, max_dist_match2, dummy_sum, dummy_centroid_m, dummy_centroid_d);
|
||
|
|
||
|
m = uk.size();
|
||
|
|
||
|
MZ = 0.0;
|
||
|
MM = 0.0;
|
||
|
sx = sy = sz = xy = yz = xz = ypz = xpz = xpy = xpypz = ss = 0.0;
|
||
|
|
||
|
if (m > 2) {
|
||
|
// for each point pair
|
||
|
for(int j = 0; j < m; j++){
|
||
|
ak = uk[j].p1;
|
||
|
bk = uk[j].p2;
|
||
|
|
||
|
// Some temporary values
|
||
|
x = (ak.x + bk.x)/2.0;
|
||
|
y = (ak.y + bk.y)/2.0;
|
||
|
z = (ak.z + bk.z)/2.0;
|
||
|
dx = ak.x - bk.x;
|
||
|
dy = ak.y - bk.y;
|
||
|
dz = ak.z - bk.z;
|
||
|
|
||
|
// Sum up all necessary values to construct MM
|
||
|
sx += x;
|
||
|
sy += y;
|
||
|
sz += z;
|
||
|
|
||
|
xpy += x*x + y*y;
|
||
|
xpz += x*x + z*z;
|
||
|
ypz += y*y + z*z;
|
||
|
xpypz += x*x + y*y + z*z;
|
||
|
|
||
|
xy += x*y;
|
||
|
xz += x*z;
|
||
|
yz += y*z;
|
||
|
|
||
|
// Sum up each part of MZ
|
||
|
MZ(1) += dx;
|
||
|
MZ(2) += dy;
|
||
|
MZ(3) += dz;
|
||
|
MZ(4) += x * dx + y * dy + z * dz;
|
||
|
MZ(5) += z * dy - y * dz;
|
||
|
MZ(6) += x * dz - z * dx;
|
||
|
MZ(7) += y * dx - x * dy;
|
||
|
}
|
||
|
// Now construct the symmetrical matrix MM
|
||
|
MM(1,1) = MM(2,2) = MM(3,3) = m;
|
||
|
|
||
|
MM(4,4) = xpypz;
|
||
|
MM(5,5) = ypz;
|
||
|
MM(6,6) = xpz;
|
||
|
MM(7,7) = xpy;
|
||
|
|
||
|
MM(1,4) = MM(4,1) = sx;
|
||
|
MM(1,6) = MM(6,1) = -sz;
|
||
|
MM(1,7) = MM(7,1) = sy;
|
||
|
|
||
|
MM(2,4) = MM(4,2) = sy;
|
||
|
MM(2,5) = MM(5,2) = sz;
|
||
|
MM(2,7) = MM(7,2) = -sx;
|
||
|
|
||
|
MM(3,4) = MM(4,3) = sz;
|
||
|
MM(3,5) = MM(5,3) = -sy;
|
||
|
MM(3,6) = MM(6,3) = sx;
|
||
|
|
||
|
MM(5,6) = MM(6,5) = -xy;
|
||
|
MM(5,7) = MM(7,5) = -xz;
|
||
|
MM(6,7) = MM(7,6) = -yz;
|
||
|
|
||
|
// Calculate the pose difference estimation
|
||
|
D = MM.i() * MZ ;
|
||
|
|
||
|
// Again going through all point pairs to faster calculate s.
|
||
|
// This cannot be done earlier as we need D, and therefore MM and MZ to do this
|
||
|
for(int j = 0; j < m; j++){
|
||
|
ak = uk[j].p1;
|
||
|
bk = uk[j].p2;
|
||
|
|
||
|
x = (ak.x + bk.x) / 2.0;
|
||
|
y = (ak.y + bk.y) / 2.0;
|
||
|
z = (ak.z + bk.z) / 2.0;
|
||
|
|
||
|
ss += sqr(ak.x - bk.x - (D(1) + x * D(4) - z * D(6) + y * D(7)))
|
||
|
+ sqr(ak.y - bk.y - (D(2) + y * D(4) + z * D(5) - x * D(7)))
|
||
|
+ sqr(ak.z - bk.z - (D(3) + z * D(4) - y * D(5) + x * D(6)));
|
||
|
}
|
||
|
|
||
|
ss = ss / (2*m - 3);
|
||
|
ss = 1.0 / ss;
|
||
|
|
||
|
if (CD) {
|
||
|
*CD = MZ * ss;
|
||
|
}
|
||
|
*C = MM * ss;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// This case should not occur
|
||
|
ss = 0.0;
|
||
|
MM(1,1) = MM(1,2) = MM(1,3) = 0.0;
|
||
|
MM(2,1) = MM(2,2) = MM(2,3) = 0.0;
|
||
|
MM(3,1) = MM(3,2) = MM(3,3) = 0.0;
|
||
|
MZ(6) = MZ(1) = MZ(2) = MZ(7) = 0.0;
|
||
|
MZ(3) = MZ(4) = MZ(5) = 0.0;
|
||
|
*C = 0;
|
||
|
if(CD)
|
||
|
*CD = 0;
|
||
|
cerr << "Error calculating covariance matrix" << endl;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A function to fill the linear system G X = B.
|
||
|
*
|
||
|
* @param gr the Graph is used to map the given covariances C and CD matrices to the correct link
|
||
|
* @param CD A vector containing all covariances C multiplied with their respective estimations D
|
||
|
* @param C A vector containing all covariances C of the pose difference estimations D
|
||
|
* @param G The matrix G specifying the linear equation
|
||
|
* @param B The vector B
|
||
|
*/
|
||
|
void lum6DQuat::FillGB3D(Graph *gr,Matrix* G, ColumnVector* B, vector<Scan *> allScans)
|
||
|
{
|
||
|
#ifdef _OPENMP
|
||
|
#pragma omp parallel for
|
||
|
#endif
|
||
|
for(int i = 0; i < gr->getNrLinks(); i++){
|
||
|
int a = gr->getLink(i,0) - 1;
|
||
|
int b = gr->getLink(i,1) - 1;
|
||
|
Scan *FirstScan = allScans[gr->getLink(i,0)];
|
||
|
Scan *SecondScan = allScans[gr->getLink(i,1)];
|
||
|
|
||
|
// cout << "***i " << i << " a: " << a << " b: " << b << endl;
|
||
|
|
||
|
Matrix Cab;
|
||
|
ColumnVector CDab;
|
||
|
covarianceQuat(FirstScan, SecondScan, nns_method, (int)my_icp->get_rnd(),
|
||
|
(int)max_dist_match2_LUM, &Cab, &CDab);
|
||
|
|
||
|
if(a >= 0){
|
||
|
B->Rows(a*7+1,a*7+7) += CDab;
|
||
|
G->SubMatrix(a*7+1,a*7+7,a*7+1,a*7+7) += Cab;
|
||
|
}
|
||
|
if(b >= 0){
|
||
|
B->Rows(b*7+1,b*7+7) -= CDab;
|
||
|
G->SubMatrix(b*7+1,b*7+7,b*7+1,b*7+7) += Cab;
|
||
|
}
|
||
|
if(a >= 0 && b >= 0) {
|
||
|
G->SubMatrix(a*7+1,a*7+7,b*7+1,b*7+7) = -Cab;
|
||
|
G->SubMatrix(b*7+1,b*7+7,a*7+1,a*7+7) = -Cab;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function is used to match a set of laser scans with any minimally
|
||
|
* connected Graph, using the globally consistent LUM-algorithm in 3D.
|
||
|
*
|
||
|
* @param gr Some Graph with no real subgraphs except for itself
|
||
|
* @param allScans Contains all laser scans
|
||
|
* @param nrIt The number of iterations the LUM-algorithm will run
|
||
|
* @return Euclidian distance of all pose shifts
|
||
|
*/
|
||
|
double lum6DQuat::doGraphSlam6D(Graph gr, vector <Scan *> allScans, int nrIt)
|
||
|
{
|
||
|
#ifdef WRITE_GRAPH_NET
|
||
|
// for debug only:
|
||
|
static int d = 0;
|
||
|
cout << "writing graph.dat ....................................." << endl;
|
||
|
d++;
|
||
|
string gfilename = "graph_" + to_string(d, 3) + ".net";
|
||
|
ofstream out(gfilename.c_str());
|
||
|
for (int i=0; i < gr.getNrLinks(); i++) {
|
||
|
int from = gr.getLink(i,0);
|
||
|
int to = gr.getLink(i,1);
|
||
|
// shouldn't be necessary, just in case a (out of date) graph file is loaded:
|
||
|
if (from < (int)allScans.size() && to < (int)allScans.size()) {
|
||
|
out << allScans[from]->get_rPos()[0] << " "
|
||
|
<< allScans[from]->get_rPos()[1] << " "
|
||
|
<< allScans[from]->get_rPos()[2] << endl
|
||
|
<< allScans[to ]->get_rPos()[0] << " "
|
||
|
<< allScans[to ]->get_rPos()[1] << " "
|
||
|
<< allScans[to ]->get_rPos()[2] << endl << endl;
|
||
|
}
|
||
|
}
|
||
|
out.close();
|
||
|
out.clear();
|
||
|
#endif
|
||
|
|
||
|
// the IdentityMatrix to transform some Scans with
|
||
|
double id[16];
|
||
|
M4identity(id);
|
||
|
|
||
|
double ret = DBL_MAX;
|
||
|
|
||
|
for(int iteration = 0;
|
||
|
iteration < nrIt && ret > epsilonLUM;
|
||
|
iteration++) {
|
||
|
|
||
|
if (nrIt > 1) cout << "Iteration " << iteration << endl;
|
||
|
|
||
|
|
||
|
// * Calculate X and CX from all Dij and Cij
|
||
|
int n = (gr.getNrScans() - 1);
|
||
|
|
||
|
// Construct the linear equation system..
|
||
|
Matrix G(7*n,7*n);
|
||
|
ColumnVector B(7*n);
|
||
|
G = 0.0;
|
||
|
B = 0.0;
|
||
|
// ...fill G and B...
|
||
|
FillGB3D(&gr, &G, &B, allScans);
|
||
|
// ...and solve it
|
||
|
ColumnVector X = solveSparseCholesky(G, B);
|
||
|
|
||
|
//cout << "X done!" << endl;
|
||
|
|
||
|
double sum_position_diff = 0.0;
|
||
|
|
||
|
// Start with second Scan
|
||
|
int loop_end = gr.getNrScans();
|
||
|
#ifdef _OPENMP
|
||
|
#pragma omp parallel for reduction(+:sum_position_diff)
|
||
|
#endif
|
||
|
for(int i = 1; i < loop_end; i++){
|
||
|
|
||
|
// Now update the Poses
|
||
|
Matrix Ha = IdentityMatrix(7);
|
||
|
|
||
|
double xa = allScans[i]->get_rPos()[0];
|
||
|
double ya = allScans[i]->get_rPos()[1];
|
||
|
double za = allScans[i]->get_rPos()[2];
|
||
|
|
||
|
double p = allScans[i]->get_rPosQuat()[0];
|
||
|
double q = allScans[i]->get_rPosQuat()[1];
|
||
|
double r = allScans[i]->get_rPosQuat()[2];
|
||
|
double s = allScans[i]->get_rPosQuat()[3];
|
||
|
|
||
|
double px = p * xa;
|
||
|
double py = p * ya;
|
||
|
double pz = p * za;
|
||
|
|
||
|
double qx = q * xa;
|
||
|
double qy = q * ya;
|
||
|
double qz = q * za;
|
||
|
|
||
|
double rx = r * xa;
|
||
|
double ry = r * ya;
|
||
|
double rz = r * za;
|
||
|
|
||
|
double sx = s * xa;
|
||
|
double sy = s * ya;
|
||
|
double sz = s * za;
|
||
|
|
||
|
// Fill Ha
|
||
|
Ha.element(3,3) = 2 * p;
|
||
|
Ha.element(4,3) = 2 * q;
|
||
|
Ha.element(5,3) = 2 * r;
|
||
|
Ha.element(6,3) = 2 * s;
|
||
|
|
||
|
Ha.element(3,4) = 2 * q;
|
||
|
Ha.element(4,4) = -2 * p;
|
||
|
Ha.element(5,4) = -2 * s;
|
||
|
Ha.element(6,4) = 2 * r;
|
||
|
|
||
|
Ha.element(3,5) = 2 * r;
|
||
|
Ha.element(4,5) = 2 * s;
|
||
|
Ha.element(5,5) = -2 * p;
|
||
|
Ha.element(6,5) = -2 * q;
|
||
|
|
||
|
Ha.element(3,6) = 2 * s;
|
||
|
Ha.element(4,6) = -2 * r;
|
||
|
Ha.element(5,6) = 2 * q;
|
||
|
Ha.element(6,6) = -2 * p;
|
||
|
|
||
|
Ha.element(0,3) = -2 * (px + sy - rz);
|
||
|
Ha.element(1,3) = -2 * (-sx + py + qz);
|
||
|
Ha.element(2,3) = -2 * (rx - qy + pz);
|
||
|
|
||
|
Ha.element(0,4) = -2 * (qx + ry + sz);
|
||
|
Ha.element(1,4) = -2 * (-rx + qy - pz);
|
||
|
Ha.element(2,4) = -2 * (-sx + py + qz);
|
||
|
|
||
|
Ha.element(0,5) = -2 * (rx - qy + pz);
|
||
|
Ha.element(1,5) = -2 * (qx + ry + sz);
|
||
|
Ha.element(2,5) = -2 * (-px - sy + rz);
|
||
|
|
||
|
Ha.element(0,6) = -2 * (sx - py - qz);
|
||
|
Ha.element(1,6) = -2 * (px + sy - rz);
|
||
|
Ha.element(2,6) = -2 * (qx + ry + sz);
|
||
|
|
||
|
// Invert it
|
||
|
Ha = Ha.i();
|
||
|
|
||
|
// Get pose estimate
|
||
|
ColumnVector Xtmp = X.Rows((i-1)*7+1,(i-1)*7+7);
|
||
|
|
||
|
// Correct pose estimate
|
||
|
ColumnVector result = Ha * Xtmp;
|
||
|
|
||
|
if(!quiet) {
|
||
|
cout << "Old pose estimate, Scan " << i << endl;
|
||
|
cout << "x: " << allScans[i]->get_rPos()[0]
|
||
|
<< " y: " << allScans[i]->get_rPos()[1]
|
||
|
<< " z: " << allScans[i]->get_rPos()[2]
|
||
|
<< " p: " << allScans[i]->get_rPosQuat()[0]
|
||
|
<< " q: " << allScans[i]->get_rPosQuat()[1]
|
||
|
<< " r: " << allScans[i]->get_rPosQuat()[2]
|
||
|
<< " s: " << allScans[i]->get_rPosQuat()[3]
|
||
|
<< endl;
|
||
|
}
|
||
|
|
||
|
double rPos[3];
|
||
|
double rPosQuat[4];
|
||
|
|
||
|
// calculate the updated Pose
|
||
|
for (int k = 0; k < 3; k++) {
|
||
|
rPos[k] = allScans[i]->get_rPos()[k] - result.element(k);
|
||
|
}
|
||
|
|
||
|
double qtmp[4];
|
||
|
qtmp[0] = result.element(3);
|
||
|
qtmp[1] = result.element(4);
|
||
|
qtmp[2] = result.element(5);
|
||
|
qtmp[3] = result.element(6);
|
||
|
|
||
|
for (int k = 0; k < 4; k++) {
|
||
|
rPosQuat[k] = allScans[i]->get_rPosQuat()[k] - qtmp[k];
|
||
|
}
|
||
|
|
||
|
Normalize4(rPosQuat);
|
||
|
|
||
|
// Update the Pose
|
||
|
if (i != gr.getNrScans() - 1) {
|
||
|
allScans[i]->transformToQuat(rPos, rPosQuat, Scan::LUM, 1);
|
||
|
} else {
|
||
|
allScans[i]->transformToQuat(rPos, rPosQuat, Scan::LUM, 2);
|
||
|
}
|
||
|
|
||
|
if(!quiet) {
|
||
|
cout << "x: " << allScans[i]->get_rPos()[0]
|
||
|
<< " y: " << allScans[i]->get_rPos()[1]
|
||
|
<< " z: " << allScans[i]->get_rPos()[2]
|
||
|
<< " p: " << allScans[i]->get_rPosQuat()[0]
|
||
|
<< " q: " << allScans[i]->get_rPosQuat()[1]
|
||
|
<< " r: " << allScans[i]->get_rPosQuat()[2]
|
||
|
<< " s: " << allScans[i]->get_rPosQuat()[3]
|
||
|
<< endl << endl;
|
||
|
}
|
||
|
|
||
|
double x[3];
|
||
|
x[0] = result.element(0);
|
||
|
x[1] = result.element(1);
|
||
|
x[2] = result.element(2);
|
||
|
sum_position_diff += Len(x);
|
||
|
}
|
||
|
cout << "Sum of Position differences = " << sum_position_diff << endl;
|
||
|
ret = (sum_position_diff / (double)gr.getNrScans());
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|