关于音视频:音视频开发进阶|第六讲色彩和色彩空间下篇

40次阅读

共计 8788 个字符,预计需要花费 22 分钟才能阅读完成。

在前两篇推文中,咱们理解了色调空间、像素、图像和视频之间的组成关系,并且比拟具体的学习了色调空间 RGB、YUV 的采样 & 存储格局。明天,咱们基于这些内容,再补充一些重要的关联常识。

咱们曾经晓得,像素是图像的根本组成单元,所以对视频图像的存储,实际上是对像素的存储。计算机在解决图像时,须要按肯定规定将像素数据从内存中读取进去。这里的“规定”,首先基于色调的采样 & 存储格局,其规定了色调重量的“存储程序”以及“分立体存储逻辑”。但仅晓得这些信息,对“单纯”的计算机来说还是不够的,咱们必须明确地通知它:要读取多少字节长度的数据,这里就会引申出“定量”的规定。

=======

图像位深

=================

由点及面,先从像素开始,理解每个像素在计算机里是如何“定量”存储的,再扩大到视频图像上 。要学习这部分内容,先给大家介绍一个新面孔: 图像位深

其实,在之前 音频因素 的推文中,咱们已接触到“音频采样位深”的概念。音频采样位深,指的是用多大的字节空间来存储声音的量化值。一般来说,音频采样位深越大,则声音采样量化的精度越高、失真越少。当初,咱们要把位深的概念延长到视频图像畛域。

在视频图像畛域,对于位深的概念比拟多,诸如:通道位深、像素位深、色调位深和图像位深 等。为了防止混同,在本文中咱们要将相干定义对立一下,并以 RGB 图像举例说明如下。

对于 RGB 图像,如果咱们别离应用 8bit(1 个字节)来存储色调空间的各个通道重量,则一个残缺的 RGB 像素将占用 3*8 = 24bit 空间(3 个字节)。此时,咱们称:

  • 通道位深:8bit,示意存储色调空间的一个重量(通道)须要 8bit 空间;
  • 像素位深:24bit,示意存储一个 RGB 像素须要 24bit 空间。

注:本文中,除非特地阐明外,咱们提及的图像位深均指像素位深。

须要补充的是,图像位深 24bit、通道位深 8bit 是比拟规范的位深配置,大家可能还会接触到诸如 32bit、16bit、8bit 等图像位深,它们并不是 3 的倍数,无奈平摊到 RGB 或者 YUV 的三个通道上。咱们应该如何了解这些“不规则”的图像位深呢?

其实,咱们只有确认到具体的通道位深,就能够比拟清晰的了解了,如下:

  • 32bit 图像位深:在 24bit RGB 图像的根底上,减少了一个 8bit 的通明通道 A。比方咱们上篇推文提到的 RGBA、BGRA 等等,能够称为 RGBA32、BGRA32;
  • 16bit 图像位深:R、G、B 通道重量,别离应用 5bit、6bit、5bit 通道位深,能够称为 RGB565;
  • 8bit 图像位深:R、G、B 通道重量,别离应用 2bit、3bit、3bit 通道位深,能够称为 RGB233。

除上述举例外,还会有诸如 RGBA4444、RGB555 等等状况。当脱离本文领域,大家在理论利用中接触到图像位深时,仍须要明确其具体含意,到底是像素位深、还是通道位深、每个通道又是怎么调配的,防止混同。

当初,让咱们再回到图像位深为 24bit、通道位深为 8bit 的配置上。在该配置下,RGB 的每一个通道重量可示意 2^8 = 256 个值。这意味着,如果仅思考 R 重量,就会有 256 种深浅不同的红色。以此类推,三个通道综合,即能够失去 (2^8)^3 = 16,777,216 种不同的组合,每种组合示意不同的色彩。这也就是之前的推文中咱们说“RGB 色调空间能够示意约 1677 万种色调”的起因。

显然,图像位深越大,其像素可示意的色彩数量就越多,视频图像的色调天然也就越丰盛、细腻,在色调突变处也会更加平滑。有一个比拟极其的比喻能够用于帮忙了解:试想咱们要绘制一幅蕴含了七色彩虹的画,那么领有七支不同色彩的画笔(高位深)和 只领有单色彩的画笔(低位深),在绘制成果上天然会呈现出微小的差别。

参考下图,别离为同一张图像,在 24bit 位深、8bit 位深(2^8 = 256 种颜色)、4bit 位深(2^4 = 16 种颜色)下的体现。

图 1:24 bit

图 2:8 bit

图 3:4 bit

