<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Visual-Inertial Odometry | Saikiran Juttu | Robotics Portfolio</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/visual-inertial-odometry/</link><atom:link href="https://juttu-s.github.io/saikiran_juttu.github.io/tags/visual-inertial-odometry/index.xml" rel="self" type="application/rss+xml"/><description>Visual-Inertial Odometry</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Fri, 26 Apr 2024 00:00:00 +0000</lastBuildDate><image><url>https://juttu-s.github.io/saikiran_juttu.github.io/media/icon_hu7729264130191091259.png</url><title>Visual-Inertial Odometry</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/visual-inertial-odometry/</link></image><item><title>Kimera VIO on EuRoC and Custom Datasets</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/project/kimera-vio/</link><pubDate>Fri, 26 Apr 2024 00:00:00 +0000</pubDate><guid>https://juttu-s.github.io/saikiran_juttu.github.io/project/kimera-vio/</guid><description>&lt;p>An evaluation and deployment project rather than an algorithms one. &lt;a href="https://github.com/MIT-SPARK/Kimera-VIO" target="_blank" rel="noopener">Kimera-VIO&lt;/a> from MIT-SPARK was used as shipped; the work here was getting it running reproducibly, reproducing its published benchmark on EuRoC, and then pointing it at a sequence it had never seen — recorded on an Intel RealSense D455 in an ordinary office — to find out what breaks when you leave the benchmark behind.&lt;/p>
&lt;hr>
&lt;h2 id="what-kimera-vio-is-doing">What Kimera-VIO is doing&lt;/h2>
&lt;p>Worth stating, because the interesting failure later only makes sense against it.&lt;/p>
&lt;p>&lt;strong>Front end.&lt;/strong> Shi-Tomasi corners on the left stereo image, tracked temporally with Lucas–Kanade optical flow, matched left-to-right and triangulated for depth, with outlier rejection on the resulting correspondences. IMU measurements between keyframes are preintegrated into a single relative-motion constraint and used for gravity alignment.&lt;/p>
&lt;p>&lt;strong>Back end.&lt;/strong> A factor graph over position, orientation and velocity, with IMU factors and visual reprojection factors as edges, optimised incrementally by &lt;strong>iSAM2&lt;/strong> in GTSAM. Robust losses (Huber, Cauchy) absorb the outliers that survive the front end. Output is a pose trajectory, velocity estimates and a sparse landmark map.&lt;/p>
&lt;p>&lt;strong>Loop closure.&lt;/strong> Kimera-RPGO detects loops and runs robust pose-graph optimisation over them to produce a globally consistent trajectory.&lt;/p>
&lt;h2 id="reproducibility">Reproducibility&lt;/h2>
&lt;p>The deliverable that mattered most was a &lt;strong>saved Docker image&lt;/strong> with Kimera-VIO, ROS and every dependency preinstalled — distributed as a tarball so the whole pipeline comes up in three commands:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker load -i kimera-vio.tar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker run -it kimera-vio
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python3 visual.py data_pgo.csv &lt;span class="c1"># inside the container&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Anyone who has built a VIO stack from source knows why this is worth doing. Kimera-VIO wants a specific GTSAM, which wants a specific Boost and Eigen, and the failure mode of getting it wrong is a pipeline that compiles and then silently estimates garbage. Pinning the whole environment means a result someone else can actually reproduce, and it makes &amp;ldquo;does this parameter change help?&amp;rdquo; a question you can answer in an afternoon instead of a week.&lt;/p>
&lt;p>Stereo image pairs and IMU streams were synchronised and fed in through ROS.&lt;/p>
&lt;hr>
&lt;h2 id="euroc-benchmark">EuRoC benchmark&lt;/h2>
&lt;p>Run on the &lt;strong>EuRoC MAV&lt;/strong> Vicon Room 1 sequences — &lt;code>V1_01&lt;/code>, &lt;code>V1_02&lt;/code> and &lt;code>V1_03&lt;/code>, spanning easy, medium and difficult, with motion-capture ground truth. That&amp;rsquo;s three of the dataset&amp;rsquo;s eleven sequences; the Machine Hall and Vicon Room 2 sets weren&amp;rsquo;t covered.&lt;/p>
&lt;p>ATE RMSE in metres, reproducing the published comparison across VIO pipelines:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Sequence&lt;/th>
&lt;th>OKVIS&lt;/th>
&lt;th>MSCKF&lt;/th>
&lt;th>ROVIO&lt;/th>
&lt;th>VINS-Mono&lt;/th>
&lt;th>Kimera-VIO&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>V1_01&lt;/td>
&lt;td>0.09&lt;/td>
&lt;td>0.34&lt;/td>
&lt;td>0.10&lt;/td>
&lt;td>0.08&lt;/td>
&lt;td>&lt;strong>0.05&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>V1_02&lt;/td>
&lt;td>0.20&lt;/td>
&lt;td>0.20&lt;/td>
&lt;td>0.10&lt;/td>
&lt;td>0.11&lt;/td>
&lt;td>&lt;strong>0.08&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>V1_03&lt;/td>
&lt;td>0.24&lt;/td>
&lt;td>0.67&lt;/td>
&lt;td>0.14&lt;/td>
&lt;td>0.18&lt;/td>
&lt;td>&lt;strong>0.07&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The pattern worth noting is the third row. Every other pipeline degrades on &lt;code>V1_03&lt;/code> — the difficult sequence, with faster and more aggressive motion — while Kimera-VIO holds at 0.07 m. Aggressive motion is &lt;em>good&lt;/em> for a visual-inertial system: it excites the IMU, which is what makes scale and gravity direction observable in the first place. Getting parameters into the range where that benchmark reproduces meant tuning IMU noise models, feature thresholds and optimiser tolerances. Throughput came out around &lt;strong>25 FPS&lt;/strong>.&lt;/p>
&lt;h2 id="custom-dataset">Custom dataset&lt;/h2>
&lt;p>Recorded with a &lt;strong>RealSense D455&lt;/strong> in a room of desks, monitors and the usual office clutter, walking an approximately oval path back to the starting point.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="The post-optimisation trajectory recovering the oval path walked during recording" srcset="
/saikiran_juttu.github.io/project/kimera-vio/trajectory_hu18271531692901669461.webp 400w,
/saikiran_juttu.github.io/project/kimera-vio/trajectory_hu14690658229423064687.webp 760w,
/saikiran_juttu.github.io/project/kimera-vio/trajectory_hu16662803654171847119.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/kimera-vio/trajectory_hu18271531692901669461.webp"
width="760"
height="576"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Trajectory after pose-graph optimisation. The plot renders the path in the x–y plane, so vertical motion isn&amp;rsquo;t shown.&lt;/em>&lt;/p>
&lt;p>There is &lt;strong>no ground truth&lt;/strong> for this sequence — no motion capture in an office — so everything here is qualitative. No ATE, no drift percentage, and any number attached to it would be invented. What it does show is loop closure working: the path returns to where it started rather than spiralling away.&lt;/p>
&lt;h3 id="starting-at-rest">Starting at rest&lt;/h3>
&lt;p>The most useful thing this sequence taught me was a failure at the very beginning of the recording, and it isn&amp;rsquo;t a bug — it&amp;rsquo;s a property of the problem.&lt;/p>
&lt;p>Visual-inertial odometry recovers metric scale and gravity direction from the accelerometer. But an accelerometer at rest measures only gravity, and there is no way to separate &amp;ldquo;which way is down&amp;rdquo; from &amp;ldquo;how fast am I accelerating&amp;rdquo; without motion to disambiguate them. Start recording with the camera stationary and the initialiser has no excitation to work with; it converges on a poor estimate, and that error is baked into every pose that follows.&lt;/p>
&lt;p>That&amp;rsquo;s exactly what happened — visible drift accumulated from the start of the trajectory, and it persisted until loop closure corrected it. It also explains the EuRoC pattern above: the &amp;ldquo;difficult&amp;rdquo; sequence is easier for VIO in this one specific respect. The practical lesson is that a data-collection protocol matters as much as a config file: move the camera through a few seconds of varied motion &lt;strong>before&lt;/strong> anything you care about, and the whole run improves.&lt;/p>
&lt;p>Elsewhere in the sequence, low-texture regions produced feature sparsity and minor drift, which pose-graph optimisation largely absorbed.&lt;/p>
&lt;h2 id="limits-and-next-steps">Limits and next steps&lt;/h2>
&lt;p>Kimera-VIO&amp;rsquo;s weak points on this data were the ones its authors document: drift in low-texture regions, and sensitivity to outlier visual measurements. The obvious extensions, in order of how much they&amp;rsquo;d change the picture:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Metric-semantic mapping.&lt;/strong> This used the VIO half of the Kimera suite only. Kimera-Semantics builds a semantically annotated mesh from the same pipeline, which is where the interesting downstream work is.&lt;/li>
&lt;li>&lt;strong>Outdoor and larger scale&lt;/strong>, to see where the factor graph stops being tractable.&lt;/li>
&lt;li>&lt;strong>LiDAR as an additional modality&lt;/strong>, for the feature-poor cases where vision alone degrades.&lt;/li>
&lt;/ul>
&lt;h2 id="stack">Stack&lt;/h2>
&lt;p>Kimera-VIO (MIT-SPARK), GTSAM, ROS, Docker, Python, matplotlib. Datasets: EuRoC MAV V1_01–V1_03; self-collected RealSense D455 stereo + IMU.&lt;/p></description></item></channel></rss>