OpenCV 4 vs 5: The Full Tutorial

blog-image

A field guide for developers who like their computer vision stable and their release notes honest.


Table of contents

  1. Why this tutorial exists
  2. Lab setup with uv
  3. Chapter 01 — Hello, OpenCV
  4. Chapter 02 — The boring 90% (that’s good)
  5. Chapter 03 — 1D arrays: the .rows trap
  6. Chapter 04 — Resize: Pillow peace treaty
  7. Chapter 05 — DNN: three engines, one API
  8. Chapter 06 — Text rendering glow-up
  9. Chapter 07 — Timing benchmarks
  10. Chapter 08 — Modules that moved
  11. Chapter 09 — Fails on 4, works on 5
  12. Chapter 10 — Pixel-diff heatmaps
  13. Chapter 11 — Will this break? (quiz)
  14. Upgrade decision tree
  15. Migration cheat sheet
  16. What we did not cover (yet)

1. Why this tutorial exists

OpenCV 5 dropped in June 2026. The marketing says “biggest leap in years.” The migration guide says “most existing code will require only minor adjustments.” Both can be true — like how moving apartments is “minor” if you only own a toothbrush and a dream.

The companion repo — gsiogkas/opencv_4v5_python_comparison — lets you prove it to yourself with:

  • Two uv environments — OpenCV 4.13 and 5.0, never touching
  • Paired Python scripts — same chapter, different folder
  • Snapshots & heatmaps — PNG evidence in snapshots/
  • Timings — because anecdotes are not benchmarks
  • A quiz + decision tree — so you leave with a plan, not just vibes

The headline differences (TL;DR)

AreaOpenCV 4.xOpenCV 5.x
DNN ONNX coverage~22% of operators80%+
DNN engineClassic layer walkerGraph engine + classic fallback + optional ORT
1D arrays(N, 1) column vectorsTrue (N,) 1D
cv2.ml, Haar, HOGIn main wheelMoved to contrib
Caffe/Darknet loadersAvailableRemoved — use ONNX
Text (putText)Hershey vector fontsTrueType + FontFace + Unicode
C API (CvMat, etc.)Deprecated but presentGone
Python importscv2.*Still cv2.*

2. Lab setup with uv

Clone the lab first:

git clone https://github.com/gsiogkas/opencv_4v5_python_comparison.git
cd opencv_4v5_python_comparison

Then sync both environments and run chapters:

cd envs/opencv4 && uv sync
cd ../opencv5 && uv sync
cd ../..

./scripts/run_all.sh          # everything
./scripts/run.sh 09_dnn_fails_works opencv4 fails.py
./scripts/run.sh 11_will_this_break opencv5 quiz.py   # interactive quiz
envs/opencv4/pyproject.toml   → opencv-python==4.13.0.92
envs/opencv5/pyproject.toml   → opencv-python==5.0.0.93

Each uv sync creates its own .venv. They do not know about each other. This is intentional and beautiful.


3. Chapter 01 — Hello, OpenCV

Scripts: examples/01_version_hello/opencv{4,5}/hello.py

Blur a checkerboard, print the version, wave politely.

OpenCV 4.13.0  — GaussianBlur ~0.07 ms — cv2.ml present: True
OpenCV 5.0.0   — GaussianBlur ~0.08 ms — cv2.ml present: False, FontFace: True

The import line didn’t change. What changed lives under cv2.


4. Chapter 02 — The boring 90% (that’s good)

Scripts: examples/02_core_image_ops/opencv{4,5}/pipeline.py

gray   = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges  = cv2.Canny(gray, 80, 160)
result = cv2.resize(edges, (320, 240), interpolation=cv2.INTER_AREA)

If your app is mostly imread → filters → contours, congratulations: you are the migration guide’s target demographic.


5. Chapter 03 — 1D arrays: the .rows trap

Scripts: examples/03_1d_arrays/opencv{4,5}/arrays.py

