<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eddie</title>
    <description>The latest articles on DEV Community by Eddie (@eddie).</description>
    <link>https://dev.to/eddie</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F163323%2F35d6574d-0142-4e55-a1be-5a4a705d69f0.png</url>
      <title>DEV Community: Eddie</title>
      <link>https://dev.to/eddie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eddie"/>
    <language>en</language>
    <item>
      <title>Building a 100% Pure-Rust SIFT &amp; Image Stitcher from Scratch (Zero OpenCV, Zero Unsafe)</title>
      <dc:creator>Eddie</dc:creator>
      <pubDate>Thu, 12 Nov 2026 09:55:25 +0000</pubDate>
      <link>https://dev.to/eddie/building-a-100-pure-rust-sift-image-stitcher-from-scratch-zero-opencv-zero-unsafe-2alj</link>
      <guid>https://dev.to/eddie/building-a-100-pure-rust-sift-image-stitcher-from-scratch-zero-opencv-zero-unsafe-2alj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftcj0djo04sxiw8leo0sa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftcj0djo04sxiw8leo0sa.jpg" alt="pure-sift SIFT matching demo" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have ever needed to do classic feature matching or image alignment in a production Rust service, you know the drill: you either pull in heavy C++ OpenCV wrappers, fight system-level CMake configurations in Docker, or deal with non-trivial FFI safety hazards.&lt;/p&gt;

&lt;p&gt;David Lowe’s landmark &lt;strong&gt;Scale-Invariant Feature Transform (SIFT)&lt;/strong&gt; patent (&lt;a href="https://patents.google.com/patent/US6711293B1/en" rel="noopener noreferrer"&gt;US Patent 6,711,293 B1&lt;/a&gt;) expired in March 2020. Yet in the Rust ecosystem, almost every serious feature matching setup still leaned on external C/C++ libraries.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://github.com/dannkkie/pure-sift" rel="noopener noreferrer"&gt;pure-sift&lt;/a&gt;&lt;/strong&gt;: an open-source, 100% pure-Rust library (&lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;) implementing the full SIFT pipeline, 2D Similarity &amp;amp; 4-Point DLT Homography RANSAC, and an automated image stitcher with zero OpenCV or C++ build dependencies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/dannkkie/pure-sift" rel="noopener noreferrer"&gt;https://github.com/dannkkie/pure-sift&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/pure-sift" rel="noopener noreferrer"&gt;https://crates.io/crates/pure-sift&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are three concrete engineering lessons learned from reading the patent claims, paper math, and implementing it from first principles in safe Rust.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. 3D Taylor Quadratic Sub-Pixel Localization Is Where the Battle Is Won
&lt;/h2&gt;

&lt;p&gt;Extrema in the Difference-of-Gaussians (DoG) scale space are detected by comparing a sample point against its 26 neighbors (8 in the current scale, 9 in the scale above, and 9 in the scale below).&lt;/p&gt;

&lt;p&gt;Finding a discrete integer coordinate &lt;code&gt;(x, y, σ)&lt;/code&gt; is easy. But discrete coordinates give jagged, noisy keypoint locations that destroy subsequent homography calculations.&lt;/p&gt;

&lt;p&gt;Claim 1 of US6711293B1 specifies expanding the scale-space function &lt;code&gt;D(x, y, σ)&lt;/code&gt; via a 3D Taylor series truncated to quadratic terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D(x) = D + (∂D/∂x)ᵀ x + ½ xᵀ (∂²D/∂x²) x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Taking the derivative and setting it to zero yields the exact sub-pixel offset vector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x̂ = - (∂²D/∂x²)⁻¹ (∂D/∂x)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Rust Implementation Details:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;3x3 Hessian Inversion&lt;/strong&gt;: Because this is evaluated for thousands of candidate points, we solve the 3×3 linear system directly using an analytical cofactor matrix rather than bringing in heavy linear algebra libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convergence Limits&lt;/strong&gt;: If any component of &lt;code&gt;x̂&lt;/code&gt; exceeds 0.5, the true extremum lies closer to an adjacent pixel. We iteratively shift the candidate center (up to 5 steps). In our unit tests against synthetic paraboloids, the sub-pixel error converges to &lt;strong&gt;&amp;lt; 0.12 pixels&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrast &amp;amp; Edge Thresholding&lt;/strong&gt;: Extrema with low contrast (&lt;code&gt;|D(x̂)| &amp;lt; 0.03&lt;/code&gt;) or high principal curvature ratio (governed by the Hessian trace and determinant ratio &lt;code&gt;r &amp;gt; 10.0&lt;/code&gt;) are filtered out to reject poorly localized edge responses.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. The &amp;gt;= 80% Secondary Orientation Peak Rule
&lt;/h2&gt;

