YUV422(UYVY)轉RGB565源代碼及其講解.md


前言

使用zmm220核心板,IFACE102版本的內核等,4300型號的LCD,XC7011_SC1145攝像頭,親測有效。
本文章使用Markdown寫法。

源碼

需要注意的是,這份代碼適用於UYVYUYVYUYVYRGB565,其它類型的轉換,看完后面的講解后自然會舉一反三。可以設置一個參數,用於選擇數據類型,函數體中用switch case。這里沒進行這個處理。

/*
* YUV422打包數據,UYVY,轉換為RGB565,
* inBuf -- YUV data
* outBuf -- RGB565 data
* imgWidth,imgHeight -- image width and height
* cvtMethod -- 無效參數
*/
int convert_uyvy_to_rgb(unsigned char *inBuf, unsigned char *outBuf, int imgWidth, int imgHeight, int cvtMethod)
{
	int rows ,cols;	/* 行列標志 */
	int y, u, v, r, g, b;	/* yuv rgb 相關分量 */
	unsigned char *YUVdata, *RGBdata;	/* YUV和RGB數據指針 */
	int Ypos, Upos, Vpos;	/* Y U V在數據緩存中的偏移 */
	unsigned int i = 0;

	YUVdata = inBuf;
	RGBdata = outBuf;
#if 0
	/*  YUYV */
	Ypos = 0;
	Upos = Ypos + 1;
	Vpos = Upos + 2;

	/* YVYU */
	Ypos = 0;
	Vpos = Ypos + 1;
	Upos = Vpos + 2;
#endif

#if 1   /* UYVY */
	Ypos = 1;
	Upos = Ypos - 1;
	Vpos = Ypos + 1;
#endif

	/* 每個像素兩個字節 */
	for(rows = 0; rows < imgHeight; rows++)
	{
		for(cols = 0; cols < imgWidth; cols++)
		{
			/* 矩陣推到,百度 */
			y = YUVdata[Ypos];
			u = YUVdata[Upos] - 128;
			v = YUVdata[Vpos] - 128;

			r = y + v + ((v * 103) >> 8);
			g = y - ((u * 88) >> 8) - ((v * 183) >> 8);
			b = y + u + ((u * 198) >> 8);

			r = r > 255?255:(r < 0?0:r);
			g = g > 255?255:(g < 0?0:g);
			b = b > 255?255:(b < 0?0:b);

			/* 從低到高r g b */
			*(RGBdata ++) = (((g & 0x1c) << 3) | (b >> 3));	/* g低5位,b高5位 */
			*(RGBdata ++) = ((r & 0xf8) | (g >> 5));	/* r高5位,g高3位 */

			/* 兩個字節數據中包含一個Y */
			Ypos += 2;
			//Ypos++;
			i++;
			/* 每兩個Y更新一次UV */
			if(!(i & 0x01))	
			{
				Upos = Ypos - 1;
				Vpos = Ypos + 1;
			}

		}
	}

	return 0;
}

這里面有幾個關鍵點,一開始寫錯了,最好的辦法是在紙上畫出來,第二次重寫的時候,在紙上畫出來,編譯運行一次性通過。光憑想象非常容易出紕漏或者進入思路誤區。

代碼分析

YUV三個分量的關系

首先你要確定ISP輸出給CPU的控制器接口cim的數據排列方式,也就是上面傳入的參數inBuf中Y、U、V三分量的存儲方式。如:YUYV、YVYU、UYVY、VYUY等。特別要注意當配置寄存器以便輸出黑白圖的時候,要確定ISP輸出的是YUV400,也就是UV也占字節,只不過都是0,如XC7011_SC1145;還是只單獨輸出Y,如:gc0308.
這個可以通過工具驗證,建議使用海康的YUVplayer,比pYUV好用很多,也准確許多。
這里分析UYVY的存儲方式,見下表:

