iOS視頻開發(fā)(四):通俗理解YUV數(shù)據(jù)

前言

系列文章:
《iOS視頻開發(fā)(一):視頻采集》
《iOS視頻開發(fā)(二):視頻H264硬編碼》
《iOS視頻開發(fā)(三):視頻H264硬解碼》
《iOS視頻開發(fā)(四):通俗理解YUV數(shù)據(jù)》

前面我們已經(jīng)介紹了在iOS開發(fā)中如果調(diào)用攝像頭進行視頻數(shù)據(jù)的采集和編解碼。但折騰了這么多呐籽,對于YUV這玩意兒還是不是特別理解锋勺。其實在我的個人實踐過程中我也一直搞不懂這個YUV,一頓惡補之后狡蝶,我們來通俗一點地講YUV這個數(shù)據(jù)格式庶橱。

1、YUV & RGB概述
2贪惹、YUV的采樣方式
3苏章、YUV的儲存方式及常見格式
4、YUV數(shù)據(jù)量計算
5奏瞬、YUV裁剪


YUV & RGB概述

RGB色彩模式是工業(yè)界的一種顏色標準布近,我們知道三原色(RGB)通過互相疊加可以得到各式各樣的顏色,是目前運用最廣的顏色系統(tǒng)之一丝格。
與我們熟知的RGB一樣撑瞧,YUV也是一種顏色編碼方法,主要運用在電視系統(tǒng)及模擬視頻領域显蝌。YUV的原理是把亮度和色度分離预伺,利用人眼對亮度的敏感度超過色度這個特性,我們偷摸摸把色度信息減少一些曼尊,人眼也很難察覺到酬诀。甚至沒有色度信息(UV分量)依舊可以顯示完整的圖像,只不過是黑白的骆撇。這樣的設計就很好地解決了彩色電視機跟黑白電視機的兼容問題瞒御。并且由于可以減少一些色度信息,YUV數(shù)據(jù)的總尺寸相對RGB數(shù)據(jù)要小一些神郊。YUV這三個字母中肴裙,Y表示亮度(灰度值),U和V表示色度(色彩及飽和度)涌乳。YUV是編譯true-color顏色空間(colorspace)的種類蜻懦,Y'UV,YUV,YCbCrYPbPr等專有名詞都可以稱為YUV夕晓。


YUV的采樣方式

YUV的主流采樣方式有:YUV4:4:4宛乃、YUV4:2:2YUV4:2:0,這是個什么意思呢征炼?我們看下面這張圖析既,黑色實心的點表示Y分量,黑色空心的點表示UV分量谆奥,那么下圖的意思就是:

YUV4:4:4 每一個Y分量都有對應一組UV分量
YUV4:2:2 每兩個Y分量共用一組UV分量
YUV4:2:0 每四個Y分量共用一組UV分量

YUV采樣示意圖.jpg

上圖我們可以看到:
YUV4:4:4:也就是說每4個Y采樣渡贾,就有相對應的4個U和4個V采樣,即畫面中每個像素都有Y分量和UV分量雄右,這種格式儲存了圖像所有的亮度和色度信息空骚。
YUV4:2:2:也就是說每4個Y采樣渐行,就有相對應的2個U和2個V采樣嚼摩。它的色度信號的掃描線(也就是上面圖里的橫線)和亮度信號一樣多仍翰,但每條掃描線上的色度采樣點只有亮度信號的一半(看4:2:2圖一根橫線上罢防,空心圓只有實心圓的一半)服鹅。當4:2:2信號被解碼的時候热凹,“缺失”的色度采樣通常由一定的內(nèi)插補點算法通過它兩側的色度信息運算補充项棠。
YUV4:2:0按字面意思應該4個Y采樣几苍,就有相對應的2個U和0個V采樣肃廓,事實上并不是這樣的智厌,事實上,4:2:0的意思是盲赊,色度采樣在每條橫向掃描線上只有亮度采樣的一半铣鹏,掃描線的條數(shù)上,也只有亮度的一半0ⅰ(看上面4:2:0這個圖诚卸,空心是上下共用的,如果放到第一根線上绘迁,那就是空心只有實心的一半合溺。再看前4根線,4根亮度線對應兩行色度缀台,也就是說空心的掃描線也只有實心的一半棠赛。)換句話說,無論是橫向還是縱向膛腐, 色度信號的分辨率都只有亮度信號的一半睛约。說得再通俗一些就是,如果第一行是4:2:0依疼,那么第二行就是4:0:2痰腮,第三行就是4:2:0這樣交替。舉個例子律罢,如果整張畫面的尺寸是720x480,那么亮度信號是720x480,色度信號只有360x240误辑。


