<?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: Silver Kernel</title>
    <description>The latest articles on DEV Community by Silver Kernel (@silver_kernel).</description>
    <link>https://dev.to/silver_kernel</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%2F4215243%2Fb095f758-cebb-4ca5-8010-55b79291c36e.jpg</url>
      <title>DEV Community: Silver Kernel</title>
      <link>https://dev.to/silver_kernel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/silver_kernel"/>
    <language>en</language>
    <item>
      <title>Spatial Qualimetry: Extracting 3D Depth Profiles in Pure NumPy</title>
      <dc:creator>Silver Kernel</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:43:25 +0000</pubDate>
      <link>https://dev.to/silver_kernel/beyond-opencv-light-weight-monocular-depth-post-processing-in-pure-numpy-35f9</link>
      <guid>https://dev.to/silver_kernel/beyond-opencv-light-weight-monocular-depth-post-processing-in-pure-numpy-35f9</guid>
      <description>&lt;p&gt;When building desktop or edge AI applications, the default instinct for many engineers is to run &lt;code&gt;pip install opencv-python&lt;/code&gt;.&lt;br&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%2Fmmn7tgre9d1mgkrcnidg.png" 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%2Fmmn7tgre9d1mgkrcnidg.png" alt="Fig 1. Hawk Eye Production Dashboard: Real-time monocular depth estimation and 2D profile extraction." width="799" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, as your architecture matures toward lightweight, self-contained edge software (such as a PyQt6 GUI app running ONNX Runtime on CPU), third-party C++ wrappers introduce real operational friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployment Bloat:&lt;/strong&gt; 40–50 MB binary wheels (&lt;code&gt;opencv-python-headless&lt;/code&gt; on Linux/Windows x64) that expand desktop installers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold-Start Latency:&lt;/strong&gt; Shared C++ library initialization delays during application startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Conflicts:&lt;/strong&gt; Threading and event-loop clashes with native Qt backends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our project — &lt;strong&gt;Hawk Eye&lt;/strong&gt;, an industrial AI edge application for monocular tire tread depth assessment — we enforced a strict rule: &lt;strong&gt;Zero CV-specific external dependencies&lt;/strong&gt; (no OpenCV, SciPy, or Scikit-Image). The pipeline relies strictly on PyQt6, ONNX Runtime, PIL, and pure NumPy.&lt;/p&gt;

&lt;p&gt;In this article, we look at how we solve camera perspective tilt in monocular depth maps using simple vector operations in NumPy.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem: Relative Depth and Perspective Distortion
&lt;/h2&gt;

&lt;p&gt;Monocular depth estimation models (like U-Net architectures optimized for edge CPU) map 2D pixels to depth predictions &lt;code&gt;Z_raw&lt;/code&gt; in range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In field diagnostics, handheld devices or smartphone cameras are almost never held perfectly parallel to the tire surface. A 5-degree perspective tilt creates an artificial linear gradient across the depth map, causing the model to mistake perspective for actual physical tread wear.&lt;/p&gt;

&lt;p&gt;To fix this, we detrend the baseline reference plane using Ordinary Least Squares (OLS) fitted via SVD.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Mathematics of Plane Fitting: Why SVD over Normal Equations?
&lt;/h2&gt;

&lt;p&gt;We model the background surface using a linear 3D equation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Z(x, y) = a * x + b * y + c&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Where &lt;code&gt;(x, y)&lt;/code&gt; are spatial pixel coordinates, &lt;code&gt;a, b&lt;/code&gt; are surface slopes, and &lt;code&gt;c&lt;/code&gt; is the global baseline offset.&lt;/p&gt;

&lt;p&gt;Given an ROI matrix &lt;code&gt;Z_raw&lt;/code&gt; of size &lt;code&gt;H x W&lt;/code&gt;, we set up an overdetermined linear system &lt;code&gt;A * x = b&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Numerical Stability: SVD vs. Normal Equations
&lt;/h3&gt;

&lt;p&gt;In textbook linear algebra, the analytical solution is written via Normal Equations:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;x_opt = inv(A^T * A) * A^T * b&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In production, forming &lt;code&gt;A^T * A&lt;/code&gt; squares the dynamic range of pixel coordinates. For a &lt;code&gt;224x224&lt;/code&gt; depth grid (&lt;code&gt;N = 50,176&lt;/code&gt; pixels, max coordinate &lt;code&gt;x_max = 223&lt;/code&gt;), the condition number of &lt;code&gt;A^T * A&lt;/code&gt; reaches &lt;code&gt;10^4 - 10^5&lt;/code&gt;. Explicit matrix inversion leads to numerical instability on low-precision CPU runtimes.&lt;/p&gt;

&lt;p&gt;Instead, we use &lt;strong&gt;Singular Value Decomposition (SVD)&lt;/strong&gt; via &lt;code&gt;np.linalg.lstsq&lt;/code&gt;. SVD solves the system directly without forming &lt;code&gt;A^T * A&lt;/code&gt;, avoiding matrix inversion instability.&lt;/p&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%2Fog34c10q37yz5arfc7c1.png" 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%2Fog34c10q37yz5arfc7c1.png" alt="Fig 2. Final 2D profile extraction after removing camera tilt and baseline noise." width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Production Python Implementation
&lt;/h2&gt;

&lt;p&gt;Below is the self-contained implementation written purely in NumPy. It fits a least-squares plane, subtracts perspective tilt, and uses percentile masking to prevent deep grooves from corrupting the surface fit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt; &lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detrend_and_scale_depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;depth_raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;scale_3d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;bias_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;groove_mask_percentile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;40.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Removes camera perspective tilt using SVD Least Squares Plane Fitting
    and converts raw relative depth predictions into millimeter profile maps.

    Convention: Assumes Z_groove &amp;lt; Z_surface (lower relative height in depth_raw).

    Parameters
    ----------
    depth_raw : np.ndarray
        2D floating-point array (H, W) of relative predictions from ONNX U-Net.
    scale_3d : float
        Optics scale factor (mm per relative depth unit).
    bias_mm : float
        Systemic bias correction in millimeters.
    groove_mask_percentile : float
        Empirical threshold (40%) masking lower depth values (grooves) 
        during plane fitting to prevent fit sags.

    Returns
    -------
    np.ndarray
        2D array (H, W) containing detrended physical tread depth in millimeters.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depth_raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;

    &lt;span class="c1"&gt;# 1. Generate 2D pixel coordinate grids
&lt;/span&gt;    &lt;span class="n"&gt;grid_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;x_flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;grid_x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;y_flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;grid_y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;z_flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depth_raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Design matrix A: Shape (N, 3) where N = H * W
&lt;/span&gt;    &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column_stack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;x_flat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_flat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_flat&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Trimmed OLS: Mask out deep grooves to fit plane ONLY on top tread blocks
&lt;/span&gt;    &lt;span class="n"&gt;threshold_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_flat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;groove_mask_percentile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z_flat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold_val&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Solve for plane parameters [a, b, c] via SVD (lstsq)
&lt;/span&gt;    &lt;span class="n"&gt;plane_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;z_flat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rcond&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plane_params&lt;/span&gt;

    &lt;span class="c1"&gt;# 4. Reconstruct background trend plane across full ROI
&lt;/span&gt;    &lt;span class="n"&gt;z_plane&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;grid_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;grid_y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 5. Detrend surface (Subtract baseline slope)
&lt;/span&gt;    &lt;span class="n"&gt;z_detrended&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depth_raw&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;z_plane&lt;/span&gt;

    &lt;span class="c1"&gt;# 6. Robust baseline shift (P05 percentile instead of min to reject noise)
&lt;/span&gt;    &lt;span class="n"&gt;baseline_floor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_detrended&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;z_detrended&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;baseline_floor&lt;/span&gt;

    &lt;span class="c1"&gt;# 7. Convert relative depth delta to physical millimeters
&lt;/span&gt;    &lt;span class="n"&gt;depth_mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_detrended&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale_3d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bias_mm&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;depth_mm&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Simulate 100x100 tile with camera tilt + 2.0 unit groove (20% of area)
&lt;/span&gt;    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ogrid&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;simulated_tilt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="n"&gt;simulated_groove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;raw_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;simulated_tilt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;simulated_groove&lt;/span&gt;

    &lt;span class="c1"&gt;# Process depth map
&lt;/span&gt;    &lt;span class="n"&gt;depth_mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detrend_and_scale_depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale_3d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Evaluation using P95 - P05 percentiles (Assumes groove area &amp;gt;= 5-10% of ROI)
&lt;/span&gt;    &lt;span class="n"&gt;groove_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth_mm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth_mm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Input Range (Tilted): [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Corrected Depth Range: [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;depth_mm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;depth_mm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Measured Groove Depth: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;groove_depth&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; mm (Expected Synthetic: 5.00 mm)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expected Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input Range (Tilted): [0.00, 6.93]
Corrected Depth Range: [0.00, 5.00] mm
Measured Groove Depth: 5.00 mm (Expected Synthetic: 5.00 mm)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production Considerations &amp;amp; Limitations
&lt;/h2&gt;

&lt;p&gt;While linear plane detrending is fast, edge deployment has specific constraints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Groove Fitting Bias (Trimmed OLS):&lt;/strong&gt; Unmasked OLS fits the plane to all pixels. If an ROI contains deep grooves, the fitted plane sags downward. Our implementation uses a 40% threshold mask (&lt;code&gt;groove_mask_percentile=40&lt;/code&gt;) to fit strictly to elevated tread blocks (valid for typical 15–35% groove occupancy).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extreme Camera Tilt:&lt;/strong&gt; If perspective tilt across the ROI exceeds the total groove depth, global percentile thresholding bleeds across regions. For severe angles (&amp;gt;30 degrees), local block-based RANSAC or quadratic fitting &lt;code&gt;z = a*x^2 + b*y^2 + c*x*y + d*x + e*y + f&lt;/code&gt; should replace global OLS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local vs. Global Slope Removal:&lt;/strong&gt; OLS removes all linear trends. This works well for measuring local groove depth relative to adjacent tread blocks, but intentional tire shoulder curvature will also be flattened.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Benchmark Results
&lt;/h2&gt;

&lt;p&gt;Replacing external CV packages with vectorized NumPy operations yielded clear benefits in our PyQt6 runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Execution Latency:&lt;/strong&gt; &lt;strong&gt;0.38 ms&lt;/strong&gt; on a &lt;code&gt;224x224&lt;/code&gt; depth ROI (median of 100 runs after 10 warm-up iterations on Intel Core i5-1135G7 @ 2.40GHz, &lt;code&gt;OMP_NUM_THREADS=1&lt;/code&gt;, NumPy 1.26.4).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package Footprint:&lt;/strong&gt; Saved &lt;strong&gt;~45 MB&lt;/strong&gt; on Linux/Windows x64 installers by removing &lt;code&gt;opencv-python-headless&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thread Safety:&lt;/strong&gt; Pure NumPy array operations run inside PyQt6 worker threads without GUI loop blocking.&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%2Frzoq3s28hfwibhjnz4vx.png" 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%2Frzoq3s28hfwibhjnz4vx.png" alt="Fig 3. Post-processing calibration UI for fine-tuning optics scale and bias." width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Linear algebra operations in pure NumPy are often enough for edge post-processing without bundling full C++ vision frameworks.&lt;/p&gt;

&lt;p&gt;While synthetic tests verify the math, production calibration against real physical depth gauges (e.g., Mitutoyo 571-201-30) remains necessary to establish the optics scale factor (&lt;code&gt;scale_3d&lt;/code&gt;) for different smartphone camera modules.&lt;/p&gt;

&lt;p&gt;By combining SVD plane fitting with percentile masking, we achieved stable profile extraction for Hawk Eye — completely independent of external computer vision packages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Interactive Diagnostic:&lt;/strong&gt; You can check your current wear pattern using our &lt;strong&gt;&lt;a href="https://tyrecam.com" rel="noopener noreferrer"&gt;AI Tire Scanner&lt;/a&gt;&lt;/strong&gt; right now. No app installation required.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>computervision</category>
      <category>python</category>
      <category>numpy</category>
      <category>math</category>
    </item>
  </channel>
</rss>
