YUV與RGB

接觸到了一些yuv相關的信息。從Camera中拿到的每一幀,它的默認格式是NV21,它是一種yuv格式,然後轉成OpenCV所需的BGR。Camera的每一幀的數據格式還可以指定成別的格式。因此開始關注了yuv這個名詞,後面還有yuv的各種衍生,因此仔細地梳理一下自己的理解,以及與RGB對比。

什麼是RGB

對RGB,並不陌生,從初中開始接觸的色光的三原色,告訴我們我們可以看到的光可以由這三種顏色按一定的比例去混合得到;後來在HTML以及Android開發中設置元素/控件的顏色時,可以通過一串數字,得到某個特定的顏色。這就是RGB的應用。
這裏寫圖片描述

RGB 模型是目前常用的一種彩色信息表達方式,它使用紅、綠、藍三原色的亮度來定量表示顏色。該模型也稱爲加色混色模型,是以RGB三色光互相疊加來實現混色的方法,因而適合於顯示器等發光體的顯示。

RGB 顏色模型可以看做三維直角座標顏色系統中的一個單位正方體。任何一種顏色在RGB 顏色空間中都可以用三維空間中的一個點來表示。在RGB 顏色空間上,當任何一個基色的亮度值爲零時,即在原點處,就顯示爲黑色。當三種基色都達到最高亮度時,就表現爲白色。在連接黑色與白色的對角線上,是亮度等量的三基色混合而成的灰色,該線稱爲灰色線。

什麼是BGR

與RGB類似,只是存儲時B位與R位的位置進行調換。

什麼是YCbCr

Y表示亮度,CbCr表示顏色。怎麼表示顏色,可以看下面這幅座標圖:
這裏寫圖片描述
Y要如何表示亮度呢,下面是Y在不同的情況下的表現:
這裏寫圖片描述
因此可以這樣理解,同樣是使用三個數來表示某個像素點的顏色,但是這三個數的意義變了,與RGB相比。然後接下來是各種yuv的衍生物。

The scope of the terms Y′UV, YUV, YCbCr, YPbPr, etc., is sometimes ambiguous and overlapping. Historically, the terms YUV and Y′UV were used for a specific analog encoding of color information in television systems, while YCbCr was used for digital encoding of color information suited for video and still-image compression and transmission such as MPEG and JPEG. Today, the term YUV is commonly used in the computer industry to describe file-formats that are encoded using YCbCr.

上面的意思是,這些術語有時真的很難區分,因爲定義也是模糊不清。不過重要的是最後面那一句話:現在的YUV是通常用於計算機領域用來表示使用YCbCr編碼的文件。所以可以粗淺地視YUV爲YCbCr。

不過我在Camera預覽中的每一幀中,除默認格式NV21外,還發現了其它的格式如YV12。去搜一些關於他們的資料時,發現都是yuv420系列的。具體有什麼差異呢?


膜拜一下大佬的博客(稍作編輯與修改):

YUV分類與意義

planar和packed
對於planar的YUV格式,先連續存儲所有像素點的Y,緊接着存儲所有像素點的U,隨後是所有像素點的V。
對於packed的YUV格式,每個像素點的Y,U,V是連續交*存儲的。

YUV,分爲三個分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度,用於指定像素的顏色。

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

YUV碼流的存儲格式其實與其採樣的方式密切相關,主流的採樣方式有三種,YUV4:4:4,YUV4:2:2,YUV4:2:0,關於其詳細原理,可以通過網上其它文章瞭解,這裏我想強調的是如何根據其採樣格式來從碼流中還原每個像素點的YUV值,因爲只有正確地還原了每個像素點的YUV值,才能通過YUV與RGB的轉換公式提取出每個像素點的RGB值,然後顯示出來。

存儲方式

用三個圖來直觀地表示採集的方式吧,以黑點表示採樣該像素點的Y分量,以空心圓圈表示採用該像素點的UV分量。
這裏寫圖片描述
先記住下面這段話,以後提取每個像素的YUV分量會用到。

YUV 4:4:4採樣,每一個Y對應一組UV分量。
YUV 4:2:2採樣,每兩個Y共用一組UV分量。
YUV 4:2:0採樣,每四個Y共用一組UV分量。

