前言
使用zmm220核心板,IFACE102版本的內核等,4300型號的LCD,XC7011_SC1145攝像頭,親測有效。
本文章使用Markdown寫法。
源碼
需要注意的是,這份代碼適用於UYVYUYVYUYVY
轉RGB565
,其它類型的轉換,看完后面的講解后自然會舉一反三。可以設置一個參數,用於選擇數據類型,函數體中用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排列等,找一個適合自己的寫法,徹底搞懂就不會再卡殼了。
(完-共勉)