1. 程式人生 > 其它 >常見YUV視訊格式分析及格式轉換

常見YUV視訊格式分析及格式轉換

技術標籤:linux

背景

除錯camera過程中遇到了許多YUV格式的視訊,而同屬於YUV格式下面又分很多種,本文介紹一下常見YUV格式資料量對比,資料如何分佈,以及與RGB格式之間的轉換方法。

基本概念

與RGB相同,YUV同樣也是描述單位畫素點顏色的三個分量,每個分量佔一個位元組,根據百度百科的定義:

YUV是編譯true-color顏色空間(color space)的種類,Y'UV, YUV, YCbCrYPbPr等專有名詞都可以稱為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;
}

參考連結:https://blog.csdn.net/xjhhjx/article/details/80291465?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160973967416780274092213%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160973967416780274092213&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-3-80291465.pc_search_result_cache&utm_term=YUV