<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Semantic SLAM | Saikiran Juttu | Robotics Portfolio</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/semantic-slam/</link><atom:link href="https://juttu-s.github.io/saikiran_juttu.github.io/tags/semantic-slam/index.xml" rel="self" type="application/rss+xml"/><description>Semantic SLAM</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Fri, 19 Apr 2024 00:00:00 +0000</lastBuildDate><image><url>https://juttu-s.github.io/saikiran_juttu.github.io/media/icon_hu7729264130191091259.png</url><title>Semantic SLAM</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/semantic-slam/</link></image><item><title>Semantic Geometric SLAM (SG-SLAM) in Dynamic Scenes</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/project/sg-slam/</link><pubDate>Fri, 19 Apr 2024 00:00:00 +0000</pubDate><guid>https://juttu-s.github.io/saikiran_juttu.github.io/project/sg-slam/</guid><description>&lt;p>Feature-based visual SLAM assumes the world holds still. It doesn&amp;rsquo;t. When someone walks through the frame, their features get matched across keyframes like any other, and the optimiser dutifully fits a camera trajectory to a person who was never part of the scene geometry. On the TUM &lt;code>fr3/walking_*&lt;/code> sequences this is not a marginal degradation — ORB-SLAM2&amp;rsquo;s absolute trajectory error goes to roughly half a metre.&lt;/p>
&lt;p>This project reimplements &lt;strong>SG-SLAM&lt;/strong> (&lt;a href="https://ieeexplore.ieee.org/document/10018238" target="_blank" rel="noopener">Cheng, Sun, Zhang &amp;amp; Zhang, &lt;em>IEEE TIM&lt;/em> vol. 72, 2023&lt;/a>), which addresses this by adding two parallel threads to ORB-SLAM2 — one for object detection, one for semantic mapping — and a feature-rejection stage in the tracking thread that consults both geometry and semantics before deciding what to keep.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="SG-SLAM running on a live RGB-D stream: ORB features tracked on the static scene while a person walks through the frame, the keyframe graph, and the semantically labelled point cloud in RViz" srcset="
/saikiran_juttu.github.io/project/sg-slam/system_hu9625658651780549619.webp 400w,
/saikiran_juttu.github.io/project/sg-slam/system_hu10093939004686469362.webp 760w,
/saikiran_juttu.github.io/project/sg-slam/system_hu15078296984376168334.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/sg-slam/system_hu9625658651780549619.webp"
width="760"
height="422"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>The three threads running together. Left: the live frame — note the green ORB features sit on desks, walls and shelving, not on the person. Bottom: the reconstructed point cloud with detected objects labelled and localised.&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="why-not-just-mask-out-the-people">Why not just mask out the people&lt;/h2>
&lt;p>The obvious approach is to run a detector, draw boxes around every &lt;em>a priori&lt;/em> dynamic class, and discard whatever falls inside. That fails in both directions, and SG-SLAM&amp;rsquo;s design is a direct response to each failure:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Things move that aren&amp;rsquo;t in a box.&lt;/strong> A detector trained on 20 classes will miss a swinging door, a rolling cart, a shadow. Masking gives you no protection outside the boxes it drew.&lt;/li>
&lt;li>&lt;strong>Things in boxes aren&amp;rsquo;t always moving.&lt;/strong> A parked chair, an empty sofa, a person sitting perfectly still — all get classified dynamic, all get thrown away. In a cluttered indoor scene that can mean discarding the best-textured features you have.&lt;/li>
&lt;/ul>
&lt;h2 id="the-rejection-criterion">The rejection criterion&lt;/h2>
&lt;p>The mechanism that avoids both is worth reading closely, because it is subtler than a mask. Reproduced from the paper&amp;rsquo;s Algorithm 1:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Input: Previous frame F1, current frame F2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Feature points P1 (previous), P2 (current)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Standard empirical threshold e_std
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Output: Set S of static feature points in the current frame
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1: P1 = CalcOpticalFlowPyrLK(F2, F1, P2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2: Remove matched pairs at image edges or with large appearance variation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3: F = FindFundamentalMat(P2, P1, 7-point method with RANSAC)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 4: for each matched pair (p1, p2) in (P1, P2) do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 5: if DynamicObjectsExist and IsInDynamicRegion(p2) then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 6: if CalcEpiLineDistance(p2, p1, F) * GetDynamicWeightValue(p2) &amp;lt; e_std then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 7: append p2 to S
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 8: end if
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 9: else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">10: if CalcEpiLineDistance(p2, p1, F) &amp;lt; e_std then
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">11: append p2 to S
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">12: end if
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">13: end if
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">14: end for
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There is &lt;strong>one&lt;/strong> threshold, &lt;code>e_std&lt;/code>, and it is compared against a point-to-epipolar-line distance in both branches. The difference is that inside a detected region (line 6) that distance is first &lt;strong>multiplied&lt;/strong> by the object class&amp;rsquo;s dynamic weight.&lt;/p>
&lt;p>So semantics act as a per-class multiplier on the geometric residual, not as a veto. A high-weight class like a person has its residual inflated, so it gets rejected on much weaker geometric evidence than a low-weight class like a chair. But a genuinely stationary object still passes — its epipolar distance is near zero, and scaling near-zero by three is still near-zero. Meanwhile line 10 runs everywhere the detector saw nothing, so unmodelled motion is still caught geometrically.&lt;/p>
&lt;p>Two implementation details that are easy to miss: the optical flow at line 1 runs &lt;strong>backwards&lt;/strong>, tracking the current frame&amp;rsquo;s points into the previous frame to reconstruct correspondences, and the fundamental matrix uses the &lt;strong>seven-point&lt;/strong> method inside RANSAC rather than the more common normalised eight-point variant.&lt;/p>
&lt;h2 id="detector">Detector&lt;/h2>
&lt;p>Detection runs an &lt;strong>SSD with a MobileNetV3 backbone under NCNN&lt;/strong>, Tencent&amp;rsquo;s mobile CPU inference framework. That choice is what makes the real-time claim plausible without a GPU in the loop — the whole point of SG-SLAM over heavier semantic-SLAM systems is that it targets mobile platforms. The tracking thread computes its geometric quantities, blocks on the detector&amp;rsquo;s 2-D result for that frame, then runs rejection and tracks; only surviving features reach local mapping, loop closing and full bundle adjustment, all of which are unmodified ORB-SLAM2.&lt;/p>
&lt;div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;align-items:start">
&lt;div>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Detection overlay with bounding boxes and class confidences" srcset="
/saikiran_juttu.github.io/project/sg-slam/detection_hu9192830305904395551.webp 400w,
/saikiran_juttu.github.io/project/sg-slam/detection_hu10414967818364573976.webp 760w,
/saikiran_juttu.github.io/project/sg-slam/detection_hu7677053416326043211.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/sg-slam/detection_hu9192830305904395551.webp"
width="368"
height="226"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Detection on a live frame.&lt;/em>&lt;/p>
&lt;/div>
&lt;div>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Semantic object map with per-object 3D coordinates in RViz" srcset="
/saikiran_juttu.github.io/project/sg-slam/semantic-map_hu10489737656378413939.webp 400w,
/saikiran_juttu.github.io/project/sg-slam/semantic-map_hu13843709679552535452.webp 760w,
/saikiran_juttu.github.io/project/sg-slam/semantic-map_hu13383796692928516754.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/sg-slam/semantic-map_hu10489737656378413939.webp"
width="368"
height="233"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Objects localised in 3-D with class labels.&lt;/em>&lt;/p>
&lt;/div>
&lt;/div>
&lt;p>The semantic mapping thread fuses the 2-D detections with per-keyframe point clouds generated from the depth images and camera poses, then extracts each object&amp;rsquo;s position and extent into a 3-D semantic object database. That database, a global OctoMap and the camera poses are all published over ROS for RViz — which is the real difference from plain ORB-SLAM2, whose output is a sparse cloud with no idea what anything is.&lt;/p>
&lt;hr>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>Benchmark figures below are the published results from Cheng et al., reproduced here rather than independently measured. Metric is &lt;strong>ATE RMSE in metres&lt;/strong> — these are error &lt;em>reductions&lt;/em>, not accuracy gains.&lt;/p>
&lt;h3 id="tum-rgb-d">TUM RGB-D&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Sequence&lt;/th>
&lt;th>Dynamics&lt;/th>
&lt;th>ORB-SLAM2&lt;/th>
&lt;th>SG-SLAM&lt;/th>
&lt;th>Reduction&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>fr3/walking_static&lt;/code>&lt;/td>
&lt;td>high&lt;/td>
&lt;td>0.4032&lt;/td>
&lt;td>0.0079&lt;/td>
&lt;td>&lt;strong>98.03%&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fr3/walking_xyz&lt;/code>&lt;/td>
&lt;td>high&lt;/td>
&lt;td>0.6826&lt;/td>
&lt;td>0.0171&lt;/td>
&lt;td>&lt;strong>97.50%&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fr3/walking_rpy&lt;/code>&lt;/td>
&lt;td>high&lt;/td>
&lt;td>0.5396&lt;/td>
&lt;td>0.0326&lt;/td>
&lt;td>&lt;strong>93.95%&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fr3/walking_halfsphere&lt;/code>&lt;/td>
&lt;td>high&lt;/td>
&lt;td>0.4462&lt;/td>
&lt;td>0.0309&lt;/td>
&lt;td>&lt;strong>93.07%&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fr3/sitting_static&lt;/code>&lt;/td>
&lt;td>low&lt;/td>
&lt;td>0.0087&lt;/td>
&lt;td>0.0060&lt;/td>
&lt;td>&lt;strong>31.03%&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="bonn-rgb-d-dynamic-9-sequences">Bonn RGB-D Dynamic (9 sequences)&lt;/h3>
&lt;p>Best and worst of the set: &lt;code>synchronous2&lt;/code> improves 1.4069 → 0.0164 m (&lt;strong>98.83%&lt;/strong>), while &lt;code>synchronous&lt;/code> — nominally the same scene, different take — only reaches 1.1411 → 0.3262 m (&lt;strong>71.41%&lt;/strong>). The &lt;code>moving_nonobstructing_box&lt;/code> pair land around 71–79%, the &lt;code>crowd&lt;/code> and &lt;code>person_tracking&lt;/code> sequences 93–97%.&lt;/p>
&lt;h3 id="reading-the-numbers-honestly">Reading the numbers honestly&lt;/h3>
&lt;p>Two things about this table are worth saying out loud, because they&amp;rsquo;re the parts a results summary usually hides.&lt;/p>
&lt;p>&lt;strong>The 31% on &lt;code>sitting_static&lt;/code> isn&amp;rsquo;t a weak result, it&amp;rsquo;s the control.&lt;/strong> That sequence is low-dynamic — a person seated, barely moving. There is almost nothing for dynamic-feature rejection to remove, so the gain collapses to a third of what the walking sequences show. The rotational-drift table is starker still: 7.99% on the same sequence. That&amp;rsquo;s the expected shape of the result, and it&amp;rsquo;s evidence the mechanism is doing what it claims rather than just globally discarding features.&lt;/p>
&lt;p>&lt;strong>RMSE improves far more than the median does.&lt;/strong> On &lt;code>walking_static&lt;/code> the ATE RMSE drops 98% while the RPE median improvements sit at 43–53%. The gain is concentrated in &lt;em>catastrophic&lt;/em> frames — the ones where a person crossing the view wrecks the pose estimate entirely — not in the typical frame. That&amp;rsquo;s the right thing for a SLAM system to fix, since a single badly-corrupted keyframe propagates into the map, but it does mean &amp;ldquo;98% better&amp;rdquo; describes the tail, not the average.&lt;/p>
&lt;hr>
&lt;h2 id="where-it-breaks">Where it breaks&lt;/h2>
&lt;p>The failure mode falls straight out of Algorithm 1, and the paper is upfront about it: &lt;strong>an object moving along the epipolar line direction is invisible to this test.&lt;/strong> Its features produce a point-to-epipolar-line distance of approximately zero despite genuinely moving, so line 10 accepts them as static. The semantic branch doesn&amp;rsquo;t save you either — multiplying a near-zero residual by a dynamic weight still clears the threshold. In practice that means someone walking directly toward or away from the camera is much harder to reject than someone crossing the view.&lt;/p>
&lt;p>The other open item is semantic map precision. Object extents come from thresholding a depth-derived point cloud inside a 2-D box, which is coarse — good enough to say &amp;ldquo;there is a monitor at roughly here&amp;rdquo;, not good enough for manipulation.&lt;/p>
&lt;h2 id="stack">Stack&lt;/h2>
&lt;p>C++ (ORB-SLAM2 core), Python, Ubuntu 18.04, ROS Melodic, NCNN, OpenCV, OctoMap, RViz. Evaluated on TUM RGB-D and the Bonn RGB-D Dynamic dataset.&lt;/p></description></item></channel></rss>