&lt;p&gt;A keypoint with only a single orientation breaks invariance under symmetric or ambiguous textures.&lt;/p&gt;

&lt;p&gt;In Lowe’s formulation, we create a 36-bin orientation histogram weighted by gradient magnitude and a Gaussian window (&lt;code&gt;σ_w = 1.5 × σ&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;The subtle detail often missed in naive implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any local histogram peak within &lt;strong&gt;80% of the dominant peak height&lt;/strong&gt; splits off into a &lt;strong&gt;new, independent keypoint&lt;/strong&gt; sharing the exact same &lt;code&gt;(x, y, σ)&lt;/code&gt; coordinates but with the secondary orientation angle.&lt;/li&gt;
&lt;li&gt;Each peak undergoes parabolic interpolation across its adjacent bins for smooth angular resolution:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;θ = θ₀ + [ (h_left - h_right) / (2 × (h_left - 2×h_center + h_right)) ] × (2π / 36)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This orientation splitting accounts for roughly 15% of the total keypoint count, but it is responsible for nearly 40% of robust matches in difficult, repeating environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Why 4-Point DLT Homography Beats 2D Similarity on Perspective Slant
&lt;/h2&gt;

&lt;p&gt;Early in the project, we tested a 2-point 2D Similarity model (estimating rotation &lt;code&gt;θ&lt;/code&gt;, uniform scale &lt;code&gt;s&lt;/code&gt;, and translation &lt;code&gt;t_x, t_y&lt;/code&gt; via 4 degrees of freedom).&lt;/p&gt;

&lt;p&gt;On planar aerial imagery with straight-down camera angles, 2D Similarity performed great:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires only 2 point correspondences per RANSAC sample.&lt;/li&gt;
&lt;li&gt;Evaluates in under 10 ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, the moment we tested heavy perspective slant (such as large out-of-plane camera tilt angles akin to the classic Oxford affine benchmarks), 2D Similarity failed completely: &lt;strong&gt;inlier count dropped to zero&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To solve this, we implemented the &lt;strong&gt;4-Point Direct Linear Transform (DLT) with SVD&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets up an 8×9 algebraic matrix &lt;code&gt;A&lt;/code&gt; from 4 point matches:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| -x  -y  -1   0   0   0   x·x'  y·x'  x' |
|  0   0   0  -x  -y  -1   x·y'  y·y'  y' |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Solves &lt;code&gt;A h = 0&lt;/code&gt; via pure-Rust Jacobi SVD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizon &amp;amp; Degeneracy Guard&lt;/strong&gt;: Checks that collinearity is avoided, points are convex, and enforces positive determinant (&lt;code&gt;det(H) &amp;gt; 0&lt;/code&gt;) to discard unphysical "turned inside-out" backplane projections.&lt;/li&gt;
&lt;li&gt;With 4-Point DLT Homography RANSAC, matching through heavy perspective angle shifts went from complete failure to &lt;strong&gt;hundreds of validated geometric inliers&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3gbipyy0f3a2vds4azow.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3gbipyy0f3a2vds4azow.jpg" alt="Stitched panorama" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture &amp;amp; Zero-Unsafe Guarantee
&lt;/h2&gt;

&lt;p&gt;To keep the crate clean for production and embedded use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;&lt;/strong&gt;: Zero raw pointer arithmetic or unchecked memory buffers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rayon Parallelism&lt;/strong&gt;: Keypoint descriptor generation is inherently embarrassingly parallel; Rayon distributes the 128-D orientation binning across CPU threads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight Optional Web Workbench&lt;/strong&gt;: A built-in Axum/Tokio inspection UI is included under the default &lt;code&gt;server&lt;/code&gt; feature for visual debugging. When using &lt;code&gt;pure-sift&lt;/code&gt; as an embedded or WebAssembly (WASM) library, pass &lt;code&gt;--no-default-features&lt;/code&gt; to compile with zero async runtime overhead.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[dependencies]&lt;/span&gt;
&lt;span class="na"&gt;pure-sift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Try It Out
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo install pure-sift

&lt;span class="c"&gt;# Extract SIFT keypoints and match two photos with RANSAC:&lt;/span&gt;
pure-sift match image1.jpg image2.jpg --model homography --output matches.jpg

&lt;span class="c"&gt;# Automatically stitch into a panorama:&lt;/span&gt;
pure-sift stitch tile_left.jpg tile_right.jpg --output panorama.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code, tests, and documentation are up on &lt;a href="https://github.com/dannkkie/pure-sift" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If you test it against your own datasets or have ideas for optimizations, issues and PRs are warmly welcome!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>computervision</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
