YUV格式,YUV是一种颜色编码方法,主要用于电视相同以及模拟视频领域,它将亮度信息(Y) 与 色彩信息(UV)分离,没有UV信息一样可以显示完整的图像。YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的带宽。
YUV分为三个分量,“Y”表示图像的明亮度,也就是灰度值。“UV”表示图像的色彩。
YUV的采样有许多种,常用的有YUV444,YUV422,YUV420等。
YUV 4:4:4采样,一个Y使用一组UV分量。
YUV 4:2:2采样,两个Y共用一组UV分量。
YUV 4:2:0采样,四个Y共用一组UV分量。
YUV利用亮度"Y",色彩“UV”代替RGB三原色来压缩图像。
假设YUV的采样率是4:2:2 (YUV422),即每一个像素对于亮度Y的采样频率是1,对于色彩U和V,则每两个像素格取一个U,V。对于单个像素来说,U和V的采样频率为亮度Y的一半,一个像素点占用2字节(16bits),而RGB24三原色一个像素每种颜色各占1字节,所以一个像素需要占用3字节(24bits)。
例YUV与RGB的大小对比:
假如一个4x4图像,如果使用RGB来表示,则占用3 * 8 * 4 * 4 = 384bits。
如果使用YUV来表示,(Y)4 * 4 * 8 +(U)4 * 4 * 8 * 0.5 +(Y)4 * 4 * 8 * 0.5 = 24 + 12 + 12 = 256bits。
不同的色彩空间的转换公式是不一样的
YUV与RGB互转公式有很多,不同的色彩空间的转换公式是不一样的。
YUV有多种表现形式
除了色彩空间,还需要注意YUV的多种表现形式,比如:
YUV:YUV是一种模拟型号,Y ∈ [0,1] U,V ∈ [-0.5, 0.5]
YCbCr:YCbCr是数字信号,它包含两种形式,分别为tv range 和 full range,tv range 主要是广播电视采用的标准,full range主要是pc端采用的标准,所以full range有时也叫pc range。
tv range的各个分量的范围为 YUV Y∈[16,235] Y∈[16,240] Y ∈ [16,240]
full range的各个分量的范围均为:0~255
我们绝大多数接触的格式是YCbCr(tv range),YCbCr420p(tv range)在ffmpeg中描述为YUV420P。
RGB <-> YUV转换
转换公式基于BT601
#1. 小数形式(RGB∈[0,255], UV∈[-128,128])
#YUV -> RGB
R = Y + 1.4075V;
G = Y - 0.3455U - 0.7169V;
B = Y + 1.779U;
#RGB -> YUV
Y = 0.299R + 0.587G + 0.114B;
U = -0.169R - 0.331G + 0.5B ;
V = 0.5R - 0.419G - 0.081B;
#2. 整数形式(减少计算量)(RGB∈[0,255], UV∈[-128,128])
#YUV -> RGB
R = Y + ((360V)>>8) ;
G = Y - ((88U + 184V)>>8) ;
B = Y + 455U>>8 ;
#RGB -> YUV
Y = (77R + 150G + 29B)>>8;
U = ((-44R - 87G + 131B)>>8);
V = ((131R - 110G - 21B)>>8);
#3. 量化为tv range公式(RGB∈[0,255], UV∈[16,240],Y∈[16,235])
#YUV -> RGB
R = 1.1644Y + 1.6019V - 223.5521;
G = 1.1644Y - 0.3928U - 0.8163V + 136.1381;
B = 1.1644Y + 2.0253U - 278.0291;
#RGB -> YUV
Y = 0.2568R + 0.5041G + 0.0979B + 16;
U = -0.1479R - 0.2896G + 0.4375B + 128;
V = 0.4375R - 0.3666G - 0.0709B + 128;
#4. tv range整数形式(减小计算量)(RGB∈[0,255], UV∈[16,240],Y∈[16,235])
#YUV -> RGB
R = (298Y + 411V - 57344)>>8
G = (298Y - 101U - 211V + 34739)>>8
B = (298Y + 519U- 71117)>>8
#RGB -> YUV
Y= (66R + 129G + 25B)>>8 + 16
U= (-38R - 74G + 112B)>>8 +128
V= (112R - 94*G - 18B)>>8 + 128
YUV的存储格式有两大类:packed和planar。
packed的存储格式 :每个像素点的Y,U,V是连续交错存储的。
常见存储格式:
planar的存储格式:先连续存储所有像素点的Y,接着存储所有像素点的U,随后是所有像素点的V。
常见存储格式:
semi-planar 存储格式:先连续存储所有像素点的Y,接着交错存储像素点U,V,即UVUVUV…。
常见存储格式:
注:
其中YUV420P 和 YUV420SP根据U,V的顺序,又可分出2种格式。
YUV420P:
YUV420SP:
数据排列如下:
I420:YYYYYYYY UU VV
YV12:YYYYYYYY VV UU
NV12:YYYYYYYY UVUV
NV21:YYYYYYYY VUVU
NV12 -> RGB 图片转换代码
/*
width:N Height:M
1: Y-1 ... ... Y-N
2: Y-1 ... ... Y-N
...
M: Y-1 ... ... Y-N
1: U-1V-1 ... ... U-N/2 V-N/2
...
M/2: .....
*/
//NV12 -> RGB
//NV12的UV分量是交叉排列的
//RGB∈[0,255], YUV∈[0,255]
//使用以下转换公式
//R = Y + 1.4075V;
//G = Y - 0.3455U - 0.7169V;
//B = Y + 1.779U;
int yuv2rgb_nv12(unsigned char* pYuvBuf, unsigned char* pRgbBuf, int height, int width)
{
if(width < 1 || height < 1 || pYuvBuf == NULL || pRgbBuf == NULL)
{
return 0;
}
const long len = height * width;
// Y与UV数据地址
unsigned char *yData = pYuvBuf;
unsigned char *uvData = yData + len;
// R、G、B数据地址
unsigned char *rData = pRgbBuf;
unsigned char *gData = rData + len;
unsigned char *bData = gData + len;
int R[4], G[4], B[4];
int Y[4], U, V;
int y0_Idx, y1_Idx, uIdx, vIdx;
for (int i = 0; i < height; i=i+2)
{
for (int j = 0; j < width; j=j+2)
{
y0_Idx = i * width + j;
y1_Idx = (i + 1) * width + j;
// Y[0]、Y[1]、Y[2]、Y[3]分别代表 Y00、Y01、Y10、Y11
Y[0] = yData[y0_Idx];
Y[1] = yData[y0_Idx + 1];
Y[2] = yData[y1_Idx];
Y[3] = yData[y1_Idx + 1];
uIdx = (i / 2) * width + j;
vIdx = uIdx + 1;
U = uvData[uIdx];
V = uvData[vIdx];
R[0] = Y[0] + 1.4075 * (V - 128);
G[0] = Y[0] - 0.3455 * (U - 128) + 0.7169 * (V - 128);
B[0] = Y[0] + 1.779 * (U - 128);
R[1] = Y[1] + 1.4075 * (V - 128);
G[1] = Y[1] - 0.3455 * (U - 128) + 0.7169 * (V - 128);
B[1] = Y[1] + 1.779 * (U - 128);
R[2] = Y[2] + 1.4075 * (V - 128);
G[2] = Y[2] - 0.3455 * (U - 128) + 0.7169 * (V - 128);
B[2] = Y[2] + 1.779 * (U - 128);
R[3] = Y[3] + 1.4075 * (V - 128);
G[3] = Y[3] - 0.3455 * (U - 128) + 0.7169 * (V - 128);
B[3] = Y[3] + 1.779 * (U - 128);
// 像素值限定在 0-255
for (int k = 0; k < 4; ++k)
{
if(R[k] >= 0 && R[k] <= 255)
{
R[k] = R[k];
}
else
{
R[k] = (R[K] < 0) ? 0 : 255;
}
if(G[k] >= 0 && G[k] <= 255)
{
G[k] = G[k];
}
else
{
G[k] = (G[K] < 0) ? 0 : 255;
}
if(B[k] >= 0 && B[k] <= 255)
{
B[k] = B[k];
}
else
{
B[k] = (B[K] < 0) ? 0 : 255;
}
}
*(rData + y0_Idx) = R[0];
*(gData + y0_Idx) = G[0];
*(bData + y0_Idx) = B[0];
*(rData + y0_Idx + 1) = R[1];
*(gData + y0_Idx + 1) = G[1];
*(bData + y0_Idx + 1) = B[1];
*(rData + y1_Idx) = R[2];
*(gData + y1_Idx) = G[2];
*(bData + y1_Idx) = B[2];
*(rData + y1_Idx + 1) = R[3];
*(gData + y1_Idx + 1) = G[3];
*(bData + y1_Idx + 1) = B[3];
}
}
return 1;
}