Objective
Process uncompressed video files stored in YUV format and convert them to RGB for visualization.
Development Environment
- Visual Studio 2022
- OpenCV 3.4.16
- C++17
- FFMPEG 6.1.1
Implementation
File paths must use double backslashes (C:\\...) or forward slashes (C:/), as single backslashes are interpreted as escape characters in C++ strings.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <opencv2/opencv.hpp>
constexpr const char* INPUT_YUV_PATH = "path/to/video.yuv";
constexpr int VIDEO_HEIGHT = 480;
constexpr int VIDEO_WIDTH = 832;
constexpr int FRAME_RATE = 30;
using namespace cv;
void convertYuv420ToRgb(const uint8_t* yuvBuffer, uint8_t* rgbOutput, int frameWidth, int frameHeight)
{
const uint8_t* yPlane = yuvBuffer;
const uint8_t* uPlane = yuvBuffer + (frameHeight * frameWidth);
const uint8_t* vPlane = uPlane + (frameHeight * frameWidth / 4);
for (int row = 0; row < frameHeight; ++row)
{
for (int col = 0; col < frameWidth; ++col)
{
const int yIdx = row * frameWidth + col;
const int uvIdx = (row / 2) * (frameWidth / 2) + (col / 2);
const int yValue = yPlane[yIdx] - 16;
const int uValue = uPlane[uvIdx] - 128;
const int vValue = vPlane[uvIdx] - 128;
const int r = (298 * yValue + 409 * vValue + 128) >> 8;
const int g = (298 * yValue - 100 * uValue - 208 * vValue + 128) >> 8;
const int b = (298 * yValue + 516 * uValue + 128) >> 8;
const int pixelOffset = (row * frameWidth + col) * 3;
rgbOutput[pixelOffset + 0] = saturate_cast<uint8_t>(b);
rgbOutput[pixelOffset + 1] = saturate_cast<uint8_t>(g);
rgbOutput[pixelOffset + 2] = saturate_cast<uint8_t>(r);
}
}
}
int main()
{
FILE* videoFile = fopen(INPUT_YUV_PATH, "rb");
if (!videoFile)
{
std::cerr << "Failed to open input file: " << INPUT_YUV_PATH << std::endl;
return -1;
}
Mat yuvFrame(VIDEO_HEIGHT + VIDEO_HEIGHT / 2, VIDEO_WIDTH, CV_8UC1);
Mat rgbFrame(VIDEO_HEIGHT, VIDEO_WIDTH, CV_8UC3);
bool continuePlayback = true;
while (continuePlayback)
{
const size_t expectedBytes = yuvFrame.total() * yuvFrame.elemSize();
const size_t bytesRead = fread(yuvFrame.data, 1, expectedBytes, videoFile);
if (bytesRead != expectedBytes)
{
std::cerr << "End of file reached or read error occurred." << std::endl;
break;
}
convertYuv420ToRgb(yuvFrame.data, rgbFrame.data, VIDEO_WIDTH, VIDEO_HEIGHT);
imshow("Original YUV", yuvFrame);
imshow("Converted RGB", rgbFrame);
if (waitKey(1000 / FRAME_RATE) >= 0)
{
continuePlayback = false;
}
}
fclose(videoFile);
destroyAllWindows();
return 0;
}
Generating Test YUV Files with FFMPEG
Convert an MKV video to YUV420p format at 832x480 resolution:
ffmpeg -i input.mkv -s 832x480 -pix_fmt yuv420p output.yuv
Inspect the YUV file properties:
ffprobe -show_format -video_size 832x480 output.yuv
Play the YUV file for verification:
ffplay -video_size 832x480 -i output.yuv
Technical Background
YUV420 I420 Memory Layout
In the I420 (YUV420 planar) format, the Y plane comes first, followed by U and V planes. The Y component has the same number of samples as pixels in the frame. The U and V planes each contain one quarter of the pixel count since theey are subsampled horizontally and vertically by a factor of 2.
The memory arrangement follows this pattern:
- Y plane: width × height bytes
- U plane: (width/2) × (height/2) bytes
- V plane: (width/2) × (height/2) bytes
Each 2×2 block of Y pixels shares a single U and V sample, which is why this forrmat achieves 12 bits per pixel compared to 24 bits for RGB.
Color Space Conversion Mathematics
The conversion from YUV to RGB employs the following formulas:
Y' = Y - 16
U' = U - 128
V' = V - 128
R = (298 × Y' + 409 × V' + 128) / 256
G = (298 × Y' - 100 × U' - 208 × V' + 128) / 256
B = (298 × Y' + 516 × U' + 128) / 256
The subtraction of 16 from Y and 128 from U,V normalizes the ranges. Y ranges from 16-235 while U and V range from 16-240. The offset brings them to a symmetric range around zero for accurate multiplication. The final division by 256 (implemented as right shift by 8) scales the results back to the valid 0-255 RGB range. The saturate_cast function ensures values exceeding 0-255 are clamped appropriately.