徹底弄懂I420格式

YUV的概念

YUV 和我們熟知的 RGB 類似,是一種顏色編碼格式。它主要用於電視系統和模擬視頻鄰域(如 Camera 系統)。YUV 包含三個分量,其中 Y 表示明亮度(Luminance 或 Luma),也就是灰度值。而 U 和 V 則表示色度(Chrominance 或 Chroma),作用是描述圖像色彩及飽和度,用於指定像素的顏色。沒有 UV 分量信息,一樣可以顯示完整的圖像,只不過是黑白的灰度圖像。YUV 格式的好處是很好地解決了彩色電視機與黑白電視機的兼容問題(當只要Y分量時就是黑白圖像)。

YUV420

YUV的採樣方式

  • 4:4:4表示完全取樣(每一個Y對應一組UV分量)
  • 4:2:2表示2:1的水平取樣,垂直完全採樣(每兩個Y共用一組UV分量)
  • 4:2:0表示2:1的水平取樣,垂直2:1採樣(每四個Y共用一組UV分量)
  • 4:1:1表示4:1的水平取樣,垂直完全採樣(每四個Y共用一組UV分量)

YUV的存儲格式

我們主要關心的是如何處理YUV數據,所以最關心的是YUV數據的存儲格式。內存中存儲的方式包括兩種:

  1. planar 格式:先連續存儲所有像素點的 Y 分量,緊接着存儲所有像素點的 U 分量,再是V分量(當然不同存儲格式的UV的先後順序是不一樣的,如I420的V在U後,YV12則是U在V後)。

  2. packed 格式:每個像素的Y,U,V分量交替存儲。

Y,U,V分量存儲時的先後順序的不同,代表了不同的格式,以下列出了幾種常見的YUV格式:

  • YUYV(屬於YUV422)
    在這裏插入圖片描述
    兩個Y共用一組UV分量。Y,U,V分量交替存儲。

  • UYVY(屬於YUV422)

在這裏插入圖片描述
可以看到與YUYV的分量的順序不一樣。

  • YUV422P(屬於YUV422)
    在這裏插入圖片描述
    是Planar存儲模式,依次是Y,U,V。U分量在V分量之前

  • YV12(屬於YUV420)

在這裏插入圖片描述
是Planar存儲模式,依次是Y,V,U。V分量在U分量之前。I420與此類似,先是存儲Y分量,再是U分量,V分量。

  • NV12(屬於YUV420)

在這裏插入圖片描述
先是Y,在是U,V分量交替出現

一個簡易存儲示意圖

I420: YYYYYYYY UU VV    =>YUV420P
YV12: YYYYYYYY VV UU    =>YUV420P
NV12: YYYYYYYY UVUV     =>YUV420SP
NV21: YYYYYYYY VUVU     =>YUV420SP

YUV420sp與YUV420p的不同之處在,存儲UV分量時,YUV420sp中的UV分量是交替存儲。

處理I420

I420屬於YUV420P(存儲格式爲Planar),這種格式很常用,在x264/265的中要求傳入的源數據就是這種格式。在libyuv中,進行YUV圖像處理(縮放,剪切,旋轉)也是要求以這種格式傳入。ffmpeg解碼h264/265後數據也是這種格式。這種格式也可以直接通過D3D,OpenGL進行渲染。

計算佔用的字節大小

一個分量佔用一個字節,每一個象素點對應一個Y分量,所以分辨率爲w*h的I420格式的圖,Y的字節數爲 w*h, U的字節數爲w*h/4,V的字節數大小爲w*h/4,總字節數即爲w*h*3/2

定位像素數據

要處理I420的數據,首先要能定位到像素分量數據。一個典型的場景是,通過freetype在YUV圖像上加字幕時,這是需要將指定座標點的像素替換爲freetype返回的字體圖數據。

那麼下面是取像素點YUV(I420格式)分量數據的公式:

size.total = size.width * size.height;
y = yuv[position.y * size.width + position.x];
u = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total];
v = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total + (size.total / 4)];

轉一張很直觀的I420存儲的示意圖。Y,U,V相同顏色的表述是同一像素的分量。可以結合這個圖套用上面的公式推算一下:

在這裏插入圖片描述

一種常用的YUV數據表示方法

如果直接分配一段內存用以存儲圖像數據(比如I420格式),是沒法知道這段內存的YUV分量特點的(每個分量的數據起始及長度)。那麼我們可以下面這種方式定義數據結構來解釋存儲結構:

enum enImageFmt
{
    enImageFmt_YUV420P
    ...
};

struct ImageSize
{
    int iWidth;
    int iHeight;
};

struct VideoFrame
{
    //Image的格式
    enImageFmt fmt;
    //圖像的分辨率
    ImageSize Size;
    //分別指向Y,U,V分量的開頭
    unsigned char *data[3];
    //分別指示Y,U,V分量一行(步長)數據大小
    unsigned short linesize[3];
}

//如下的一個示例代碼
//指針pImage指向一幅YUV420P格式的圖像,通過該結構體指示它的內存結構

VideoFrame frame;
frame.fmt = enImageFmt_YUV420P;
frame.Size.w = w;
frame.Size.h = h;
//Y分量的步長
frame.linesize[0] = w;
//U分量的步長(其長度就是Y分量步長的一半,同理V分量)
frame.linesize[1] = w/2;
//V分量的步長
frame.linesize[2] = w/2;
	
//那麼data[0]指向的就是Y數據的起始
frame.data[0] = (unsigned char*)pImage;
//U數據的起始
frame.data[1] = frame.data[0] + frame.linesize[0]*h;
//V數據的起始
frame.data[2] = frame.data[1] + frame.linesize[1]*h/2;

該結構體不止可以指示YUV420P的結構,也可以指示其它YUV格式的結構。在ffmpeg中也有類型的結構定義。

總結:

雖然主要是講解的如果處理I420格式,但是前面介紹了幾種不同YUV的存儲格式。可以結合存儲示意圖,類推出如何處理其它YUV格式數據。

參考

https://zh.wikipedia.org/wiki/YUV

https://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章