天天看點

音視訊開發進階|第六講:色彩和色彩空間·下篇

在前兩篇推文中,我們了解了色彩空間、像素、圖像和視訊之間的組成關系,并且比較詳細的學習了色彩空間 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 種深淺不同的紅色。以此類推,三個通道綜合,即可以得到 (28)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

圖像寬、高

說到圖像的寬高,大家直覺上可能會聯想到 “厘米”、“英寸” 等長度機關,其實,從圖像處理的角度并非如此。在視訊圖像處理上,描述圖像寬高時,通常使用的是“計數機關”,而其具體的數值,則由圖像的分辨率決定。

關于視訊圖像的分辨率,在系列推文中還沒有和大家正式介紹,但大家對 “分辨率” 肯定是不陌生的。在各大視訊/圖檔網站上、在各種視訊/圖像檔案規格中,我們常看到諸如 540x960(540P)、720x1280(720P)、1080x1920(1080P)等參數,它們就是所謂的分辨率,其表示的含義為:圖像在水準方向、垂直方向上,每行、每列的像素 “個數”。

  • 寬(Width):水準方向每行的像素個數,等于圖像分辨率的寬
  • 高(Height):垂直方向每列的像素個數,等于圖像分辨率的高

如下圖所示,對于分辨率為 540x960 (寬 x 高)的 RGB 圖像,其水準方向每行有 540 個 RGB 像素,垂直方向每列有 960 個 RGB 像素。

音視訊開發進階|第六講:色彩和色彩空間·下篇

圖4:像素排布,分辨率 540x960,寬 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 這個位元組長度告知計算機,計算機也一絲不苟地按要求去讀取一張 538x960 的圖檔。卻可能會得到如下的結果:

音視訊開發進階|第六講:色彩和色彩空間·下篇

圖5:原圖,分辨率 538x960

音視訊開發進階|第六講:色彩和色彩空間·下篇

圖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 個像素的采樣區域,三個平面分别為 4x2、2x2、2x2 的數組
音視訊開發進階|第六講:色彩和色彩空間·下篇

對于 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 個像素的采樣區域,三個平面分别為 4x2、2x1、2x1 的數組。
音視訊開發進階|第六講:色彩和色彩空間·下篇

對于 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 個像素的采樣區域,兩個平面分别為 4x2、4x1 的數組,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的采樣邏輯。

繼續閱讀