常見視頻原始數據格式分析 — YUV

1. YUV 簡介

YUV 是比較常用的原始視頻數據數據格式,視頻採集芯片輸出的碼流大部分都是 YUV 數據流形式,而視頻處理(如 H264、H265編碼等),也是在原始 YUV 碼流進行編碼和解析。所以,瞭解熟悉 YUV 數據流對於做視頻領域的人而言,至關重要。

YUV,分爲三個分量,Y:表示明亮度(Luminance 或 Luma),也就是灰度值;而 U 和 V :表示的則是色度(Chrominance 或 Chroma),作用是描述影像色彩及飽和度,用於指定像素的顏色。這樣設計分開的主要原因是,人眼對色度的敏感程度要低於對亮度的敏感程度。

與我們熟知的 RGB 類似,YUV 也是一種顏色編碼方法,主要用於電視系統以及模擬視頻領域,它將亮度信息(Y)與色彩信息(UV)分離,沒有 UV 信息一樣可以顯示完整的圖像,只不過是黑白的,這樣的設計很好地解決了彩色電視機與黑白電視的兼容問題。並且,YUV 不像 RGB 那樣要求三個獨立的視頻信號同時傳輸,所以用 YUV 方式傳送佔用極少的頻寬。

2. YUV 採樣方式

YUV 碼流有多種不同的格式,要分析 YUV 碼流,就必須搞清楚你面對的到底是哪一種格式,並且必須搞清楚這種格式的 YUV 採樣和分佈情況。

大分類:packed & planar

YUV 按像素分佈規律被分爲兩大類,packed 打包格式和 plannar 平面格式,

  1. – 將 YUV 分量存放在同一數組中,通常是幾個相鄰的像素組成一個宏像素(macro-pixel)
  2. – 將 YUV 分量分別存儲在三個數組中,先連續存儲所有像素點的 Y,緊接着存儲所有像素點的 U,隨後是所有像素點的 V

小分類:YUV444 & YUV422 & YUV420

目前比較主流的採樣方式有:YUV444、YUV422、YUV420,下面會對這三種格式存儲方式加以介紹,並說明如何根據其採樣格式來從碼流中還原每個點的 YUV 值。

常講的的 YUV A:B:C 的意思一般是指基於 4 個象素來講,其中 Y 採樣了 A 次,U 採樣了 B 次,V 採樣了 C 次

下面的三個圖可以直觀的表示採集的方式,以黑點表示像素點的 Y 分量,以空心圓圈表示該像素點的 UV 分量。

image

先記住下面這句話,以後提取每個像素的 YUV 分量會用到。

YUV 4:4:4 採樣,每一個 Y 對應一組 UV 分量,每像素 24 位

YUV 4:2:2 採樣,每兩個 Y 共用一組 UV 分量,每像素 16 位

YUV 4:2:0 採樣,每四個 Y 共用一組 UV 分量,每像素 12 位

3. 存儲方式

YUV 的優點之一是基於人眼對色度的敏感程度要低於對亮度的敏感程度,可以適當降低色度的採樣率,同時不會明顯降低視覺質量。因此,常見的 YUV422、YUV420 都適當降低了色度分量。

下面用圖的形式給出常見的 YUV 碼流的存儲方式,其中,Cb、Cr 的含義等同於 U、V。

1) YUVY 格式(屬於 YUV422)

image

YUYV 爲 YUV422 採樣方式的存儲格式之一,相鄰的兩個 Y 分量共用其相鄰的兩個Cb、Cr,因此,對於像素點 Y’00、Y’01 而言,其 Cb、Cr 的值均爲 Cb00、Cr00,其他的像素點的 YUV 取值依次類推。

2) UYVY 格式 (屬於YUV422)

image

UYVY 格式也是 YUV422 採樣方式的存儲格式之一,只不過與 YUYV 不同的是 UV 的排列順序不一樣而已,還原其每個像素點的YUV值的方法與上面一樣。

3) YUV422P(屬於YUV422)

image

YUV422P 是一種 plannar 模式,即平面模式。與前面其他 YUV422 採訪方式不同,它並不是將 YUV 數據交錯存儲,而是先存放所有的 Y 分量,然後存儲所有的 U分量, 最後存儲所有的 V 分量。

如上圖所示。其每一個像素點的 YUV 值提取方法也是遵循 YUV422 格式的最基本提取方法,即兩個 Y 共用一個 UV。比如, 對於像素點 Y’00、Y’01 而言,其 Cb、Cr 的值均爲 Cb00、Cr00。

4) YV12,YU12格式(屬於YUV420)

image

YU12 和 YV12 屬於 YUV420 採樣格 式之一,也是一種 plannar
模式,將 Y、U、V 分量分別打包,依次存儲。

其每一個像素點的 YUV 數據提取遵循 YUV420 格式的提取方式,即 4 個 Y 分量共用一 組 UV,即上圖中,Y’00、Y’01、Y’10、Y’11 共用 Cr00、Cb00,其他依次類推。

5) NV12、NV21(屬於YUV420)

image

NV12 和 NV21 屬於 YUV420 採樣格式之一,也是一種 plannar 模式,但是與 YV12 不同的是,將 Y 和 UV 分爲兩個 plane,Y 連續存儲,但是 UV 交錯存儲。

