Skip to content

LK bilinear_interp blends border and interior pixels at the left/top edge (hit by even win_size since #144) #154

Description

@kalwalt

Fact

bilinear_interp (src/video/optical_flow.rs) clamps the base index to the image, but computes the interpolation weight from the unclamped coordinate:

let x0 = (x.floor() as i32).clamp(0, cols as i32 - 1) as usize;
let x1 = (x0 + 1).min(cols - 1);
let ax = x - x.floor();          // from the *unclamped* x

For a sample left of (or above) the image this blends the border pixel with its interior neighbour instead of replicating the border:

sample x floor x0, x1 ax result border-replicate would give
−0.5 −1 0, 1 0.5 0.5·p0 + 0.5·p1 p0
−1.5 −2 0, 1 0.5 0.5·p0 + 0.5·p1 p0
−1.0 −1 0, 1 0.0 p0 p0

The right/bottom edge is unaffected: there x0 = x1 = cols − 1, so any weight yields the border pixel. The behaviour is therefore asymmetric between edges, and at the top/left it matches neither replicate nor any other standard border mode. The same applies to y.

When it triggers

Any LK sample that lands at a negative non-integer coordinate:

Affected quantities: the gradient matrix H (so min_eigen and the determinant guard, and with them the lost/tracked decision), the Newton mismatch b, and the tracking error. Qodo's review of #153 gives a concrete case: a 4×4 window at (0, 0) on an x + y ramp gets a minimum eigenvalue below the default 1e-4 threshold, where border-replicated sampling would keep it above.

Suggested fix

Clamp the coordinate before deriving indices and weights, which gives consistent border replication on all four edges:

let x = x.clamp(0.0, (cols - 1) as f32);
let y = y.clamp(0.0, (rows - 1) as f32);
// then floor / x0 / x1 / ax exactly as today

Regression tests (hand-derivable):

  • a sample at x = −0.5 (and −1.5) must equal the border pixel p0;
  • an even window (e.g. 4×4) at an integer corner point: min_eigen / status must match border-replicated sampling;
  • the existing right/bottom behaviour must stay unchanged.

Related: OpenCV parity at the border (separate, larger)

Clamping gives replicate semantics, which fixes the inconsistency but is not full OpenCV parity. As far as I know, OpenCV's calcOpticalFlowPyrLK samples from pyramid levels padded by winSize with border extrapolation (BORDER_REFLECT_101 for the image) and marks a point lost at level 0 when its window falls outside the padded area (iprevPt.x < -winSize.width || ... in lkpyramid.cpp). This should be checked against the source before acting on it. Worth its own issue if exact border parity matters; the fix above stands on its own.

Found in Qodo's review of the v0.9.0 release PR (#153, finding 4). Verified by reading bilinear_interp on dev (ef13ed8).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions