g2o
Loading...
Searching...
No Matches
Classes | Functions | Variables
sclam_pure_calibration.cpp File Reference
#include <cassert>
#include <csignal>
#include <fstream>
#include <iostream>
#include <map>
#include "closed_form_calibration.h"
#include "edge_se2_pure_calib.h"
#include "g2o/core/hyper_dijkstra.h"
#include "g2o/core/sparse_optimizer.h"
#include "g2o/stuff/color_macros.h"
#include "g2o/stuff/command_args.h"
#include "g2o/stuff/filesys_tools.h"
#include "g2o/stuff/macros.h"
#include "g2o/stuff/string_tools.h"
#include "g2o/stuff/timeutil.h"
#include "g2o/types/data/data_queue.h"
#include "g2o/types/data/robot_laser.h"
#include "g2o/types/sclam2d/odometry_measurement.h"
#include "g2o/types/sclam2d/vertex_odom_differential_params.h"
#include "g2o/types/slam2d/vertex_se2.h"
#include "gm2dl_io.h"
#include "motion_information.h"
#include "sclam_helpers.h"
Include dependency graph for sclam_pure_calibration.cpp:

Go to the source code of this file.

Classes

class  VertexBaseline
 
class  EdgeCalib
 

Functions

int main (int argc, char **argv)
 

Variables

static Eigen::Vector2d linearSolution
 

Function Documentation

◆ main()

int main ( int  argc,
char **  argv 
)

Definition at line 101 of file sclam_pure_calibration.cpp.

101 {
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 void setMeasurement(const Measurement &m)
Definition base_edge.h:122
void setInformation(const InformationType &information)
Definition base_edge.h:111
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)
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
void allocateSolverForSclam(SparseOptimizer &optimizer, bool levenberg)
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)

References g2o::OptimizableGraph::addEdge(), g2o::OptimizableGraph::addVertex(), g2o::allocateSolverForSclam(), g2o::DataQueue::buffer(), g2o::ClosedFormCalibration::calibrate(), g2o::SparseOptimizer::clear(), g2o::SparseOptimizer::computeActiveErrors(), g2o::OdomConvert::convertToMotion(), g2o::OdomConvert::convertToVelocity(), g2o::BaseVertex< D, T >::estimate(), g2o::DataQueue::findClosestData(), g2o::SE2::fromVector(), g2o::Gm2dlIO::ID_LASERPOSE, g2o::Gm2dlIO::ID_ODOMCALIB, g2o::SparseOptimizer::initializeOptimization(), g2o::SE2::inverse(), g2o::OdomAndLaserMotion::laserMotion, g2o::MotionInformation::laserMotion, g2o::RobotLaser::laserPose(), linearSolution, g2o::MotionMeasurement::measurement(), g2o::MotionInformation::odomMotion, g2o::RobotLaser::odomPose(), g2o::SparseOptimizer::optimize(), g2o::CommandArgs::param(), g2o::CommandArgs::paramLeftOver(), g2o::CommandArgs::parseArgs(), g2o::Gm2dlIO::readRobotLaser(), g2o::SE2::rotation(), g2o::OptimizableGraph::save(), g2o::BaseVertex< D, T >::setEstimate(), g2o::OptimizableGraph::Vertex::setFixed(), g2o::OptimizableGraph::Vertex::setId(), g2o::BaseEdge< D, E >::setInformation(), g2o::BaseEdge< D, E >::setMeasurement(), g2o::SparseOptimizer::setVerbose(), g2o::HyperGraph::Edge::setVertex(), g2o::VelocityMeasurement::setVl(), g2o::VelocityMeasurement::setVr(), g2o::MotionInformation::timeInterval, g2o::RobotData::timestamp(), g2o::SE2::toVector(), g2o::SE2::translation(), g2o::OdomAndLaserMotion::velocityMeasurement, g2o::VelocityMeasurement::vl(), and g2o::VelocityMeasurement::vr().

Variable Documentation

◆ linearSolution

Eigen::Vector2d linearSolution
static