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、V1V2
  • Semi-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 字节

根据数据排列方式和压缩类型,又可以组合出多种类型,常用如下:

类型压缩方式排列方式详细说明
I420YUV420PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
U1 U2 V1 V2
YV12YUV420PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
V1 V2 U1 U2
NV12YUV420Semi-PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
U1 V1 U2 V2
NV21YUV420Semi-PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
V1 U1 V2 U2
I422YUV422PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
U1 U2 U3 U4
V1 V2 V3 V4
YV16YUV422PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
V1 V2 V3 V4
U1 U2 U3 U4
NV16YUV422Semi-PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
U1 V1 U2 V2
U3 V3 U4 V4
NV61YUV422Semi-PlanarY1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
V1 U1 V2 U2
V3 U3 V4 U4
YUVYYUV422Interleaved(Packed)Y1 U1 V1 Y2
Y3 U2 V2 Y4
Y5 U3 V3 Y6
VYUYYUV422Interleaved(Packed)V1 Y1 U1 Y2
V2 Y3 U2 Y4
V3 Y5 U3 Y6
UYVYYUV422Interleaved(Packed)U1 Y1 V1 Y2
U2 Y3 V2 Y4
U3 Y5 V3 Y6
I444YUV444PlanarY1 Y2 Y3 Y4
U1 U2 U3 U4
V1 V2 V3 V4
YV24YUV444PlanarY1 Y2 Y3 Y4
V1 V2 V3 V4
U1 U2 U3 U4
NV24YUV444Semi-PlanarY1 Y2 Y3 Y4
U1 V1 U2 V2
U3 V3 U4 V4
NV42YUV444Semi-PlanarY1 Y2 Y3 Y4
V1 U1 V2 U2
V3 U3 V4 U4
YUV444YUV444PackedY1 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=RY
C g = G − Y C_g = G - Y Cg=GY
C b = B − Y C_b = B - Y Cb=BY
按照 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=R0.299R0.587G0.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.701R0.587G0.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.701R0.587G0.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.5R0.419G0.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.5R0.419G0.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.169R0.331G+0.5B+128
对于 YUV 转换为 RGB 则有:
C r = ( R − Y ) / 1.402 + 128 C_r = (R - Y) / 1.402 + 128 Cr=(RY)/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(Cr128)
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=Y0.344(Cb128)0.714(Cr128)
B = Y + 1.772 ∗ ( C b − 128 ) B = Y + 1.772 * (C_b - 128) B=Y+1.772(Cb128)

代码实现

生成的图像可以使用 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);
	}
}
  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值