YUV的儲存方式

YUV的儲存格式有兩大類:
planar:先連續(xù)存儲所有像素點的Y沧踏,緊接著存儲所有像素點的U,隨后是所有像素點的V巾钉。
packed:每個像素點的Y翘狱、U、V是連續(xù)交替存儲的

YUYV(YUY2)格式(屬于YUV422)

YUYV格式為像素保留Y砰苍,而UV在水平空間上相隔二個像素采樣一次(Y0U0Y1V0)潦匈,(Y2U2Y3V2)…其中,(Y0U0Y1V0)就是一個macro-pixel(宏像素)赚导,它表示了2個像素茬缩,(Y2U2Y3V2)是另外的2個像素。以此類推吼旧。

YVYU凰锡、UYVY格式(屬于YUV422)

跟YUY2類似,只不過是排列順序不一樣而已圈暗。
例如UYVY格式掂为,那么排列順序就是(U0Y0V0Y1)、(U2Y2V2Y3)...

YUV422P格式(屬于YUV422)

YUV422P也屬于YUV422的一種员串,但它并不是將YUV數(shù)據(jù)交錯存儲勇哗,而是先存放所有的Y分量,然后存儲所有的U(Cb)分量寸齐,最后存儲所有的V(Cr)分量智绸,其每一個像素點的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即兩個Y共用一個UV访忿。

YV12格式(屬于YUV420)


YV12是Plane模式的瞧栗,也就是Y、U海铆、V三個分量分別打包迹恐,依次存儲。例如2x2圖像:YYYYVU卧斟,4*4圖像:YYYYYYYYYYYYYYYYVVVVUUUU

NV12殴边、NV21格式(屬于YUV420)


NV12NV21是一種two-plane模式,即Y和UV分為兩個Plane珍语,其中U和V是交錯儲存的锤岸。NV12是U在前,V在后板乙,而NV21是V在前U在后是偷。例如4x4圖像:
NV12:YYYYYYYYYYYYYYYYUVUVUVUV
NV21:YYYYYYYYYYYYYYYYVUVUVUVU

I420格式(屬于YUV420)

I420是 planar 存儲方式拳氢,分量存儲順序依次是 Y, Cb(U), Cr(V),例如4x4圖像:YYYYYYYYYYYYYYYYUUUUVVVV

更多格式可參考:YUV pixel formats


YUV數(shù)據(jù)量計算

我們以YUV444蛋铆、YUV422馋评、YUV420這三種采樣格式舉例。我們知道刺啦,RGB圖像三個分量都必須全部存儲留特,例如一張4x4像素的RGB圖像大小為4x4x3=48字節(jié)。

  • YUV444為一個像素點有一個Y玛瘸、一個U蜕青、一個V,所以一張4x4像素的YUV444圖像大小為4x4x3=48字節(jié)糊渊,跟RGB一樣大右核。Y、U再来、V三個分量的大小都是4x4=16字節(jié)
  • YUV422為兩個Y共用一個U和V蒙兰,Y分量為全采樣,即4x4=16字節(jié)芒篷,U分量和V分量只有Y分量的一半搜变,即U分量為4x4/2=8字節(jié),V分量也是4x4/2=8字節(jié)针炉,也就是說一張4x4像素的YUV422圖像大小為4x4x2=32字節(jié)挠他。
  • YUV420為4個Y共用一個U和V,Y分量為全采樣篡帕,及4x4=16字節(jié)殖侵,U分量和V分量只有Y分量的四分之一,即U分量和V分量的大小均為4x4/4=4字節(jié)镰烧,也就是說一張4x4像素的YUV420圖像大小為:4x4x(3/2)=24字節(jié)拢军。