YUV三分量各占一個字節,每兩個Y共享一對UV,所以每個像素兩個字節,在代碼中的表現就是,Y遞增兩次才刷新一次UV,注意這里Y的遞增是2,和網上的版本不同
據此可以很清晰的看出,對於UYVY來說,Y、U、V三個分量的關系如下:

Y=i和Y=i+2時
U=i-1;
V=i+1;

循環遍歷

這個很容易搞錯,特別是在做圖像的裁剪時,一不注意就會數組越界導致段錯誤。
處理YUV422、RGB888等數據有個很實用的規律:對於遍歷圖像緩存數據的操作,特別是一個像素點對應多個字節的時候,for循環的遍歷參數i、j等參考像素點的寬和高遞增;而在循環體中涉及到圖像緩存數據的操作,一律按照字節數來操作。RGB565數據處理也可以參考這個規律,只不過增加了數據的裁剪增補而已。
比如分析上面的例子:
圖像的像素點寬高是imgHeight、imgWidth,所以for循環的寫法就是:

for(rows = 0; rows < imgHeight; rows++)
{
   for(cols = 0; cols < imgWidth; cols++)
   {
   }
}

循環的參考條件是像素點的寬和高,這樣能保證大方向不會出錯——遍歷每個像素點,不會漏數據(這是對每次遞增1來說的,還要看循環體的具體操作)。
如果每個像素點都要操作,且一個像素點對應兩個字節,那么在循環體中你一次就要處理兩個字節的數據,這能明白吧?但是針對UYVY這種YUV數據來說,參見上表,一般理解為每4個字節對應兩個像素,因為這樣理解包含了YUV數據的特性,不會出現Y占一個字節,UV各占半個字節的錯覺。所以上面代碼的寫法思路就是:保證最里層的循環體執行兩次——遍歷兩個像素點——處理4個字節數據——更新兩次Y——更新一次UV,這點很關鍵,否則循環體代碼越寫越亂,分析如下:

/* 矩陣推到,百度 */
			y = YUVdata[Ypos];
			u = YUVdata[Upos] - 128;
			v = YUVdata[Vpos] - 128;

			r = y + v + ((v * 103) >> 8);
			g = y - ((u * 88) >> 8) - ((v * 183) >> 8);
			b = y + u + ((u * 198) >> 8);

			r = r > 255?255:(r < 0?0:r);
			g = g > 255?255:(g < 0?0:g);
			b = b > 255?255:(b < 0?0:b);

			/* 從低到高r g b */
			*(RGBdata ++) = (((g & 0x1c) << 3) | (b >> 3));	/* g低5位,b高5位 */
			*(RGBdata ++) = ((r & 0xf8) | (g >> 5));	/* r高5位,g高3位 */

這部分代碼是網上提供的YUV和RGB的轉換公式,實測證明有效,但是要注意幾點

  • 括號不能少

  • 要做數值邊界判斷和處理

  • RGB的存儲方式
    yuv分量轉換的來的rgb分量,都是各占一個字節,實際的RGB中,R、B各占5bits,G占6bits,總共是16bits兩個字節。在這兩個字節中,低字節存放g的低3位(8bits中高6bits中的低3bits)和b的全部(8bits中的高5位),高字節存放r的全部(8bits中的高5位)和g的高3位(8bits中高6bits中的高3bits)

/* 兩個字節數據中包含一個Y */
			Ypos += 2;
			//Ypos++;
			i++;
			/* 每兩個Y更新一次UV */
			if(!(i & 0x01))	
			{
				Upos = Ypos - 1;
				Vpos = Ypos + 1;
			}

這個應該很好懂了,j++兩次,循環體執行兩次,Y更新了兩次,UV更新了一次。

結束語

明白了這種寫法的原理,你就可以舉一反三了,比如:每次處理兩個像素、數據按照YUYV排列等,找一個適合自己的寫法,徹底搞懂就不會再卡殼了。
(完-共勉)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM