常見YUV視訊格式分析及格式轉換
技術標籤:linux
背景
除錯camera過程中遇到了許多YUV格式的視訊,而同屬於YUV格式下面又分很多種,本文介紹一下常見YUV格式資料量對比,資料如何分佈,以及與RGB格式之間的轉換方法。
基本概念
與RGB相同,YUV同樣也是描述單位畫素點顏色的三個分量,每個分量佔一個位元組,根據百度百科的定義:
YUV是編譯true-color顏色空間(color space)的種類,Y'UV, YUV, YCbCr,YPbPr等專有名詞都可以稱為YUV,彼此有重疊。“Y”表示明亮度(Luminance或Luma),也就是灰階值,“U”和“V”表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度,用於指定畫素的顏色。
儲存方式
YUV格式通常有兩大類:打包(packed)模式和平面(plane)模式。packed將YUV分量存放在同一個陣列中,每個畫素的YUV分量順次排布;而plane使用三個陣列分開存放YUV三個分量,YUV各佔一個數組。
常見格式及轉換方法
YUV444
YUV 4:4:4 取樣,每一個 Y對應一組UV分量,相當於每個畫素都獨佔用一組YUV,每個畫素佔用3個位元組;
該格式相比於RGB24,單個畫素佔資料量是相同的,並未起到壓縮資料的作用,可能是由於這個原因,在我日常開發中並未遇到。
1.YUV444P
儲存方式為plane模式;
YUV444P儲存順序:YYYY...UUUU...VVVV...。
YUV422
YUV 4:2:2 取樣,每兩個Y共用一組UV分量,相當於每個Y均攤到1/2 UV,每個畫素佔據2個位元組,16bit。
1.YUV422: YUYV & UYVY
YUV422作為比較常見的視訊格式,YUYV & UYVY是我平時遇到比較多的兩種,儲存方式均為packed;
UYVY儲存順序:UYVY UYVY...;
YUYV儲存順序:YUYV YUYV...;
附上兩種格式轉換為RGB24的方法:
/* Brief: Convert frame format from UYVY to RGB888 * src_buffer: data start address * des_buffer: save data to address * w: image width * h: image height * return: result */ static int UYVYToRGB888(unsigned char *src_buffer, unsigned char *des_buffer, int w, int h) { unsigned char *yuv, *rgb; unsigned char u, v, y1, y2; yuv = src_buffer; rgb = des_buffer; if (yuv == NULL || rgb == NULL) { printf ("error: input data null!\n"); return -1; } int size = w * h; int i = 0; for(i = 0; i < size; i += 2) { y1 = yuv[2*i + 1]; y2 = yuv[2*i + 3]; u = yuv[2*i]; v = yuv[2*i + 2]; rgb[3*i] = (unsigned char)(y1 + (u - 128) + ((104*(u - 128))>>8)); rgb[3*i + 1] = (unsigned char)(y1 - (89*(v - 128)>>8) - ((183*(u - 128))>>8)); rgb[3*i + 2] = (unsigned char)(y1 + (v - 128) + ((199*(v - 128))>>8)); rgb[3*i + 3] = (unsigned char)(y2 + (u - 128) + ((104*(u - 128))>>8)); rgb[3*i + 4] = (unsigned char)(y2 - (89*(v - 128)>>8) - ((183*(u - 128))>>8)); rgb[3*i + 5] = (unsigned char)(y2 + (v - 128) + ((199*(v - 128))>>8)); } return 0; }
/* Brief: Convert pixel format from YUYV to RGB888
* y: data Y
* u: data U
* v: data V
* return: RGB format pixel data
*/
static int convert_yuv_to_rgb_pixel(int y, int u, int v)
{
unsigned int pixel32 = 0;
unsigned char *pixel = (unsigned char *)&pixel32;
int r, g, b;
r = y + (1.370705 * (v-128));
g = y - (0.698001 * (v-128)) - (0.337633 * (u-128));
b = y + (1.732446 * (u-128));
if(r > 255) r = 255;
if(g > 255) g = 255;
if(b > 255) b = 255;
if(r < 0) r = 0;
if(g < 0) g = 0;
if(b < 0) b = 0;
pixel[0] = r * 220 / 256;
pixel[1] = g * 220 / 256;
pixel[2] = b * 220 / 256;
return pixel32;
}
/* Brief: Convert frame format from YUYV to RGB888
* yuv: data start address
* rgb: save data to address
* width: image width
* height: image height
* return: result
*/
static int YUYVToRGB888(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
unsigned int in, out = 0;
unsigned int pixel_16;
unsigned char pixel_24[3];
unsigned int pixel32;
int y0, u, y1, v;
for(in = 0; in < width * height * 2; in += 4) {
pixel_16 =
yuv[in + 3] << 24 |
yuv[in + 2] << 16 |
yuv[in + 1] << 8 |
yuv[in + 0];
y0 = (pixel_16 & 0x000000ff);
u = (pixel_16 & 0x0000ff00) >> 8;
y1 = (pixel_16 & 0x00ff0000) >> 16;
v = (pixel_16 & 0xff000000) >> 24;
pixel32 = convert_yuv_to_rgb_pixel(y0, u, v);
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
pixel32 = convert_yuv_to_rgb_pixel(y1, u, v);
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
}
return 0;
}
沒有簡化合併為一個函式,功能是好用的對付看吧。
2.YUV422P:YU16 & YV16
同屬於YUV422,資料量是相同的,不同的是儲存方式使用plane,即平面模式,YUV分別儲存在三個陣列;
YU16儲存順序:YYYY...UU...VV...;16代表單位畫素16bit,別名I422;
YV16儲存順序:YYYY...VV...UU...;倆都沒用過不多提了。
3.YUV422SP:NV16 & NV61
儲存方式使用two-plane模式,即Y佔用一個plane,UV佔用一個plane;
NV16儲存順序:YYYY...UV UV...;16代表單位畫素16bit;
NV61儲存順序:YYYY...VU VU...;這倆也沒用過不多提了。
YUV420
YUV 4:2:0 取樣,每四個Y共用一組UV分量,相當於每個Y均攤到1/4 UV,每個畫素佔據1.5個位元組,12bit;
相比於YUV422,420佔據了更少的儲存空間,所以顏色上也會有一定的犧牲。
1.YUV422P: YU12 & YV12
兩種格式都屬於plane模式,YUV三種分量各佔一個plane;
YU12儲存順序:YYYY YYYY...UU...VV...,12代表單位畫素12bit,別名I420;
附上各分量排布圖,四個相同顏色的Y公用一組同顏色的UV分量:
YV12儲存順序:YYYY YYYY...VV...UU...
2.YUV420SP: NV12 & NV21
這兩種格式的資料儲存方式屬於two-plane模式,即Y佔用一個plane,UV佔用一個plane;
NV12儲存順序:YYYY YYYY...UV UV...,12代表單位畫素12bit,附上各分量排布圖:
NV21儲存順序:YYYY YYYY...VU VU...
附上NV12轉化RGB24方法:
int NV12_T_RGB(unsigned char *yuyv , unsigned char *rgb, unsigned int width , unsigned int height)
{
const int nv_start = width * height ;
uint32_t i, j, index = 0, rgb_index = 0;
uint8_t y, u, v;
int r, g, b, nv_index = 0;
for(i = 0; i < height; i++){
for(j = 0; j < width; j ++){
nv_index = i / 2 * width + j - j % 2;
y = yuyv[rgb_index];
u = yuyv[nv_start + nv_index ];
v = yuyv[nv_start + nv_index + 1];
r = y + (140 * (v-128))/100; //r
g = y - (34 * (u-128))/100 - (71 * (v-128))/100; //g
b = y + (177 * (u-128))/100; //b
if(r > 255) r = 255;
if(g > 255) g = 255;
if(b > 255) b = 255;
if(r < 0) r = 0;
if(g < 0) g = 0;
if(b < 0) b = 0;
index = rgb_index % width + (height - i - 1) * width;
//rgb[index * 3+0] = b;
//rgb[index * 3+1] = g;
//rgb[index * 3+2] = r;
// 顛倒影象
rgb[height * width * 3 - (i + 1) * width * 3 + 3 * j] = r;
rgb[height * width * 3 - (i + 1) * width * 3 + 3 * j + 1] = g;
rgb[height * width * 3 - (i + 1) * width * 3 + 3 * j + 2] = b;
// //正面圖像
// rgb[i * width * 3 + 3 * j + 0] = r;
// rgb[i * width * 3 + 3 * j + 1] = g;
// rgb[i * width * 3 + 3 * j + 2] = g;
rgb_index++;
}
}
return 0;
}
附錄
附上RGB565 ->RGB888轉換方法:
/* Brief: Convert frame format from RGB565 to RGB888
* src_buffer: data start address
* des_buffer: save data to address
* w: image width
* h: image height
* return: result
*/
static int RGB565ToRGB888(unsigned char *src_buffer, unsigned char *des_buffer, int w, int h)
{
#define RGB565_RED 0xf800
#define RGB565_GREEN 0x07e0
#define RGB565_BLUE 0x001f
unsigned char *rgb565, *rgb888;
unsigned char r5, g6, b5;
rgb565 = src_buffer;
rgb888 = des_buffer;
if (rgb565 == NULL || rgb888 == NULL)
{
printf ("error: input data null!\n");
return -1;
}
int size = w * h;
int i = 0;
for(i = 0; i < size; i += 2)
{
unsigned short src_pixel;
src_pixel = (rgb565[2*i] << 8) + rgb565[2*i + 1];
r5 = (src_pixel & RGB565_RED) >> 8;
g6 = (src_pixel & RGB565_GREEN) >> 3;
b5 = (src_pixel & RGB565_BLUE) << 3;
rgb888[3*i] = r5;
rgb888[3*i + 1] = g6;
rgb888[3*i + 2] = b5;
}
return 0;
}