能够看到,在 24bit 图像位深下,蓝天、云彩、企鹅绒毛的色彩天然细腻、过渡平滑,画面主次显明。而位深越低,因为可示意的色彩数量缩小,局部色彩数据失落并被代替,开始呈现色彩趋同或断层,画面也越发的不天然。除了升高位深外,位深 24bit 若往上升,也有更高的 30bit(通道位深 10bit)、36bit(通道位深 12bit)等等。

那么问题来了,参考下面的比照成果,咱们是否应该无条件地应用高位深呢?

答案是否定的。

须要留神的是,尽管图像的位深越大,可能示意的色彩越多,但 相应须要的存储空间也越大,传输所需的带宽也越多,带来老本的晋升,对于软硬件的要求也更刻薄。更何况,24 bit 图像位深已蕴含 1677 万种色彩,这远远超过了人眼的视觉感知能力,足以满足绝大部分业务场景。综合考量,现阶段仍次要应用 24 bit 的图像位深。

以上就是本期课程对于图像位深的相干常识。

图像宽高(Width、Height)与跨距(Stride)

==========================================

再回到全文的开始:“图像的根本组成单元为像素,对视频图像的存储,实际上是对像素的存储”。基于图像位深,咱们能够确定存储一个像素所需的字节数,上面,能够开始“领导”计算机如何定量读取图像数据了。

像素在图像中是一行一行排列、并逐行存储在内存中的,计算机在读取图像时,就须要逐行地、正确地读取出每一行的像素。这里就引出两个问题:每一行到底有多少个像素?计算机每获取一行数据须要读取多少个字节呢?要解答这两个问题,咱们须要再学习两个概念:图像的宽高(Width、Height) 跨距(Stride)


1

图像宽、高 

说到图像的宽高,大家直觉上可能会联想到“厘米”、“英寸”等长度单位,其实,从图像处理的角度并非如此。在视频图像处理上,形容图像宽高时,通常应用的是“计数单位”,而其具体的数值,则由图像的分辨率决定。

对于视频图像的分辨率,在系列推文中还没有和大家正式介绍,但大家对“分辨率”必定是不生疏的。在各大视频 / 图片网站上、在各种视频 / 图像文件规格中,咱们常看到诸如 540×960(540P)、720×1280(720P)、1080×1920(1080P)等参数,它们就是所谓的 分辨率 ,其示意的含意为: 图像在程度方向、垂直方向上,每行、每列的像素“个数”

  • 宽(Width):程度方向每行的像素个数,等于图像分辨率的宽
  • 高(Height):垂直方向每列的像素个数,等于图像分辨率的高

如下图所示,对于分辨率为 540×960(宽 x 高)的 RGB 图像,其程度方向每行有 540 个 RGB 像素,垂直方向每列有 960 个 RGB 像素。

图 4:像素排布,分辨率 540×960,宽 x 高

不难发现,分辨率宽高相乘失去的数值 = 图像中像素的总个数,540 x 960 的 RGB 图像中蕴含 518400 个像素,分辨率越高,像素的个数也就越多。

对于分辨率的常识,咱们今后还会有专题作进一步探讨,明天大家理解到分辨率与图像宽高、像素个数的关系即可。

当初,咱们曾经通过分辨率信息,确定了图像每行的像素个数,能够尝试计算每行数据的长度(字节)。因为视频图像的解决通常是逐行进行的,计算机更关注每行有多少数据,而对于具体有多少行(Height)没有太多的要求。

以 24bit 的 RGB 图像为例,假如分辨率为 538 x 960,因为每个像素的 R、G、B 重量都间断存储在同一立体上(详见前文 色调和色调空间 - 中篇),咱们能够通过如下步骤,计算每行像素的字节长度:

  • 每行像素的个数 = 图像分辨率宽 = 538
  • 每行像素的字节长度 = 像素位深 x 每行像素的个数 = 24 bit x 538 = 1614 byte(注:1 byte = 8 bit)

如上,咱们失去论断:对于分辨率为 538 x 960 的 RGB 图像,每行有 1614 byte 数据。计算过程看起来清晰明了,有理有据,于是咱们信念满满地将 1614  byte 这个字节长度告知计算机,计算机也精打细算地按要求去读取一张 538×960 的图片。却可能会失去如下的后果:

图 5:原图,分辨率 538×960 

图 6:按每行 1614 byte 数据,进行读取和渲染

咱们发现,理论渲染进去的图像,呈现出规定的斜条纹,与原图相比已面目全非。