下面我用圖的形式給出常見的YUV碼流的存儲方式,並在存儲方式後面附有取樣每個像素點的YUV數據的方法,其中,Cb、Cr的含義等同於U、V。

YUVY 格式 (屬於YUV422)

這裏寫圖片描述
YUYV爲YUV422採樣的存儲格式中的一種,相鄰的兩個Y共用其相鄰的兩個Cb、Cr,分析,對於像素點Y’00、Y’01 而言,其Cb、Cr的值均爲 Cb00、Cr00,其他的像素點的YUV取值依次類推。

UYVY 格式 (屬於YUV422)

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

YUV422P(屬於YUV422)

這裏寫圖片描述
YUV422P也屬於YUV422的一種,它是一種Plane模式,即平面模式,並不是將YUV數據交錯存儲,而是先存放所有的Y分量,然後存儲所有的U(Cb)分量,最後存儲所有的V(Cr)分量,如上圖所示。其每一個像素點的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即兩個Y共用一個UV。比如,對於像素點Y’00、Y’01 而言,其Cb、Cr的值均爲 Cb00、Cr00。

YV12,YU12格式(屬於YUV420)

這裏寫圖片描述
YU12和YV12屬於YUV420格式,也是一種Plane模式,將Y、U、V分量分別打包,依次存儲。其每一個像素點的YUV數據提取遵循YUV420格式的提取方式,即4個Y分量共用一組UV。注意,上圖中,Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00,其他依次類推。

NV12、NV21(屬於YUV420)

這裏寫圖片描述

NV12和NV21屬於YUV420格式,是一種two-plane模式,即Y和UV分爲兩個Plane,但是UV(CbCr)爲交錯存儲,而不是分爲三個plane。其提取方式與上一種類似,即Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00

YUV文件大小計算

以720×488大小圖象YUV420 planar爲例,其存儲格式是: 共大小爲(720×480×3>>1)字節,

分爲三個部分:Y,U和V

Y分量: (720×480)個字節
U(Cb)分量:(720×480>>2)個字節
V(Cr)分量:(720×480>>2)個字節

三個部分內部均是行優先存儲,三個部分之間是Y,U,V 順序存儲。

0--720×480字節是Y分量值,
720×480--720×480×5/4字節是U分量
720×480×5/4 --720×480×3/2字節是V分量。

4 :2: 2 和4:2:0 轉換

最簡單的方式:

YUV4:2:2 —> YUV4:2:0 Y不變,將U和V信號值在行(垂直方向)在進行一次隔行抽樣。 YUV4:2:0 —> YUV4:2:2 Y不變,將U和V信號值的每一行分別拷貝一份形成連續兩行數據。

在YUV420中,一個像素點對應一個Y,一個4X4的小方塊對應一個U和V。對於所有YUV420圖像,它們的Y值排列是完全相同的,因爲只有Y的圖像就是灰度圖像。YUV420sp與YUV420p的數據格式它們的UV排列在原理上是完全不同的。420p它是先把U存放完後,再存放V,也就是說UV它們是連續的。而420sp它是UV、UV這樣交替存放的。(見下圖) 有了上面的理論,我就可以準確的計算出一個YUV420在內存中存放的大小。 width * hight =Y(總和) U = Y / 4 V = Y / 4。所以YUV420 數據在內存中的長度是 width * hight * 3 / 2,

假設一個分辨率爲8X4的YUV圖像,它們的格式如下圖:
這裏寫圖片描述
YUV420p數據格式如下圖
這裏寫圖片描述

旋轉90度的算法:

