# OpenCV 5 Image & Video I/O: imread, VideoCapture, VideoWriter

> Learn OpenCV 5 image and video I/O in C++ with imread, imwrite, VideoCapture and VideoWriter—including silent-failure checks and complete code.

- Canonical URL: https://www.funvisiontutorials.com/blog/opencv-5-image-video-io-imread-videocapture/
- Author: Vladimir Kucera
- Published: 2026-07-23
- Updated: 2026-09-11
- Topics: OpenCV basics

OpenCV image and video I/O looks simple—until `imread` returns an empty `cv::Mat` or `VideoWriter` silently creates a 0-byte file. This practical C++ guide shows the checks that prevent those failures.

You’ll learn `imread`, `imshow`, `imwrite`, `VideoCapture` and `VideoWriter` in OpenCV 5, with complete buildable code.

## 1\. Loading Images — And the Check I Never Skip

```cpp
#include <opencv2/opencv.hpp>
using namespace cv;

Mat img = imread("photo.jpg");

if (img.empty()) {
    std::cerr << "Could not read photo.jpg" << std::endl;
    return -1;
}
```

`imread` never throws. Wrong path, corrupted file, unsupported format — you just get an empty Mat, and the crash happens later, somewhere else. Always check `empty()`.

Need grayscale directly? Pass the flag and skip a conversion:

```cpp
Mat gray = imread("photo.jpg", IMREAD_GRAYSCALE);
```

## 2\. Displaying — Why waitKey Is Not Optional

```cpp
imshow("My photo", img);
waitKey(0);
```

I used to forget `waitKey()` and wonder why the window flashed and disappeared. The truth: `imshow` only queues the drawing — `waitKey` is what actually pumps the GUI events. No `waitKey`, no window.

## 3\. Saving with imwrite

```cpp
imwrite("output.png", img);  // lossless
imwrite("output.jpg", img, {IMWRITE_JPEG_QUALITY, 90});
```

-   `.png` → lossless, bigger files, my default for intermediate results
-   `.jpg` → compressed, quality flag 0–100

The codec is chosen purely by the file extension. Simple, but easy to forget.

## 4\. Video Capture — Files and Cameras, Same API

```cpp
VideoCapture cap(0);              // webcam
// VideoCapture cap("movie.mp4"); // or a file — identical code below

if (!cap.isOpened()) {
    std::cerr << "Cannot open video source" << std::endl;
    return -1;
}

Mat frame;
while (cap.read(frame)) {
    imshow("Video", frame);
    if (waitKey(1) == 'q') break;
}
```

What clicked for me: video is just a loop over Mats. `cap.read()` returns false at end-of-file or on camera failure — that's your natural exit condition.

## 5\. The Trap I Fell Into: VideoWriter Frame Size

Here is the weekend-ruining bug I mentioned:

```cpp
int w = (int)cap.get(CAP_PROP_FRAME_WIDTH);
int h = (int)cap.get(CAP_PROP_FRAME_HEIGHT);
double fps = cap.get(CAP_PROP_FPS);
if (fps <= 0) fps = 30.0;  // webcams often report 0!

VideoWriter out("rec.mp4",
                VideoWriter::fourcc('m','p','4','v'),
                fps,
                Size(w, h));

while (cap.read(frame)) {
    out.write(frame);  // frame size MUST match Size(w, h)
}
```

If the frame you write has a different size than the one you promised in the constructor, `VideoWriter` writes nothing — no error, no warning, just an empty file. Now I always assert the sizes match before a long recording.

## 6\. Full Code Example + Build Instructions

My `CMakeLists.txt` (set the directory where OpenCV is installed, for example `C:/opencv5/opencv-5.x/build/install`):

```cmake
cmake_minimum_required(VERSION 3.22)
project(cv5test)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(OPENCV_DIR "C:/projects/opencv5/opencv-5.x/build/install")
list(APPEND CMAKE_PREFIX_PATH "${OPENCV_DIR}")

find_package(OpenCV 5.0.0 REQUIRED)

add_executable(cv5test main.cpp)
target_link_libraries(cv5test PRIVATE ${OpenCV_LIBS})
target_include_directories(${PROJECT_NAME} PRIVATE ${OpenCV_INCLUDE_DIRS})

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory_if_newer
        "${OPENCV_DIR}/x64/vc18/bin"
        $<TARGET_FILE_DIR:${PROJECT_NAME}>
    COMMENT "Verifying and copying required OpenCV and FFmpeg runtime DLLs..."
)
```

Configure and build command for Visual Studio 18 2026:

```bash
cmake -S . -B build -G "Visual Studio 18 2026" -A x64
cmake --build build --config Release --parallel
```

Full source code loads the `ooo.jpg` image and `town0.avi` video:

```cpp
#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <string>
#include <iostream>
#include <stdio.h>

using namespace cv;
using namespace std;

int main()
{
    std::cout << "OpenCV version: " << CV_VERSION << std::endl;

    Mat img = imread("ooo.jpg");
    if (img.empty()) {
        std::cerr << "Could not read photo.jpg" << std::endl;
        return -1;
    }

    Mat gray = imread("ooo.jpg", IMREAD_GRAYSCALE);
    imshow("My photo", img);
    waitKey(0);
    imshow("My photo", gray);
    waitKey(0);

    imwrite("output.png", img);
    imwrite("output.jpg", img, {IMWRITE_JPEG_QUALITY, 90});

    VideoCapture cap("town0.avi");
    if (!cap.isOpened()) {
        std::cerr << "Cannot open video source" << std::endl;
        return -1;
    }

    Mat frame;
    while (cap.read(frame)) {
        imshow("Video", frame);
        if (waitKey(1) == 'q') break;
    }

    int w = (int)cap.get(CAP_PROP_FRAME_WIDTH);
    int h = (int)cap.get(CAP_PROP_FRAME_HEIGHT);
    double fps = cap.get(CAP_PROP_FPS);
    if (fps <= 0) fps = 30.0;

    VideoWriter out("rec.mp4",
                    VideoWriter::fourcc('m', 'p', '4', 'v'),
                    fps,
                    Size(w, h));

    while (cap.read(frame)) {
        out.write(frame);
    }
}
```

## 7\. My Key Takeaways

-   `imread` fails silently — check `empty()` every single time.
-   `waitKey` drives the GUI, not just the keyboard.
-   The file extension selects the codec in `imwrite`.
-   A `VideoWriter` frame-size mismatch can create a silent empty file.
-   Webcams may report FPS as 0 — always have a fallback.

## Conclusion

I/O is boring until it fails silently. These small habits made my capture code boring in the best way — it just works.

In the next article, we'll transform those loaded images: resize, crop, rotate and color spaces — including the HSV trick that makes color detection actually robust.

**Tip:** Need help to install or compile an OpenCV C++ program? Use agents in Antigravity, Cursor, Gemini CLI or Claude to do this for you.