为什么会呈现这样的问题呢?难道是计算机呈现了 Bug?或者说,计算机是无辜的,图像每行的像素个数实际上并不等于图像的分辨率宽度?要解答这些问题,咱们就须要理解另外一个概念:跨距(Stride)。


2

图像跨距 

咱们晓得,计算机的处理器次要是 32 位或 64 位的,当处理器执行运算时,一次读取的残缺数据量最好为 4 字节 或 8 字节的倍数。如果咱们要求计算机读取非 4 字节或 非 8 字节对齐的数据,它就须要进行额定的解决工作。额定工作的引入,势必会影响效率和性能。为了躲避这样的问题,就须要 在原始数据的根底上,再减少一些“有效数据”,使待处理的数据量对齐到 4 字节 或 8 字节。这样计算机能力以最高效的形式工作。当然,对齐规定也不肯定是 4 字节 / 8 字节 的倍数,理论仍取决于具体的软硬件零碎。

回过头来,看看后面计算失去的“1614 byte”,大家是否发现问题了呢?

是的,这并非一个 4 字节或 8 字节倍数的数值。所以,基于前述的考量,如果在一个要求 4 字节 或 8 字节 对齐的零碎内存中存储该图像,往往须要减少一些额定数据,将 1614 byte 对齐到比方 1616 byte。而这里的 1616 byte,即称为图像的跨距(Stride)。

跨距(Stride),是图像存储在内存中,每一行数据所占空间的实在大小,它大于或等于通过图像分辨率宽度计算的字节长度。每读取一个 Stride 长度的数据,意味着残缺读取了图像的一行,下次读取就该“换行”了。其中,用于补齐至 Stride 而减少的额定数据,咱们称之为填充(Padding)。Padding 仅影响图像在内存中的存储形式,无需(也不能够)用于理论渲染。

咱们能够通过下图,直观的了解 Width、Padding 和 Stride 的关系。

图 7:Width、Padding 和 Stride

参考上图,从 Start 地位开始,计算机只有按 Stride 读取每一行图像数据,再按 Width 进行理论的渲染,防止将有效的 Padding 渲染进去,能力显示出失常的图像。如果仅应用 Width 计算 Stride(比方下面,咱们通知计算机将 Stride 设置为 1614 byte),那么就可能会 误将局部 Padding,视为无效的图像数据进行渲染,行与行之间的像素绝对地位也将产生累计偏移,呈现诸如斜条纹等异样。

咱们也能够通过一些简化的形式,来 了解斜条纹产生的原理

参考下图,咱们先疏忽“字节长度”,简略地把图像数据、填充数据的单位都对立至“像素”。假如原图的 Width x Height = 6 x 8,存储时将 Stride 对齐为 8。图中黑白局部为实在图像(原图左侧),彩色局部为填充的 Padding(原图右测),两头存在的空白间隙仅为不便辨别。

图 8:原图,Stride = 8,Width = 6

若应用正确的配置,Stride = 8 进行读取,Width = 6 进行渲染,则仅会显示出黑白局部,彩色局部的 Padding 在渲染时会被疏忽。

如果应用谬误的 Stride = 7,正确的 Width = 6,会呈现如下问题:从第一行开始,少读取了一块 Padding,并将这部分少读取的 Padding,误当作第二行的“无效图像”进行读取、排列。最终,计算机再以 Width = 6 进行渲染时,将失去如下图像,呈现了右侧下沉的斜条纹成果。

图 9:谬误,Stride = 7,Width = 6,右侧下沉的斜条纹

同理,Stride 偏大、Width 偏大、Width 偏小,都会影响图片的读取和渲染,大家在解决时须要留神。咱们在上面也展现出相干的简化参考图:

图 10:Stride = 9,Width = 6,左测下沉的斜条纹

图 11:Stride = 8,Width = 7,多渲染出一列 Padding 数据

留神,理论利用中如果 Padding 数据被谬误渲染进去,不肯定都是彩色的,具体由填充的数据而定。如果都应用 0 值填充,那么 RGB 图像的 Padding 为彩色,YUV 图像的 Padding 则为绿色。其余可能的谬误状况,大家能够本人尝试推演一下,在此就不过多开展。


3

分立体 YUV 的 Width、Stride 

下面对于 Width、Stride 的探讨,都是基于 RGB 图像来举例。对于 RGB 图像,其色调空间重量是同一立体、间断存储的,个别只需思考一个立体的 Width 和 Stride。

而 YUV 图像比拟非凡,它可能应用分立体(Planar、Semi-Planar)的存储形式(详见色调和色调空间 - 中篇)。

