1. 程式人生 > >YUV和RGB的相互轉換實驗報告

YUV和RGB的相互轉換實驗報告

1、彩色空間轉換基本原理

1)彩色空間轉換公式:

為了實現格式轉換,我們首先要明確待轉換格式和目標格式的特點和相互轉換關係,這是程式設計實現轉換的核心。對於RGB轉YUV的過程,我們要首先拿到RGB檔案的資料,再通過上圖的YUV計算公式對其做運算,得到YUV資料,從而實現轉換。而對於YUV轉RGB則要首先獲得YUV資料,用第二組RGB公式計算得到RGB資料。 在本實驗中,轉換公式如下。

Y = 0.298R + 0.612G + 0.117B;

U = -0.168R - 0.330G + 0.498B + 128;

V = 0.449R - 0.435G - 0.083B + 128;

R = Y + 1.4075( V - 128);

G = Y - 0.3455( U - 128) - 0.7169( V - 128);

B = Y + 1.779( U - 128);

其中,計算UV分量時+128是因為UV分量有負值,加上128剛好可以將變化範圍調整為0—255。所以在做YUV轉RGB時,要特別注意,給UV分量減去128才能代入去乘轉換系數。

2)瞭解兩種格式的儲存方式:

RGB儲存方式:RGB三個分量按照B、G、R的順序儲存。(4:4:4)


YUV儲存方式:先存Y再存UV分量。(4:2:0)


3)初始化引數。

指向輸入、輸出檔案的指標:FILE * rgbfile,FILE * yuvfile;

檔案的相關資訊:寬framewidth, 高frameheight;

指向所開闢空間的指標:unsigned char型,yuv_Buf,rgb_Buf;

注:根據YUV和RGB儲存方式的特點,申請空間時RGB和YUV採用不同的方法。儲存RGB只開一塊完整的空間,用一個buffer;而YUV三個分量各自分開儲存,儘管在記憶體中也是一維的資料流,但是為了方便計算和指標操作,三個分量分別用三個buffer,開三塊空間儲存。

2、實驗流程分析

RGB2YUV:首先,主函式中,開啟待轉換的RGB檔案fopen,讀出其中的資料,即將資料寫到rgb_Buf空間中;其次,進入轉換函式,對rgb_Buf空間的資料進行計算處理(y_buffer指向yuv_Buf,u_buffer & v_buffer各自開闢空間),y的計算結果寫入y_buffer(yuv_Buf

),UV分量計算結果存在u_buffer和v_buffer;然後,將UVbuffer中的UV分量進行下采樣(YUV4:2:0),將結果存入目標空間sub_u_buf,sub_v_buf(出口:u_out,v_out),釋放掉轉換函式中開闢的空間;最後,回到主函式,將結果寫出到目標檔案中去fwrite,釋放掉所開空間。

YUV2RGB:首先,主函式中,開啟YUV檔案,讀出其中的資料,將資料寫到yuv_Buf中;其次,對yuv_Buf的資料進行上取樣,即,Y不動,將UV分量補全存入up_yuv_buf所開空間(將一個畫素點的uv值賦給對應點及右、下、右下點);最後,對yuv_Buf,up_yuv_buf中的YUV資料進行計算,還原出RGB資料,存入rgb_Buf,釋放掉轉換函式中開闢的空間;最後,將結果輸出到目標檔案,釋放掉所開空間。

檢驗:第一步,將RGB素材轉換得到YUV1,用yuv播放器看是否正確;第二步,再將得到的YUV1轉回RGB格式,並再次將RGB轉YUV2,若YUV2和此前得到的YUV1完全一致,則YUV2RGB轉換無誤;否則,應就問題再做修改和除錯。

3、關鍵程式碼及其分析

1)YUV2RGB過程UV上取樣部分

    在轉換函式中開闢一塊空間up_yuv_buf,空間大小為2*size剛好存放UV分量。psu1、psu2指標指向空間中存U分量部分的奇偶兩行,pu指向原空間中U分量所在位置。psv指標同u。一次迴圈中,利用指標對UV分量各自的奇偶兩行同時賦值,隨後各自加1,再次賦值,實現四個畫素點取同樣的值,即,上取樣。

for (i = 0; i < y_dim / 2; i++)
	{
		psu1 = up_yuv_buf + 2 * i * x_dim;  //U偶數行指標
		psu2 = up_yuv_buf + (2 * i + 1) * x_dim;  //U奇數行指標
		psv1 = up_yuv_buf + size + 2 * i * x_dim;   //V偶數行指標
		psv2 = up_yuv_buf + size + (2 * i + 1) * x_dim;  //V奇數行指標
		pu = u_buf + i * x_dim / 2;
		pv = v_buf + i * x_dim / 2;

		for (j = 0; j < x_dim / 2; j++)
		{
			*psu1 = *pu; *psu2 = *pu;     //奇數偶數行第一次畫素點賦值,UV同時
			*psv1 = *pv; *psv2 = *pv;     
			psu1++; psu2++;
			psv1++; psv2++;
			*psu1 = *pu; *psu2 = *pu;     //奇數偶數行第二次畫素點賦值,UV同時			*psv1 = *pv; *psv2 = *pv;
			psu1++; psu2++;
			psv1++; psv2++;
			pu++; pv++;
		}
	}
