OpenCV 5.0 landed on June 4, 2026, and the 4.x to 5.x jump is the first OpenCV upgrade in years that can break a working build. The good news: most of the breakage is mechanical, and the official migration guide documents every change. This is the checklist we run on client codebases, in the order that catches problems earliest.
1. Bump the toolchain to C++17
OpenCV 5.0 requires C++17: GCC 8 (7.x with caveats), Clang 9, or MSVC 2017 19.14 and later, and Python 3.6+ (Python 2 is gone). Fix CMake first, because nothing else compiles until you do:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenCV 5 REQUIRED)
target_link_libraries(app PRIVATE ${OpenCV_LIBS})
On embedded targets, check what compiler the BSP actually ships before you plan anything else. A board stuck on GCC 7 turns a two-week port into a BSP upgrade project.
2. Find the legacy C API
The 1.x C API (CvMat, IplImage, cvCreateMat, cvFindContours, cvReleaseImage...) has been fully removed. Most code written in the last decade is clean, but ported-from-2010 modules hide C calls in surprising places. Inventory them before touching anything:
grep -rnE '\b(IplImage|CvMat|CvSeq|CvMemStorage|CvPoint|cvCreate|cvRelease|cvFind|cvCvt|cvLoad|cvSave|cvShow|cvNamed|cvWait)' \
--include='*.c' --include='*.cpp' --include='*.h' --include='*.hpp' src/ | sort > c_api_inventory.txt
wc -l c_api_inventory.txt
Each hit maps to a cv::Mat equivalent. The CV_8U-style type macros still exist, so those lines are fine. The old 2.x transition guide in the 4.x docs remains the best reference for the C to C++ mapping.
3. Relocate moved modules
Several modules changed homes. Update includes and Java imports; Python needs no changes because everything stays under cv2.
| 4.x | 5.x |
|---|---|
calib3d (calibrateCamera, stereoCalibrate) | calib |
calib3d (StereoBM, StereoSGBM, reprojectImageTo3D) | stereo |
calib3d (findHomography, solvePnP, estimateAffine*) | geometry |
imgproc (convexHull, minAreaRect, fitEllipse, Subdiv2D) | geometry |
features2d | features |
ml, gapi | opencv_contrib |
CascadeClassifier, HOGDescriptor | opencv_contrib (xobjdetect) |
| SURF, BRIEF, FREAK, DAISY | opencv_contrib (xfeatures2d) |
opencv2/calib3d.hpp still exists as an umbrella header, so C++ code keeps compiling; switch to the specific headers in new code. Java is the exception: org.opencv.calib3d.Calib3d must become org.opencv.geometry.Geometry / org.opencv.calib.Calib, and org.opencv.features2d.* becomes org.opencv.features.*.
If you use Haar cascades, HOG, cv::ml or G-API, your build now needs contrib:
git clone https://github.com/opencv/opencv_contrib.git
cmake -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules \
-DBUILD_LIST=core,imgproc,imgcodecs,videoio,dnn,features,geometry,calib,stereo,xobjdetect,ml \
../opencv
For Python, cv2.CascadeClassifier is a good moment to move to the DNN face detector in the main repo:
import cv2
det = cv2.FaceDetectorYN.create("face_detection_yunet.onnx", "", (320, 320))
det.setInputSize((img.shape[1], img.shape[0]))
_, faces = det.detect(img) # rows: x, y, w, h, 5 landmarks, score
4. Audit 1D array assumptions
This one is subtle and bites Python and C++ alike. In 4.x, cv::Mat(std::vector<float>) produced an Nx1 column; in 5.x it is a true 1D array:
std::vector<float> v = {1.f, 2.f, 3.f};
cv::Mat m(v);
// 4.x: dims == 2, rows == 3, cols == 1
// 5.x: dims == 1, rows == 1, cols == 3
Code that reads .rows or .cols on such a Mat is now wrong; .total() and m.at<float>(i) are correct in both versions. If you truly need the old layout:
cv::Mat col = cv::Mat(v).reshape(1, (int)v.size());
In Python, a 1D np.array passed as InputArray maps to a 1D Mat, so anything that assumed a column vector needs the same check. Grep for .rows, .cols and .shape[1] near vector-backed inputs.
5. Extend type() / depth() switches
Five element types are new: CV_16BF, CV_32U, CV_64U, CV_64S, CV_Bool. Any switch (mat.depth()) without a default: now silently mishandles them. Add the cases, or at minimum:
default:
CV_Error(cv::Error::StsUnsupportedFormat, "Unsupported depth");
CV_Bool matrices are now accepted as masks anywhere a CV_8U mask was.
6. Replace Caffe and Darknet loaders
readNetFromCaffe() and readNetFromDarknet() are gone. Convert to ONNX and load with readNetFromONNX() — we cover the conversion paths in Modernizing legacy Caffe/Darknet pipelines to ONNX. TFLite models still load through the classic engine with no changes.
7. Decide on a DNN engine per model
readNet* now defaults to ENGINE_AUTO: try the new graph engine, fall back to the classic one. The new engine is CPU-only in 5.0, so GPU pipelines must pin the classic engine:
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_CLASSIC)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_CLASSIC);
OPENCV_FORCE_DNN_ENGINE=1|2|3|4 (classic, new, auto, ONNX Runtime) lets you test each engine without a rebuild. Run every model through both engines and diff the outputs before you trust AUTO in production.
8. Regenerate numeric baselines deliberately
Three changes alter pixel output and will fail any pixel-exact test:
INTER_NEARESTnow matches Pillow (and equalsINTER_NEAREST_EXACT).warpAffine,warpPerspectiveandremapuse revised bilinear/bicubic interpolation without lookup-table approximations — more accurate, slightly different.putTextrenders through a new TrueType engine (Rubik by default) even for the legacyFONT_HERSHEY_*calls, so text size and appearance change.
Also, VideoCapture::get() returns -1 (test with < 0) for unsupported properties instead of 0.
Do not blanket-regenerate golden images. Run the suite, review each diff, confirm it is interpolation or text and not a logic error, then regenerate.
9. Rebuild: install matrix
- pip:
pip install "opencv-python>=5.0"(oropencv-contrib-pythonwhen you need the contrib modules). NumPy 2.x is supported. - conda:
conda install -c conda-forge opencv=5. - CMake from source: the snippet in step 3; add
-DWITH_CUDA=ON -DCUDA_ARCH_BIN=8.7on Jetson Orin,-DWITH_ONNXRUNTIME=ONif you want the bundled ONNX Runtime engine.
10. Verify parity
Before the switch ships, run accuracy and latency parity on real hardware: same inputs, 4.x and 5.x side by side, per model and per image-processing stage you flagged in step 8. A small harness that loads both builds in separate virtualenvs and diffs outputs with np.allclose(atol=...) pays for itself on the first regression it catches.
Need this done against a codebase with years of history? See our OpenCV 5 migration services.