最近弄了一個讀取y4m檔案轉成yuv的流的事情,記錄一些yuv相關的細節

為什麼會有yuv

因為我們目前的顯示器顯示的原理都是三原色,幾乎所有的視訊資料最後都要轉為rgb格式才能渲染到顯示屏上,而原始的rgb格式儲存太耗費空間

rgb儲存空間是每個畫素點需要 rbg三個屬性,每個屬性八個bit來儲存,也就是24bit

而yuv則是從另外一個角度描述圖片,y是明亮度(灰介值),uv是則是色度,而人眼對色度沒有那麼敏感,可以通過減少uv的採集從而減少儲存

為什麼叫yuv?

查了很久,基本可以確認yuv三個字母不是某些縮寫,yuv的另一個叫法是Y Cb(cut blue) Cr(Cut red),這一個可以理解的叫法,對於一個影象點(rgb),可以使用y(亮度)和cb(和藍色的色差)以及cr(和紅色的色差)來近似表達;yuv中的uv貌似跟紋理貼圖中的uv座標系有些關聯;

減少採集的方式是設定取樣比

  • 取樣比通常表示為 J:a:b,以表示一個寬為 J 畫素、高為 2 畫素的取樣區域內 Y Cb Cr(Y U V)的取樣比:

    • J 表示取樣區域的寬度,通常為 4;
    • a 表示第一行色度取樣數;
    • b 表示第二行色度取樣與第一行色度取樣的不同樣點數;(零代表Ja0合J0a依次交替,並不是uv某個維度不採樣的意思)

上圖來源:https://imgs.piasy.com/2018-04-27-sampling_systems_and_ratios.png

壓縮比計算示例

簡單算下壓縮率,如上面的圖,4*2 =8個畫素,用rbg需要8*3*8=192bit

用yuv444需要8*8(Y)+ 4*2*8(U)+4*2*8(V)=192bit,是一樣的

用yuv422需要8*8(Y)+ 2*2*8(U)+2*2*8(V)=128bit,節省了1/3,

用yuv420需要8*8(Y)+ 2*2*8(UV)=96bit,節省了一半(零代表Ja0合J0a依次交替,並不是uv某個維度不採樣的意思

YUV 儲存格式

YUV 資料有兩種儲存格式:平面格式(planar format)和打包格式(packed format)。

  • planar: 有時也稱 triplanar,有三個 plane,每種分量連續儲存,先儲存所有的 Y 分量,再儲存所有的 Cb 分量,最後儲存所有的 Cr 分量(也可以 Cr 在前,Cb 在後);
  • packed: 只有一個 plane,n 個樣點的 Y Cb Cr 分量一起儲存,接著儲存下 n 個樣點的分量;n 的取值、其中三種分量的儲存方式,也有多種組合;
  • semi planar: 有兩個 plane,先儲存所有的 Y 分量,後面 Cb 和 Cr 分量一起儲存;

i420

I420 的取樣比是 4:2:0,它是 planar 儲存方式,分量儲存順序依次是 Y, Cb, Cr

nv12(ios)

NV12 的取樣比是 4:2:0,它是 semi planar 儲存方式,先儲存 Y 分量,後面 Cb 和 Cr 分量一起儲存,Cb 在前,Cr 在後

nv21(Android)

NV21 和 NV12 類似,取樣比是 4:2:0,也是 semi planar 儲存方式,先儲存 Y 分量,後面 Cb 和 Cr 分量一起儲存,只不過 Cr 在前,Cb 在後

i420 轉nv21

從上面的介紹可以看到,i420是yuv分開儲存,y是一致的,我們只要分別拿到u和v,改變一些順序即可從i420轉到nv21

  private void I420ToNv21(byte[] i420bytes, int width, int height) {
byte[] nv21bytes = new byte[i420bytes.length];
int y_len = width * height;
int uv_len = y_len / 4;
System.arraycopy(i420bytes, 0, nv21bytes, 0, y_len);
for (int i =0; i < uv_len; i++) {
byte u = i420bytes[y_len + i];
byte v = i420bytes[y_len + uv_len + i];
nv21bytes[y_len + i*2] = v;
nv21bytes[y_len + i*2 +1] = u;
}
System.arraycopy(nv21bytes, 0, i420bytes, 0, nv21bytes.length);
nv21bytes = null;
}

stride 或 pitch

YUV 資料在記憶體中儲存時,每行畫素的資料後面可能還有填充位元組,這主要是因為有些系統/環境/操作對記憶體的位元組對齊有要求,比如 64 位元組對齊,那麼寬度為 720 畫素的影象,一行就不滿足 64 對齊的要求,那就要填充到 768 畫素。儲存一行畫素所需的位元組數,就叫 stride,也叫 pitch。比如這裡舉的例子,寬為 720,stride 或 pitch 就是 64。

rgb to yuv

yuv to rgb

y4m

y4m是yuv檔案的一種儲存格式,上面的yuv都是整個圖片或者視訊的yuv,y4m檔案中有明確的幀的劃分,處理起來會更方便一些

相信至此,對於yuv的來歷、作用以及儲存使用,

p