1)YUV2RGB過程計算RGB部分

   防溢位:此處定義了三個float變數:tmpr,tmpg,tmpb分別暫存計算得到的float型rgb數值,然後判斷,如果數值大於225,令其等於225;如果小於0,令其等於0。隨後再將變數賦給r/g/b指標指向的位置。這一步非常重要,float型16位,unsigned char型8位,計算得到的數值在強制型別轉換時很可能溢位,導致的結果是影象上明顯可見數值溢位的點,小於0的是紅色點,大於255的是藍色點。

for (i = 0; i < y_dim; i++)
{
	for(j=0;j<x_dim;j++)
	{
		g = b + 1;
		r = b + 2;
		tmpr=*y + RGBYUV1402[*v];
		tmpg=*y - RGBYUV0344[*u] - RGBYUV0714[*v];
		tmpb=*y + RGBYUV1772[*u];
		//To prevent overflow
		tmpr=tmpr>255?255:(tmpr<0?0:tmpr);
		tmpg=tmpg>255?255:(tmpg<0?0:tmpg);
		tmpb=tmpb>255?255:(tmpb<0?0:tmpb);
		*r=(unsigned char)tmpr;
		*g=(unsigned char)tmpg;
		*b=(unsigned char)tmpb;
		y ++;
		u ++;
		v ++;
		b += 3;
	}
}

4、實驗結果及分析

(為了方便查詢錯誤,找到一個既能播放RGB也能播放YUV的播放器,分享:http://wwww.236.6122.net/uploadFile/2013/vooya.rar)

結果1:YUV檔案為灰色,無影象。畫素點YUV(204,127,127)

原因:在函式內,給rgb_buffer又開了一次空間。rgb_buffer本身已經指向出口rgb_out,已經指向了主函式中為目標開的空間rgb_Buf,所以在函式內沒必要再開空間,加一條開空間的程式碼反而會導致計算得到的數值都給了後開的空間,從而無法寫入真正的目標空間。由於目標空間是空的,寫入檔案時RGB的值都是預設值,轉換回YUV後就是初始值(204,127,127)。

rgb_buffer = (unsigned char *)rgb_out;
	
//rgb_buffer = (unsigned char *)malloc(3 * size * sizeof(unsigned char));//已經指向函數出口,即一段空間,這裡不用再開空間,反而造成無法將資料寫出到指定空間
up_yuv_buf = (unsigned char *)malloc(2 * size * sizeof(unsigned char));

結果2:YUV檔案為灰色,有模糊影象。以及修復後為紫色,檢查rgb檔案也為紫色,有模糊影象。

  

原因:當時在上取樣時,給up_yuv_buf開了存放YUV三個分量的空間,並且將Y進行了搬移,搬移過程中指標的初始定位語句放在了迴圈內,導致每一行都是第一行的Y分量數值,所以只有第一行是正常的結果。將播放器放大兩倍發現第一行是正確的,所以確定是指標的問題,檢查程式碼果然是搬移時出錯,修改後可以得到影象。而影象呈紫色是因為如下所示程式碼中,i沒有減去128,而是在上面陣列呼叫時寫作:

RGBYUV1402[*v-128];
注:這個陣列的定義只從0-155,先減掉128必然會有數值小於0的問題出現,導致影象出錯。正確做法應該在下面的函式中減去128。
void InitLookupTable()
{
	int i;

	for (i = 0; i < 256; i++) RGBYUV1402[i] = (float)(1.402 * (i-128));
	for (i = 0; i < 256; i++) RGBYUV0344[i] = (float)(0.34414 * (i-128));
	for (i = 0; i < 256; i++) RGBYUV0714[i] = (float)(0.71414 * (i-128));
	for (i = 0; i < 256; i++) RGBYUV1772[i] = (float)(1.772 * (i-128));
	
}

結果三:rgb檔案上有藍色紅色點,yuv檔案上相反。


原因:上文提到的沒有對計算得到的RGB數值進行判斷,導致強制型別轉換成unsigned char之後資料溢位了,從而導致影象上某些點變成紅色和藍色。

for (i = 0; i < y_dim; i++)
	{
		for(j=0;j255?255:(tmpr<0?0:tmpr);
			tmpg=tmpg>255?255:(tmpg<0?0:tmpg);
			tmpb=tmpb>255?255:(tmpb<0?0:tmpb);

			*r=(unsigned char)tmpr;
			*g=(unsigned char)tmpg;
			*b=(unsigned char)tmpb;
			y ++;
			u ++;
			v ++;
			b += 3;
		}
	}

  

結果四:正確結果

  

(欣慰之餘,告誡自己,查錯艱難,請一次寫對。)

另外附上三個其他yuv檔案的轉換結果:我們可以發現,YUV轉RGB的過程中,顏色發生了十分明顯的改變,藍色系會偏紅,紅色系會偏藍。這種變化在最後一幅圖的紅色袖子上體現的相當明顯。