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 平面格式,
- – 將 YUV 分量存放在同一數組中,通常是幾個相鄰的像素組成一個宏像素(macro-pixel)
- – 將 YUV 分量分別存儲在三個數組中,先連續存儲所有像素點的 Y,緊接着存儲所有像素點的 U,隨後是所有像素點的 V
小分類:YUV444 & YUV422 & YUV420
目前比較主流的採樣方式有:YUV444、YUV422、YUV420,下面會對這三種格式存儲方式加以介紹,並說明如何根據其採樣格式來從碼流中還原每個點的 YUV 值。
常講的的 YUV A:B:C 的意思一般是指基於 4 個象素來講,其中 Y 採樣了 A 次,U 採樣了 B 次,V 採樣了 C 次
下面的三個圖可以直觀的表示採集的方式,以黑點表示像素點的 Y 分量,以空心圓圈表示該像素點的 UV 分量。
先記住下面這句話,以後提取每個像素的 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)
YUYV 爲 YUV422 採樣方式的存儲格式之一,相鄰的兩個 Y 分量共用其相鄰的兩個Cb、Cr,因此,對於像素點 Y’00、Y’01 而言,其 Cb、Cr 的值均爲 Cb00、Cr00,其他的像素點的 YUV 取值依次類推。
2) UYVY 格式 (屬於YUV422)
UYVY 格式也是 YUV422 採樣方式的存儲格式之一,只不過與 YUYV 不同的是 UV 的排列順序不一樣而已,還原其每個像素點的YUV值的方法與上面一樣。
3) YUV422P(屬於YUV422)
YUV422P 是一種 plannar 模式,即平面模式。與前面其他 YUV422 採訪方式不同,它並不是將 YUV 數據交錯存儲,而是先存放所有的 Y 分量,然後存儲所有的 U分量, 最後存儲所有的 V 分量。
如上圖所示。其每一個像素點的 YUV 值提取方法也是遵循 YUV422 格式的最基本提取方法,即兩個 Y 共用一個 UV。比如, 對於像素點 Y’00、Y’01 而言,其 Cb、Cr 的值均爲 Cb00、Cr00。
4) YV12,YU12格式(屬於YUV420)
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)
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
下面是一個運行實例:
輸出原圖:
程序輸出三個分量:
1). output_420_y.y
2). output_420_u.y
3). output_420_v.y
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 的工作就可以了。
輸入原圖:
處理後圖片:
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;
}
}
}
其他待添加…