YUV色彩空间详解:从理论到实践
YUV色彩空间详解:从理论到实践
YUV 图像格式
1. YUV 颜色空间简介
YUV是一种颜色编码方式,它将图像信息分为亮度(Y)和色度(U 和 V)两个部分。该格式的优点在于,它将亮度信息与色度信息分开,使得对亮度和色度的处理可以独立进行。YUV的结构适用于视频图像的高效存储和传输,尤其是在视频压缩领域中应用广泛。
Y分量(亮度):亮度分量 Y 主要控制图像的亮暗细节,包含了大部分可见的信息。由于人眼对亮度的敏感度较高,在图像处理中保留更多的亮度信息对视觉效果的影响更大。
U分量(色度蓝色差分):U分量记录图像中蓝色相对于亮度的差异,用来描述蓝色和黄色之间的对比。
V分量(色度红色差分):V分量记录图像中红色相对于亮度的差异,用来描述红色和青色之间的对比。
2. YUV 采样格式
YUV有多个不同的采样格式,采样方式主要体现在对U和V分量的采样率不同,常见的采样方式包括:
YUV 4:4:4:每个像素都有完整的Y、U、V分量,色度采样比例为4:4:4。此格式保留了最高的图像质量,但数据量较大。
YUV 4:2:2:在该格式下,每两个水平像素共享一个U和V分量。即色度的采样率为亮度采样率的一半,形成4:2:2的采样比例。此格式在广播视频中常见,既节省数据又保证了良好的画质。
YUV 4:2:0:每四个像素共享一个U和V分量,色度采样率更低。该采样方式进一步压缩了色度信息,在一些高压缩视频应用中广泛使用,例如MPEG、H.264。
3. RGB 与 YUV 之间的转换
RGB(红、绿、蓝)是另一个颜色空间,通常用于显示设备。YUV与RGB颜色空间之间的转换可以通过线性变换实现,公式如下:
RGB 转 YUV
给定RGB颜色空间中的一个像素 ((R, G, B)),其转换为YUV的公式为:
$$
\begin{cases}
Y & = 0.299 \cdot R + 0.587 \cdot G + 0.114 \cdot B \
U & = 0.492 \cdot (B - Y) = -0.147 \cdot R - 0.289 \cdot G + 0.436 \cdot B \
V & = 0.877 \cdot (R - Y) = 0.615 \cdot R - 0.515 \cdot G - 0.100 \cdot B \
\end{cases}
$$
这些公式表明,Y分量是RGB的加权平均值,用于表示亮度,而U和V分量则用于记录色度信息,即蓝色和红色相对于亮度的差异。
YUV 转 RGB
将YUV颜色空间转换为RGB颜色空间,公式如下:
$$
\begin{cases}
R = Y + 1.13983 \cdot V \
G = Y - 0.39465 \cdot U - 0.58060 \cdot V \
B = Y + 2.03211 \cdot U \
\end{cases}
$$
在实际操作中,YUV的不同采样方式会影响色度的采样率。在4:2:2或4:2:0采样模式下,需要在转换到RGB之前将色度信息上采样到与亮度相同的分辨率。
4. YUV 格式的应用
视频压缩:YUV格式被广泛用于视频压缩,特别是在MPEG、H.264等视频编码中。通过降低U和V色度分量的采样率,YUV可以大幅减少数据量,同时保持良好的图像质量。
图像处理:在图像处理中,YUV格式能方便地进行颜色空间转换和颜色调整。由于亮度与色度的独立性,YUV更适合在颜色平衡和亮度调整中使用。
电视广播:YUV在电视信号的传输中应用广泛,因其兼容模拟和数字标准,能够提供高质量的图像传输。
数字摄影:在数码相机和视频摄影中,YUV格式常用于RAW数据的存储,使得在后期处理中有更高的颜色调整灵活性。
通过使用YUV颜色空间,可以在数据量和图像质量之间取得平衡,为数字图像和视频压缩提供了高效的解决方案。
5. 实例YUV422转为BGR:
读取一个YUV422格式的图像并转为BGR格式显示。
TEST(Demo, TestReadYUV)
{
std::string image_path = "/media/hello/data/avm_resource/calibration_image/front.yuv";
int width = 1920; // 图像的宽度
int height = 1536; // 图像的高度
// 读取YUV图像文件
FILE *file = fopen(image_path.c_str(), "rb");
if (!file) {
std::cerr << "无法打开文件: " << image_path << std::endl;
return ;
}
// calculate the pixels size
int file_size = 0;
fseek(file, 0, SEEK_END);
file_size = ftell(file);
fseek(file, 0, SEEK_SET);
std::cout << "file size: " << file_size <<
"\n width * height * 3 / 2: " << width * height * 3 / 2 <<
"\n width * height * 2: " << width * height * 2 << std::endl;
// 创建一个Mat对象来存储YUV数据
cv::Mat yuv_image(height , width , CV_8UC2);
fread(yuv_image.data, 1, 2 * width * height, file);
fclose(file);
// 将YUV图像转换为BGR图像
cv::Mat bgr_image;
cv::cvtColor(yuv_image, bgr_image, cv::COLOR_YUV2BGR_YUYV);
std::cout << "bgr_image size: " << bgr_image.size() << std::endl;
std::cout << "bgr_image channels: " << bgr_image.channels() << std::endl;
// 显示图像
cv::namedWindow("BGR Image", cv::WINDOW_NORMAL);
cv::imshow("BGR Image", bgr_image);
cv::waitKey(0);
}
输出:
$9│ Note: Google Test filter = Demo.TestReadYUV
$9│ [==========] Running 1 test from 1 test suite.
$9│ [----------] Global test environment set-up.
$9│ [----------] 1 test from Demo
$9│ [ RUN ] Demo.TestReadYUV @ ./test/test_find_circle.cpp:45
$9│ │ file size: 5898240
$9│ │ width * height * 3 / 2: 4423680
$9│ │ width * height * 2: 5898240
$9│ │ bgr_image size: [1920 x 1536]
$9│ │ bgr_image channels: 3
$9│ [ OK ] Demo.TestReadYUV (2058 ms)
$9│
$9│ [----------] 1 test from Demo (2058 ms total)
$9│
$9│ [==========] 1 test from 1 test suite ran. (2058 ms total)
$9│ [ PASSED ] 1 test.
参考链接
- https://wiki.videolan.org/YUV/
- https://en.wikipedia.org/wiki/Y%E2%80%B2UV
- https://answers.opencv.org/question/87083/yuyv422-to-bgr/