从整个图像的角度看,YUV 图像的每一行仍旧有 Width = 720 个像素。然而从存储的角度看,Y、U、V 重量可能寄存在不同的立体,计算机想要了解 YUV 色调,就须要晓得:在每个立体上、每次要读取多少数据,能力正确地组合成原始图像的一行像素

“在每个立体上、每次要读取多少数据”,意味着须要晓得每个立体的 Width 和 Stride。而思考到 U、V 重量绝对于 Y 重量可能有降采样,各个重量立体的 Width、Stride 可能不同,必须要按存储规定别离求取。

上面,咱们针对常见的 YUV 格局:I422、I420 和 NV21,具体讨论一下,何谓分立体的 Width 和 Stride。

咱们将基于通道位深 = 8 bit,图像分辨率 Width = 720,Height = 1280,开展后续内容的解说。为不便了解、简化过程,咱们 假如处理器以 4 字节对齐,通过各立体 Width 计算失去的数据长度若满足 4 的倍数,即可作为各立体的 Stride,无需思考 Padding 填充。

对于这三种 YUV 格局的采样 & 存储原理,大家可具体参考上一篇推文 色调和色调空间 - 中篇,上面用到时仅做简述。

因为 I422、I420 和 NV21 的 Y 立体采样逻辑雷同,Y 重量均为全采样,咱们先对立进行计算。

对于 Y 立体,因为 Y 重量为全采样,故:

  • Width\_Y\_Plane = 每行 Y 重量个数 = 图像每行像素个数 = Width = 720
  • Stride\_Y\_Plane = Width\_Y\_Plane x 通道位深 = Width\_Y\_Plane x 8 bit = 720 byte

注:因为 Width\_Y\_Plane x 8 bit = 720 byte 满足预设的对齐要求,故间接作为 Stride\_Y\_Plane,理论利用中,须要另外进行确认。前面若有相似解决,不再反复阐明。

对于 U、V 立体,因为 U、V 重量在不同 YUV 格局下有不同的采样、存储逻辑,须要按规定具体计算。

3.1  I422

I422 的采样和存储逻辑简述为:

  • 采样:Y 重量全采集,宽度方向每两个 Y 重量共用一组 UV 重量,高度方向每行独立采集 UV 重量
  • 存储:Y、U、V 别离存储于三个立体,对于一个宽度为 4 个像素、高度为 2 个像素的采样区域,三个立体别离为 4×2、2×2、2×2 的数组 

对于 U 立体,U 重量程度方向的采样为 Y 重量的 1/2,故:

  • Width\_U\_Plane = 每行 U 重量个数 = 每行像素个数 /2 = Width/2 = 360
  • Stride\_U\_Plane =  Width\_U\_Plane x 通道位深 = 360 byte

对于 V 立体,其采样存储逻辑与 U 立体统一,故:

  • Stride\_V\_Plane =  Width\_V\_Plane x 通道位深 = 360 byte。

如果应用数组 Stride\_I422[3] 记录三个立体的跨距(字节长度),即有 Stride\_I422[3] = {Width, Width/2, Width/2}(应用 Width 的数值大小来示意)

3.2  I420

I420 的采样和存储逻辑简述为:

  • 采样:Y 重量全采集,宽度方向和高度方向每四个 Y 重量共用一组 UV 重量,也即第二行复用第一行的 UV 采样;
  • 存储:Y、U、V 别离存储于三个立体,对于一个宽度为 4 个像素、高度为 2 个像素的采样区域,三个立体别离为 4×2、2×1、2×1 的数组。

对于 U 立体,U 重量程度方向的采样为 Y 重量的 1/2(每行),故:

  • Width\_U\_Plane = 每行 U 重量个数 = 每行像素个数 /2 = Width/2 = 360
  • Stride\_U\_Plane =  Width\_U\_Plane x 通道位深 = 360byte

对于 V 立体,其采样存储逻辑与 U 立体统一,故:

  • Stride\_V\_Plane =  Width\_V\_Plane x 通道位深 = 360byte

如果应用数组 Stride\_I420[3] 记录三个立体的跨距(字节长度),即为 Stride\_I420[3] = {Width, Width/2, Width/2}

3.3  NV21

NV21 的采样和存储逻辑简述为:

  • 采样:Y 重量全采集,宽度方向和高度方向每四个 Y 重量共用一组 UV 重量,也即第二行复用第一行的 UV 采样
  • 存储:Y、UV 别离存储于两个立体,对于一个宽度为 4 个像素、高度为 2 个像素的采样区域,两个立体别离为 4×2、4×1 的数组,UV 独特存储于第二个立体,并按 V、U 的程序交织寄存

对于 UV 立体,因为 U、V 程度方向采样均为 Y 重量的 1 /2(每行),并且间断交织存储,故:

  • Width\_UV\_Plane = 每行 U 重量个数 + V 重量个数 = 每行像素个数 /2 + 每行像素个数 /2 =  Width = 720
  • Stride\_UV\_Plane =  Width\_UV\_Plane x 通道位深 = 720 byte

如果应用数组 Stride\_NV21[2] 记录两个立体的跨距(字节长度),即为 Stride\_NV21[2] = {Width, Width}

须要特地留神的是,尽管综合所有立体来说,I422、I420、NV21 每次读取的 Stride 总和,均为 Width x 2:

  • Stride\_I422[0] + Stride\_I422[1] + Stride\_I422[2] = Width x 2
  • Stride\_I420[0] + Stride\_I420[1] + Stride\_I420[2] = Width x 2
  • Stride\_NV21[0] + Stride\_NV21[1] = Width x 2 

但对于 I420 和 NV21,因其“宽度方向和高度方向每四个 Y 重量,共用一组 UV 重量”的个性,每次读取 U、V 立体、或 UV 立体的一行数据,理论是供 Y 立体的两行数据共用的。因而,均匀下来,读取整张图像的数据总量会存在差别:

  • Data\_I422 = Data\_Y + Data\_U + Data\_V

\= Height x Width + Height x Width/2 + Height x Width/2 

\= Height x Width x 2

  • Data\_I420 = Data\_Y + Data\_U + Data\_V

\= Height x Width + Height/2 x Width/2 + Height/2 x Width/2 

\= Height x Width x 1.5

  • Data\_NV21 = Data\_Y + Data\_UV

\= Height x Width + Height/2 x Width 

\= Height x Width x 1.5

能够看到,Data\_I422 大于其余两个。这也证实了,YUV420 绝对于 YUV422,前者采样数据量更小、压缩率更大。

总结

===============

以上,即为常见 YUV 格局 Width、Stride 的计算方法。如果大家在了解上有些难度,能够再回顾一下 色调和色调空间 - 中篇 的内容,联合进行梳理。须要再次强调的是,为不便了解,下面的讲述中默认:应用 Width 间接计算失去的 Stride 合乎对齐要求,无需思考 Padding 填充,而实际中思考到不同零碎、硬件芯片的对齐解决差别,实在的 Stride 是否要做补齐,仍需再具体确认。

至此,对于计算机如何正确地、“定量”读取视频图像数据,咱们也有了肯定的理解。思考到硬件芯片、操作系统的多样性,色调空间采样 & 存储格局的多样性,要齐全厘清所有的“定量”规定,还是比拟麻烦的。

对于集成 ZEGO SDK 开发音视频利用的同学,ZEGO 音视频引擎已适配了支流的平台和零碎,大家可释怀地将视频图像的采集、解决、转换、渲染工作交给 SDK,从这些繁琐的细节中解放出来、专一于业务玩法的设计与实现。当然,思考到灵活性,ZEGO SDK 也提供了“自定义视频采集”的性能,容许开发者自行采集、解决原始视图数据,以满足特定的采集源(比方屏幕采集)或者做进阶的视频前解决(比方美颜特效)需要。开发者只须要将采集、解决后的数据,通过指定接口塞给 SDK 即可。

不过,在应用“自定义视频采集”性能时,后面提及的色调空间、采样 & 存储格局、Width 和 Stride 等概念,就须要你了然于胸,否则就可能呈现诸如“斜条纹”的问题。

最初,咱们通过一个思维导图,再梳理一下本文的核心内容。

参考推文中的举例,假如原图的 Width x Height = 6 x 8,存储时将 Stride 对齐为 8。当应用 Stride = 8,Width = 4,进行读取和渲染,会呈现什么问题?

本期思考题

参考推文中的举例,假如原图的 Width x Height = 6 x 8,存储时将 Stride 对齐为 8。当应用 Stride = 8,Width = 4,进行读取和渲染,会呈现什么问题?

(🤫下期揭秘)

上期思考题 揭秘 ⬇️

作为请参照 YUV 采样格局的“规定”,阐明 YUV 4:1:1 的采样逻辑。

对于一个宽度为 4 个像素、高度为 2 个像素的采样区域(两行四列),第一行须要采样 4 个亮度重量 Y,1 个色度重量组 UV,第二行须要采样 4 个亮度重量 Y,1 个色度重量组 UV。

也即程度方向上,Y 重量为全采样,UV 重量组绝对于 Y 重量做 1 /4 降采样。

正文完
 0