Metal之探究理解视频渲染RGB与YUV颜色编码

举报
Serendipity·y 发表于 2022/02/16 23:34:23 2022/02/16
【摘要】 一、颜色编码 ① 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

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。