A field guide for developers who like their computer vision stable and their release notes honest.
Table of contents
- Why this tutorial exists
- Lab setup with uv
- Chapter 01 — Hello, OpenCV
- Chapter 02 — The boring 90% (that’s good)
- Chapter 03 — 1D arrays: the
.rowstrap - Chapter 04 — Resize: Pillow peace treaty
- Chapter 05 — DNN: three engines, one API
- Chapter 06 — Text rendering glow-up
- Chapter 07 — Timing benchmarks
- Chapter 08 — Modules that moved
- Chapter 09 — Fails on 4, works on 5
- Chapter 10 — Pixel-diff heatmaps
- Chapter 11 — Will this break? (quiz)
- Upgrade decision tree
- Migration cheat sheet
- 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)
| Area | OpenCV 4.x | OpenCV 5.x |
|---|---|---|
| DNN ONNX coverage | ~22% of operators | 80%+ |
| DNN engine | Classic layer walker | Graph engine + classic fallback + optional ORT |
| 1D arrays | (N, 1) column vectors | True (N,) 1D |
cv2.ml, Haar, HOG | In main wheel | Moved to contrib |
| Caffe/Darknet loaders | Available | Removed — use ONNX |
Text (putText) | Hershey vector fonts | TrueType + FontFace + Unicode |
C API (CvMat, etc.) | Deprecated but present | Gone |
| Python imports | cv2.* | 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
| Version | Shape of length-5 vector |
|---|---|
| OpenCV 4.x | (5, 1) — 2D column |
| OpenCV 5.x | (5,) — true 1D |
Rule: never use
.rowsto 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}/…
| Constant | Meaning |
|---|---|
ENGINE_CLASSIC | Old 4.x engine (CUDA/OpenVINO live here) |
ENGINE_NEW | Graph engine (CPU for now) |
ENGINE_AUTO | Default — new first, classic fallback |
ENGINE_ORT | ONNX 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

| Operation | OpenCV 4.13 | OpenCV 5.0 | Winner |
|---|---|---|---|
| GaussianBlur 31×31 | 2.183 ms | 2.362 ms | 4 (~8%) |
| resize 0.5× LINEAR | 0.031 ms | 0.023 ms | 5 (~26%) |
| warpAffine | 0.934 ms | 0.544 ms | 5 (~42%) |
| morphologyEx GRADIENT | 1.612 ms | 0.911 ms | 5 (~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

| API | 4.13 | 5.0 |
|---|---|---|
cv2.ml | ✓ | ✗ → contrib |
CascadeClassifier | ✓ | ✗ → contrib |
HOGDescriptor | ✓ | ✗ → contrib |
SIFT / ORB / findHomography | ✓ | ✓ |
FaceDetectorYN | varies | ✓ |
FontFace / ENGINE_AUTO | ✗ | ✓ |
Python still calls cv2.findHomography(...) — C++ headers moved (calib3d → geometry/calib/stereo); the binding layer shrugged.
11. Chapter 09 — Fails on 4, works on 5
Scripts:
examples/09_dnn_fails_works/opencv4/fails.pyexamples/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 → SoftmaxScatterND— sparse tensor updates

Measured on this machine:
| Model | OpenCV 4.13 | OpenCV 5.0 |
|---|---|---|
| Trilu causal mask | ✗ Unsupported ONNX op: Trilu | ✓ forward ~0.44 ms, shape (1,8,8) |
| ScatterND update | ✗ Unsupported 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:
| Probe | OpenCV 4 | OpenCV 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,GaussianBlurfindContours,Canny,SIFT,ORBcv2.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.sizeor 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_CLASSICif 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
ptcloudmodule- Native FP16/BF16
Mattypes - 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.
