g2o
Loading...
Searching...
No Matches
sclam_pure_calibration.cpp
Go to the documentation of this file.
1// g2o - General Graph Optimization
2// Copyright (C) 2011 R. Kuemmerle, G.Grisetti, W. Burgard
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright notice,
10// this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above copyright
12// notice, this list of conditions and the following disclaimer in the
13// documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27#include <cassert>
28#include <csignal>
29#include <fstream>
30#include <iostream>
31#include <map>
32
34#include "edge_se2_pure_calib.h"
40#include "g2o/stuff/macros.h"
42#include "g2o/stuff/timeutil.h"
48#include "gm2dl_io.h"
49#include "motion_information.h"
50#include "sclam_helpers.h"
51
52using namespace std;
53using namespace g2o;
54
55static Eigen::Vector2d linearSolution;
56
57class VertexBaseline : public BaseVertex<1, double> {
58 public:
60
61 virtual void setToOriginImpl() { _estimate = 1.; }
62 virtual void oplusImpl(const double* update) { _estimate += update[0]; }
63 virtual bool read(std::istream&) { return false; }
64 virtual bool write(std::ostream&) const { return false; }
65};
66
68 : public BaseBinaryEdge<3, OdomAndLaserMotion, VertexSE2, VertexBaseline> {
69 public:
72
73 void computeError() {
74 const VertexSE2* laserOffset = static_cast<const VertexSE2*>(_vertices[0]);
75 const VertexBaseline* odomParams =
76 dynamic_cast<const VertexBaseline*>(_vertices[1]);
77
78 // get the calibrated motion given by the odometry
79 double rl = -odomParams->estimate() * linearSolution(0);
80 double rr = odomParams->estimate() * linearSolution(1);
81 VelocityMeasurement calibratedVelocityMeasurement(
82 measurement().velocityMeasurement.vl() * rl,
83 measurement().velocityMeasurement.vr() * rr,
84 measurement().velocityMeasurement.dt());
86 calibratedVelocityMeasurement, odomParams->estimate());
87 SE2 Ku_ij;
88 Ku_ij.fromVector(mm.measurement());
89
90 SE2 laserMotionInRobotFrame = laserOffset->estimate() *
91 measurement().laserMotion *
92 laserOffset->estimate().inverse();
93 SE2 delta = Ku_ij.inverse() * laserMotionInRobotFrame;
94 _error = delta.toVector();
95 }
96
97 virtual bool read(std::istream&) { return false; }
98 virtual bool write(std::ostream&) const { return false; }
99};
100
101int main(int argc, char** argv) {
102 bool fixLaser;
103 int maxIterations;
104 bool verbose;
105 string inputFilename;
106 string outputfilename;
107 string rawFilename;
108 string odomTestFilename;
109 string dumpGraphFilename;
110 // command line parsing
111 CommandArgs commandLineArguments;
112 commandLineArguments.param("i", maxIterations, 10, "perform n iterations");
113 commandLineArguments.param("v", verbose, false,
114 "verbose output of the optimization process");
115 commandLineArguments.param("o", outputfilename, "",
116 "output final version of the graph");
117 commandLineArguments.param("test", odomTestFilename, "",
118 "apply odometry calibration to some test data");
119 commandLineArguments.param("dump", dumpGraphFilename, "",
120 "write the graph to the disk");
121 commandLineArguments.param("fixLaser", fixLaser, false,
122 "keep the laser offset fixed during optimization");
123 commandLineArguments.paramLeftOver("gm2dl-input", inputFilename, "",
124 "gm2dl file which will be processed");
125 commandLineArguments.paramLeftOver("raw-log", rawFilename, "",
126 "raw log file containing the odometry");
127
128 commandLineArguments.parseArgs(argc, argv);
129
130 SparseOptimizer optimizer;
131 optimizer.setVerbose(verbose);
132
133 allocateSolverForSclam(optimizer);
134
135 // loading
136 DataQueue odometryQueue;
137 int numLaserOdom = Gm2dlIO::readRobotLaser(rawFilename, odometryQueue);
138 if (numLaserOdom == 0) {
139 cerr << "No raw information read" << endl;
140 return 0;
141 }
142 cerr << "Read " << numLaserOdom << " laser readings from file" << endl;
143
144 Eigen::Vector3d odomCalib(1., 1., 1.);
145 SE2 initialLaserPose;
146 DataQueue robotLaserQueue;
147 int numRobotLaser = Gm2dlIO::readRobotLaser(inputFilename, robotLaserQueue);
148 if (numRobotLaser == 0) {
149 cerr << "No robot laser read" << endl;
150 return 0;
151 } else {
152 RobotLaser* rl =
153 dynamic_cast<RobotLaser*>(robotLaserQueue.buffer().begin()->second);
154 initialLaserPose = rl->odomPose().inverse() * rl->laserPose();
155 cerr << PVAR(initialLaserPose.toVector().transpose()) << endl;
156 }
157
158 // adding the measurements
159 vector<MotionInformation> motions;
160 {
161 std::map<double, RobotData*>::const_iterator it =
162 robotLaserQueue.buffer().begin();
163 std::map<double, RobotData*>::const_iterator prevIt = it++;
164 for (; it != robotLaserQueue.buffer().end(); ++it) {
166 RobotLaser* prevLaser = dynamic_cast<RobotLaser*>(prevIt->second);
167 RobotLaser* curLaser = dynamic_cast<RobotLaser*>(it->second);
168 mi.laserMotion = prevLaser->laserPose().inverse() * curLaser->laserPose();
169 // get the motion of the robot in that time interval
170 RobotLaser* prevOdom = dynamic_cast<RobotLaser*>(
171 odometryQueue.findClosestData(prevLaser->timestamp()));
172 RobotLaser* curOdom = dynamic_cast<RobotLaser*>(
173 odometryQueue.findClosestData(curLaser->timestamp()));
174 mi.odomMotion = prevOdom->odomPose().inverse() * curOdom->odomPose();
175 mi.timeInterval = prevOdom->timestamp() - curOdom->timestamp();
176 prevIt = it;
177 motions.push_back(mi);
178 }
179 }
180
181 if (1) {
182 VertexSE2* laserOffset = new VertexSE2;
183 laserOffset->setId(Gm2dlIO::ID_LASERPOSE);
184 laserOffset->setEstimate(initialLaserPose);
185 optimizer.addVertex(laserOffset);
186 VertexOdomDifferentialParams* odomParamsVertex =
188 odomParamsVertex->setId(Gm2dlIO::ID_ODOMCALIB);
189 odomParamsVertex->setEstimate(Eigen::Vector3d(1., 1., 1.));
190 optimizer.addVertex(odomParamsVertex);
191 for (size_t i = 0; i < motions.size(); ++i) {
192 const SE2& odomMotion = motions[i].odomMotion;
193 const SE2& laserMotion = motions[i].laserMotion;
194 const double& timeInterval = motions[i].timeInterval;
195 // add the edge
196 MotionMeasurement mm(odomMotion.translation().x(),
197 odomMotion.translation().y(),
198 odomMotion.rotation().angle(), timeInterval);
201 meas.laserMotion = laserMotion;
202 EdgeSE2PureCalib* calibEdge = new EdgeSE2PureCalib;
203 calibEdge->setVertex(0, laserOffset);
204 calibEdge->setVertex(1, odomParamsVertex);
205 calibEdge->setInformation(Eigen::Matrix3d::Identity());
206 calibEdge->setMeasurement(meas);
207 if (!optimizer.addEdge(calibEdge)) {
208 cerr << "Error adding calib edge" << endl;
209 delete calibEdge;
210 }
211 }
212
213 if (fixLaser) {
214 cerr << "Fix position of the laser offset" << endl;
215 laserOffset->setFixed(true);
216 }
217
218 cerr << "\nPerforming full non-linear estimation" << endl;
219 optimizer.initializeOptimization();
220 optimizer.computeActiveErrors();
221 optimizer.optimize(maxIterations);
222 cerr << "Calibrated laser offset (x, y, theta):"
223 << laserOffset->estimate().toVector().transpose() << endl;
224 odomCalib = odomParamsVertex->estimate();
225 cerr << "Odometry parameters (scaling factors (v_l, v_r, b)): "
226 << odomParamsVertex->estimate().transpose() << endl;
227 optimizer.clear();
228 }
229
230 // linear least squares for some parameters
231 {
232 Eigen::MatrixXd A(motions.size(), 2);
233 Eigen::VectorXd x(motions.size());
234 for (size_t i = 0; i < motions.size(); ++i) {
235 const SE2& odomMotion = motions[i].odomMotion;
236 const SE2& laserMotion = motions[i].laserMotion;
237 const double& timeInterval = motions[i].timeInterval;
238 MotionMeasurement mm(odomMotion.translation().x(),
239 odomMotion.translation().y(),
240 odomMotion.rotation().angle(), timeInterval);
242 A(i, 0) = velMeas.vl() * timeInterval;
243 A(i, 1) = velMeas.vr() * timeInterval;
244 x(i) = laserMotion.rotation().angle();
245 }
246 // linearSolution = (A.transpose() * A).inverse() * A.transpose() * x;
247 linearSolution = A.colPivHouseholderQr().solve(x);
248 // cout << PVAR(linearSolution.transpose()) << endl;
249 }
250
251 // constructing non-linear least squares
252 VertexSE2* laserOffset = new VertexSE2;
253 laserOffset->setId(Gm2dlIO::ID_LASERPOSE);
254 laserOffset->setEstimate(initialLaserPose);
255 optimizer.addVertex(laserOffset);
256 VertexBaseline* odomParamsVertex = new VertexBaseline;
257 odomParamsVertex->setId(Gm2dlIO::ID_ODOMCALIB);
258 odomParamsVertex->setEstimate(1.);
259 optimizer.addVertex(odomParamsVertex);
260 for (size_t i = 0; i < motions.size(); ++i) {
261 const SE2& odomMotion = motions[i].odomMotion;
262 const SE2& laserMotion = motions[i].laserMotion;
263 const double& timeInterval = motions[i].timeInterval;
264 // add the edge
265 MotionMeasurement mm(odomMotion.translation().x(),
266 odomMotion.translation().y(),
267 odomMotion.rotation().angle(), timeInterval);
270 meas.laserMotion = laserMotion;
271 EdgeCalib* calibEdge = new EdgeCalib;
272 calibEdge->setVertex(0, laserOffset);
273 calibEdge->setVertex(1, odomParamsVertex);
274 calibEdge->setInformation(Eigen::Matrix3d::Identity());
275 calibEdge->setMeasurement(meas);
276 if (!optimizer.addEdge(calibEdge)) {
277 cerr << "Error adding calib edge" << endl;
278 delete calibEdge;
279 }
280 }
281
282 if (fixLaser) {
283 cerr << "Fix position of the laser offset" << endl;
284 laserOffset->setFixed(true);
285 }
286
287 cerr << "\nPerforming partial non-linear estimation" << endl;
288 optimizer.initializeOptimization();
289 optimizer.computeActiveErrors();
290 optimizer.optimize(maxIterations);
291 cerr << "Calibrated laser offset (x, y, theta):"
292 << laserOffset->estimate().toVector().transpose() << endl;
293 odomCalib(0) = -1. * linearSolution(0) * odomParamsVertex->estimate();
294 odomCalib(1) = linearSolution(1) * odomParamsVertex->estimate();
295 odomCalib(2) = odomParamsVertex->estimate();
296 cerr << "Odometry parameters (scaling factors (v_l, v_r, b)): "
297 << odomCalib.transpose() << endl;
298
299 {
300 SE2 closedFormLaser;
301 Eigen::Vector3d closedFormOdom;
302 ClosedFormCalibration::calibrate(motions, closedFormLaser, closedFormOdom);
303 cerr << "\nObtaining closed form solution" << endl;
304 cerr << "Calibrated laser offset (x, y, theta):"
305 << closedFormLaser.toVector().transpose() << endl;
306 cerr << "Odometry parameters (scaling factors (v_l, v_r, b)): "
307 << closedFormOdom.transpose() << endl;
308 }
309
310 if (dumpGraphFilename.size() > 0) {
311 cerr << "Writing " << dumpGraphFilename << " ... ";
312 optimizer.save(dumpGraphFilename.c_str());
313 cerr << "done." << endl;
314 }
315
316 // optional input of a separate file for applying the odometry calibration
317 if (odomTestFilename.size() > 0) {
318 DataQueue testRobotLaserQueue;
319 int numTestOdom =
320 Gm2dlIO::readRobotLaser(odomTestFilename, testRobotLaserQueue);
321 if (numTestOdom == 0) {
322 cerr << "Unable to read test data" << endl;
323 } else {
324 ofstream rawStream("odometry_raw.txt");
325 ofstream calibratedStream("odometry_calibrated.txt");
326 RobotLaser* prev = dynamic_cast<RobotLaser*>(
327 testRobotLaserQueue.buffer().begin()->second);
328 SE2 prevCalibratedPose = prev->odomPose();
329
330 for (DataQueue::Buffer::const_iterator it =
331 testRobotLaserQueue.buffer().begin();
332 it != testRobotLaserQueue.buffer().end(); ++it) {
333 RobotLaser* cur = dynamic_cast<RobotLaser*>(it->second);
334 assert(cur);
335
336 double dt = cur->timestamp() - prev->timestamp();
337 SE2 motion = prev->odomPose().inverse() * cur->odomPose();
338
339 // convert to velocity measurement
340 MotionMeasurement motionMeasurement(motion.translation().x(),
341 motion.translation().y(),
342 motion.rotation().angle(), dt);
343 VelocityMeasurement velocityMeasurement =
344 OdomConvert::convertToVelocity(motionMeasurement);
345
346 // apply calibration
347 VelocityMeasurement calibratedVelocityMeasurement = velocityMeasurement;
348 calibratedVelocityMeasurement.setVl(odomCalib(0) *
349 calibratedVelocityMeasurement.vl());
350 calibratedVelocityMeasurement.setVr(odomCalib(1) *
351 calibratedVelocityMeasurement.vr());
353 calibratedVelocityMeasurement, odomCalib(2));
354
355 // combine calibrated odometry with the previous pose
356 SE2 remappedOdom;
357 remappedOdom.fromVector(mm.measurement());
358 SE2 calOdomPose = prevCalibratedPose * remappedOdom;
359
360 // write output
361 rawStream << prev->odomPose().translation().x() << " "
362 << prev->odomPose().translation().y() << " "
363 << prev->odomPose().rotation().angle() << endl;
364 calibratedStream << calOdomPose.translation().x() << " "
365 << calOdomPose.translation().y() << " "
366 << calOdomPose.rotation().angle() << endl;
367
368 prevCalibratedPose = calOdomPose;
369 prev = cur;
370 }
371 }
372 }
373
374 return 0;
375}
virtual bool write(std::ostream &) const
write the vertex to a stream
virtual bool read(std::istream &)
read the vertex from a stream, i.e., the internal state of the vertex
virtual void setToOriginImpl()
sets the node to the origin (used in the multilevel stuff)
virtual bool write(std::ostream &) const
write the vertex to a stream
virtual bool read(std::istream &)
read the vertex from a stream, i.e., the internal state of the vertex
virtual void oplusImpl(const double *update)
EIGEN_STRONG_INLINE const Measurement & measurement() const
accessor functions for the measurement represented by the edge
Definition base_edge.h:119
virtual void setMeasurement(const Measurement &m)
Definition base_edge.h:122
void setInformation(const InformationType &information)
Definition base_edge.h:111
ErrorVector _error
Definition base_edge.h:149
Templatized BaseVertex.
Definition base_vertex.h:51
const EstimateType & estimate() const
return the current estimate of the vertex
void setEstimate(const EstimateType &et)
set the estimate for the vertex also calls updateCache()
static bool calibrate(const MotionInformationVector &measurements, SE2 &laserOffset, Eigen::Vector3d &odomParams)
Command line parsing of argc and argv.
bool parseArgs(int argc, char **argv, bool exitOnError=true)
void paramLeftOver(const std::string &name, std::string &p, const std::string &defValue, const std::string &desc, bool optional=false)
void param(const std::string &name, bool &p, bool defValue, const std::string &desc)
a simple queue to store data and retrieve based on a timestamp
Definition data_queue.h:41
const Buffer & buffer() const
Definition data_queue.h:53
RobotData * findClosestData(double timestamp) const
calibrate odometry and laser based on a set of measurements
static const int ID_LASERPOSE
Definition gm2dl_io.h:47
static const int ID_ODOMCALIB
Definition gm2dl_io.h:48
static int readRobotLaser(const std::string &filename, DataQueue &queue)
Definition gm2dl_io.cpp:252
void setVertex(size_t i, Vertex *v)
VertexContainer _vertices
A 2D odometry measurement expressed as a transformation.
const Vector3 & measurement() const
static VelocityMeasurement convertToVelocity(const MotionMeasurement &m)
static MotionMeasurement convertToMotion(const VelocityMeasurement &vi, double l=1.0)
void setFixed(bool fixed)
true => this node should be considered fixed during the optimization
double timestamp() const
Definition robot_data.h:46
laser measurement obtained by a robot
Definition robot_laser.h:43
SE2 laserPose() const
Definition robot_laser.h:52
const SE2 & odomPose() const
Definition robot_laser.h:53
represent SE2
Definition se2.h:43
const Vector2 & translation() const
translational component
Definition se2.h:57
Vector3 toVector() const
convert to a 3D vector (x, y, theta)
Definition se2.h:105
SE2 inverse() const
invert :-)
Definition se2.h:83
void fromVector(const Vector3 &v)
assign from a 3D vector (x, y, theta)
Definition se2.h:102
const Rotation2D & rotation() const
rotational component
Definition se2.h:61
int optimize(int iterations, bool online=false)
void setVerbose(bool verbose)
virtual bool initializeOptimization(HyperGraph::EdgeSet &eset)
velocity measurement of a differential robot
2D pose Vertex, (x,y,theta)
Definition vertex_se2.h:41
int main()
Definition gicp_demo.cpp:44
void allocateSolverForSclam(SparseOptimizer &optimizer, bool levenberg)
Definition jet.h:876
static Eigen::Vector2d linearSolution
VelocityMeasurement velocityMeasurement
virtual bool addEdge(HyperGraph::Edge *e)
virtual bool save(std::ostream &os, int level=0) const
save the graph to a stream. Again uses the Factory system.
virtual bool addVertex(HyperGraph::Vertex *v, Data *userData)
utility functions for handling time related stuff