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數據的存儲格式。內存中存儲的方式包括兩種:
-
planar 格式:先連續存儲所有像素點的 Y 分量,緊接着存儲所有像素點的 U 分量,再是V分量(當然不同存儲格式的UV的先後順序是不一樣的,如I420的V在U後,YV12則是U在V後)。
-
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