<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Object Recognition | Saikiran Juttu | Robotics Portfolio</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/object-recognition/</link><atom:link href="https://juttu-s.github.io/saikiran_juttu.github.io/tags/object-recognition/index.xml" rel="self" type="application/rss+xml"/><description>Object Recognition</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 30 Mar 2024 00:00:00 +0000</lastBuildDate><image><url>https://juttu-s.github.io/saikiran_juttu.github.io/media/icon_hu7729264130191091259.png</url><title>Object Recognition</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/object-recognition/</link></image><item><title>Real-Time 2D Object Recognition with Feature Matching</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/project/object-recognition/</link><pubDate>Sat, 30 Mar 2024 00:00:00 +0000</pubDate><guid>https://juttu-s.github.io/saikiran_juttu.github.io/project/object-recognition/</guid><description>&lt;p>Recognise objects on a tabletop from a live webcam, using classical computer vision and nothing else — no learned features, no pretrained backbone. Camera overhead, dark objects on a white surface, everything computed per frame in C++.&lt;/p>
&lt;p>The constraint that made this interesting: most of the pipeline had to be written from scratch. Two of the first four stages were required to be; three ended up that way. The only OpenCV algorithm doing real work is connected-component labelling.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="A watch and a pen segmented with oriented bounding boxes, axis-of-least-central-moment arrows, and live feature values overlaid" srcset="
/saikiran_juttu.github.io/project/object-recognition/features-annotated_hu1285270646848479672.webp 400w,
/saikiran_juttu.github.io/project/object-recognition/features-annotated_hu4246342232475753791.webp 760w,
/saikiran_juttu.github.io/project/object-recognition/features-annotated_hu5357358603646478662.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/object-recognition/features-annotated_hu1285270646848479672.webp"
width="640"
height="480"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Two objects at once. Blue boxes are the oriented bounding boxes, red arrows the axis of least central moment, cyan text the live feature values.&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="pipeline">Pipeline&lt;/h2>
&lt;h3 id="thresholding-without-otsu">Thresholding, without Otsu&lt;/h3>
&lt;p>Rather than call a threshold function, the threshold is found by &lt;strong>2-means clustering on sampled pixel values&lt;/strong>. Sample the frame, converge two centroids — one settles on the dark object population, one on the light background — and put the threshold at their midpoint.&lt;/p>
&lt;p>The appeal is that it&amp;rsquo;s &lt;em>adaptive by construction&lt;/em>. As the lighting shifts, both centroids move and the threshold tracks them, without a hand-tuned constant anywhere. It&amp;rsquo;s a genuinely better fit for a live feed than a fixed cut, and it&amp;rsquo;s about fifteen lines of code.&lt;/p>
&lt;h3 id="morphological-cleanup">Morphological cleanup&lt;/h3>
&lt;p>The thresholded feed had holes in it — printed text and specular highlights on dark objects read as background. So: &lt;strong>dilation first to close the gaps, then erosion to remove the speckle&lt;/strong> the dilation amplified. Written by hand rather than called, and the ordering was driven by looking at the actual defect rather than reaching for a default.&lt;/p>
&lt;h3 id="segmentation">Segmentation&lt;/h3>
&lt;p>&lt;code>cv::connectedComponentsWithStats&lt;/code> labels the regions; components below a size threshold are dropped as noise. Survivors get distinct colours for display.&lt;/p>
&lt;h3 id="features-from-moments-up">Features, from moments up&lt;/h3>
&lt;p>Raw and central moments computed directly, then five descriptors per region:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Feature&lt;/th>
&lt;th>What it captures&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Centroid (x, y)&lt;/td>
&lt;td>Region position&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>θ&lt;/td>
&lt;td>Angle of the axis of least central moment — the object&amp;rsquo;s orientation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Percent filled&lt;/td>
&lt;td>Region area ÷ oriented bounding box area&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Bounding box ratio&lt;/td>
&lt;td>Oriented box aspect ratio&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Stored to CSV alongside a label typed at capture time, which makes the training set inspectable — you can open it and see why the classifier does what it does.&lt;/p>
&lt;h2 id="classification">Classification&lt;/h2>
&lt;p>Two classifiers over the same features:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Nearest neighbour&lt;/strong> on cumulative scaled Euclidean distance — closest labelled example wins.&lt;/li>
&lt;li>&lt;strong>k-NN with k = 4&lt;/strong> — take the four nearest, majority vote.&lt;/li>
&lt;/ul>
&lt;p>The k-NN version is the more robust of the two, and the reason is visible in the failure mode of the first. Nearest neighbour commits to a single best match, so when two classes differ only marginally in feature space, one noisy frame is enough to flip the decision. Requiring agreement among four neighbours means a single outlier can&amp;rsquo;t carry the vote.&lt;/p>
&lt;hr>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>&lt;strong>11 object classes&lt;/strong>, roughly 30 labelled samples captured at varying positions and orientations: watch, pen, mobile, spoon, bracelet, earbuds box, pendrive, statue, controller, star, clutch. Over 15 trials across five classes, classification accuracy ran &lt;strong>93.33–100%&lt;/strong>.&lt;/p>
&lt;p>The more informative result is how well the shape features separate the classes:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Feature&lt;/th>
&lt;th>Range across the 11 classes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Percent filled&lt;/td>
&lt;td>0.34 (bracelet — a hollow loop) → 0.97 (phone — a filled rectangle)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Bounding box ratio&lt;/td>
&lt;td>1.04 (near-square box) → 8.88 (pen)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Those two numbers do most of the discriminating, and they&amp;rsquo;re both scale- and rotation-invariant, which is the whole reason for computing orientation first and measuring the box &lt;em>after&lt;/em> aligning to it. Deliberately including several elongated objects — pen at 8.88, watch strap at 5.31, spoon at 3.99 — was what stress-tested it; anything can separate a pen from a phone, but separating a pen from a watch strap needs the percent-filled term to pull its weight.&lt;/p>
&lt;p>An extension pushed it to &lt;strong>multiple objects simultaneously&lt;/strong>, segmenting and classifying every region in the frame rather than assuming one object at a time.&lt;/p>
&lt;h2 id="honest-limitations">Honest limitations&lt;/h2>
&lt;p>&lt;strong>The feature vector includes absolute centroid position.&lt;/strong> Centroid x and y range from 165 to 484 px across the dataset, and feeding those into a scaled-Euclidean distance means &lt;em>where the object sits in frame&lt;/em> contributes to the class decision — in a system whose stated goal is translation invariance. The three shape descriptors are the invariant ones and are doing the real work; the centroid terms are a liability I&amp;rsquo;d drop.&lt;/p>
&lt;p>&lt;strong>Lighting drives everything.&lt;/strong> The 2-means threshold adapts, but it can only adapt to a bimodal scene. Introduce a shadow gradient across the white surface, or a mid-grey object, and the two-cluster assumption stops holding.&lt;/p>
&lt;p>&lt;strong>No timing was measured.&lt;/strong> The system runs interactively on a live feed, but there&amp;rsquo;s no frame-rate figure behind that, so I won&amp;rsquo;t claim one.&lt;/p>
&lt;h2 id="stack">Stack&lt;/h2>
&lt;p>C++, OpenCV 4, CMake. Thresholding, morphology and the entire moment/feature pipeline hand-written; &lt;code>connectedComponentsWithStats&lt;/code> for labelling. Training data self-collected via an in-app capture-and-label mode, with recording built in for demos.&lt;/p></description></item></channel></rss>