VersionShape of length-5 vector
OpenCV 4.x(5, 1) — 2D column
OpenCV 5.x(5,) — true 1D

Rule: never use .rows to mean “number of elements.” Use .size (NumPy) or .total() (C++).

Need the old layout? arr.reshape(-1, 1).


6. Chapter 04 — Resize: Pillow peace treaty

Scripts: examples/04_resize_nearest/opencv{4,5}/resize.py

OpenCV 5 aligns INTER_NEAREST with Pillow’s rounding rules. If you have golden-file tests for resize, regenerate baselines when upgrading — even when 4-vs-5 looks identical on a particular image, your production fixtures may not.


7. Chapter 05 — DNN: three engines, one API

Scripts: examples/05_dnn_onnx/opencv{4,5}/…

ConstantMeaning
ENGINE_CLASSICOld 4.x engine (CUDA/OpenVINO live here)
ENGINE_NEWGraph engine (CPU for now)
ENGINE_AUTODefault — new first, classic fallback
ENGINE_ORTONNX Runtime (build-dependent)
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_CLASSIC)

readNetFromCaffe / readNetFromDarknet are gone in 5.x — convert to ONNX.

For the dramatic “this model won’t even load” story, skip ahead to Chapter 09.


8. Chapter 06 — Text rendering glow-up

Scripts: examples/06_text_rendering/opencv{4,5}/text.py

Legacy FONT_HERSHEY_* still works on OpenCV 5, but it renders through the new TrueType engine (Rubik). ~14% of pixels differed on our panel — enough to torch a pixel-exact CI job.

New API:

font = cv2.FontFace("sans")
cv2.putText(img, "你好 🎉", (10, 50), (255, 255, 255), font, 28)

9. Chapter 07 — Timing benchmarks

Scripts: examples/07_timing_benchmarks/opencv{4,5}/benchmark.py

OperationOpenCV 4.13OpenCV 5.0Winner
GaussianBlur 31×312.183 ms2.362 ms4 (~8%)
resize 0.5× LINEAR0.031 ms0.023 ms5 (~26%)
warpAffine0.934 ms0.544 ms5 (~42%)
morphologyEx GRADIENT1.612 ms0.911 ms5 (~43%)

OpenCV 5’s revised warp/morph paths shine here. Re-run on your hardware:

./scripts/run.sh 07_timing_benchmarks opencv4 benchmark.py
./scripts/run.sh 07_timing_benchmarks opencv5 benchmark.py

10. Chapter 08 — Modules that moved

Scripts: examples/08_modules_moved/opencv{4,5}/modules.py

API4.135.0
cv2.ml✗ → contrib
CascadeClassifier✗ → contrib
HOGDescriptor✗ → contrib
SIFT / ORB / findHomography
FaceDetectorYNvaries
FontFace / ENGINE_AUTO

Python still calls cv2.findHomography(...) — C++ headers moved (calib3dgeometry/calib/stereo); the binding layer shrugged.


11. Chapter 09 — Fails on 4, works on 5

Scripts:

  • examples/09_dnn_fails_works/opencv4/fails.py
  • examples/09_dnn_fails_works/opencv5/works.py

This is the shareable chapter. We built ONNX graphs using ops that transformers actually need:

  • Trilu — causal attention mask → Softmax
  • ScatterND — sparse tensor updates

Measured on this machine:

ModelOpenCV 4.13OpenCV 5.0
Trilu causal maskUnsupported ONNX op: Trilu✓ forward ~0.44 ms, shape (1,8,8)
ScatterND updateUnsupported ONNX op: ScatterND[0, 9, 0, 8, 0, 7, 0, 0]
./scripts/run.sh 09_dnn_fails_works opencv4 fails.py
./scripts/run.sh 09_dnn_fails_works opencv5 works.py

OpenCV 4’s ~22% ONNX coverage is not a vibe — it’s a brick wall.
OpenCV 5’s 80%+ coverage is why your transformer finally loads.


12. Chapter 10 — Pixel-diff heatmaps

