在前两篇推文中,咱们理解了色调空间、像素、图像和视频之间的组成关系,并且比拟具体的学习了色调空间 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 降采样。