<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dead Reckoning | Saikiran Juttu | Robotics Portfolio</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/dead-reckoning/</link><atom:link href="https://juttu-s.github.io/saikiran_juttu.github.io/tags/dead-reckoning/index.xml" rel="self" type="application/rss+xml"/><description>Dead Reckoning</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Tue, 09 Apr 2024 00:00:00 +0000</lastBuildDate><image><url>https://juttu-s.github.io/saikiran_juttu.github.io/media/icon_hu7729264130191091259.png</url><title>Dead Reckoning</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/dead-reckoning/</link></image><item><title>Dead Reckoning with IMU &amp; GPS in Vehicle Navigation</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/project/dead-reckoning/</link><pubDate>Tue, 09 Apr 2024 00:00:00 +0000</pubDate><guid>https://juttu-s.github.io/saikiran_juttu.github.io/project/dead-reckoning/</guid><description>&lt;p>Take away the GPS fix and a vehicle has to work out where it is from acceleration and rotation alone. This project does that end to end on real data — instrument a car, calibrate the magnetometer properly, fuse a heading estimate, integrate forward velocity, and reconstruct the trajectory — then compares the result against GPS to find out how long dead reckoning actually survives.&lt;/p>
&lt;p>The short answer: &lt;strong>about three minutes.&lt;/strong> After that it is off by the length of a city block.&lt;/p>
&lt;p>Coursework for &lt;strong>EECE5554: Robotics Sensing and Navigation&lt;/strong> at Northeastern.&lt;/p>
&lt;hr>
&lt;h2 id="setup">Setup&lt;/h2>
&lt;p>A &lt;strong>VectorNav VN-100&lt;/strong> IMU mounted inside the vehicle in a custom holder, deliberately aligned so its &lt;strong>x-axis points along the vehicle&amp;rsquo;s forward direction&lt;/strong> and the unit sits level. That alignment is doing real work later — it&amp;rsquo;s what lets &lt;code>accel_x&lt;/code> be treated as forward acceleration and &lt;code>accel_y&lt;/code> as lateral without a rotation. A magnetic-mount GPS puck went on the roof, both feeding one laptop over ROS.&lt;/p>
&lt;p>Two datasets:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Circles&lt;/strong> — five laps of the Ruggles station circle, recorded purely to excite the magnetometer through a full 360° of heading. This is calibration data, not navigation data.&lt;/li>
&lt;li>&lt;strong>Driving&lt;/strong> — a 2–3 km route around Boston with at least ten turns, roughly 1300 s, routed to avoid tunnels and underpasses so the GPS reference stays continuous.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="magnetometer-calibration">Magnetometer calibration&lt;/h2>
&lt;p>An uncalibrated magnetometer driven in a circle should trace a circle. It doesn&amp;rsquo;t, and the two ways it fails are visually distinguishable from the same scatter plot:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Magnetometer X-Y scatter before and after calibration — the measured ring offset from the origin, the calibrated ring centred on it" srcset="
/saikiran_juttu.github.io/project/dead-reckoning/magnetometer-calibration_hu6564607695943048432.webp 400w,
/saikiran_juttu.github.io/project/dead-reckoning/magnetometer-calibration_hu4157646788420770913.webp 760w,
/saikiran_juttu.github.io/project/dead-reckoning/magnetometer-calibration_hu17823870522234568360.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/dead-reckoning/magnetometer-calibration_hu6564607695943048432.webp"
width="760"
height="573"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Five laps of the Ruggles circle. Orange is raw, blue is corrected.&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>The ring is off-centre&lt;/strong> → &lt;strong>hard iron&lt;/strong> distortion. A constant field offset from permanent magnets, ferrous structure or current-carrying conductors near the sensor. The raw ring here sits up and to the right of the origin, biased by roughly 0.27 G in x and 0.22 G in y. Correction is a per-axis bias subtraction that recentres the cluster.&lt;/li>
&lt;li>&lt;strong>The ring is deformed&lt;/strong> → &lt;strong>soft iron&lt;/strong> distortion. Non-uniform scaling and rotation from nearby ferrous material. You can see it in both rings as a distinct kink near the +x side — the shape isn&amp;rsquo;t circular and no amount of translation fixes that. Correction is a matrix that scales and rotates the data back toward a circle.&lt;/li>
&lt;/ul>
&lt;p>The parameters come from a least-squares ellipse fit to the circle-driving data, applied as bias subtraction followed by the correction matrix. Being able to read &lt;em>which&lt;/em> distortion you have off the plot before fixing either is the useful part of this exercise — the two have different causes and different remedies, and the geometry tells you which you&amp;rsquo;re looking at.&lt;/p>
&lt;h2 id="heading">Heading&lt;/h2>
&lt;p>Neither heading source is usable alone. The magnetometer gives absolute heading but is noisy and sensitive to the vehicle&amp;rsquo;s own electrical and magnetic environment. The gyro is clean over short intervals but integrates its bias without bound — the raw integrated yaw here climbs to about &lt;strong>+405°&lt;/strong> by t ≈ 560 s and plateaus near 300°, which is meaningless as an absolute heading.&lt;/p>
&lt;p>A complementary filter takes the half of each signal that&amp;rsquo;s trustworthy:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>0.5 Hz low-pass&lt;/strong> on magnetometer yaw — keeps the slow-moving absolute reference, discards the noise&lt;/li>
&lt;li>&lt;strong>0.1 Hz high-pass&lt;/strong> on gyro yaw — keeps short-term rate fidelity, discards the drift&lt;/li>
&lt;li>blended with coefficient &lt;strong>0.98&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Four-panel comparison: low-passed magnetometer yaw, high-passed gyro yaw, complementary filter output, and the VN-100&amp;rsquo;s own onboard heading estimate" srcset="
/saikiran_juttu.github.io/project/dead-reckoning/heading-fusion_hu16052623581733354419.webp 400w,
/saikiran_juttu.github.io/project/dead-reckoning/heading-fusion_hu9034352427183490476.webp 760w,
/saikiran_juttu.github.io/project/dead-reckoning/heading-fusion_hu864362406907695216.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/dead-reckoning/heading-fusion_hu16052623581733354419.webp"
width="760"
height="372"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>The two filtered branches, the fused output, and the VN-100&amp;rsquo;s internal estimate for comparison.&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="forward-velocity-and-the-number-that-gave-the-game-away">Forward velocity, and the number that gave the game away&lt;/h2>
&lt;p>Integrating raw accelerometer output produces nonsense. The raw forward-velocity trace peaks around &lt;strong>+35 m/s&lt;/strong> and dips to &lt;strong>−15 m/s&lt;/strong> — the car did neither. That&amp;rsquo;s accelerometer bias being integrated into an unbounded ramp, plus road vibration and the gravity component that any unnoticed slope injects into the forward axis.&lt;/p>
&lt;p>Two corrections bring it into physical range: a high-pass filter on forward acceleration to strip the low-frequency bias, then clamps — negative velocities to zero, plus an upper bound. That gets the adjusted trace to a plausible &lt;strong>0–11 m/s&lt;/strong>, against GPS-derived velocity peaking at 12.7 m/s.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Raw integrated forward velocity against the filtered and clamped version" srcset="
/saikiran_juttu.github.io/project/dead-reckoning/forward-velocity_hu6766549600153078957.webp 400w,
/saikiran_juttu.github.io/project/dead-reckoning/forward-velocity_hu2268536860800196880.webp 760w,
/saikiran_juttu.github.io/project/dead-reckoning/forward-velocity_hu229241971526026250.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/dead-reckoning/forward-velocity_hu6766549600153078957.webp"
width="760"
height="398"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Blue: integrate the raw signal and the car reaches 35 m/s and reverses. Orange: after high-pass filtering and clamping.&lt;/em>&lt;/p>
&lt;p>Then aligning the dead-reckoned track to GPS required scaling the IMU distances by &lt;strong>2.25×&lt;/strong>.&lt;/p>
&lt;p>That factor is the most informative number in the whole project, and it isn&amp;rsquo;t a calibration constant — it&amp;rsquo;s a symptom. A 2.25× scale error means the integrated velocity was systematically &lt;strong>less than half&lt;/strong> the true speed, and both corrections above are implicated. The high-pass filter that removed accelerometer bias also removed genuine low-frequency acceleration, because a car accelerating gently from a light and a slowly-drifting bias occupy the same part of the spectrum. And clamping negative velocity to zero doesn&amp;rsquo;t just discard drift — it &lt;strong>rectifies&lt;/strong> the signal, converting every real deceleration the drifting integrator pushed below zero into &amp;ldquo;stationary&amp;rdquo;. Distance travelled comes out short in both cases.&lt;/p>
&lt;p>&lt;strong>The filter that fixed the drift destroyed the scale.&lt;/strong> Getting both would need the bias estimated as a state rather than filtered out — which is the argument for a Kalman filter over hand-tuned Butterworths, and the direction I&amp;rsquo;d take this next.&lt;/p>
&lt;h2 id="trajectory">Trajectory&lt;/h2>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Dead-reckoned trajectory beside the GPS ground track, both in Easting/Northing metres" srcset="
/saikiran_juttu.github.io/project/dead-reckoning/trajectories_hu12573546119952805973.webp 400w,
/saikiran_juttu.github.io/project/dead-reckoning/trajectories_hu11079378006867108752.webp 760w,
/saikiran_juttu.github.io/project/dead-reckoning/trajectories_hu14489892853533858604.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/dead-reckoning/trajectories_hu12573546119952805973.webp"
width="760"
height="347"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Dead-reckoned track (left) against GPS (right). The shape survives; the extent doesn&amp;rsquo;t.&lt;/em>&lt;/p>
&lt;p>Projecting forward velocity onto the fused heading and integrating gives the track above, aligned to GPS by matching start coordinates and the first straight segment.&lt;/p>
&lt;ul>
&lt;li>The two agree to within about &lt;strong>2 m for the first 150–200 s&lt;/strong>&lt;/li>
&lt;li>After that they diverge, reaching roughly &lt;strong>400 m&lt;/strong> of displacement error by the end of the ~2–3 km route&lt;/li>
&lt;/ul>
&lt;p>Dead reckoning worked best exactly where you&amp;rsquo;d expect — early, on straight, level road. It degraded with each successive turn, and the two causes are &lt;strong>gyro bias instability&lt;/strong> feeding heading error into every subsequent position update, and &lt;strong>unanticipated road slopes&lt;/strong> tilting gravity onto the forward axis where it gets integrated as acceleration.&lt;/p>
&lt;p>One honest wrinkle in the alignment: the IMU track was rotated 14° and the GPS track 25°, in opposite service of making the first straight segments agree. Rotating &lt;em>both&lt;/em> by &lt;em>different&lt;/em> amounts means the result no longer references true north — a single rotation of the difference would have been the defensible choice.&lt;/p>
&lt;h2 id="the-takeaway">The takeaway&lt;/h2>
&lt;p>There&amp;rsquo;s a usable operating envelope here and it&amp;rsquo;s narrow. Roughly &lt;strong>150–200 seconds of GPS outage&lt;/strong> is survivable at 2 m accuracy, which is enough to cross a tunnel or an urban canyon. It is nowhere near enough to navigate on, and the failure is not graceful — error grows superlinearly as heading error compounds. Any real system needs periodic absolute fixes, and the interesting engineering question is how sparse those fixes can be, not whether you need them.&lt;/p>
&lt;h2 id="stack">Stack&lt;/h2>
&lt;p>ROS (rosbag, sensor nodes), Python, SciPy (&lt;code>butter&lt;/code>/&lt;code>filtfilt&lt;/code>, least-squares fitting), matplotlib, UTM projection. Hardware: VectorNav VN-100, magnetic-mount GPS puck, Northeastern&amp;rsquo;s instrumented &amp;ldquo;nuance&amp;rdquo; vehicle.&lt;/p></description></item></channel></rss>