RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (2024)

RGB与YUV色彩空间相互转换

原理

RGB与YUV空间的对应关系

根据电视原理的相关知识可知,RGB与的YUV对应关系为:
{ Y = 0.299 R + 0.587 G + 0.114 B U = − 0.1684 R − 0.3316 G + 0.5 B = 0.564 ( B − Y ) V = 0.5 R − 0.4187 G − 0.0813 B = 0.713 ( R − Y ) (1) \begin{cases} Y= 0.299\ R &+ 0.587\ G &+ 0.114\ B \\ U= -0.1684\ R &- 0.3316\ G &+ 0.5\ B &= 0.564\ (B-Y) \\ V= 0.5\ R &- 0.4187\ G &- 0.0813\ B &= 0.713\ (R-Y) \\ \end{cases} \tag{1} Y=0.299RU=0.1684RV=0.5R+0.587G0.3316G0.4187G+0.114B+0.5B0.0813B=0.564(BY)=0.713(RY)(1)
其中,为了使色差信号的动态范围控制在[-0.5, 0.5],需要进行量化前的归一化处理,需要引入数字色差信号的压缩系数(分别为0.564与0.713)。

量化电平的分配

参考《现代电视原理》7.4.2节“视频信号量化电平的分配”部分:

在进行8 bit量化时,需要在上下两端留出一定的余量,作为信号超越动态范围的保护带。具体地:

  • 对于亮度信号,在256级的上端留出20级,下端留出16级作为余量,即Y的动态范围为16—235
  • 对于两个色差信号,在256级的上端留出15级,下端留出16级作为余量,即U、V的动态范围为16—240
RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (1)
RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (2)

根据码电平数字表达式
量 化 等 级 = i n t { 量 化 等 级 最 大 值 − 量 化 等 级 最 小 值 模 拟 电 平 最 大 值 − 模 拟 电 平 最 小 值 × 对 应 的 数 字 电 平 公 式 + 0 电 平 对 应 得 量 化 等 级 } (2) 量化等级={\rm{int}}\left\{ \dfrac{量化等级最大值-量化等级最小值}{模拟电平最大值-模拟电平最小值}\times 对应的数字电平公式+0电平对应得量化等级 \right\} \tag{2} =int{×+0}(2)
可知
{ Y ′ = i n t { 235 − 16 0.5 − ( − 0.5 ) Y + 16 } U ′ = i n t { 240 − 16 1 − 0 U + 128 } V ′ = i n t { 240 − 16 1 − 0 V + 128 } (3) \begin{cases} Y' = {\rm int}\left\{\dfrac {235-16}{0.5-(-0.5)}Y+16 \right\}\\ U' = {\rm int}\left\{\dfrac {240-16}{1-0}U+128 \right\}\\ V' = {\rm int}\left\{\dfrac {240-16}{1-0}V+128 \right\} \end{cases} \tag{3} Y=int{0.5(0.5)23516Y+16}U=int{1024016U+128}V=int{1024016V+128}(3)
其中,

  • i n t {\rm int} int表示向下取整;
  • Y ′ Y' Y U ′ U' U V ′ V' V为数字量化电平, Y Y Y U U U V V V为归一化的模拟电平( Y ∈ [ 0 , 1 ] Y\in [0,1] Y[0,1] U , V ∈ [ − 0.5 , 0.5 ] U,V\in [-0.5,0.5] U,V[0.5,0.5]);
  • 考虑到色差信号有负值,需要将原来的0值对应到128,故加上128。

由于读取的RGB文件已经进行了8 bit的量化(RGB三个分量范围均为0—255),所以要对公式 ( 2 ) (2) (2)进行修正,先将 Y Y Y映射到-0.5—0.5, U U U V V V映射到0—1:
{ Y ′ = i n t { 219 255 Y + 16 } U ′ = i n t { 224 255 U + 128 } V ′ = i n t { 224 255 V + 128 } (4) \begin{cases} Y' = {\rm int}\left\{ \dfrac {219}{255}Y+16 \right\}\\ U' = {\rm int}\left\{ \dfrac {224}{255}U+128 \right\}\\ V' = {\rm int}\left\{ \dfrac {224}{255}V+128 \right\} \end{cases} \tag{4} Y=int{255219Y+16}U=int{255224U+128}V=int{255224V+128}(4)
带入 ( 1 ) (1) (1)式,得:
{ Y = 66 R + 129 G + 25 B 255 + 16 U = − 38 R − 74 G + 112 B 255 + 128 V = 112 R − 94 G − 18 B 255 + 128 (5) \begin{cases} Y= \dfrac {66R + 129G + 25B}{255} + 16 \\ U= \dfrac{-38R - 74G + 112B}{255} +128 \\ V= \dfrac{112R - 94G - 18B}{255} + 128 \end{cases} \tag{5} Y=25566R+129G+25B+16U=25538R74G+112B+128V=255112R94G18B+128(5)
为了提高计算机的计算效率且不会造成过大的误差,在程序中使用>> 8来代替除以255的计算。

( 5 ) (5) (5)式写为矩阵形式:
[ Y U V ] = 1 255 [ 66 129 25 − 38 − 74 112 112 − 94 − 18 ] [ R G B ] + [ 16 16 128 ] (6) \begin{bmatrix} Y \\ U \\ V \end{bmatrix} = \dfrac {1}{255} \begin{bmatrix} 66 & 129 & 25 \\ -38 & -74 & 112 \\ 112 & -94 & -18 \end{bmatrix} \begin{bmatrix} R \\ G \\ B \end{bmatrix} + \begin{bmatrix} 16 \\ 16 \\ 128 \end{bmatrix} \tag{6} YUV=2551663811212974942511218RGB+1616128(6)
并记 A = [ 66 129 25 − 38 − 74 112 112 − 94 − 18 ] \boldsymbol A= \begin{bmatrix} 66 & 129 & 25 \\ -38 & -74 & 112 \\ 112 & -94 & -18 \end{bmatrix} A=663811212974942511218

反解,得:
[ R G B ] = 255 A T [ Y − 16 U − 16 V − 128 ] (7) \begin{bmatrix} R \\ G \\ B \end{bmatrix} = 255\boldsymbol A^{\rm T} \begin{bmatrix} Y-16 \\ U-16 \\ V-128 \end{bmatrix} \tag{7} RGB=255ATY16U16V128(7)
由于 A − 1 \boldsymbol A^{-1} A1数量级较小,直接使用会造成较大的计算误差,因而转化为
[ R G B ] = 25 5 2 ⋅ ( 1 255 A T ) [ Y − 16 U − 16 V − 128 ] (8) \begin{bmatrix} R \\ G \\ B \end{bmatrix} = 255^2 \cdot \left(\dfrac 1 {255}\boldsymbol A^{\rm T}\right) \begin{bmatrix} Y-16 \\ U-16 \\ V-128 \end{bmatrix} \tag{8} RGB=2552(2551AT)Y16U16V128(8)
整理得:

R = (298 * Y + 411 * V - 57344) >> 8;G = (298 * Y - 101 * U - 211 * V + 34739) >> 8;B = (298 * Y + 519 * U - 71117) >> 8;

main函数的命令行参数

main函数实际上具有两个形参,int argcchar* argv[]。虽然很多情况下是缺省的,但在例如涉及文件的操作中,使用命令行参数可以为编程提供一定的便利。

设置方法如下:在Visual Studio中,依次点击菜单栏中的项目→项目属性,在项目属性页的配置属性菜单下,点击“调试”。通过浏览文件夹的方式设置工作目录,并在命令参数中输入n个字符串(以空格分隔)。

RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (3)

命令行参数的设置

这些字符串将会自动传递给argv,作为其第1n个元素(第0个元素为"项目名.exe"),而argc的值为n+1

源代码

declarations.h

#pragma oncevoid rgbLookupTable();void yuvLookupTable();void rgb2yuv(FILE*, int, int, int, unsigned char*, unsigned char*, unsigned char*, unsigned char*);void yuv2rgb(FILE*, int, int, int, unsigned char*, unsigned char*, unsigned char*, unsigned char*);void errorData(int, unsigned char*, char* []);

rgb2yuv.cpp

#include <iostream>#include "declarations.h"int rgb66[256], rgb129[256], rgb25[256];int rgb38[256], rgb74[256], rgb112[256];int rgb94[256], rgb18[256];void rgbLookupTable(){for (int i = 0; i < 256; i++){rgb66[i] = 66 * i;rgb129[i] = 129 * i;rgb25[i] = 25 * i;rgb38[i] = 38 * i;rgb74[i] = 74 * i;rgb112[i] = 112 * i;rgb94[i] = 94 * i;rgb18[i] = 18 * i;}}void rgb2yuv(FILE* yuvFile, int rgbSize, int w, int h, unsigned char* rgbBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf){unsigned char* uBuf444 = NULL;// 下采样前的U分量缓冲区unsigned char* vBuf444 = NULL;// 下采样前的V分量缓冲区uBuf444 = new unsigned char[rgbSize / 3];// 4:4:4格式vBuf444 = new unsigned char[rgbSize / 3];int pxNum = w * h;// RGB to YUV (4:4:4)for (int i = 0; i < pxNum; i++)// i为图像像素序号{unsigned char r = rgbBuf[3 * i + 2];// RGB图像第i个像素的R分量unsigned char g = rgbBuf[3 * i + 1];// RGB图像第i个像素的G分量unsigned char b = rgbBuf[3 * i];// RGB图像第i个像素的B分量//yBuf[i] = ((66 * r + 129 * g + 25 * b) >> 8) + 16;//uBuf444[i] = ((-38 * r - 74 * g + 112 * b) >> 8) + 128;//vBuf444[i] = ((112 * r - 94 * g - 18 * b) >> 8) + 128;rgbLookupTable();// 使用查找表,提高运算效率yBuf[i] = ((rgb66[r] + rgb129[g] + rgb25[b]) >> 8) + 16;uBuf444[i] = ((-rgb38[r] - rgb74[g] + rgb112[b]) >> 8) + 128;vBuf444[i] = ((rgb112[r] - rgb94[g] - rgb18[b]) >> 8) + 128;}// 4:4:4 to 4:2:0for (int i = 0; i < h; i += 2){for (int j = 0; j < w; j += 2){uBuf[i / 2 * w / 2 + j / 2] = uBuf444[i * w + j];vBuf[i / 2 * w / 2 + j / 2] = vBuf444[i * w + j];}}delete[]uBuf444;delete[]vBuf444;fwrite(yBuf, sizeof(unsigned char), rgbSize / 3, yuvFile);fwrite(uBuf, sizeof(unsigned char), rgbSize / 12, yuvFile);fwrite(vBuf, sizeof(unsigned char), rgbSize / 12, yuvFile);}

yuv2rgb.cpp

#include <iostream>#include "declarations.h"int yuv298[256], yuv411[256];int yuv101[256], yuv211[256];int yuv519[256];void yuvLookupTable(){for (int i = 0; i < 256; i++){yuv298[i] = 298 * i;yuv411[i] = 411 * i;yuv101[i] = 101 * i;yuv211[i] = 211 * i;yuv519[i] = 519 * i;}}void yuv2rgb(FILE* rgbFile, int yuvSize, int w, int h, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf, unsigned char* rgbBuf){unsigned char* uBuf444 = new unsigned char[yuvSize * 2 / 3];// 还原成4:4:4的U分量缓冲区unsigned char* vBuf444 = new unsigned char[yuvSize * 2 / 3];// 还原成4:4:4的V分量缓冲区int pxNum = w * h;// 图像中的总像素数// 4:2:0 to 4:4:4for (int i = 0; i < h / 2; i++)// i控制行{for (int j = 0; j < w / 2; j++)// j控制列{uBuf444[2 * i * w + 2 * j] = uBuf[i * w / 2 + j];uBuf444[2 * i * w + 2 * j + 1] = uBuf[i * w / 2 + j];uBuf444[2 * i * w + 2 * j + w] = uBuf[i * w / 2 + j];uBuf444[2 * i * w + 2 * j + w + 1] = uBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j] = vBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j + 1] = vBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j + w] = vBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j + w + 1] = vBuf[i * w / 2 + j];}}// YUV (4:4:4) to RGBfor (int i = 0; i < pxNum; i++){// 中间变量均使用int型,以保证足够的精度,防止溢出int y = yBuf[i];// YUV图像第i个像素的Y分量int u = uBuf444[i];// YUV图像第i个像素的U分量(4:4:4)int v = vBuf444[i];// YUV图像第i个像素的V分量(4:4:4)int r;int g;int b;yuvLookupTable();//r = (298 * y + 411 * v - 57344) >> 8;// 还原的RGB图像的R分量r = (yuv298[y] + yuv411[v] - 57344) >> 8;// 还原的RGB图像的R分量if (r < 0)r = 0;// 修正if (r > 255)r = 255;//g = (298 * y - 101 * u - 211 * v + 34739) >> 8;// 还原的RGB图像的G分量g = (yuv298[y] - yuv101[u] - yuv211[v] + 34739) >> 8;// 还原的RGB图像的G分量if (g < 0)g = 0;if (g > 255)g = 255;//b = (298 * y + 519 * u - 71117) >> 8;// 还原的RGB图像的B分量b = (yuv298[y] + yuv519[u] - 71117) >> 8;// 还原的RGB图像的B分量if (b < 0)b = 0;if (b > 255)b = 255;rgbBuf[3 * i + 2] = (unsigned char)r;// 还原的RGB图像的R分量rgbBuf[3 * i + 1] = (unsigned char)g;// 还原的RGB图像的G分量rgbBuf[3 * i] = (unsigned char)b;// 还原的RGB图像的B分量}delete[]uBuf444;delete[]vBuf444;fwrite(rgbBuf, sizeof(unsigned char), yuvSize * 2, rgbFile);}

errorData.cpp

#include <iostream>#include "declarations.h"using namespace std;void errorData(int yuvSize, unsigned char* rgbBuf, char* argv[]){FILE* rgbOriFile = NULL;// 原始RGB图像文件指针FILE* errorFile = NULL;// 误差数据文件指针const char* rgbOriName = argv[1];// 原始RGB图像文件名const char* errorName = argv[4];// 误差数据文件名// 打开文件if (fopen_s(&rgbOriFile, rgbOriName, "rb") == 0){cout << "Successfully opened " << rgbOriName << "." << endl;}else{cout << "Failed to open " << rgbOriName << "." << endl;exit(0);}if (fopen_s(&errorFile, errorName, "w") == 0){cout << "Successfully opened " << errorName << "." << endl;}else{cout << "Failed to open " << errorName << "." << endl;exit(0);}unsigned char* rgbOriBuf = new unsigned char[yuvSize * 2];fread(rgbOriBuf, sizeof(unsigned char), yuvSize * 2, rgbOriFile);// 将误差数据输出到csv文件fprintf(errorFile, "Pixel,B Error,G Error,R Error\n");for (int i = 0; i < yuvSize * 2 / 3; i++){fprintf(errorFile, "%d,%d,%d,%d\n", i, (int)abs(rgbBuf[3 * i] - rgbOriBuf[3 * i]), (int)abs(rgbBuf[3 * i + 1] - rgbOriBuf[3 * i + 1]), (int)abs(rgbBuf[3 * i + 2] - rgbOriBuf[3 * i + 2]));}delete[]rgbOriBuf;fclose(rgbOriFile);fclose(errorFile);}

main.cpp

#include <iostream>#include "declarations.h"using namespace std;int main(int argc, char* argv[]){FILE* rgbOriFilePtr = NULL;// 原RGB图像的文件指针FILE* yuvFilePtr = NULL;// YUV图像的文件指针FILE* rgbRecFilePtr = NULL;// 复原的RGB文件的文件指针const char* rgbOriFileName = argv[1];// 原RGB图像文件名const char* yuvFileName = argv[2];// YUV图像文件名const char* rgbRecFileName = argv[3];// 复原RGB图像文件名int width = 256;// 图像宽int height = 256;// 图像高int rgbFileSize;// RGB图像总字节数int yuvFileSize;// YUV图像总字节数unsigned char* rgbOriBuffer = NULL;// 原RGB图像缓冲区unsigned char* yBuffer = NULL;// Y分量缓冲区unsigned char* uBuffer = NULL;// U分量缓冲区unsigned char* vBuffer = NULL;// V分量缓冲区unsigned char* rgbRecBuffer = NULL;// 复原RGB图像缓冲区// 打开文件if (fopen_s(&rgbOriFilePtr, rgbOriFileName, "rb") == 0){cout << "Successfully opened " << rgbOriFileName << "." << endl;}else{cout << "Failed to open " << rgbOriFileName << "." << endl;exit(0);}if (fopen_s(&yuvFilePtr, yuvFileName, "wb+") == 0){cout << "Successfully opened " << yuvFileName << "." << endl;}else{cout << "Failed to open " << yuvFileName << "." << endl;exit(0);}if (fopen_s(&rgbRecFilePtr, rgbRecFileName, "wb") == 0){cout << "Successfully opened " << rgbRecFileName << "." << endl;}else{cout << "Failed to open " << rgbRecFileName << "." << endl;exit(0);}// 计算原RGB图像总字节数fseek(rgbOriFilePtr, 0L, SEEK_END);rgbFileSize = ftell(rgbOriFilePtr);rewind(rgbOriFilePtr);cout << "The space that " << rgbOriFileName << " accounts for is " << rgbFileSize << " Bytes = " << rgbFileSize / 1024 << " kB." << endl;yuvFileSize = rgbFileSize / 2;// 建立缓冲区rgbOriBuffer = new unsigned char[rgbFileSize];yBuffer = new unsigned char[rgbFileSize / 3];uBuffer = new unsigned char[rgbFileSize / 12];// 4:2:0格式vBuffer = new unsigned char[rgbFileSize / 12];rgbRecBuffer = new unsigned char[rgbFileSize];fread(rgbOriBuffer, sizeof(unsigned char), rgbFileSize, rgbOriFilePtr);// RGB图像读入缓冲区rgb2yuv(yuvFilePtr, rgbFileSize, width, height, rgbOriBuffer, yBuffer, uBuffer, vBuffer);yuv2rgb(rgbRecFilePtr, yuvFileSize, width, height, yBuffer, uBuffer, vBuffer, rgbRecBuffer);errorData(yuvFileSize, rgbRecBuffer, argv);delete[]rgbOriBuffer;delete[]yBuffer;delete[]uBuffer;delete[]vBuffer;delete[]rgbRecBuffer;fclose(rgbOriFilePtr);fclose(yuvFilePtr);fclose(rgbRecFilePtr);}

实验结果与误差分析

RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (4)

down.rgb

RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (5)

down_transformed.yuv

RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (6)

down_recoverd.rgb

以上三张图分别是原RGB图像、通过RGB转换的YUV图像和通过YUV复原的RGB图像。对比第1、3张图,几乎通过肉眼分辨不出差别。为了量化误差,在程序中,利用errorData函数计算了两张RGB图像各像素的三个分量的误差,并输出到了csv文件中。

由于在C++中进行数据分析与可视化并不方便,考虑到数据量较大,因而采用R进行分析。

在R中分别作出boxplot和直方图:

errorData <- read.csv("errorData.csv")b.error <- errorData[, 2]g.error <- errorData[, 3]r.error <- errorData[, 4]boxplot(r.error, g.error, b.error, horizontal = TRUE, names = c("R Error", "G Error", "B Error"), col = c("coral2", "palegreen1", "skyblue1"))hist(r.error, freq = FALSE, xlab = "Pixel", ylab = "Frequency of R Error", col = "coral2")hist(g.error, freq = FALSE, xlab = "Pixel", ylab = "Frequency of G Error", col = "palegreen1")hist(b.error, freq = FALSE, xlab = "Pixel", ylab = "Frequency of B Error", col = "skyblue1")
RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (7)
RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (8)
RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (9)
RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (10)

可以再求出各分量误差的Empirical CDF:

> ecdf.r.error <- ecdf(r.error)> ecdf.g.error <- ecdf(g.error)> ecdf.b.error <- ecdf(b.error)> ecdf.r.error(5)[1] 0.9351196> ecdf.g.error(5)[1] 0.9855804> ecdf.b.error(5)[1] 0.8774567

图表显示,该色度空间的转换不能做到100%的准确。误差来源可能有:

  • 由于从4:4:4的RGB图像转换为4:2:0的YUV图像时,舍弃掉了3/4的色度信息,因而在还原为YUV文件时是无法还原出舍弃部分的色度信息的;
  • 在进行色彩空间转换的公式推导时,使用了移位运算代替了除法运算,并且在计算过程中存在多次四舍五入;
  • 在YUV向RGB的转换时,存在部分数据溢出。

但R、G、B分量分别有93.5%、98.6%、87.8%的像素误差小于等于5,因而该算法的色彩空间转换的误差并不大,效果是可以接受的;由于人眼对色度的敏感度远高于对亮度的敏感度,误差也在人眼的分辨能力之外。

实验中需要注意的问题

  1. 在进行RGB和YUV的转换时,要特别留意数组下标,保证不会越界;

  2. 在将YUV还原为RGB时,可能会出现数据溢出(如下图),因而三个分量都需要分别判断,若有溢出,要置为0或255;

  3. RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (11)

    down_recovered.rgb(数据有溢出)

r = (yuv298[y] + yuv411[v] - 57344) >> 8;// 还原的RGB图像的R分量if (r < 0)r = 0;// 修正if (r > 255)r = 255;g = (yuv298[y] - yuv101[u] - yuv211[v] + 34739) >> 8;// 还原的RGB图像的G分量if (g < 0)g = 0;if (g > 255)g = 255;b = (yuv298[y] + yuv519[u] - 71117) >> 8;// 还原的RGB图像的B分量if (b < 0)b = 0;if (b > 255)b = 255;
r = (yuv298[y] + yuv411[v] - 57344) >> 8;// 还原的RGB图像的R分量if (r < 0)r = 0;// 修正if (r > 255)r = 255;g = (yuv298[y] - yuv101[u] - yuv211[v] + 34739) >> 8;// 还原的RGB图像的G分量if (g < 0)g = 0;if (g > 255)g = 255;b = (yuv298[y] + yuv519[u] - 71117) >> 8;// 还原的RGB图像的B分量if (b < 0)b = 0;if (b > 255)b = 255;
  1. 在转换过程中,中间变量要使用int型(4字节)而不能使用unsigned char型(只有1字节),为数据溢出留出空间。
RGB与YUV色彩空间的相互转换_rgb转yuv颜色值-CSDN博客 (2024)

References

Top Articles
Latest Posts
Article information

Author: Allyn Kozey

Last Updated:

Views: 5515

Rating: 4.2 / 5 (63 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Allyn Kozey

Birthday: 1993-12-21

Address: Suite 454 40343 Larson Union, Port Melia, TX 16164

Phone: +2456904400762

Job: Investor Administrator

Hobby: Sketching, Puzzles, Pet, Mountaineering, Skydiving, Dowsing, Sports

Introduction: My name is Allyn Kozey, I am a outstanding, colorful, adventurous, encouraging, zealous, tender, helpful person who loves writing and wants to share my knowledge and understanding with you.