+1 (415) 360-7596

Migrating from OpenCV 4.x to 5.0: a practical checklist

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.x5.x
calib3d (calibrateCamera, stereoCalibrate)calib
calib3d (StereoBM, StereoSGBM, reprojectImageTo3D)stereo
calib3d (findHomography, solvePnP, estimateAffine*)geometry
imgproc (convexHull, minAreaRect, fitEllipse, Subdiv2D)geometry
features2dfeatures
ml, gapiopencv_contrib
CascadeClassifier, HOGDescriptoropencv_contrib (xobjdetect)
SURF, BRIEF, FREAK, DAISYopencv_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_NEAREST now matches Pillow (and equals INTER_NEAREST_EXACT).
  • warpAffine, warpPerspective and remap use revised bilinear/bicubic interpolation without lookup-table approximations — more accurate, slightly different.
  • putText renders through a new TrueType engine (Rubik by default) even for the legacy FONT_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" (or opencv-contrib-python when 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.7 on Jetson Orin, -DWITH_ONNXRUNTIME=ON if 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.