Metal之探究理解视频渲染RGB与YUV颜色编码
【摘要】
一、颜色编码
① RGB 颜色编码
RGB 三个字母分别代表了 红、绿、蓝,这三种颜色作为三个基底颜色,将它们以不同的比例相加,可以产生多种多样的颜色。RGB 图像中,每个像素点都有红、绿、蓝三个基底...
一、颜色编码
① 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)而色度又定义了颜色的两个方面:色调和饱和度。
-
- Y 通道数值越高,图片则越亮;
-
- U 通道数值越高,颜色就越接近蓝色;
-
- V 通道数值越高,颜色就越接近红色。
- 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、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 的水平下采样,没有垂直下采样。
- 对于每两个 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分量。
- 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 分量。 - packed 打包模式:
指每个像素点的 Y、U、V 分量是连续交替存储的。
② 基于 YUV 4:2:2 采样的格式
常见的基于 YUV 4:2:2 采样的格式如下:YUYV 格式、UYVY 格式、YUV 422P 格式。
- YUYV 格式
YUYV 格式是采用打包格式进行存储的,指每个像素点都采用 Y 分量,但是每隔一个像素采样它的 UV 分量,排列顺序如下:
Y0 UO Y1 V0 Y2 U2 Y3 V2
(Y0 和 Y1 公用 U0 V0 分量,Y2 和 Y3 公用 U2 V2 分量….) - 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 采样的格式
- 基于 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 的交替顺序进行存储了,具体查看看下图:
- YUV420P 的格式:
五、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)
);
- 1
- 2
- 3
- 4
- 5
- 6
- 将上面向量与矩阵乘法写成行列式形式,可能更符合大家的直觉:
R = Y + 1.402*V - 0.701
G = Y - 0.3441*U - 0.7141*V + 0.5291
B = Y + 1.772*U - 0.886
- 1
- 2
- 3
- 可以发现,这个 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)
// YUV 到 RGB
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//RGB 到 YUV
box1.simdTransform = p
// box2.simdTransform = box2.simdTransform * ycbcrToRGBTransform.inverse * box2.simdTransform.inverse
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
// 用 shader 进行可视化显示
func simpleProgram(node:SCNNode) {
let program = SCNProgram()
program.vertexFunctionName = "vertexShader"
program.fragmentFunctionName = "fragmentShader"
// 赋值给**SCNGeometry**或者**SCNMaterial**
guard let material = node.geometry?.materials.first else { fatalError() }
material.program = program
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
// 默认的头文件
#include <metal_stdlib>
using namespace metal;
// 与 SceneKit 配合使用时,需要的头文件
#include <SceneKit/scn_metal>
struct VertexInput {
float3 position [[attribute(SCNVertexSemanticPosition)]];
};
struct ColorInOut {
float4 position [[position]];
float4 color;
};
struct MyNodeData {
float4x4 modelViewProjectionTransform;
};
// 顶点着色器函数,输出为 ColorInOut 类型,输入为 VertexInput 类型的变量 in,和 MyNodeData 类型的变量指针 scn_node
vertex ColorInOut vertexShader(VertexInput in [[stage_in]], constant MyNodeData& scn_node [[buffer(0)]]) {
ColorInOut out;
// 将模型空间的顶点补全为 float4 类型,进行 MVP 变换
out.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
// 加 0.5,将坐标从[-0.5~0.5],转换到[0~1] 以代表颜色
out.color = float4(in.position + 0.5, 1);
return out;
}
// 片元着色器函数,输出为 half4,输入为 ColorInOut 类型的变量 in
fragment half4 fragmentShader(ColorInOut in [[stage_in]]) {
return half4(in.color);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
六、总结
- 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 分量。
文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/Forever_wj/article/details/108297864
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)