Scripts: examples/10_pixel_diffs/opencv{4,5}/fixtures.py
Builder: scripts/make_heatmaps.py

We generate matching fixtures on both versions, then paint where pixels disagree.

Text (putText) — golden-test trauma

7.9% of pixels differed on the chapter-10 text panel (max Δ = 205). If your CI asserts np.array_equal, it will fail. That is a feature of the upgrade, not a bug in your pipeline.

Warp (warpAffine bilinear) — quiet numeric drift

Only ~0.7% of pixels moved, and max Δ was 3. Easy to miss in a casual glance; deadly for pixel-exact baselines. Matches the migration guide: revised bilinear/bicubic warping is more accurate and slightly different.

Nearest resize (this checkerboard)

On this synthetic board, 4 vs 5 nearest output was identical (0 differing pixels). Spec alignment with Pillow still matters for other sizes/assets — see chapter 04 and regenerate your goldens.

./scripts/run.sh 10_pixel_diffs opencv4 fixtures.py
./scripts/run.sh 10_pixel_diffs opencv5 fixtures.py
cd envs/opencv4 && uv run python ../../scripts/make_heatmaps.py

Full stats: pixel_diff_report.md.


13. Chapter 11 — Will this break? (quiz)

Script: examples/11_will_this_break/opencv{4,5}/quiz.py

Ten multiple-choice questions. Play interactively, or dump the key:

# Interactive (needs a real terminal)
./scripts/run.sh 11_will_this_break opencv5 quiz.py

# Answer key / CI-friendly
./scripts/run.sh 11_will_this_break opencv5 quiz.py --answers

# Live API probes that back the quiz narrative
./scripts/run.sh 11_will_this_break opencv4 quiz.py --check
./scripts/run.sh 11_will_this_break opencv5 quiz.py --check

Sample probes from our run:

ProbeOpenCV 4OpenCV 5
imread
cv2.ml
CascadeClassifier
readNetFromDarknet
FontFace
ENGINE_AUTO

Scoring rubric (honor system): 10/10 upgrade with swagger · 7–9 read ch.09 + decision tree · <7 re-run the lab, you found why it exists.


14. Upgrade decision tree

Stakeholder one-liner: OpenCV 5 is safe for most Python vision apps, mandatory if modern ONNX is blocking you, and a golden-test chore if your CI is pixel-exact.


15. Migration cheat sheet

Do nothing (probably fine)

  • imread, imwrite, cvtColor, resize, threshold, GaussianBlur
  • findContours, Canny, SIFT, ORB
  • cv2.dnn.readNetFromONNX (same call; engine may differ)
  • Most geometry/calib via cv2.* in Python

Check / update

  • Code assuming 1D data is shape (N, 1) → use .size or reshape
  • Golden tests for resize / warp / putText → see heatmaps in ch.10
  • VideoCapture.get() — unsupported props return -1 in 5.x
  • DNN parity — try ENGINE_CLASSIC if outputs drift

Replace / remove

  • Caffe/Darknet loaders → ONNX
  • cv2.ml.* / Haar / HOG → contrib or DNN/sklearn
  • Legacy C API → C++ API
  • Build: C++17 minimum

16. What we did not cover (yet)

  • LLM/VLM inference inside OpenCV 5
  • LaMa inpainting / LightGlue matchers
  • ptcloud module
  • Native FP16/BF16 Mat types
  • ORT GPU execution providers

PRs welcome — especially with bad puns.


Epilogue

OpenCV 5 is not a rewrite of everything you know. It’s a surgical modernization: kill the C API, fix DNN, align resize with the ecosystem, move classical ML to contrib, and give putText a font that doesn’t look like 1998.

./scripts/run_all.sh
./scripts/run.sh 11_will_this_break opencv5 quiz.py

May your contours be closed and your ONNX graphs acyclic.


Tutorial code: gsiogkas/opencv_4v5_python_comparison (MIT). OpenCV: Apache 2 / BSD.

comments powered by Disqus