其提取方式與上一種類似,即 Y’00、Y’01、Y’10、Y’11 共用 Cr00、Cb00。

這種採樣存儲方式也被稱爲 YUV420SP,即 UV 交錯存儲,作爲區分,上文 4) 介紹的存儲方式被稱爲 YUV420P。

下面用一種表說明幾種常見存儲方式所佔用的內存,以 w*h 大小的圖像爲例:

採樣方式 佔用
YUV 4:4:4 Y(w * h) + U(w * h) + V(W * h) = 3 * w * h
YUV 4:2:2 Y(w * h) + U(w * h * 1/2) + V(W * h * 1/2) = 2 * w * h
YUV 4:2:0 Y(w * h) + U(w * h * 1/4) + V(W * h * 1/4) = 1.5 * w * h

4. 幾種常見 YUV 視頻像素處理

4.1 分離 YUV420P 像素數據中的 Y、U、V 分量

由於 YUV420P 各分量的連續存儲性質,可以很方便快捷的直接提取出三個分量。

下面的程序將 YUV420P 中的 Y、U、V 三個分量分離出來。代碼如下:

/** 
 * Split Y, U, V planes in YUV420P file. 
 * @param url  Location of Input YUV file. 
 * @param w    Width of Input YUV file. 
 * @param h    Height of Input YUV file. 
 * @param num  Number of frames to process. 
 * 
 */  
void simplest_yuv420_split(char *url, int w, int h)
{  
    FILE *fp = fopen(url, "rb+");  
    FILE *fp1 = fopen("output_420_y.y", "wb+");  
    FILE *fp2 = fopen("output_420_u.y", "wb+");  
    FILE *fp3=fopen("output_420_v.y", "wb+");  

    unsigned char *pic = (unsigned char*) malloc(w * h * 3 / 2);  

    fread(pic, 1, w * h * 3 / 2, fp);  
    //Y  
    fwrite(pic, 1, w * h, fp1);  
    //U  
    fwrite(pic + w * h, 1, w * h / 4, fp2);  
    //V  
    fwrite(pic + w * h * 5 / 4, 1, w * h / 4, fp3);  

    free(pic);  
    
    fclose(fp);  
    fclose(fp1);  
    fclose(fp2);  
    fclose(fp3);  
}  

從代碼可以看出, 如果視頻幀的寬和高分別爲 w 和 h,那麼一幀 YUV420P 像素數據一共佔用 (w * h * 3 / 2) Byte 的數據。其中前 (w * h) Byte存儲 Y,接着的 (w * h * 1 / 4) Byte 存儲 U,最後 (w * h * 1 / 4) Byte 存儲V。

上述調用函數的代碼運行後,將會把一張分辨率爲 256x256 分辨率 的 YUV420P 格式的像素數據文件分離成爲三個文件:

output_420_y.y:純 Y 數據,分辨率爲 256x256 
output_420_u.y:純 U 數據,分辨率爲 128x128 
output_420_v.y:純 V 數據,分辨率爲 128x128

下面是一個運行實例:

輸出原圖:

image

程序輸出三個分量:

1). output_420_y.y

image

2). output_420_u.y

image

3). output_420_v.y

image

4.2 將 YUV420P 像素數據的亮度減半

本程序中的函數可以通過將 YUV 數據中的亮度分量 Y 的數值減半的方法,降低圖像的亮度。函數代碼如下所示

/** 
 * Halve Y value of YUV420P file 
 * @param url     Location of Input YUV file. 
 * @param w       Width of Input YUV file. 
 * @param h       Height of Input YUV file. 
 * @param num     Number of frames to process. 
 */  
int simplest_yuv420_halfy(char *url, int w, int h)
{  
    FILE *fp = fopen(url,"rb+");  
    FILE *fp1 = fopen("output_half.yuv", "wb+");  

    unsigned char *pic = (unsigned char*)malloc(w * h * 3 / 2);  

    fread(pic, 1, w * h * 3 / 2, fp);  
    //Half  
    for (int j = 0; j < w * h; j++) {  
        unsigned char temp = pic[j] / 2;  
        pic[j] = temp;  
    }  
    fwrite(pic, 1, w * h * 3 / 2, fp1);  

    free(pic);  
    fclose(fp);  
    fclose(fp1);  

    return 0;  
} 

從代碼可以看出,如果打算將圖像的亮度減半,只要將圖像的每個像素的 Y 值取出來分別進行除以 2 的工作就可以了。

輸入原圖:

image

處理後圖片:

image

4.3 將 YUV420SP 圖片旋轉 90 度

void YUV420spRotate90(uchar *des, uchar *src, int width, int height)
{
    int wh = width * height;
    //旋轉Y
    int k = 0;
    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {
            des[k] = src[width * j + i];
            k++;
        }
    }

    for (int i = 0; i < width; i += 2) {
        for (int j = 0; j < height / 2; j++) {
            des[k] = src[wh+ width * j + i];
            des[k+1] = src[wh + width * j + i + 1];
            k += 2;
        }
    }
}

其他待添加…

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