YUV颜色空间与RGB的转换
HVS
人类视觉系统(HVS,human visual system)对 色度 的敏感度 是远小于 亮度
,所以可以对 色度 采用更小的采样率来压缩数据,对亮度采用正常的采样率即可。其中色度又包含 色调
和 色饱和度
。
YCbCr
YUV、Y’UV、YCbCr、YPbPr 都属于YUV颜色模型,其中Y表示亮度,U表示色度,V表示饱和度
。在计算机音视频领域中,YUV 一般为 YCbCr
,其中 Cb 表示蓝色分量,Cr表示红色分量。
YUV分类
按照数据排列方式,YUV主要分为两种格式:
平面格式 Planar
:YUV 三个分量分开存放,例如 Y1Y2、U1U2、V1V2Semi-Planar
:Y 分量单独存放,UV 分量交错存放,例如 Y1Y2、U1V1、U2V2打包格式 Packed
:YUV 三个分量全部交错存放,例如 Y1U1V1、Y2U2V2
根据 HVS 对其压缩,按压缩方式对其可以分为三类:
YUV 4:4:4
,每一个 Y 分量对于一对 UV 分量,每像素占用 (Y + U + V = 8 + 8 + 8 = 24bits)3 字节YUV 4:2:2
,每两个 Y 分量共用一对 UV 分量,每像素占用 (Y + 0.5U + 0.5V = 8 + 4 + 4 = 16bits)2 字节YUV 4:2:0
,每四个 Y 分量共用一对 UV 分量,每像素占用 (Y + 0.25U + 0.25V = 8 + 2 + 2 = 12bits)1.5 字节
根据数据排列方式和压缩类型,又可以组合出多种类型,常用如下:
类型 | 压缩方式 | 排列方式 | 详细说明 |
---|---|---|---|
I420 | YUV420 | Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 U1 U2 V1 V2 |
YV12 | YUV420 | Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 V1 V2 U1 U2 |
NV12 | YUV420 | Semi-Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 U1 V1 U2 V2 |
NV21 | YUV420 | Semi-Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 V1 U1 V2 U2 |
I422 | YUV422 | Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 U1 U2 U3 U4 V1 V2 V3 V4 |
YV16 | YUV422 | Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 V1 V2 V3 V4 U1 U2 U3 U4 |
NV16 | YUV422 | Semi-Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 U1 V1 U2 V2 U3 V3 U4 V4 |
NV61 | YUV422 | Semi-Planar | Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 V1 U1 V2 U2 V3 U3 V4 U4 |
YUVY | YUV422 | Interleaved(Packed) | Y1 U1 V1 Y2 Y3 U2 V2 Y4 Y5 U3 V3 Y6 |
VYUY | YUV422 | Interleaved(Packed) | V1 Y1 U1 Y2 V2 Y3 U2 Y4 V3 Y5 U3 Y6 |
UYVY | YUV422 | Interleaved(Packed) | U1 Y1 V1 Y2 U2 Y3 V2 Y4 U3 Y5 V3 Y6 |
I444 | YUV444 | Planar | Y1 Y2 Y3 Y4 U1 U2 U3 U4 V1 V2 V3 V4 |
YV24 | YUV444 | Planar | Y1 Y2 Y3 Y4 V1 V2 V3 V4 U1 U2 U3 U4 |
NV24 | YUV444 | Semi-Planar | Y1 Y2 Y3 Y4 U1 V1 U2 V2 U3 V3 U4 V4 |
NV42 | YUV444 | Semi-Planar | Y1 Y2 Y3 Y4 V1 U1 V2 U2 V3 U3 V4 U4 |
YUV444 | YUV444 | Packed | Y1 U1 V1 Y2 U2 V1 Y3 U3 V3 Y4 U4 V4 |
YUV 与 RGB 转换
YUV 与 RGB 关系有如下公式:
Y
=
K
r
R
+
K
g
G
+
K
b
B
Y = K_rR+K_gG+K_bB
Y=KrR+KgG+KbB
其中:
K
r
+
K
g
+
K
b
=
1
K_r+K_g+K_b = 1
Kr+Kg+Kb=1
根据不同的标准,
K
r
K_r
Kr、
K
g
K_g
Kg、
K
b
K_b
Kb的值存在差异:
- BT.601标准[1] —— 标清数字电视(SDTV): Y = 0.299 R + 0.587 G + 0.114 B Y = 0.299 R+0.587 G+0.114B Y=0.299R+0.587G+0.114B
- BT.709标准[2] —— 高清数字电视(HDTV): Y = 0.2126 R + 0.7152 G + 0.0722 B Y = 0.2126 R+0.7152 G+0.0722B Y=0.2126R+0.7152G+0.0722B
- BT.2020标准[3] —— 超高清数字电视(UHDTV): Y = 0.2627 R + 0.6780 G + 0.0593 B Y = 0.2627 R+0.6780 G+0.0593B Y=0.2627R+0.6780G+0.0593B
对于
C
r
C_r
Cr 、
C
g
C_g
Cg 、
C
b
C_b
Cb 定义有:
C
r
=
R
−
Y
C_r = R - Y
Cr=R−Y
C
g
=
G
−
Y
C_g = G - Y
Cg=G−Y
C
b
=
B
−
Y
C_b = B - Y
Cb=B−Y
按照 BT.601标准 将其套入公式可以推导出如下公式:
C
r
=
R
−
0.299
R
−
0.587
G
−
0.114
B
C_r = R - 0.299 R-0.587 G-0.114B
Cr=R−0.299R−0.587G−0.114B
C
r
=
0.701
R
−
0.587
G
−
0.114
B
C_r = 0.701 R-0.587 G-0.114 B
Cr=0.701R−0.587G−0.114B
对 R、G、B进行归一化,使 R 为 1,其余值为 0 ,则有
C
r
C_r
Cr最大值为 0.701;使 R 为 0 ,其余值为 1,则有最小值 - 0.701,即
C
r
C_r
Cr 取值范围为 [-0.701,0.701],对其进行归一化则有:
C
r
=
(
0.701
R
−
0.587
G
−
0.114
B
)
/
(
0.701
+
0.701
)
C_r = (0.701 R-0.587 G-0.114 B) / (0.701+0.701)
Cr=(0.701R−0.587G−0.114B)/(0.701+0.701)
C
r
=
0.5
R
−
0.419
G
−
0.081
B
C_r = 0.5 R - 0.419 G-0.081 B
Cr=0.5R−0.419G−0.081B
当 R = 255 时,
C
r
C_r
Cr 值最大为 128,当 R = 0,G = 255,B = 255 时,
C
r
C_r
Cr值最小为 -128,所以需要将其对齐到区间 [0,255],则有:
C
r
=
0.5
R
−
0.419
G
−
0.081
B
+
128
C_r = 0.5 R - 0.419 G-0.081 B + 128
Cr=0.5R−0.419G−0.081B+128
按照如上方式推导可得:
C
b
=
−
0.169
R
−
0.331
G
+
0.5
B
+
128
C_b = -0.169 R - 0.331 G + 0.5 B + 128
Cb=−0.169R−0.331G+0.5B+128
对于 YUV 转换为 RGB 则有:
C
r
=
(
R
−
Y
)
/
1.402
+
128
C_r = (R - Y) / 1.402 + 128
Cr=(R−Y)/1.402+128
所以(最终还要对值进行判断是否在 [0,255] 区间,如果小于0则为0,如果大于255则为255):
R
=
Y
+
1.402
∗
(
C
r
−
128
)
R = Y + 1.402 * (C_r - 128)
R=Y+1.402∗(Cr−128)
G
=
Y
−
0.344
∗
(
C
b
−
128
)
−
0.714
∗
(
C
r
−
128
)
G = Y - 0.344 * (C_b - 128) - 0.714 * (C_r - 128)
G=Y−0.344∗(Cb−128)−0.714∗(Cr−128)
B
=
Y
+
1.772
∗
(
C
b
−
128
)
B = Y + 1.772 * (C_b - 128)
B=Y+1.772∗(Cb−128)
代码实现
生成的图像可以使用 YUView 打开查看(https://github.com/IENT/YUView)
#define LIBYUV 1 //使用 LIBYUV 的快速计算公式
#define R 2 //R的顺序
#define G 1 //G的顺序
#define B 0 //B的顺序
#define BPP 3 //BPP是一个像素占的字节数
//取平均值,加1为了实现四舍五入
#define AVGB(a, b) (((a) + (b) + 1) >> 1)
//下面三个函数为RGB-->>Yuv420的公式
static __inline uint8_t rgb_2_y(uint8_t r, uint8_t g, uint8_t b)
{
if (LIBYUV) {
return (38 * r + 75 * g + 15 * b + 64) >> 7;
}
else {
return 0.299 * r + 0.587 * g + 0.114 * b;
}
}
static __inline uint8_t rgb_2_u(uint8_t r, uint8_t g, uint8_t b)
{
if (LIBYUV) {
return (127 * b - 84 * g - 43 * r + 0x8080) >> 8;
}
else {
return -0.169 * r - 0.331 * g + 0.5 * b + 128;
}
}
static __inline uint8_t rgb_2_v(uint8_t r, uint8_t g, uint8_t b)
{
if (LIBYUV) {
return (127 * r - 107 * g - 20 * b + 0x8080) >> 8;
}
else {
return 0.5 * r - 0.419 * g - 0.081 * b + 128;
}
}
void rgb_2_nv21_y_row(const uint8_t* rgb, int width, uint8_t* y_buffer) {
for (int x = 0;x < width;x++) {
y_buffer[0] = rgb_2_y(rgb[R], rgb[G], rgb[B]);
rgb += BPP;
y_buffer++;
}
}
void rgb_2_nv21_vu_row(const uint8_t* rgb, int width, int width_bytes, uint8_t* vu_buffer) {
/*
* A B
* C D 四个像素RGB值的平均值,再计算出U,V
*/
const uint8_t* rgb_next_row = rgb + width_bytes;
for (int x = 0;x < width - 1;x += 2) {
uint8_t r = AVGB(AVGB(rgb[R], rgb[R + BPP]), AVGB(rgb_next_row[R], rgb_next_row[R + BPP]));
uint8_t g = AVGB(AVGB(rgb[G], rgb[G + BPP]), AVGB(rgb_next_row[G], rgb_next_row[G + BPP]));
uint8_t b = AVGB(AVGB(rgb[B], rgb[B + BPP]), AVGB(rgb_next_row[B], rgb_next_row[B + BPP]));
vu_buffer[0] = rgb_2_v(r, g, b);
vu_buffer[1] = rgb_2_u(r, g, b);
vu_buffer += 2;
rgb += (2 * BPP);
rgb_next_row += (2 * BPP);
}
//如果是奇数列,最后一列只对两个数求平均
if (width & 1) {
uint8_t r = AVGB(rgb[R], rgb_next_row[R]);
uint8_t g = AVGB(rgb[G], rgb_next_row[G]);
uint8_t b = AVGB(rgb[B], rgb_next_row[B]);
vu_buffer[0] = rgb_2_v(r, g, b);
vu_buffer[1] = rgb_2_u(r, g, b);
}
}
//RGB 转 NV21 (YUV420SP)
void rgb_2_nv21(const uint8_t* rgb, int width, int height, uint8_t** yuv_buffer, int* yuv_size) {
int yuv_width = (width & 1) ? width + 1 : width;
int yuv_height = (height & 1) ? height + 1 : height;
*yuv_size = (yuv_width * yuv_height * BPP + 1) >> 1;
*yuv_buffer = (uint8_t*)malloc(*yuv_size);
uint8_t* y_buffer = *yuv_buffer;
uint8_t* vu_buffer = y_buffer + (width * height);
int rgb_offset = 0;
int width_bytes = width * BPP;
for (int y = 0;y < height - 1;y += 2) {
rgb_2_nv21_vu_row(rgb + rgb_offset, width, width_bytes, vu_buffer);
vu_buffer += (width & 1) ? width + 1 : width;
rgb_2_nv21_y_row(rgb + rgb_offset, width, y_buffer);
y_buffer += width;
rgb_offset += width_bytes;
rgb_2_nv21_y_row(rgb + rgb_offset, width, y_buffer);
y_buffer += width;
rgb_offset += width_bytes;
}
//如果是奇数行
if (height & 1) {
rgb_2_nv21_y_row(rgb + rgb_offset, width, y_buffer);
rgb_2_nv21_vu_row(rgb + rgb_offset, width, 0, vu_buffer);
}
}