音视频开发学习(四) -- RGB YUV 格式

文章目录

  • RGB 常见格式
    • RGB555
    • RGB565
    • RGB24
    • RGB32
  • YUV 的常见格式
    • YUV 4:4:4
    • YUV 4:2:2
    • YUV 4:2:0
    • YUV 的存储格式
      • YUYV
      • UYVY
      • YUV 422P
      • YUV420P 和 YUV420SP
      • YU12 和 YV12
      • NV21 和 NV12
  • YUV 与 RGB 转换

RGB 常见格式

RGB555

用16个bit表示一个像素,其中最高位不用,R(红色), G(绿色), B(蓝色)都用5个bit来表示,从高位到低位排列如下:
高字节 低字节
X R R R R R G G G G G B B B B B (X表示不用,可以忽略)
可以组合使用屏蔽字和移位操作来得到RGB各分量的值:

#define RGB555_MASK_RED 0x7C00    // 11111  00000  00000
#define RGB555_MASK_GREEN 0x03E0   //  11111 00000
#define RGB555_MASK_BLUE 0x001F  //  11111
R = (wPixel & RGB555_MASK_RED) >> 10; // 取值范围0-31
G = (wPixel & RGB555_MASK_GREEN) >> 5; // 取值范围0-31
B = wPixel & RGB555_MASK_BLUE; // 取值范围0-31

RGB565

用16个bit表示一个像素,5个bit表示R(红色),6个bit表示G(绿色),5个bit表示B(蓝色),从高位到低位排列如下:
R R R R R G G G G G G B B B B B
可以组合使用屏蔽字和移位操作来得到RGB各分量的值:

#define RGB565_MASK_RED 0xF800
#define RGB565_MASK_GREEN 0x07E0
#define RGB565_MASK_BLUE 0x001F
R = (wPixel & RGB565_MASK_RED) >> 11; // 取值范围0-31
G = (wPixel & RGB565_MASK_GREEN) >> 5; // 取值范围0-63
B = wPixel & RGB565_MASK_BLUE; // 取值范围0-31
#define RGB(r,g,b) (unsigned int)( (r|0x08 << 11) | (g|0x08 << 6) | b|0x08 )
#define RGB(r,g,b) (unsigned int)( (r|0x08 << 10) | (g|0x08 << 5) | b|0x08 )

该代码可以解决24位与16位相互转换的问题

RGB24

用24个bit表示一个像素,R(红色), G(绿色), B(蓝色)都用8个bit来表示,从高位到低位排列如下:

B B B B B B B B G G G G G G G G R R R R R R R R

#define RGB24_MASK_BLUE 0xFF0000
#define RGB24_MASK_GREEN 0xFF00
#define RGB24_MASK_RED 0x00FF
B = (wPixel & RGB24_MASK_BLUE) >> 16; // 取值范围0-255
G = (wPixel & RGB24_MASK_GREEN) >> 8; // 取值范围0-255
R = wPixel & RGB24_MASK_RED; // 取值范围0-255

注意在内存中RGB各分量的排列顺序为:BGR BGR BGR…。通常可以使用RGBTRIPLE数据结构来操作一个像素,它的定义为:

typedef struct tagRGBTRIPLE {
BYTE rgbtBlue; // 蓝色分量
BYTE rgbtGreen; // 绿色分量
BYTE rgbtRed; // 红色分量
} RGBTRIPLE;

RGB32

RGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32就是带Alpha通道的RGB24。Alpha 表示透明)
B B B B B B B B G G G G G G G G R R R R R R R R A A A A A A A A

#define RGB32_MASK_BLUE 0xFF000000
#define RGB32_MASK_GREEN 0xFF0000
#define RGB32_MASK_RED 0xFF00
#define RGB32_MASK_A 0xFF
B = (wPixel & RGB32_MASK_BLUE) >> 24; // 取值范围0-255
G = (wPixel & RGB32_MASK_GREEN) >> 16; // 取值范围0-255
R = (wPixel & RGB32_MASK_BLUE) >> 8; // 取值范围0-255
A = wPixel & RGB32_MASK_A; // 取值范围0-255

注意在内存中RGB各分量的排列顺序为:BGRA BGRA BGRA…。通常可以使用RGBQUAD数据结构来操作一个像素,它的定义为:

typedef struct tagRGBQUAD {
BYTE rgbBlue; // 蓝色分量
BYTE rgbGreen; // 绿色分量
BYTE rgbRed; // 红色分量
BYTE rgbReserved; // 保留字节(用作Alpha通道或忽略)
} RGBQUAD。

YUV 的常见格式

[*] – Y 分量
[ ] – UV 分量
YUV 图像的主流采样方式有如下三种:

YUV 4:4:4 采样
YUV 4:2:2 采样
YUV 4:2:0 采样

YUV 4:4:4

YUV 4:4:4 表示 Y、U、V 三分量采样率相同,即每个像素的三分量信息完整,都是 8bit,每个像素占用 3 个字节。

如下图所示:

[*] [ ] [*] [ ] 
[*] [ ] [*] [ ]
[*] [ ] [*] [ ] 
[*] [ ] [*] [ ] 

四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
采样的码流为: Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3
映射出的像素点为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]

可以看到这种采样方式与 RGB 图像大小是一样的。

YUV 4:2:2

YUV 4:2:2 表示 UV 分量的采样率是 Y 分量的一半。

如下图所示:

[*] [ ] [*] [*] [ ] [*]
[*] [ ] [*] [*] [ ] [*]
[*] [ ] [*] [*] [ ] [*]
[*] [ ] [*] [*] [ ] [*]

