Skip to content

opencv5 migration: dual-version (4/5) compatibility #44

Description

@petercorke

MVTB currently defensively pins opencv-python<5.0.0 / opencv-contrib-python<5.0.0 in
pyproject.toml. This isn't just "prepare for a future release" — it already broke CI for
real once: on 2026-07-29, conda-forge's unpinned opencv conda package started resolving to
5.0.0, and CI failed across the whole matrix (every OS × Python version). Confirmed causes at
the time: cv2.BRISK_create relocation, cv2.aruco.estimatePoseSingleMarkers removal, and
"MSER indexing return shape changed" (this last one is disputed — see below). That incident
was patched reactively by pinning conda's opencv<5 to match pyproject.toml's existing
pip-level pin, but the underlying problem (CI's conda/micromamba path and the pip-installable
package path can silently diverge — see the separate conda/micromamba tech-debt issue) was
never fixed, so the same failure mode can recur the moment that pin is forgotten or another
conda-forge quirk resurfaces.

Every finding below was verified empirically (not just read off OpenCV's own migration guide):
opencv-contrib-python==5.0.0.93 installed in a throwaway venv, diffed function-by-function
against a real 4.13.0 install.

Confirmed technical findings

Feature detector relocationBRISK_create, AKAZE_create, KAZE_create moved from
cv2 to cv2.xfeatures2d in OpenCV 5 (fully functional there in the standard
opencv-contrib-python wheel, verified via real detectAndCompute() calls). SIFT, ORB
stay in main cv2 in both versions, unaffected. Affects
src/machinevisiontoolbox/ImagePointFeatures.py's detector-factory dict.

Singleton middle-dimension removal ("1D array semantics" change) — functions wrapping
std::vector<T> now produce true (N, M) arrays instead of the old (N, 1, M) convention.
Not universal — verified case by case, do not apply a blanket fix everywhere.

Confirmed affected (hard crashes on v5 today):

  • src/machinevisiontoolbox/ImageLineFeatures.py:160lines[:, 0, :] after
    cv2.HoughLinesP; v4 output (N,1,4), v5 output (N,4)IndexError.
  • src/machinevisiontoolbox/ImageFiducials.py:145-149id[0] on the ids array from
    ArucoDetector.detectMarkers(); v4 shape (N,1), v5 shape flat (N,)IndexError.
  • src/machinevisiontoolbox/Camera.py:2230,2236findChessboardCorners/cornerSubPix
    shape changes (49,1,2)(49,2); may not currently break functionally (no
    shape-dependent indexing downstream) but should be normalized defensively.

Confirmed not affected (verified empirically, no fix needed): findContours,
approxPolyDP, convexHull, HoughLines, projectPoints, minAreaRect, fitEllipse,
minEnclosingCircle, boxPoints, solvePnP, Rodrigues, findHomography (+ mask),
decomposeHomographyMat, findEssentialMat/recoverPose, findFundamentalMat, MSER
detectRegions output (verified identical (M,2) in both versions).

Discrepancy to resolve, not silently drop: the historical CI incident's third confirmed
cause, "MSER indexing return shape changed," doesn't match this empirical finding. Either the
original CI failure was actually about something else MSER-adjacent, or it's already been
independently fixed/was a red herring — re-check before assuming MSER needs no work.

Hard removal, no relocationcv2.aruco.estimatePoseSingleMarkers (used in
ImageFiducials.py:142-146) is completely gone in OpenCV 5, not moved. Needs a real
reimplementation via cv2.solvePnP(..., flags=cv2.SOLVEPNP_IPPE_SQUARE) per marker (OpenCV's
own documented replacement for planar square markers) — this replacement works unchanged on
OpenCV 4 too, so no version branching needed.

Lower-priority, verify not necessarily fix:

  • Numeric drift in warpAffine/warpPerspective/remap (revised interpolation, expected
    benign) — audit tests for exact-equality assertions on warped pixel data.
  • cv2.VideoCapture.get() returns -1 instead of 0 for unsupported properties on v5 — check
    Sources.py for == 0 comparisons that should be <= 0 or in (0, -1).
  • FONT_HERSHEY_* renders via a different embedded font on v5 — affects any exact pixel-diff
    test on annotated/labelled images.

Plan

  1. Remove conda/micromamba from ci.yml first (see the separate tech-debt issue) — this closes
    the two-disconnected-sourcing-paths gap that let the original incident happen, and makes the
    dual-version test matrix in step 4 trivial (plain pip version pins, no conda solver).
  2. Cut a fresh branch off main for the actual compat fixes rather than reviving the stalled,
    untested opencv5 branch.
  3. Apply the fixes above, each with a loud inline comment and its own regression test, verified
    against both a real OpenCV 4.13 and a real OpenCV 5.0.0 environment.
  4. Relax pyproject.toml's opencv pin to a range supporting both (e.g. >=4.5,<6.0.0), add an
    OpenCV-version axis to the CI matrix (pinning specific tested versions, not an open range),
    keep the empirical probe scripts used to verify all of this as a checked-in, reusable dev
    tool, and track opencv-python/opencv-contrib-python in Dependabot (without auto-merge).

Verification

Full test suite run under both a real OpenCV 4.13 and a real OpenCV 5.0.0 environment before
considering this done, and confirm the new CI matrix actually catches a deliberately
reintroduced version of one of these bugs before trusting it as a safety net.

Metadata

Metadata

Assignees

No one assigned

    Labels

    tech-debtKnown technical debt / deferred cleanup, not a live bug

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions