A geometry-based pipeline for estimating per-groove tread depth from a monocular camera pan, without training a task-specific ML model.
Four circumferential grooves tracked across a camera pan. Green = groove lips, re-fitted on every frame; blue = groove centerline (top-surface reference); red = tracked Tread Wear Indicator (TWI). The depth-related signal comes from the relative motion between the surface and TWI references.
- The problem
- The idea: measure the slide, not the shadow
- How the pipeline works
- Example result
- Calibration and outputs
- Why a training-free approach
- Running the code
- Repository layout
- Roadmap
Tread depth is a vertical measurement, but an RGB image records only a 2D projection. Manual gauges and dedicated profilometry solve that measurement directly; this project explores a complementary route for a setup that already captures a short camera pan across the tread.
The changing viewpoint provides roughly 120 observations per groove in this example. Combining those observations makes it possible to recover a small relative-motion signal and fit one depth-related parameter per groove. Capture calibration then maps that pixel-domain result to millimeters.
The goal is therefore straightforward: estimate per-groove tread depth from a monocular RGB video while keeping the method explicit, inspectable, and independent of task-specific model training.
The method tracks two features at different depths: the groove lips, which provide a reference for the tread surface, and a Tread Wear Indicator inside the groove. Their relative motion during the camera pan produces a parallax signal that can be fitted with a geometric model.
The prototype combines geometric detection, optical flow, template-based localization, and nonlinear least squares. It needs no pretrained depth model, labeled tire dataset, or offline training stage, and the estimation path is traceable from measured image displacement through the configured capture geometry. The central idea is to turn the changing viewpoint into the depth cue itself.
The cue used here is motion parallax. As the camera swings past the tire, features at different depths move differently in the image. The top of the tread and the TWI provide two trackable references separated in depth, so their relative image motion encodes groove depth under the specified capture geometry.
Instead of estimating depth from the appearance of a single frame, the pipeline asks: over the whole pan, how far does the tracked TWI move relative to the groove's top-surface reference?
Tread Wear Indicators (TWI, or "wear bars") provide a distinct reference inside the principal grooves. Their configured height also provides the offset that converts the surface-to-TWI measurement into total groove depth. In this example, that height is set to 1.6 mm.
This makes the TWI useful in two ways:
- Tracking target — its raised surface is more visually distinct than the surrounding groove floor.
- Height reference — when its height is known for the tire being inspected, it can be added to the estimated surface-to-TWI depth.
The same groove at two points in the arc. Cyan = the groove centerline computed from its two lips; red = the tracked TWI reference. Their horizontal offset
Let
With a camera orbiting at a constant angular rate
should trace a sine wave in time:
-
$A$ — fitted parallax amplitude in pixels. Under the model assumptions, it is proportional to the surface-to-TWI depth. -
$C$ — a groove-specific baseline offset that absorbs alignment differences, including the transverse angle of outer grooves on the curved (toroidal) tread. -
$\omega,\ t_0$ — the camera's angular rate and the head-on frame. Both are capture parameters (see Calibration and outputs).
Fitting the whole time series for
The derivation is designed for a controlled camera pan with the following geometry:
- The tire remains static while the camera moves.
- The sweep passes through head-on at a roughly constant angular rate.
- Camera-to-tread distance
$Z$ stays approximately constant across the arc. - Pinhole projection and the small-depth regime
$\Delta d \ll Z$ are good approximations. - Each measured groove has two visible lips, is approximately circumferential, and is within the detector's ±45° head-on orientation tolerance.
- Each measured groove contains a visible TWI with a known height.
- Rolling-shutter skew is negligible at the selected sweep speed.
- Lighting remains stable enough for optical-flow and template tracking.
Scale and camera motion are capture-calibration inputs; Calibration and outputs shows how they connect the image measurements to metric depth.
The two groove lips define the tread-surface reference. A TWI provides a second, trackable plane inside the groove, so the estimator can use their relative motion without requiring a cross-sectional model of the tire mold. A mold-specific model could instead infer depth from tapered groove width; the TWI route keeps the measurement tied to two directly tracked image features and a configurable wear-bar height.
| Stage | Module | What happens |
|---|---|---|
| A | frame_extractor.py |
Decode the video, sample every N-th frame, convert to grayscale, crop to the tread ROI. |
| B | groove_detector.py |
On the head-on frame only: find the groove lip pairs geometrically. |
| C | lip_tracker.py |
Propagate those lips through time with Lucas–Kanade optical flow; fit a curve to each lip per frame. |
| D | twi_tracker.py |
Locate each TWI by template matching on the head-on frame, then track it with a MIL tracker. |
| E | pipeline.py |
Extract |
| F | depth_estimator.py |
Apply the IQR gate, fit the sine model for |
A. Sampling and ROI. Frames are decoded, sampled at a fixed step, grayscaled and cropped to a rectangle over the tread face. Working on a crop keeps the 1D intensity profiles meaningful and keeps the run fast.
B. Head-on initialization — geometry, once. Full geometric detection runs on the frame where the camera is closest to perpendicular and the groove edges are clearest. The resulting geometry initializes both forward and backward tracking through the sequence:
- Band isolation — only a narrow horizontal band through the vertical center of the ROI is used, which limits the effect of distortion near the top and bottom of a wide-angle frame.
- Canny + probabilistic Hough (
cv2.HoughLinesP) with a generous ±45° tolerance, so diagonal and oriented tread patterns still register as "vertical" groove edges. Segment x-positions are then clustered. - Rotation-invariant polarity check — to decide whether an edge is a left lip (bright surface → dark groove) or a right lip (dark → bright), the detector samples pixels on either side of each clustered x-position at the band center. This keeps the check orientation-agnostic for diagonal edges.
- Surface thresholding — a likely lip should border the brighter top surface, so each candidate is required to be at least 75% as bright as the tread surface (estimated as the 75th percentile of the intensity profile) on its outer side. This helps reject internal shadows and bright bumps inside the groove.
- Pairing — left and right lips are matched under min/max width constraints to reduce pairing across adjacent grooves; if too many candidates survive, the strongest by combined edge prominence are kept.
C. Temporal lip tracking. For each lip, five tracking points are spread across the central 80% of the ROI and pushed through the sequence with pyramidal Lucas–Kanade (cv2.calcOpticalFlowPyrLK), both forward and backward from the head-on frame, so error accumulates outward from the best-conditioned frame instead of from one end of the clip. The current curve uses LK-valid points, while previous point locations remain as tracker state for the next update.
Because a tire is toroidal and the lens is wide, the grooves are visibly curved in the image — they are not straight lines. So each lip is re-fitted every frame as a second-degree polynomial
D. TWI localization and tracking. The TWI in each groove is found on the head-on frame by normalized cross-correlation (cv2.matchTemplate) against a small template crop. The search is masked to that groove's lip interval, preserving groove identity. From there each TWI is tracked with cv2.TrackerMIL, again forward and backward from the head-on frame.
E. Parallax extraction at matched height. The grooves are slanted and curved, so comparing a surface midpoint taken at the top of the ROI against a TWI sitting 300 px lower injects a large artificial offset that has nothing to do with depth. Instead, for every frame the tracked TWI's own
F. Robust frame selection. Before fitting, the pipeline applies a median/MAD gate to the tracked surface and TWI positions, followed by an interquartile-range gate on
G. Fit and conversion. scipy.optimize.curve_fit solves for
Input. A 17.9 s, 1620×1080 @ 30 fps camera pan of a stationary tire on a shop lift. The pipeline uses a 570×400 px ROI over the tread and samples every third frame in the configured 0–380 window: 127 frames, from index 0 through 378.
The clip sampled every ~2.5 s. The camera moves from an oblique view on one side, through head-on around frame 190, to an oblique view from the other. The change in viewpoint provides the parallax cue.
The fixed tread ROI (red) at the start, middle and end of the processed window. It has to contain all four grooves across the whole sweep, which is exactly what src/debug_roi.py is for.
Result scope. In the following table, parallax, residuals, and R² come directly from the tracked footage. The millimeter column applies the documented example calibration. All four grooves produce coherent fits with sub-pixel residuals:
| Groove | Estimated depth (mm) | Fit residual (px) | R² of sine fit | Frames used |
|---|---|---|---|---|
tread_1 (shoulder) |
5.8 | 0.5 | 0.981 | 115 / 127 |
tread_2 |
6.4 | 0.6 | 0.983 | 118 / 127 |
tread_3 |
6.2 | 0.9 | 0.955 | 108 / 127 |
tread_4 (shoulder) |
5.3 | 0.6 | 0.962 | 102 / 127 |
This run uses $S = 0.20$ mm/px and a 60° source-video arc; see Calibration and outputs. Stdout reports depth and fit residual, while R² is computed for the diagnostic figure. The run takes about 20 seconds on a laptop CPU without a GPU.
Per-groove diagnostics regenerated as outputs/debug_fit.png:
What the numbers show. The fitted parallax amplitudes span 18–24 px, and all four grooves track the physical model closely: R² is 0.955–0.983, residual scatter is 0.5–0.9 px, and each fit retains 80–93% of the sampled frames. Under the example calibration, the pipeline produces per-groove estimates of 5.3–6.4 mm in a single CPU run.
The implementation separates video-derived measurements from capture-specific inputs. Tracking and model fitting recover the parallax signal from the clip; calibration supplies the geometry needed for metric conversion. This keeps the estimator reusable across capture setups without retraining.
| Role | Quantity | How it is supplied |
|---|---|---|
| Video measurement | Relative parallax |
Computed from the tracked surface and TWI positions. |
| Model output | Amplitude |
|
| Capture calibration | Scale |
From camera intrinsics and working distance, or from a known tread-plane dimension. Example setting: 0.20 mm/px. |
| Motion calibration | Source-video arc / |
From a fixed-rig sweep rate, encoder, IMU, or visual ego-motion. Example setting: 60° across the full source video. |
| Tire input | TWI height |
From the tire specification. Example setting: 1.6 mm. |
| Sequence reference | Head-on frame |
Selected from the clip or supplied by a fixed rig. Example setting: frame 190. |
For the documented clip, scale and source-video arc are example configuration values rather than measurements recovered from the footage. In a calibrated capture, those same fields hold measured rig values; the tracking and estimation code is unchanged.
This prototype uses an explicit projective-geometry model rather than a learned depth predictor. The relationship between observed motion and reported depth stays inspectable: each result includes the fitted amplitude, residual scatter, and a per-frame diagnostic plot.
The implementation runs on a CPU with OpenCV, NumPy, SciPy, and Matplotlib. It includes no pretrained depth weights, labeled-data requirement, or offline training pipeline. Capture-specific geometry is represented explicitly in Config: adapting the pipeline means setting the ROI, head-on frame, groove count, TWI template, scale, and camera arc rather than collecting data and retraining a model.
Several methods can contribute to tread-depth measurement. This qualitative design comparison shows the role each one naturally serves; it is not a measured benchmark.
| Approach | Strong fit when | Role in this design space |
|---|---|---|
| Parallax + TWI (this project) | A calibrated RGB sweep and visible TWI are available | Produces explicit per-groove geometry without task-specific training data. |
| SfM / multi-view triangulation | Surface texture supports stable correspondence across views | Reconstructs broader 3D structure, followed by a metric-scale step. |
| Monocular depth networks | A suitable target-domain model and metric anchor are available | Provides dense, single-frame depth inference. |
| Supervised regression (image → mm) | Representative gauge-labeled data cover the intended tires and camera setups | Learns a direct task-specific mapping from appearance to depth. |
| Structured light / laser triangulation | Dedicated calibrated hardware is part of the inspection station | Measures a direct tread profile with active illumination. |
| Occlusion geometry | A wide, controlled sweep produces a clear visibility boundary at the groove floor | Offers a compact geometric cue for compatible captures. |
- Python 3.10+ (the code uses PEP 604 type unions)
opencv-python,numpy,scipy,matplotlib— all inrequirements.txt
Verified on Python 3.13 with OpenCV 4.12, NumPy 2.2, SciPy 1.16, Matplotlib 3.10. CPU only; no GPU or external model weights are used.
pip install -r requirements.txtThe sample clip used throughout this README ships with the repository at assets/tire_pan_sample.mp4, so the documented run works straight after cloning. To use your own footage, point Config.video_path at it:
# config.py
video_path: str = "assets/my_tire_pan.mp4"All asset paths in config.py are relative to the project root, so run from there:
# optional: check that the ROI covers the tread across the whole pan
python src/debug_roi.py
# full pipeline
python src/pipeline.pyEverything generated at run time lands in outputs/:
| Output | Where |
|---|---|
| Depth table for every groove, with fit residuals | stdout |
| Per-groove parallax scatter + fitted model | outputs/debug_fit.png |
| Per-frame tracking overlays (lips, centerline, TWI, median/MAD-rejected tracks in magenta) | outputs/frames/ |
| ROI preview frames | outputs/debug_roi_*.png |
Tire Tread Depth Estimation Results (TWI Tracking)
Scale assumption : S = 0.2 mm/px (Z~350mm, f~1750px)
TWI Height offset: 1.6 mm
Orbital arc : 60.0 deg (assumed)
Head-on frame : 190
-----------------------------------------------
Groove Depth (mm) Fit residual (px)
-----------------------------------------------
tread_1 5.8 0.5
tread_2 6.4 0.6
tread_3 6.2 0.9
tread_4 5.3 0.6
-----------------------------------------------
.
├── README.md
├── config.py # every tunable parameter, one dataclass
├── requirements.txt
├── src/
│ ├── pipeline.py # orchestrator: run this
│ ├── frame_extractor.py # decode, sample, grayscale, ROI crop
│ ├── groove_detector.py # head-on lip detection (Canny + Hough + polarity)
│ ├── lip_tracker.py # Lucas–Kanade lip tracking + per-frame curve fit
│ ├── twi_tracker.py # TWI template match + MIL tracking
│ ├── depth_estimator.py # IQR filter, sine fit, px → mm
│ └── debug_roi.py # ROI preview utility
├── assets/ # static files only
│ ├── tire_pan_sample.mp4 # sample clip the documented example runs on
│ ├── twi_template.png # TWI crop used for template matching
│ ├── debug_fit.png # committed snapshot of the fits, shown above
│ └── fig_*.{png,jpg} # figures used in this README
└── outputs/ # everything a run generates (created on demand, gitignored)
├── debug_fit.png
├── debug_roi_*.png
└── frames/
The current implementation establishes the full path from a tracked monocular video to per-groove estimates and fit diagnostics. Further work can build validation, repeatability, and broader capture support around that core:
- Gauge-labeled evaluation — pair video runs with measurements across multiple tires, tread patterns, wear states, and capture conditions, then report absolute-error and repeatability distributions.
- Rendered end-to-end fixture — use known 3D geometry and camera motion to exercise groove detection, lip tracking, TWI matching, and estimation in one automated test.
- Broader capture coverage — validate the configuration workflow across tread patterns, camera geometries, and sweep speeds beyond the bundled example.
- Automatic setup — derive the tread ROI, groove count, and head-on frame from the sequence.
- Multi-reference TWI tracking — initialize several in-groove references and combine consistent tracks robustly.
- Generalized groove extraction — support additional tread geometries beyond approximately circumferential grooves.
-
Measured ego-motion — use an encoder, IMU, or background feature tracks so
$D_x$ is fit against measured viewing angle instead of frame index. - Capture characterization — quantify rolling-shutter effects across sweep speeds and select the appropriate sensor or motion profile.



