1 |
|
|
/********************************************************************* |
2 |
|
|
* Software License Agreement (BSD License) |
3 |
|
|
* |
4 |
|
|
* Copyright (c) 2013, PAL Robotics, S.L. |
5 |
|
|
* All rights reserved. |
6 |
|
|
* |
7 |
|
|
* Redistribution and use in source and binary forms, with or without |
8 |
|
|
* modification, are permitted provided that the following conditions |
9 |
|
|
* are met: |
10 |
|
|
* |
11 |
|
|
* * Redistributions of source code must retain the above copyright |
12 |
|
|
* notice, this list of conditions and the following disclaimer. |
13 |
|
|
* * Redistributions in binary form must reproduce the above |
14 |
|
|
* copyright notice, this list of conditions and the following |
15 |
|
|
* disclaimer in the documentation and/or other materials provided |
16 |
|
|
* with the distribution. |
17 |
|
|
* * Neither the name of the PAL Robotics nor the names of its |
18 |
|
|
* contributors may be used to endorse or promote products derived |
19 |
|
|
* from this software without specific prior written permission. |
20 |
|
|
* |
21 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
22 |
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
23 |
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
24 |
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
25 |
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
26 |
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
27 |
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
28 |
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
29 |
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
30 |
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
31 |
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
32 |
|
|
* POSSIBILITY OF SUCH DAMAGE. |
33 |
|
|
*********************************************************************/ |
34 |
|
|
|
35 |
|
|
/* |
36 |
|
|
* Author: Luca Marchionni |
37 |
|
|
* Author: Bence Magyar |
38 |
|
|
* Author: Enrique Fernández |
39 |
|
|
* Author: Paul Mathieu |
40 |
|
|
*/ |
41 |
|
|
|
42 |
|
|
#include "odometry.h" |
43 |
|
|
|
44 |
|
|
#include <dynamic-graph/command-bind.h> |
45 |
|
|
#include <dynamic-graph/command-direct-getter.h> |
46 |
|
|
#include <dynamic-graph/command-direct-setter.h> |
47 |
|
|
#include <dynamic-graph/factory.h> |
48 |
|
|
|
49 |
|
|
#include <boost/bind.hpp> |
50 |
|
|
#include <cmath> |
51 |
|
|
|
52 |
|
|
namespace dynamicgraph { |
53 |
|
|
namespace details { |
54 |
|
|
namespace bacc = boost::accumulators; |
55 |
|
|
|
56 |
|
|
Odometry::Odometry(size_t velocity_rolling_window_size) |
57 |
|
|
: x_(0.0), |
58 |
|
|
y_(0.0), |
59 |
|
|
heading_(0.0), |
60 |
|
|
linear_(0.0), |
61 |
|
|
angular_(0.0), |
62 |
|
|
wheelSeparation_(0.0), |
63 |
|
|
left_wheelRadius_(0.0), |
64 |
|
|
right_wheelRadius_(0.0), |
65 |
|
|
left_wheel_old_pos_(0.0), |
66 |
|
|
right_wheel_old_pos_(0.0), |
67 |
|
|
velocity_rolling_window_size_(velocity_rolling_window_size), |
68 |
|
|
linear_acc_(RollingWindow::window_size = velocity_rolling_window_size), |
69 |
|
|
angular_acc_(RollingWindow::window_size = velocity_rolling_window_size), |
70 |
|
|
integrate_fun_(boost::bind(&Odometry::integrateExact, this, _1, _2)) {} |
71 |
|
|
|
72 |
|
|
void Odometry::init() { |
73 |
|
|
// Reset accumulators: |
74 |
|
|
resetAccumulators(); |
75 |
|
|
} |
76 |
|
|
|
77 |
|
|
bool Odometry::update(double left_pos, double right_pos, double dt) { |
78 |
|
|
/// Get current wheel joint positions: |
79 |
|
|
const double left_wheel_cur_pos = left_pos * left_wheelRadius_; |
80 |
|
|
const double right_wheel_cur_pos = right_pos * right_wheelRadius_; |
81 |
|
|
|
82 |
|
|
/// Estimate velocity of wheels using old and current position: |
83 |
|
|
const double left_wheel_est_vel = left_wheel_cur_pos - left_wheel_old_pos_; |
84 |
|
|
const double right_wheel_est_vel = right_wheel_cur_pos - right_wheel_old_pos_; |
85 |
|
|
|
86 |
|
|
/// Update old position with current: |
87 |
|
|
left_wheel_old_pos_ = left_wheel_cur_pos; |
88 |
|
|
right_wheel_old_pos_ = right_wheel_cur_pos; |
89 |
|
|
|
90 |
|
|
/// Compute linear and angular diff: |
91 |
|
|
const double linear = (right_wheel_est_vel + left_wheel_est_vel) * 0.5; |
92 |
|
|
const double angular = |
93 |
|
|
(right_wheel_est_vel - left_wheel_est_vel) / wheelSeparation_; |
94 |
|
|
|
95 |
|
|
/// Integrate odometry: |
96 |
|
|
integrate_fun_(linear, angular); |
97 |
|
|
|
98 |
|
|
/// We cannot estimate the speed with very small time intervals: |
99 |
|
|
if (dt < 0.0001) return false; // Interval too small to integrate with |
100 |
|
|
|
101 |
|
|
/// Estimate speeds using a rolling mean to filter them out: |
102 |
|
|
linear_acc_(linear / dt); |
103 |
|
|
angular_acc_(angular / dt); |
104 |
|
|
|
105 |
|
|
linear_ = bacc::rolling_mean(linear_acc_); |
106 |
|
|
angular_ = bacc::rolling_mean(angular_acc_); |
107 |
|
|
|
108 |
|
|
return true; |
109 |
|
|
} |
110 |
|
|
|
111 |
|
|
void Odometry::updateOpenLoop(double linear, double angular, double dt) { |
112 |
|
|
/// Save last linear and angular velocity: |
113 |
|
|
linear_ = linear; |
114 |
|
|
angular_ = angular; |
115 |
|
|
|
116 |
|
|
/// Integrate odometry: |
117 |
|
|
integrate_fun_(linear * dt, angular * dt); |
118 |
|
|
} |
119 |
|
|
|
120 |
|
|
void Odometry::setWheelParams(double wheelSeparation, double left_wheelRadius, |
121 |
|
|
double right_wheelRadius) { |
122 |
|
|
wheelSeparation_ = wheelSeparation; |
123 |
|
|
left_wheelRadius_ = left_wheelRadius; |
124 |
|
|
right_wheelRadius_ = right_wheelRadius; |
125 |
|
|
} |
126 |
|
|
|
127 |
|
|
void Odometry::setVelocityRollingWindowSize( |
128 |
|
|
size_t velocity_rolling_window_size) { |
129 |
|
|
velocity_rolling_window_size_ = velocity_rolling_window_size; |
130 |
|
|
|
131 |
|
|
resetAccumulators(); |
132 |
|
|
} |
133 |
|
|
|
134 |
|
|
void Odometry::integrateRungeKutta2(double linear, double angular) { |
135 |
|
|
const double direction = heading_ + angular * 0.5; |
136 |
|
|
|
137 |
|
|
/// Runge-Kutta 2nd order integration: |
138 |
|
|
x_ += linear * cos(direction); |
139 |
|
|
y_ += linear * sin(direction); |
140 |
|
|
heading_ += angular; |
141 |
|
|
} |
142 |
|
|
|
143 |
|
|
/** |
144 |
|
|
* \brief Other possible integration method provided by the class |
145 |
|
|
* \param linear |
146 |
|
|
* \param angular |
147 |
|
|
*/ |
148 |
|
|
void Odometry::integrateExact(double linear, double angular) { |
149 |
|
|
if (fabs(angular) < 1e-6) |
150 |
|
|
integrateRungeKutta2(linear, angular); |
151 |
|
|
else { |
152 |
|
|
/// Exact integration (should solve problems when angular is zero): |
153 |
|
|
const double heading_old = heading_; |
154 |
|
|
const double r = linear / angular; |
155 |
|
|
heading_ += angular; |
156 |
|
|
x_ += r * (sin(heading_) - sin(heading_old)); |
157 |
|
|
y_ += -r * (cos(heading_) - cos(heading_old)); |
158 |
|
|
} |
159 |
|
|
} |
160 |
|
|
|
161 |
|
|
void Odometry::resetAccumulators() { |
162 |
|
|
linear_acc_ = RollingMeanAcc(RollingWindow::window_size = |
163 |
|
|
velocity_rolling_window_size_); |
164 |
|
|
angular_acc_ = RollingMeanAcc(RollingWindow::window_size = |
165 |
|
|
velocity_rolling_window_size_); |
166 |
|
|
} |
167 |
|
|
|
168 |
|
|
} // namespace details |
169 |
|
|
|
170 |
|
|
namespace bacc = boost::accumulators; |
171 |
|
|
|
172 |
|
|
Odometry::Odometry(const std::string& name) |
173 |
|
|
: Entity(name), |
174 |
|
|
wheelsPositionSIN( |
175 |
|
|
NULL, "Odometry(" + name + ")::input(vector)::wheelsPosition"), |
176 |
|
|
baseVelocityEstimationSOUT( |
177 |
|
|
boost::bind(&Odometry::computeVelocityEstimation, this, _1, _2), |
178 |
|
|
wheelsPositionSIN, |
179 |
|
|
"Odometry(" + name + ")::output(vector)::baseVelocityEstimation"), |
180 |
|
|
baseVelocityFilteredEstimationSOUT( |
181 |
|
|
boost::bind(&Odometry::computeVelocityFilteredEstimation, this, _1, |
182 |
|
|
_2), |
183 |
|
|
baseVelocityEstimationSOUT, |
184 |
|
|
"Odometry(" + name + |
185 |
|
|
")::output(vector)::baseVelocityFilteredEstimation"), |
186 |
|
|
baseVelocitySIN(NULL, |
187 |
|
|
"Odometry(" + name + ")::input(vector)::baseVelocity"), |
188 |
|
|
baseConfigSOUT(boost::bind(&Odometry::computeBaseConfig, this, _1, _2), |
189 |
|
|
baseVelocitySIN, |
190 |
|
|
"Odometry(" + name + ")::output(vector)::baseConfig"), |
191 |
|
|
basePoseSOUT( |
192 |
|
|
boost::bind(&Odometry::computeBasePose, this, _1, _2), baseConfigSOUT, |
193 |
|
|
"Odometry(" + name + ")::output(matrixHomogeneous)::basePose") |
194 |
|
|
|
195 |
|
|
, |
196 |
|
|
dt_(1.), |
197 |
|
|
x_(0.0), |
198 |
|
|
y_(0.0), |
199 |
|
|
heading_(0.0), |
200 |
|
|
wheelSeparationInv_(0.0), |
201 |
|
|
wheelRadii_(0.0, 0.0), |
202 |
|
|
wheelOldPos_(0.0, 0.0), |
203 |
|
|
rollingWindowSize_(10), |
204 |
|
|
linear_acc_(RollingWindow::window_size = rollingWindowSize_), |
205 |
|
|
angular_acc_(RollingWindow::window_size = rollingWindowSize_) { |
206 |
|
|
signalRegistration(wheelsPositionSIN << baseVelocityEstimationSOUT |
207 |
|
|
<< baseVelocityFilteredEstimationSOUT |
208 |
|
|
<< baseVelocitySIN << baseConfigSOUT |
209 |
|
|
<< basePoseSOUT); |
210 |
|
|
|
211 |
|
|
// By default, use the velocity estimation |
212 |
|
|
// (i.e. loop closed with wheel position sensors). |
213 |
|
|
baseVelocitySIN.plug(&baseVelocityEstimationSOUT); |
214 |
|
|
|
215 |
|
|
addCommand("setWheelParams", |
216 |
|
|
command::makeCommandVoid3( |
217 |
|
|
*this, &Odometry::setWheelParams, |
218 |
|
|
command::docCommandVoid3( |
219 |
|
|
"Sets the wheel parameters: radius and separation", |
220 |
|
|
"double: Separation between left and right wheels [m]", |
221 |
|
|
"double: Left wheel radius [m]", |
222 |
|
|
"double: Right wheel radius [m]"))); |
223 |
|
|
addCommand("setBasePose", |
224 |
|
|
command::makeCommandVoid3( |
225 |
|
|
*this, &Odometry::setBasePose, |
226 |
|
|
command::docCommandVoid3( |
227 |
|
|
"Set the base pose to integrate from.", "double: x [m]", |
228 |
|
|
"double: y [m]", "double: heading [rad]"))); |
229 |
|
|
addCommand("setPeriod", |
230 |
|
|
command::makeDirectSetter(*this, &dt_, |
231 |
|
|
"Set period (only affects the velocity " |
232 |
|
|
"estimation and nothing else).")); |
233 |
|
|
addCommand("getPeriod", command::makeDirectGetter(*this, &dt_, "the period")); |
234 |
|
|
} |
235 |
|
|
|
236 |
|
|
Vector& Odometry::computeVelocityEstimation(Vector& vel, int time) { |
237 |
|
|
vel.resize(2); |
238 |
|
|
const Vector& wheelCurAng = wheelsPositionSIN(time); |
239 |
|
|
|
240 |
|
|
/// Get current wheel joint positions: |
241 |
|
|
Eigen::Array2d wheelCurPos = wheelCurAng.array() * wheelRadii_; |
242 |
|
|
|
243 |
|
|
/// Estimate velocity of wheels using old and current position: |
244 |
|
|
Eigen::Array2d wheelEstVel = (wheelCurPos - wheelOldPos_) / dt_; |
245 |
|
|
|
246 |
|
|
/// Update old position with current: |
247 |
|
|
wheelOldPos_ = wheelCurPos; |
248 |
|
|
|
249 |
|
|
/// Compute linear and angular diff: |
250 |
|
|
vel[0] = (wheelEstVel[1] + wheelEstVel[0]) * 0.5; |
251 |
|
|
vel[1] = (wheelEstVel[1] - wheelEstVel[0]) * wheelSeparationInv_; |
252 |
|
|
|
253 |
|
|
/// Estimate speeds using a rolling mean to filter them out: |
254 |
|
|
linear_acc_(vel[0]); |
255 |
|
|
angular_acc_(vel[1]); |
256 |
|
|
|
257 |
|
|
return vel; |
258 |
|
|
} |
259 |
|
|
|
260 |
|
|
Vector& Odometry::computeVelocityFilteredEstimation(Vector& vel, int time) { |
261 |
|
|
baseVelocityEstimationSOUT.recompute(time); |
262 |
|
|
|
263 |
|
|
vel.resize(2); |
264 |
|
|
vel[0] = bacc::rolling_mean(linear_acc_); |
265 |
|
|
vel[1] = bacc::rolling_mean(angular_acc_); |
266 |
|
|
|
267 |
|
|
return vel; |
268 |
|
|
} |
269 |
|
|
|
270 |
|
|
Vector& Odometry::computeBaseConfig(Vector& base, int time) { |
271 |
|
|
base.resize(3); |
272 |
|
|
const Vector& vel = baseVelocitySIN(time); |
273 |
|
|
integrateExact(vel[0] * dt_, vel[1] * dt_); |
274 |
|
|
|
275 |
|
|
base << x_, y_, heading_; |
276 |
|
|
return base; |
277 |
|
|
} |
278 |
|
|
|
279 |
|
|
sot::MatrixHomogeneous& Odometry::computeBasePose(sot::MatrixHomogeneous& M, |
280 |
|
|
int time) { |
281 |
|
|
const Vector& base = baseConfigSOUT(time); |
282 |
|
|
M.setIdentity(); |
283 |
|
|
M.translation().head<2>() = base.head<2>(); |
284 |
|
|
double c = cos(base(2)), s = sin(base(2)); |
285 |
|
|
M.linear().topLeftCorner<2, 2>() << c, -s, s, c; |
286 |
|
|
return M; |
287 |
|
|
} |
288 |
|
|
|
289 |
|
|
void Odometry::setBasePose(const double& x, const double& y, |
290 |
|
|
const double& heading) { |
291 |
|
|
x_ = x; |
292 |
|
|
y_ = y; |
293 |
|
|
heading_ = heading; |
294 |
|
|
|
295 |
|
|
resetAccumulators(); |
296 |
|
|
} |
297 |
|
|
|
298 |
|
|
void Odometry::setWheelParams(const double& wheelSeparation, |
299 |
|
|
const double& leftWheelRadius, |
300 |
|
|
const double& rightWheelRadius) { |
301 |
|
|
wheelSeparationInv_ = 1 / wheelSeparation; |
302 |
|
|
wheelRadii_ << leftWheelRadius, rightWheelRadius; |
303 |
|
|
} |
304 |
|
|
|
305 |
|
|
void Odometry::setVelocityRollingWindowSize(const size_t& rollingWindowSize) { |
306 |
|
|
rollingWindowSize_ = rollingWindowSize; |
307 |
|
|
resetAccumulators(); |
308 |
|
|
} |
309 |
|
|
|
310 |
|
|
void Odometry::integrateRungeKutta2(double linear, double angular) { |
311 |
|
|
const double direction = heading_ + angular * 0.5; |
312 |
|
|
|
313 |
|
|
/// Runge-Kutta 2nd order integration: |
314 |
|
|
x_ += linear * cos(direction); |
315 |
|
|
y_ += linear * sin(direction); |
316 |
|
|
heading_ += angular; |
317 |
|
|
} |
318 |
|
|
|
319 |
|
|
/** |
320 |
|
|
* \brief Other possible integration method provided by the class |
321 |
|
|
* \param linear |
322 |
|
|
* \param angular |
323 |
|
|
*/ |
324 |
|
|
void Odometry::integrateExact(double linear, double angular) { |
325 |
|
|
if (fabs(angular) < 1e-6) |
326 |
|
|
integrateRungeKutta2(linear, angular); |
327 |
|
|
else { |
328 |
|
|
/// Exact integration (should solve problems when angular is zero): |
329 |
|
|
const double heading_old = heading_; |
330 |
|
|
const double r = linear / angular; |
331 |
|
|
heading_ += angular; |
332 |
|
|
x_ += r * (sin(heading_) - sin(heading_old)); |
333 |
|
|
y_ += -r * (cos(heading_) - cos(heading_old)); |
334 |
|
|
} |
335 |
|
|
} |
336 |
|
|
|
337 |
|
|
void Odometry::resetAccumulators() { |
338 |
|
|
linear_acc_ = RollingMeanAcc(RollingWindow::window_size = rollingWindowSize_); |
339 |
|
|
angular_acc_ = |
340 |
|
|
RollingMeanAcc(RollingWindow::window_size = rollingWindowSize_); |
341 |
|
|
} |
342 |
|
|
|
343 |
|
|
DYNAMICGRAPH_FACTORY_ENTITY_PLUGIN(Odometry, "Odometry"); |
344 |
|
|
|
345 |
|
|
} // namespace dynamicgraph |