四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
采样的码流为: Y0 U0 Y1 V1 Y2 U2 Y3 U3
映射出的像素点为:[Y0 U0 V1]、[Y1 U0 V1]、[Y2 U2 V3]、[Y3 U2 V3]

其中,每采样一个像素点,都会采样其 Y 分量,而 U、V 分量都会间隔采集一个,映射为像素点时,第一个像素点和第二个像素点共用了 U0、V1 分量,以此类推。从而节省了图像空间。

比如一张 1920 * 1280 大小的图片,采用 YUV 4:2:2 采样时的大小为:

(1920 * 1280 * 8 + 1920 * 1280 * 0.5 * 8 * 2 ) / 8 / 1024 / 1024 = 4.68M

可以看出,比 RGB 节省了三分之一的存储空间。

YUV 4:2:0

YUV 4:2:0 并不意味着不采样 V 分量。它指的是对每条扫描线来说,只有一种色度分量以 2:1 的采样率存储,相邻的扫描行存储不同的色度分量。也就是说,如果第一行是 4:2:0,下一行就是 4:0:2,在下一行就是 4:2:0,以此类推。

如下图所示:

[*] [*] [*] [*]
  [ ]     [ ]
[*] [*] [*] [*]

[*] [*] [*] [*]
  [ ]     [ ]
[*] [*] [*] [*]
图像像素为:
[Y0 U0 V0]、[Y1 U1 V1]、 [Y2 U2 V2]、 [Y3 U3 V3]
[Y5 U5 V5]、[Y6 U6 V6]、 [Y7 U7 V7] 、[Y8 U8 V8]
​
采样的码流为:
Y0 U0 Y1 Y2 U2 Y3 
Y5 V5 Y6 Y7 V7 Y8
​
映射出的像素点为:
[Y0 U0 V5]、[Y1 U0 V5]、[Y2 U2 V7]、[Y3 U2 V7]
[Y5 U0 V5]、[Y6 U0 V5]、[Y7 U2 V7]、[Y8 U2 V7]

其中,每采样一个像素点,都会采样 Y 分量,而 U、V 分量都会隔行按照 2:1 进行采样。

一张 1920 * 1280 大小的图片,采用 YUV 4:2:0 采样时的大小为:

(1920 * 1280 * 8 + 1920 * 1280 * 0.25 * 8 * 2 ) / 8 / 1024 / 1024 = 3.51M

相比 RGB,节省了一半的存储空间。

YUV 的存储格式

YUV 数据有两种存储格式:平面格式(planar format)和打包格式(packed format)。

  • planar format:先连续存储所有像素点的 Y,紧接着存储所有像素点的 U,随后是所有像素点的 V。
  • packed format:每个像素点的 Y、U、V 是连续交错存储的。

因为不同的采样方式和存储格式,就会产生多种 YUV 存储方式,这里只介绍基于 YUV422 和 YUV420 的存储方式。

YUYV

YUYV 格式属于 YUV422,采用打包格式进行存储,Y 和 UV 分量按照 2:1 比例采样,每个像素都采集 Y 分量,每隔一个像素采集它的 UV 分量。

Y0 U0 Y1 V0 Y2 U2 Y3 V2

Y0 和 Y1 共用 U0 V0 分量,Y2 和 Y3 共用 U2 V2 分量。

UYVY

UYVY 也是 YUV422 采样的存储格式中的一种,只不过与 YUYV 排列顺序相反。

U0 Y0 V0 Y1 U2 Y2 V2 Y3

YUV 422P

YUV422P 属于 YUV422 的一种,它是一种 planer 模式,即 Y、U、V 分别存储。

YUV420P 和 YUV420SP

YUV420P 是基于 planar 平面模式进行存储,先存储所有的 Y 分量,然后存储所有的 U 分量或者 V 分量。

同样,YUV420SP 也是基于 planar 平面模式存储,与 YUV420P 的区别在于它的 U、V 分量是按照 UV 或者 VU 交替顺序进行存储。

YU12 和 YV12

YU12 和 YV12 格式都属于 YUV 420P 类型,即先存储 Y 分量,再存储 U、V 分量,区别在于:YU12 是先 Y 再 U 后 V,而 YV12 是先 Y 再 V 后 U 。

NV21 和 NV12

NV12 和 NV21 格式都属于 YUV420SP 类型。它也是先存储了 Y 分量,但接下来并不是再存储所有的 U 或者 V 分量,而是把 UV 分量交替连续存储。

NV12 是 IOS 中有的模式,它的存储顺序是先存 Y 分量,再 UV 进行交替存储。

NV21 是 安卓 中有的模式,它的存储顺序是先存 Y 分量,在 VU 交替存储。

YUV 与 RGB 转换

音视频开发学习(四) -- RGB YUV 格式_第1张图片
音视频开发学习(四) -- RGB YUV 格式_第2张图片
那么什么时候该用哪一种转换呢?比较典型的场景是在iOS平台中使用摄像头采集出YUV数据之后,上传显卡成为一个纹理ID,这个时候就需要做YUV到RGB的转换(具体的细节会在后面的章节中详细讲解)。在iOS的摄像头采集出一帧数据之后(CMSampleBufferRef),我们可以在其中调用CVBufferGetAttachment来获取YCbCrMatrix,用于决定使用哪一个矩阵进行转换,对于Android的摄像头,由于其是直接纹理ID的回调,所以不涉及这个问题。其他场景下需要大家自行寻找对应的文档,以找出适合的转换矩阵进行转换。

你可能感兴趣的:(音视频,RGB,格式,YUV,格式,RGB24,YUV422)