<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Homography | Saikiran Juttu | Robotics Portfolio</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/homography/</link><atom:link href="https://juttu-s.github.io/saikiran_juttu.github.io/tags/homography/index.xml" rel="self" type="application/rss+xml"/><description>Homography</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Mon, 18 Mar 2024 00:00:00 +0000</lastBuildDate><image><url>https://juttu-s.github.io/saikiran_juttu.github.io/media/icon_hu7729264130191091259.png</url><title>Homography</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/tags/homography/</link></image><item><title>Camera Calibration and Augmented Reality</title><link>https://juttu-s.github.io/saikiran_juttu.github.io/project/calibration/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><guid>https://juttu-s.github.io/saikiran_juttu.github.io/project/calibration/</guid><description>&lt;p>Everything in marker-based AR rests on one number: how well you know the camera. This project works through that chain end to end in C++/OpenCV — detect a calibration target, solve for intrinsics, recover live pose, and project virtual geometry that stays locked to the target as the camera moves.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="A green wireframe object rendered over a chessboard with rainbow corner polylines and RGB origin axes" srcset="
/saikiran_juttu.github.io/project/calibration/virtual-object_hu5133569573855807652.webp 400w,
/saikiran_juttu.github.io/project/calibration/virtual-object_hu6246078329211090082.webp 760w,
/saikiran_juttu.github.io/project/calibration/virtual-object_hu6792843897147638943.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/calibration/virtual-object_hu5133569573855807652.webp"
width="474"
height="411"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>The virtual object holding its pose against the board.&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="calibration">Calibration&lt;/h2>
&lt;p>A &lt;strong>9×6 chessboard giving 54 interior corners&lt;/strong>, detected on a live stream with sub-pixel refinement. Pressing &lt;code>s&lt;/code> banks the current frame&amp;rsquo;s corner locations along with their corresponding 3-D world points; &lt;code>cv::calibrateCamera&lt;/code> then solves for intrinsics and distortion. Fewer than five frames and it refuses — the parameters aren&amp;rsquo;t determined.&lt;/p>
&lt;p>Recovered intrinsics for the laptop camera, from an initial guess of nothing more than a principal point at the image centre:&lt;/p>
$$K = \begin{bmatrix} 611.41 &amp; 0 &amp; 311.84 \\ 0 &amp; 613.70 &amp; 236.82 \\ 0 &amp; 0 &amp; 1 \end{bmatrix}$$&lt;p>&lt;strong>Final reprojection error: 0.545 px.&lt;/strong> Intrinsics and distortion coefficients are written out as OpenCV YAML so later runs skip calibration entirely.&lt;/p>
&lt;h3 id="two-cameras-one-surprise">Two cameras, one surprise&lt;/h3>
&lt;p>Running the same procedure on an external USB webcam gave a notably better fit:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Camera&lt;/th>
&lt;th>fx, fy&lt;/th>
&lt;th>cx, cy&lt;/th>
&lt;th>Reprojection error&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Laptop integrated&lt;/td>
&lt;td>611.41, 613.70&lt;/td>
&lt;td>311.84, 236.82&lt;/td>
&lt;td>0.545 px&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>External USB webcam&lt;/td>
&lt;td>690.48, 693.37&lt;/td>
&lt;td>344.83, 239.23&lt;/td>
&lt;td>&lt;strong>0.150 px&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The webcam calibrates about &lt;strong>3.6× better&lt;/strong> — and the interesting part is that it does so while having &lt;em>substantially larger&lt;/em> distortion coefficients (k1 = −0.446 against −0.215, k3 = −4.44 against −1.76). More distortion, better fit. Those aren&amp;rsquo;t contradictory: the reprojection error measures how well the &lt;em>model&lt;/em> explains the observations, not how mild the lens is. A strongly distorted lens whose distortion is well-described by the radial polynomial will beat a milder lens whose residual behaviour the model can&amp;rsquo;t represent. Cheap wide optics can calibrate beautifully; the question is whether the model matches the physics.&lt;/p>
&lt;h2 id="pose">Pose&lt;/h2>
&lt;p>With intrinsics fixed, &lt;code>solvePnP&lt;/code> returns rotation and translation per frame. The sanity check is nicer than eyeballing an overlay — translate the camera to the right and watch the numbers:&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">tvec [7.378, 6.753, 40.531]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tvec [7.627, 6.719, 40.485]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tvec [7.759, 6.711, 40.504]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tvec [8.070, 6.713, 40.486]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>X climbs monotonically, 7.378 → 8.070, while Y holds near 6.71 and Z near 40.49. That&amp;rsquo;s exactly right for pure lateral motion, and it&amp;rsquo;s a check on the physical meaning of the solution rather than on whether the render happens to look plausible.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Projected 3D axes anchored at the board origin with all 54 corners detected and linked" srcset="
/saikiran_juttu.github.io/project/calibration/projected-axes_hu14538787005341851264.webp 400w,
/saikiran_juttu.github.io/project/calibration/projected-axes_hu3207897690070648796.webp 760w,
/saikiran_juttu.github.io/project/calibration/projected-axes_hu17627915606788624727.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/calibration/projected-axes_hu14538787005341851264.webp"
width="633"
height="388"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Detected corners and the projected 3-D axes at the board origin.&lt;/em>&lt;/p>
&lt;p>&lt;code>cv::projectPoints&lt;/code> then puts 3-D world points back onto the image plane — the board corners, RGB axes at the origin, and a virtual object defined as &lt;strong>15 points forming an inverted C&lt;/strong>, connected by lines drawn between their projections.&lt;/p>
&lt;hr>
&lt;h2 id="extensions">Extensions&lt;/h2>
&lt;p>&lt;strong>Making the marker stop looking like a marker.&lt;/strong> The strongest result here: find the board&amp;rsquo;s four extreme corners, warp an arbitrary image into that quadrilateral, and render the virtual object on top of the warped result. The chessboard is still doing all the pose work underneath, but it&amp;rsquo;s no longer visible — which is the difference between a calibration demo and something you&amp;rsquo;d actually want in a scene.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="An arbitrary image warped over the chessboard region with the virtual object drawn on top" srcset="
/saikiran_juttu.github.io/project/calibration/target-overlay_hu10769559096996454137.webp 400w,
/saikiran_juttu.github.io/project/calibration/target-overlay_hu16920568668342420622.webp 760w,
/saikiran_juttu.github.io/project/calibration/target-overlay_hu14363001692669884525.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/calibration/target-overlay_hu10769559096996454137.webp"
width="491"
height="395"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>The board&amp;rsquo;s squares fully replaced, virtual geometry still tracking correctly.&lt;/em>&lt;/p>
&lt;p>&lt;strong>Two boards at once.&lt;/strong> A (9,6) and a (6,6) board handled in a single frame, each solved for pose independently and each rendered with its own coloured pyramid. It degrades gracefully — remove one board and the other keeps tracking.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="Two checkerboards of different sizes tracked simultaneously, each with its own coloured wireframe pyramid" srcset="
/saikiran_juttu.github.io/project/calibration/multi-board_hu16617972295414141342.webp 400w,
/saikiran_juttu.github.io/project/calibration/multi-board_hu10299970603627648674.webp 760w,
/saikiran_juttu.github.io/project/calibration/multi-board_hu7114270305068571192.webp 1200w"
src="https://juttu-s.github.io/saikiran_juttu.github.io/saikiran_juttu.github.io/project/calibration/multi-board_hu16617972295414141342.webp"
width="656"
height="445"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;em>Independent pose solutions for two different board geometries in one frame.&lt;/em>&lt;/p>
&lt;p>&lt;strong>Pre-recorded video mode.&lt;/strong> Pressing &lt;code>p&lt;/code> runs the whole pipeline over recorded footage instead of the live feed, inserting virtual objects into video after the fact.&lt;/p>
&lt;h2 id="toward-markerless">Toward markerless&lt;/h2>
&lt;p>&lt;code>cv::cornerHarris&lt;/code> was added to explore features that don&amp;rsquo;t require a known target, swept over threshold, block size, aperture size and the Harris free parameter &lt;em>k&lt;/em>. Raising the threshold from 150 to 200 visibly thins the detections, dropping the finer corners first.&lt;/p>
&lt;p>Detection is where this stopped, and it&amp;rsquo;s worth being precise about that: &lt;strong>the markerless pipeline was designed, not built.&lt;/strong> Going from Harris corners to a pose estimate needs frame-to-frame correspondence — SIFT or SURF descriptors matched across frames, then a transformation recovered from the matches, then projection using that pose. That&amp;rsquo;s the natural continuation and it isn&amp;rsquo;t implemented here.&lt;/p>
&lt;h2 id="a-note-on-the-target">A note on the target&lt;/h2>
&lt;p>The chessboard in these figures is being &lt;strong>displayed on a phone screen&lt;/strong> rather than printed. Convenient, and it never creases — but it&amp;rsquo;s a glossy emissive surface, which introduces specular highlights and probably accounts for some of the laptop camera&amp;rsquo;s worse reprojection error. A matte printed target on rigid backing is the better choice if the calibration itself is what matters.&lt;/p>
&lt;h2 id="stack">Stack&lt;/h2>
&lt;p>C++, OpenCV 4, CMake, on Windows and Ubuntu. &lt;code>calibrateCamera&lt;/code>, &lt;code>solvePnP&lt;/code>, &lt;code>projectPoints&lt;/code>, &lt;code>cornerHarris&lt;/code>; intrinsics persisted as OpenCV YAML. Two cameras: a laptop integrated camera and an external USB webcam.&lt;/p></description></item></channel></rss>