一、颜色编码
① RGB 颜色编码
RGB 三个字母分别代表红、绿、蓝,这三种颜色作为三个基底颜色 ,将它们以不同的比例相加,可以产生多种多样的颜色。 RGB 图像中,每个像素点都有红、绿、蓝三个基底颜色,其中每种原色都占用 8 bit,也就是一个字节(0-255),那么一个像素点也就占用 24 bit,也就是三个字节。
在图像显示中,一张 1280 * 720 大小的图片,就代表着它有 1280 * 720 个像素点。其中每一个像素点的颜色显示都采用 RGB 编码方法,将 RGB 分别取不同的值,就会展示不同的颜色,就占用 1280 * 720 * 3 / 1024 / 1024 = 2.63 MB 存储空间。
② YUV 颜色编码
YUV 是指亮度参量 和色度参量 分开表示的像素格式,而这样分开的好处就是不但可以避免相互干扰,还可以降低色度的采样率 而不会对图像质量影响太大。 YUV 颜色编码采用的是明亮度 和色度 来指定像素的颜色。 Y 表示明亮度(Luminance、Luma) U 和 V 表示色度(Chrominance、Chroma)而色度又定义了颜色的两个方面:色调和饱和度。
YUV 颜色编码格式在默认情况下是图像和视频压缩 的标准。 和 RGB 表示图像类似,每个像素点都包含 Y、U、V 分量。但是它的 Y 和 UV 分量是可以分离的,如果没有 UV 分量一样可以显示完整的图像,只不过是黑白的,如下图所示:
对于 YUV 图像来说,并不是每个像素点都需要包含了 Y、U、V 三个分量,根据不同的采样格式,可以每个 Y 分量都对应自己的 UV 分量,也可以几个 Y 分量共用 UV 分量。
二、RGB 与 YUV 的相互转换
对于图像显示器来说,它是通过 RGB 模型来显示图像的,而在传输图像数据时又是使用 YUV 模型,这是因为 YUV 模型可以节省带宽。因此就需要采集图像时将 RGB 模型转换到 YUV 模型,显示时再将 YUV 模型转换为 RGB 模型。 RGB 到 YUV 的转换,就是将图像所有像素点的 R、G、B 分量转换到 Y、U、V 分量。公式如下: Y = 0.299 * R + 0.587 * G + 0.114 * B U = -0.147 * R - 0.289 * G + 0.436 * B V = -0.615 * R - 0.515 * G - 0.100 * B YUV 到 RGB 的转换如下: R = Y + 1.14 * V G = Y - 0.39 * U - 0.58 * V B = Y + 2.03 * U 此时的转换结束后,每个像素点都有完整的 Y、U、V 分量。而之前提到 Y 和 UV 分量是可以分离的,接下来通过不同的采样方式,可以将图像的 Y、U、V 分量重新组合。 不同采样格式都是在一张图像所有像素的 RGB 转换到 YUV 基础上进行的。
三、采样方式
YUV 的优点之一是,色度频道的采样率可比 Y 频道低,同时不会明显降低视觉质量。 有一种表示法可用来描述 U 和 V 与 Y 的采样频率比例,这个表示法称为 A:B:C 表示法。
① YUV 4:4:4 采样
表示色度频道没有下采样, 每个 Y 分量对应自己的 UV 分量,每一个 Y 分量对于一对 UV 分量,每像素占用 (Y + U + V = 8 + 8 + 8 = 24bits)3 字节。 其中的 Y、U、V 三个分量的采样比例是相同的,所以每个像素点的分量信息都是完整的,每个分量占用 8bit,一个像素点占用 1 个字节。与 RGB 颜色编码相比,并没有节省带宽,占用的存储空间也没有减少。
假设原始图像的像素为(一对[]表示一个像素点): [Y0, U0, V0]; [Y1, U1, V1]; [Y2, U2, V2]; [Y3, U3, V3]; 将原始图像像素按照YUV4:4:4采样的码流为(相对原始像素是原样输出): 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 颜色模型的图像大小是一样,并没有达到节省带宽的目的,当将 RGB 图像转换为 YUV 图像时,也是先转换为 YUV 4:4:4 采样的图像。
② YUV 4:2:2 采样
表示 2:1 的水平下采样,没有垂直下采样,每两个 Y 分量共用一对 UV 分量,每像素占用 (Y + 0.5U + 0.5V = 8 + 4 + 4 = 16bits)2 字节 对于每两个 U 样例或 V 样例,每个扫描行都包含四个 Y 样例。 两个 Y 分量共用一套 UV 分量,意味着 UV 分量是 Y 分量采样的一半,Y 分量和 UV 分量按照 2 : 1 的比例采样。如果水平方向有 4 个像素点,那么采样了 4 个 Y 分量,而只采样了 2 个 UV 分量。
每采样一个像素点,都会采样 Y 分量,而 U、V 分量则会间隔一个采集一个; 假设原始图像的像素为(一对[]表示一个像素点): [Y0, U0, V0]; [Y1, U1, V1]; [Y2, U2, V2]; [Y3, U3, V3]; 将原始图像像素按照 YUV4:2:2 采样的码流为: Y0, U0, Y1, V1, Y2, U2, Y3, V3 最后映射还原的像素点为: [Y0, U0, V1]; [Y1, U0, V1]; [Y2, U2, V3]; [Y3, U2, V3]; 采样的码流映射为像素点,还是要满足每个像素点有 Y、U、V 三个分量。但是可以看到,第一和第二像素点公用了 U0、V1 分量,第三和第四个像素点公用了 U2、V3 分量,这样就节省了图像空间。
③ YUV 4:2:0 采样
表示 2:1 的水平下采样,2:1 的垂直下采样,4 个 Y 分量共用一套 UV 分量,每四个 Y 分量共用一对 UV 分量,每像素占用 (Y + 0.25U + 0.25V = 8 + 2 + 2 = 12bits)1.5 字节 YUV 4:2:0 采样,并不是指只采样 U 分量而不采样 V 分量。而是指,在每一行扫描时,只扫描一种色度分量(U 或者 V),和 Y 分量按照 2 : 1 的方式采样。 第一行扫描时,YU 按照 2 : 1 的方式采样,那么第二行扫描时,YV 分量按照 2:1 的方式采样。 对于每个色度分量来说,它的水平方向和竖直方向的采样和 Y 分量相比都是 2:1 。 在田字格的 4 个像素点中,4 个 Y 分量共用了一套 UV 分量,如图所示
假设原始图像的像素为(一对 [] 表示一个像素点): [Y0, U0, V0]; [Y1, U1, V1]; [Y2, U2, V2]; [Y3, U3, V3]; [Y5, U5, V5]; [Y6, U6, V6]; [Y7, U7, V7]; [Y8, U8, V8]; 将原始图像像素按照YUV4:2:0采样的码流为: 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 分量是共用了一套 UV 分量,而且是按照 2*2 的小方格的形式分布的,相比 YUV 4:2:2 采样中两个 Y 分量共用一套 UV 分量,这样更能够节省空间。
④ YUV 4:1:1 采样
表示 4:1 的水平下采样,没有垂直下采样。对于每个 U 样例或 V 样例,每个扫描行都包含四个 Y 样例。 与其他格式相比,4:1:1 采样不太常用,本文不对其进行详细讨论
四、YUV 存储格式
① 平面格式与打包格式
YUV 格式可以分为打包格式和平面格式。在打包格式中,Y、U 和 V 组件存储在一个数组中,像素被组织到了一些巨像素组中,巨像素组的布局取决于格式;在平面格式中,Y、U 和 V 组件作为三个单独的平面进行存储。
planar 平面格式:指先连续存储所有像素点的 Y 分量,然后存储 U 分量,最后是 V 分量,如 YV12、YU12 格式;
packed 打包模式:指每个像素点的 Y、U、V 分量是连续交替存储的,如 YUYV 、NV21 格式。
② 基于 YUV 4:2:2 采样的格式
常见的基于 YUV 4:2:2 采样的格式:YUYV 格式、UYVY 格式、YUV 422P 格式。
YUYV 格式是采用打包格式 进行存储的,指每个像素点都采用 Y 分量,但是每隔一个像素采样它的 UV 分量,排列顺序如下:
Y0 UO Y1 V0 Y2 U2 Y3 V2
( Y0 和 Y1 公用 U0 V0 分量,Y2 和 Y3 公用 U2 V2 分量…. )
YUYV 是 2 个Y 分量共用一对 UV 分量,YUYV 格式的存储格式:
( 0 ~ 7 ) Y00 U00 Y01 V00 Y02 U01 Y03 V01
( 8 ~ 15 ) Y10 U10 Y11 V10 Y12 U11 Y13 V11
( 16 ~ 23 ) Y20 U20 Y21 V20 Y22 U21 Y23 V21
( 24 ~ 31 ) Y30 U30 Y31 V30 Y32 U31 Y33 V31
一幅 720P (1280x720分辨率) 的图片,使用 YUV422 采样时占用存储大小为:
Y 分量:1280 * 720 = 921600 字节
U 分量:1280 * 720 * 0.5 = 460800 字节
V 分量:1280 * 720 * 0.5 = 460800 字节
总大小:Y 分量 + U 分量 + V 分量 = (1280 * 720 + 1280 * 720 * 0.5 * 2 ) / 1024 / 1024 = 1.76 MB
由上面计算可以看出 YUV422 采样的图像比 RGB 模型图像节省了 1/3 的存储空间,在传输时占用的带宽也会随之减小。
UYVY 格式:UYVY 格式也是采用打包格式 进行存储,它的顺序和 YUYV 相反,先采用 U 分量再采样 Y 分量,排列顺序如下:
U0 Y0 V0 Y1 U2 Y2 V2 Y3
( Y0 和 Y1 公用 U0 V0 分量,Y2 和 Y3 公用 U2 V2 分量…. )
YUV 422P 格式:YUV 422P 格式,又叫做 I422,采用的是平面格式进行存储,先存储所有的 Y 分量,再存储所有的 U 分量,再存储所有的 V 分量。
③ 基于 YUV 4:2:0 采样的格式
(A)YV12/YU12 (YUV420 采样方式)
基于 YUV 4:2:0 采样的格式主要有 YUV 420P 和 YUV 420SP 两种类型,每个类型又对应其他具体格式。
YUV 420P 类型 YUV 420SP 类型 YV12 格式 NV12 格式 YU12 格式 NV21 格式
YUV 420P 和 YUV 420SP 都是基于 Planar 平面模式进行存储的,先存储所有的 Y 分量后, YUV420P 类型就会先存储所有的 U 分量或者 V 分量,而 YUV420SP 则是按照 UV 或者 VU 的交替顺序进行存储,具体查看看下图:
( 0 ~ 3 ) Y00 Y01 Y02 Y03
( 4 ~ 7 ) Y10 Y11 Y12 Y13
( 8 ~ 11 ) Y20 Y21 Y22 Y23
( 12 ~ 15 ) Y30 Y31 Y32 Y33
( 16 ~ 17 ) V00 V01
( 18 ~ 19 ) V10 V11
( 20 ~ 21 ) U00 U01
( 22 ~ 23 ) U10 U11
( 0 ~ 3 ) Y00 Y01 Y02 Y03
( 4 ~ 7 ) Y10 Y11 Y12 Y13
( 8 ~ 11 ) Y20 Y21 Y22 Y23
( 12 ~ 15 ) Y30 Y31 Y32 Y33
( 16 ~ 17 ) U00 U01
( 18 ~ 19 ) U10 U11
( 20 ~ 21 ) V00 V01
( 22 ~ 23 ) V10 V11
一幅 720P (1280x720分辨率) 的图片,使用 YUV420 采样时(格式 YV12/YU12 )占用存储大小为:
Y 分量:1280 * 720 = 921600 字节
U 分量:1280 * 720 * (1 / 4 ) = 230400 字节
V 分量:1280 * 720 * (1 / 4 ) = 230400 字节
总大小:Y 分量 + U 分量 + V 分量 = (1280 * 720 + 1280 * 720 * (1 / 4 )* 2 ) / 1024 / 1024 = 1.32 MB
由上面计算可以看出 YUV420 采样(格式 YV12/YU12 )的图像比 RGB 模型图像节省了 1/2 的存储空间。
(B)NV21/NV12 (YUV420 采样方式)
NV21/NV12 属于 YUV420SP,YUV420SP 格式有 2 个平面,Y 分量存储于一个平面,UV 分量交错存储于另一个平面。
( 0 ~ 3 ) Y00 Y01 Y02 Y03
( 4 ~ 7 ) Y10 Y11 Y12 Y13
( 8 ~ 11 ) Y20 Y21 Y22 Y23
( 12 ~ 15 ) Y30 Y31 Y32 Y33
( 16 ~ 19 ) V00 U00 V01 U01
( 20 ~ 23 ) V10 U10 V11 U11
( 0 ~ 3 ) Y00 Y01 Y02 Y03
( 4 ~ 7 ) Y10 Y11 Y12 Y13
( 8 ~ 11 ) Y20 Y21 Y22 Y23
( 12 ~ 15 ) Y30 Y31 Y32 Y33
( 16 ~ 19 ) U00 V00 U01 V01
( 20 ~ 23 ) U10 V10 U11 V11
NV21 与 NV12 格式的区别仅在于 UV 分量排列的先后顺序不同。一幅 720P (1280x720分辨率) 的图片,使用 YUV420 采样时(格式 NV21/NV12 )占用存储大小为:
Y 分量:1280 * 720 = 921600 字节
UV 分量:1280 * 720 * (1 / 2 ) = 460800 字节
总大小:Y 分量 + UV 分量 = (1280 * 720 + 1280 * 720 * (1 / 2 )) / 1024 / 1024 = 1.32 MB
由上面计算可以看出 YUV420 采样(格式 NV21/NV12 )的图像比 RGB 模型图像也节省 1/2 的存储空间。
五、RGB 与YUV 转换矩阵的几何含义
YUV 与 RGB 的转换公式不止一种,主要原因是具体格式下,标准不同,这里采用苹果 Demo 中给出的转换矩阵,其它转换公式中,具体数值可能不同:
let ycbcrToRGBTransform = float4x4 (
simd_float4 ( + 1.0000 , + 1.0000 , + 1.0000 , + 0.0000 ) ,
simd_float4 ( + 0.0000 , - 0.3441 , + 1.7720 , + 0.0000 ) ,
simd_float4 ( + 1.4020 , - 0.7141 , + 0.0000 , + 0.0000 ) ,
simd_float4 ( - 0.7010 , + 0.5291 , - 0.8860 , + 1.0000 )
) ;
将上面向量与矩阵乘法写成行列式形式,可能更符合大家的直觉:
R = Y + 1.402 * V - 0.701
G = Y - 0.3441 * U - 0.7141 * V + 0.5291
B = Y + 1.772 * U - 0.886
可以发现,这个 YUV 转 RGB 的公式其实是个线性变换,用几何的方式表达就是说:
将一个 RGB 的颜色用 xyz 坐标表示,那么将这个坐标(旋转、缩放、平移)之后,新的 xyz 坐标就可以表示 YUV 颜色值;
反之也是,将一个 YUV 颜色分量当做 xyz 坐标,那么将这个坐标逆向(旋转、缩放、平移)之后,新的 xyz 坐标就可以表示 RGB 颜色值; 于是,可以在 3D 空间中画一个边长为 1 的正方体,后方左下角(0, 0, 0) 就代表黑色,前方右上角(1, 1, 1) 就代表白色,如下图右下角立方体。同样复制一个,并将其坐标用矩阵转换到 YUV 空间,如下图左上角倾斜的长方体。
对于 RGB 的立方体,比较简单:它的 x 坐标越大,越往右方,颜色越红;y 坐标越大,越往上方,颜色越绿;z 坐标越大,越往前方,颜色越蓝。 而 YUV 的长方体,它的 x 坐标越大,越往右方,亮度越大;y 坐标越大,越往上方,颜色从黄到蓝;z 坐标越大,越往前方,颜色从青绿到红。
let box1 = scene. rootNode. childNode ( withName: "box" , recursively: true) !
let box2 = scene. rootNode. childNode ( withName: "box2" , recursively: true) !
simpleProgram ( node: box1)
simpleProgram ( node: box2)
let ycbcrToRGBTransform = float4x4 (
simd_float4 ( + 1.0000 , + 1.0000 , + 1.0000 , + 0.0000 ) ,
simd_float4 ( + 0.0000 , - 0.3441 , + 1.7720 , + 0.0000 ) ,
simd_float4 ( + 1.4020 , - 0.7141 , + 0.0000 , + 0.0000 ) ,
simd_float4 ( - 0.7010 , + 0.5291 , - 0.8860 , + 1.0000 )
) ;
let p = ycbcrToRGBTransform. inverse
box1. simdTransform = p
func simpleProgram ( node: SCNNode) {
let program = SCNProgram ( )
program. vertexFunctionName = "vertexShader"
program. fragmentFunctionName = "fragmentShader"
guard let material = node. geometry? . materials. first else { fatalError ( ) }
material. program = program
}
# include <metal_stdlib>
using namespace metal;
# include <SceneKit/scn_metal>
struct VertexInput {
float3 position [ [ attribute ( SCNVertexSemanticPosition) ] ] ;
} ;
struct ColorInOut {
float4 position [ [ position] ] ;
float4 color;
} ;
struct MyNodeData {
float4x4 modelViewProjectionTransform;
} ;
vertex ColorInOut vertexShader ( VertexInput in [ [ stage_in] ] , constant MyNodeData& scn_node [ [ buffer ( 0 ) ] ] ) {
ColorInOut out;
out. position = scn_node. modelViewProjectionTransform * float4 ( in . position, 1.0 ) ;
out. color = float4 ( in . position + 0.5 , 1 ) ;
return out;
}
fragment half4 fragmentShader ( ColorInOut in [ [ stage_in] ] ) {
return half4 ( in . color) ;
}
六、总结
YUV4:4:4 中 Y、U、V 分量的采样比例相同,既可以理解为原始图像像素点原样输出,存储空间没有任何变化。 YUV4:2:2 采样格式,是指每采样一个像素点,都会采样 Y 分量,而 U、V 分量则会间隔一个采集一个,本质是通过左右相邻像素点共用 U/V 分量。相比 RGB 颜色编码格式,节省了 1/3 的存储空间,同时节约了在传输时的带宽。 YUV4:2:0 采样格式,是实际开发中最常用的颜色编码格式,相比 YUV4:2:2 采样格式,更能节省空间。是指在 2*2 的田字格中有 4 个像素点,其中 4 个 Y 分量共用一套 UV 分量,其本质是通过田字格的上下左右像素点共用 U/V 分量。