最近弄了一個讀取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