public static void rotateYUV240SP(byte[] src,byte[] des,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;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

YV12和I420的區別

一般來說,直接採集到的視頻數據是RGB24的格式,RGB24一幀的大小size=width×heigth×3 Bit,RGB32的size=width×heigth×4,如果是I420(即YUV標準格式4:2:0)的數據量是 size=width×heigth×1.5 Bit。 在採集到RGB24數據後,需要對這個格式的數據進行第一次壓縮。即將圖像的顏色空間由RGB2YUV。因爲,X264在進行編碼的時候需要標準的YUV(4:2:0)。但是這裏需要注意的是,雖然YV12也是(4:2:0),但是YV12和I420的卻是不同的,在存儲空間上面有些區別。如下: YV12 : 亮度(行×列) + U(行×列/4) + V(行×列/4)

I420 : 亮度(行×列) + V(行×列/4) + U(行×列/4)

可以看出,YV12和I420基本上是一樣的,就是UV的順序不同

繼續我們的話題,經過第一次數據壓縮後RGB24->YUV(I420)。這樣,數據量將減少一半,爲什麼呢?呵呵,這個就太基礎了,我就不多寫了。同樣,如果是RGB24->YUV(YV12),也是減少一半。但是,雖然都是一半,如果是YV12的話效果就有很大損失。然後,經過X264編碼後,數據量將大大減少。將編碼後的數據打包,通過RTP實時傳送。到達目的地後,將數據取出,進行解碼。完成解碼後,數據仍然是YUV格式的,所以,還需要一次轉換,這樣windows的驅動纔可以處理,就是YUV2RGB24。

YUV420P和 YUV420SP的區別

YUV420P,Y,U,V三個分量都是平面格式,分爲I420和YV12。I420格式和YV12格式的不同處在U平面和V平面的位置不同。在I420格式中,U平面緊跟在Y平面之後,然後纔是V平面(即:YUV);但YV12則是相反(即:YVU)。
YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 NV12與NV21類似,U 和 V 交錯排列,不同在於UV順序。

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


Y′UV420p (and Y′V12 or YV12) to RGB888 conversion

Y′UV420p is a planar format, meaning that the Y′, U, and V values are grouped together instead of interspersed. The reason for this is that by grouping the U and V values together, the image becomes much more compressible. When given an array of an image in the Y′UV420p format, all the Y′ values come first, followed by all the U values, followed finally by all the V values.

The Y′V12 format is essentially the same as Y′UV420p, but it has the U and V data switched: the Y′ values are followed by the V values, with the U values last. As long as care is taken to extract U and V values from the proper locations, both Y′UV420p and Y′V12 can be processed using the same algorithm.

As with most Y′UV formats, there are as many Y′ values as there are pixels. Where X equals the height multiplied by the width, the first X indices in the array are Y′ values that correspond to each individual pixel. However, there are only one fourth as many U and V values. The U and V values correspond to each 2 by 2 block of the image, meaning each U and V entry applies to four pixels. After the Y′ values, the next X/4 indices are the U values for each 2 by 2 block, and the next X/4 indices after that are the V values that also apply to each 2 by 2 block.

Translating Y′UV420p to RGB is a more involved process compared to the previous formats. Lookup of the Y′, U and V values can be done using the following method:

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)];
rgb = Y′UV420toRGB888(y, u, v);
  • 1
  • 2
  • 3
  • 4
  • 5

這裏寫圖片描述
As shown in the above image, the Y′, U and V components in Y′UV420 are encoded separately in sequential blocks. A Y′ value is stored for every pixel, followed by a U value for each 2×2 square block of pixels, and finally a V value for each 2×2 block. Corresponding Y′, U and V values are shown using the same color in the diagram above. Read line-by-line as a byte stream from a device, the Y′ block would be found at position 0, the U block at position x×y (6×4 = 24 in this example) and the V block at position x×y + (x×y)/4 (here, 6×4 + (6×4)/4 = 30).

Y′UV420sp (NV21) to RGB conversion (Android)

This format (NV21) is the standard picture format on Android camera preview. YUV 4:2:0 planar image, with 8 bit Y samples, followed by interleaved V/U plane with 8bit 2x2 subsampled chroma samples.
C++ code used on Android to convert pixels of YUVImage:

void YUVImage::yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue,
        uint8_t *r, uint8_t *g, uint8_t *b) const {
    int rTmp = yValue + (1.370705 * (vValue-128));
    int gTmp = yValue - (0.698001 * (vValue-128)) - (0.337633 * (uValue-128));
    int bTmp = yValue + (1.732446 * (uValue-128));
    *r = clamp(rTmp, 0, 255);
    *g = clamp(gTmp, 0, 255);
    *b = clamp(bTmp, 0, 255);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

參考:
http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html
https://www.jianshu.com/p/a91502c00fb0
https://en.wikipedia.org/wiki/YUV

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