YUV裁剪

我們來運用一下上面所講的關于YUV的知識,下面我們拿一張I420的YUV圖像來對其進行裁剪怔鳖。
上面說到茉唉,I420的排列方式就是先全部放Y,然后放U结执,然后放V度陆,由于I420是4:2:0采樣的,那么一幅YUV圖像的總大小為4x4x(3/2)=24字節(jié)献幔,前4x4字節(jié)存放全部的Y懂傀,從4x4到4x4x(5/4)存放U,最后從4x4x(5/4)到4x4x(3/2)存放V蜡感。

先從簡單的入手

假如把一幅4x4的圖像蹬蚁,將其左上角2x2的畫面裁剪出來恃泪,應該怎么做呢?我們把YUV三個分量畫成下面的圖來分析一下:


如上圖所示缚忧,首先Y分量每個像素點都有一個Y悟泵,那么總用就有16個Y杈笔,U和V水平和垂直都是Y的一半闪水,也就是只有4個U和4個V,其中蒙具,Y1球榆、Y2、Y5禁筏、Y6共用U1和V1持钉,以此類推,那么我們要裁剪左上角2x2像素篱昔,不就是把Y1每强,Y2,Y5州刽,Y6空执,U1,V1取出來穗椅,按I420的方式排列辨绊,就這么簡單!
也就是說匹表,原來4x4的I420圖像數(shù)據(jù)為Y1Y2Y3Y4Y5Y6Y7Y8Y9Y10Y11Y12Y13Y14Y15Y16U1U2U3U4V1V2V3V4门坷,我們裁出的左上角2x2圖像數(shù)據(jù)為Y1Y2Y5Y6U1V1。就這么簡單袍镀!

加強一下

我們用一張256x256大小的圖片默蚌,來做裁剪,這次我們要裁中間的那部分:將256x256的圖片裁出(x, y, w, h) = (64, 64, 128, 128)的那部分苇羡。大致就是下面這張圖上的紅框區(qū)域



我們分幾步走:

  • 讀取源圖像數(shù)據(jù)绸吸,記為sourceData
  • 開辟一塊內(nèi)存來放裁剪后的數(shù)據(jù),記為destData
  • 讀取sourceData的Y分量并提取要裁剪的Y分量數(shù)據(jù)寫入destData
  • 讀取sourceData的U分量并提取要裁剪的U分量數(shù)據(jù)寫入destData
  • 讀取sourceData的V分量并提取要裁剪的V分量數(shù)據(jù)寫入destData
  • destData寫入到文件

我們直接把I420裁剪過程封裝成一個方法宣虾,代碼加注釋看看如何一步一步實現(xiàn):

/**
 I420裁剪

 @param sourceData 源圖像數(shù)據(jù)
 @param sourceW 源圖像寬度
 @param sourceH 源圖像高度
 @param destData 裁剪后圖像數(shù)據(jù)
 @param x 從源圖像的x坐標開始裁剪
 @param y 從源圖像的y坐標開始裁剪
 @param w 裁剪寬度
 @param h 裁剪高度
 */
void clipI420(const unsigned char *sourceData,  const int sourceW, const int sourceH, unsigned char *destData, int x, int y, const int w, const int h)
{
    // 拷貝一只指針出來使喚
    const unsigned char *source = sourceData;
    // 把要裁剪的x和y都換成偶數(shù)惯裕,因為我們2行Y用一行U和一行V,奇數(shù)就難搞了
    x = (int)(x + 1) / 2 * 2;
    y  = (int)(y + 1) / 2 * 2;
    
    // 指針移動到要裁剪的Y數(shù)據(jù)的開頭
    source += y * sourceW + x;

    //總共要掃描要裁剪的高的行數(shù)绣硝,將里面在區(qū)域內(nèi)的Y值提取出來
    for (int i = 0; i < h; i++)
    {
        // 裁剪這一行的目標Y分量
        memcpy(destData, source, w);
        // 移動指針到下一行
        source += sourceW;
        destData += w;
    }
    
    //把源數(shù)據(jù)指針移到U分量的開頭
    source  = sourceData + sourceW * sourceH;
    // 把源數(shù)據(jù)的指針移到要裁剪的U分量的地方蜻势,UV分量都是行列只有Y分量的一半,所以U分量的要裁剪的y坐標應該是在(y/2)*(sourceW/2)的地方鹉胖,再加上UV分量的x坐標的偏移量
    source += (y * sourceW / 4 + x / 2);
    // UV分量都是行列都只有Y分量的一半握玛,所以指針的移動也是一半的步長
    for (int i = 0; i < h / 2; i++)
    {
        memcpy(destData, source, w / 2);
        source += sourceW / 2;
        destData += w / 2;
    }
    
    //把源數(shù)據(jù)指針移到V分量的開頭
    source  = sourceData + sourceW * sourceH * 5 / 4;
    // 以下跟U分量一個道理
    source += (y * sourceW / 4 + x / 2);

    for (int i = 0; i < h / 2; i++)
    {
        memcpy(destData, source, w / 2);
        source += sourceW / 2;
        destData += w / 2;
    }
}

void clipLena()
{
    int sourceW = 256;
    int sourceH = 256;
    int souceDataLength = sourceW * sourceH * 3 / 2;
    // 讀取源圖像
    FILE *fp = fopen("lena_256x256_yuv420p.yuv", "rb+");
    unsigned char *sourceData = (unsigned char *)malloc(souceDataLength);
    fread(sourceData, 1, souceDataLength, fp);
    fclose(fp);
    
    // 開辟儲存裁剪后的圖像的內(nèi)存
    int clipX = 64;
    int clipY = 64;
    int clipW = 128;
    int clipH = 128;
    int destDataLength = clipW * clipH * 3 / 2;
    unsigned char *destData = (unsigned char *)malloc(souceDataLength);
    
    // 進行裁剪
    clipI420(sourceData, sourceW, sourceH, destData, clipX, clipY, clipW, clipH);
    // 裁剪后數(shù)據(jù)寫入文件
    FILE *fp1 = fopen("lena_64x64_yuv420p","wb+");
    fwrite(destData, 1, destDataLength, fp1);
    fclose(fp1);
    free(sourceData);
    free(destData);
}

我自己在做這個的時候的一些疑問:
1够傍、裁剪的x和y為什么得偶數(shù)?
4:2:0這種格式不就是兩行用的是同一個UV挠铲,兩列用的也是同一個UV冕屯,例如第一行和第二行Y共用了第一行UV,針對這個256x256的圖像來說拂苹,我們把數(shù)據(jù)分為三部分:|----Y----|-U-|-V-|安聘,前兩行像素(0, 0, 256, 2),取的是數(shù)據(jù)是Y部分的前256x2字節(jié)瓢棒,U部分的前128字節(jié)浴韭,V部分的前128字節(jié),也就是兩行Y脯宿,一行U和一行V念颈。如果我們只取一行像素(0, 0, 256, 1),那就沒法玩了连霉,因為這第一行的U和V跟第二行Y共享的榴芳。
2、怎么計算UV分量數(shù)據(jù)從哪里開始榷搴场窟感?
還是拿4x4圖像舉例,4行4列有16個像素财边,也就是有16個Y肌括,4個U和4個V。也就是數(shù)據(jù)最前面4x4=16字節(jié)是Y分量酣难,U分量是接著2x2=4字節(jié)谍夭,V分量是最后2x2=4字節(jié)。UV的行列數(shù)都是Y分量的一半憨募〗羲鳎回到256x256的圖像,我們裁(64, 64, 128, 128)這一塊菜谣,第64行Y的地方是第32行U珠漂,每行U是128字節(jié),那就是U分量128x32尾膊,這也就是要裁剪的y坐標媳危。一行U是128字節(jié),那么在原圖64列的位置U應該是在這一行的64/2=32的位置冈敛。這也就是source += (y * sourceW / 4 + x / 2);這一行的由來待笑。
3、換成其他格式怎么裁剪呢抓谴?
例如NV21怎么裁呢暮蹂?其實也就是根據(jù)數(shù)據(jù)儲存格式和采樣方式從源數(shù)據(jù)中取出我們想要的數(shù)據(jù)并按規(guī)則排列就可以實現(xiàn)了寞缝。
4、還有哪些知識點沒碰到仰泻?
給YUV圖像加水印荆陆、YUV旋轉、翻轉集侯、YUV與RGB互轉等等被啼。留坑后面再補。浅悉。趟据。


總結

YUV其實沒那么神秘券犁,主要是理解采樣比例和儲存方式也就沒什么太大的難度了术健。本篇沒單獨整理Demo,上面I420裁剪的代碼是在Mac上寫的粘衬,理論上Windows也可以跑荞估。YUV文件大家可以在下面雷神的文章《[視音頻數(shù)據(jù)處理入門:RGB、YUV像素數(shù)據(jù)處理》里頭找到稚新。
最近在學習研究OpenGL和Metal渲染相關的知識勘伺,下一篇來講一下關于視頻渲染相關的知識吧!


參考文獻

YUV pixel formats
視音頻數(shù)據(jù)處理入門:RGB褂删、YUV像素數(shù)據(jù)處理
YUV格式詳解
YUV和RGB格式分析
YUV 數(shù)據(jù)格式完全解析

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末飞醉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屯阀,更是在濱河造成了極大的恐慌缅帘,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件难衰,死亡現(xiàn)場離奇詭異钦无,居然都是意外死亡,警方通過查閱死者的電腦和手機盖袭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門失暂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鳄虱,你說我怎么就攤上這事∽疽眩” “怎么了决记?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長悠栓。 經(jīng)常有香客問我霉涨,道長按价,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任笙瑟,我火速辦了婚禮楼镐,結果婚禮上,老公的妹妹穿的比我還像新娘往枷。我一直安慰自己框产,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布错洁。 她就那樣靜靜地躺著秉宿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屯碴。 梳的紋絲不亂的頭發(fā)上描睦,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音导而,去河邊找鬼忱叭。 笑死,一個胖子當著我的面吹牛今艺,可吹牛的內(nèi)容都是我干的韵丑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼虚缎,長吁一口氣:“原來是場噩夢啊……” “哼撵彻!你這毒婦竟也來了?” 一聲冷哼從身側響起实牡,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陌僵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铲掐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拾弃,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年摆霉,在試婚紗的時候發(fā)現(xiàn)自己被綠了豪椿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡携栋,死狀恐怖搭盾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情婉支,我是刑警寧澤鸯隅,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響蝌以,放射性物質發(fā)生泄漏炕舵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一跟畅、第九天 我趴在偏房一處隱蔽的房頂上張望咽筋。 院中可真熱鬧,春花似錦徊件、人聲如沸奸攻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睹耐。三九已至,卻和暖如春部翘,著一層夾襖步出監(jiān)牢的瞬間硝训,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工略就, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捎迫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓表牢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贝次。 傳聞我的和親對象是個殘疾皇子崔兴,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 轉自:http://www.cnblogs.com/azraelly/archive/2013/01/01/284...
    rickytang0閱讀 869評論 0 1
  • 由于H.264等壓縮算法都是在YUV的顏色空間上進行的,所有在進行壓縮前蛔翅,首先要進行顏色空間的轉換敲茄。如果攝像頭采集...
    眷卿三世閱讀 13,602評論 2 6
  • 導語 今天跟大家分享的這篇文章,也是之前自己總結的山析,大致就是想說明一下堰燎,視頻的裸數(shù)據(jù)yuv格式的各種分類。剛開始接...
    bigonelby閱讀 9,158評論 0 12
  • 配置 【git config】 git help 獲取幫助笋轨,也可以寫成 git --help 基本操作 【...
    小小的開發(fā)人員閱讀 358評論 0 0
  • 一句流傳很久的話:“秀才遇到兵秆剪,有理說不清”,現(xiàn)在越品爵政,覺得越有滋味仅讽。其實這不是口才以及講不講理的問題,這就是學識...
    個兒123閱讀 154評論 0 0