關於 YCbCr(YUV) 格式視頻流的介紹

文章源於Bingo,支持原創

分享Bingo 的博文:

https://www.cnblogs.com/crazybingo/archive/2012/06/07/2540595.html
https://www.cnblogs.com/crazybingo/archive/2012/06/08/2541700.html

https://www.cnblogs.com/crazybingo/archive/2011/03/27/1996946.html

RGB 轉 YCbCr, 實際上只是色度空間的轉換, 前者爲三原色色度空間, 後者爲亮度與色差,其中 RGB 轉 YCbCr 的公式如下所示:

由於 Verilog HDL 無法進行浮點運算,因此使用擴大 256 倍,再向右移 8Bit的方式,來轉換公式, 如下所示:

此時,剩下的問題就是如何將上面的公式移植到 FPGA 中去。 FPGA 富含乘法器,移位寄存器,加法器, 如果直接用表達式描述上面的運算, 理論上也是能計算出結果的; 不過代價是效率、資源; 同時當表達式小於 0 的時候, 運算結果出錯!!!

因此, 在正式進行算法移植前,我們需要進行運算的拆分, 同時進行一定的變換。 過程中爲了防止過程中出現負數, 先將 128 提取到括號內,如下:


此時 Y 必然爲正值, Cb 括號內最小值爲:

 因此 Cb 括號內必然大於 0。同樣可以推斷出 Cr 必然大於 0。經過上述變換後,可以放心的進行運算,而不用考慮運算結果溢出的問題。

1、RGB888 轉 YCbCr 的 HDL 實現

1) 第二步,分別計算出 Y、 Cb、 Cr 中每一個乘法的乘積, HDL 如下:
 

//Step 1
reg	[15:0]	img_red_r0,		img_red_r1,		img_red_r2;	
reg	[15:0]	img_green_r0,	img_green_r1,	img_green_r2; 
reg	[15:0]	img_blue_r0,	img_blue_r1,	img_blue_r2; 
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_red_r0		<=	0; 		
		img_red_r1		<=	0; 		
		img_red_r2		<=	0; 	
		img_green_r0	<=	0; 		
		img_green_r1	<=	0; 		
		img_green_r2	<=	0; 	
		img_blue_r0		<=	0; 		
		img_blue_r1		<=	0; 		
		img_blue_r2		<=	0; 			
		end
	else
		begin
		img_red_r0		<=	per_img_red 	* 	8'd77; 		
		img_red_r1		<=	per_img_red 	* 	8'd43; 	
		img_red_r2		<=	per_img_red 	* 	8'd128; 		
		img_green_r0	<=	per_img_green 	* 	8'd150; 		
		img_green_r1	<=	per_img_green 	* 	8'd85; 			
		img_green_r2	<=	per_img_green 	* 	8'd107; 
		img_blue_r0		<=	per_img_blue 	* 	8'd29; 		
		img_blue_r1		<=	per_img_blue 	* 	8'd128; 			
		img_blue_r2		<=	per_img_blue 	* 	8'd21; 		
		end
end

2) 計算出 Y、 Cb、 Cr 括號內的值, HDL 如下:

//--------------------------------------------------
//Step 2
reg	[15:0]	img_Y_r0;	
reg	[15:0]	img_Cb_r0; 
reg	[15:0]	img_Cr_r0; 
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_Y_r0	<=	0; 		
		img_Cb_r0	<=	0; 		
		img_Cr_r0	<=	0; 	
		end
	else
		begin
		img_Y_r0	<=	img_red_r0 	+ 	img_green_r0 	+ 	img_blue_r0; 		
		img_Cb_r0	<=	img_blue_r1 - 	img_red_r1 		- 	img_green_r1	+	16'd32768; 		
		img_Cr_r0	<=	img_red_r2 	+ 	img_green_r2 	+ 	img_blue_r2		+	16'd32768; 		
		end
end

( 3) 第三步, 右移 8Bit。 這裏由於 Step2 計算結果爲 16Bit, 因此提取高
8Bit 即可, HDL 如下所示:

//--------------------------------------------------
//Step 3
reg	[7:0]	img_Y_r1;	
reg	[7:0]	img_Cb_r1; 
reg	[7:0]	img_Cr_r1; 
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_Y_r1	<=	0; 		
		img_Cb_r1	<=	0; 		
		img_Cr_r1	<=	0; 	
		end
	else
		begin
		img_Y_r1	<=	img_Y_r0[15:8];
		img_Cb_r1	<=	img_Cb_r0[15:8];
		img_Cr_r1	<=	img_Cr_r0[15:8]; 
		end
end

前面說過, VIP 處理模塊輸入的數據接口時序, 與輸出完全一樣。前面計算出 Y、 Cb、 Cr 我們消耗了(1)(2)(3) 這三個時鐘, 因此需要將輸入的行場信號、使能信號同步移動 3 個時鐘, 採用寄存器移位實現

 RGB565 轉 RGB888 相 關 理 論 , 請 詳 見 Bingo 博 客 :

https://www.cnblogs.com/crazybingo/archive/2012/06/08/2542336.html

2、YCbCr422 轉 RGB888

關於ITU-R BT.656 協議

https://www.cnblogs.com/crazybingo/archive/2011/03/27/1996974.html

由於 ITU-R BT.656 視頻信號爲 YUV 信號,同時,目前 CMOS 攝像頭支持RGB565、 YCbCr、 Bayer 這幾種模式。只玩過 RGB565,未免不太爽了。研究YUV 格式視頻的解碼,對於未來 BT.656 視頻流的處理,以及相關算法的瞭解,很有好處。既然決定了做視頻圖像算法, 那麼 YCbCr 轉 RGB888 算法, 就必須搞定。
YUV 信號的提出,是因爲國際上出現彩色電視,爲了兼容黑白電視的信號而設計的,其由於視頻碼率,壓縮,兼容性的優勢,一直被沿用至今。如下是完整的 YUV4:2:2 的視頻格式數據流:

 OV7725 在 YCbCr422 格式下輸出的視頻流格式如下。 當然由於 OV7725 同時輸出了行場信號,我們可以直接硬件解碼,不需要通過 FF0000XY 識別。同行OV7725 輸出的 YCbCr422 是閹割版的 BT.656 視頻流信號, 即視頻流逐行輸出,不存在標準 BT.656 所謂的奇場,偶場信號。

3、YUV/YCbCr 視頻格式簡說
YUV 由 Y、 U、 V 複合而成,其中 Y:亮度(16-235) , U:色彩 V:飽和度。YUV 有很多格式,比如 4:2:2; 4:2:2; 4:2:0 等。使用最多的是 YUV422 格式,如下圖所示:

YUV422 模式即水平方向上 UV 的採樣速度爲 Y 的一半,相當於每兩個點採樣一個 U、 V,每一個點採樣一個 Y。這樣被允許的原因是因爲,我們的眼睛對亮度的敏感度遠大於對色度的敏感度,因此可以通過犧牲色度的採樣率來達到圖像數據壓縮的目的。
 當年的黑白電視,只有亮度,即 Y; YUV 格式的出現很好的兼容了不同制式的電視,因爲 YUV 既能兼容灰度信號,又能通過 YUV2RGB 可以轉換爲彩色圖像,兼容彩色液晶。不明白的孩子,可以直接讓{R,G,B}={Y,Y,Y},看看是不是黑白灰度的圖像。

YUV 主要應用在模擬系統中,而 YCbCr 是通過 YUV 信號的發展,通過了校正,主要應用在數字視頻中的一種格式, 一般意義上 YCbCr 即爲 YUV 信號,沒有嚴格的劃分。 CbCr 分別爲藍色色差、紅色色差,詳細的說明請看 Bingo 博文,這裏不是重點。
http://www.cnblogs.com/crazybingo/archive/2012/06/07/2540595.html

4、YUV422 轉 YUV444
首先,第一步,前面得到的 YCbCr422 爲 2:1 的分量,爲了更直觀的實現YCbCr 轉 RGB 的算法,我們首先將 YCbCr422 轉換成 YCbCr444, 即通過 Cb、Cr 的分配,完整的將每個像素均賦予 YCbCr 的格式。通過多級寄存及分量拼接, 從第三個像素的時刻開始,持續輸出完整的 YCbCr 的格式, 實現的步驟如下:

首先,第一步,前面得到的 YCbCr422 爲 2:1 的分量,爲了更直觀的實現YCbCr 轉 RGB 的算法,我們首先將 YCbCr422 轉換成 YCbCr444, 即通過 Cb、Cr 的分配,完整的將每個像素均賦予 YCbCr 的格式。 這裏 Bingo 通過多級寄存及分量拼接, 從第三個像素的時刻開始,持續輸出完整的 YCbCr 的格式, 實現的步驟如下:
(1) 寄存 Cb0、 Y0
(2) 寄存 Cr0、 Y1
(3) 輸出 Y0、 Cb0、 Cr0, 寄存 Cb1、 Y2
(4) 輸出 Y1、 Cb0、 Cr0, 寄存 Cr1、 Y3
(5) 輸出 Y2、 Cb1、 Cr1, 寄存 Cb02、 Y02
(6) 輸出 Y3、 Cb1、 Cr1, 寄存 Cr02、 Y12
(7) ……
可見, 通過(1) 與(2) 的寄存,從(3) 開始,便可以持續的輸出完整的YCbCr 格式。 但是問題(1) 與(2) 消耗了 2 個時鐘,因此我們需要人爲的生成 2 個時鐘, 來補充最後 2 個像素的數據輸出。 這可以通過 per_frame_clken 的寄存實現, 如下所示:

//------------------------------------------
//lag n pixel clocks	 
reg	[4:0]	post_frame_vsync_r;
reg	[4:0]	post_frame_href_r;
reg	[4:0]	post_frame_clken_r;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		post_frame_vsync_r 	<= 	0;
		post_frame_href_r 	<= 	0;
		post_frame_clken_r 	<= 	0;
		end
	else
		begin
		post_frame_vsync_r 	<= 	{post_frame_vsync_r[3:0], per_frame_vsync};
		post_frame_href_r 	<= 	{post_frame_href_r[3:0], per_frame_href};
		post_frame_clken_r 	<= 	{post_frame_clken_r[3:0], per_frame_clken};
		end
end
assign	post_frame_vsync 	= 	post_frame_vsync_r[4];
assign	post_frame_href 	= 	post_frame_href_r[4];
assign	post_frame_clken 	= 	post_frame_clken_r[4];
wire	yuv_process_href	= 	per_frame_href || post_frame_href_r[3];
wire	yuv_process_clken	= 	per_frame_clken || post_frame_clken_r[3];

這裏延時 4 個時鐘的原因,是由於 cmos_pclk 的時鐘頻率,爲拼接後輸出的速度的 2 倍。因此 4 次寄存,剛好延時了 2 個像素。 yuv_process_href 與yuv_process_clken 作爲前面(1) ~(7) ……的讀取使能與讀取時鐘信號。 這裏給出 YCbCr422 恢復 YCbCr444 的實現方式, 如下:

//-------------------------------------------
//convert YCbCr422 to YCbCr444
reg	[2:0]	yuv_state;
reg	[7:0]	mY0, mY1, mY2, mY3;
reg	[7:0]	mCb0, mCb1;
reg	[7:0]	mCr0, mCr1;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		yuv_state <= 3'd0;
		{mY0, mCb0, mCr0} <= {8'h0, 8'h0, 8'h0};
		mY1 <= 8'h0;
		{mY2, mCb1, mCr1} <= {8'h0, 8'h0, 8'h0};
		mY3	<= 8'h0;
		{post_img_Y, post_img_Cb, post_img_Cr} <= {8'h0, 8'h0, 8'h0};
		end
	else if(yuv_process_href)	//lag 2 data enable clock and need 2 more clocks
		begin
		if(yuv_process_clken)	//lag 2 data enable clock and need 2 more clocks
			case(yuv_state)		//{Cb,Y},{Cr,Y}---YCbCr
			3'd0:	begin	//reg p0
					yuv_state <= 3'd1;	
					{mCb0, mY0} <= per_frame_YCbCr;	
					end		
			3'd1:	begin	//reg p1
					yuv_state <= 3'd2;	
					{mCr0, mY1} <= per_frame_YCbCr;	
					end		
			
			3'd2:	begin	//p0;	reg p2
					yuv_state <= 3'd3;	
					{mCb1, mY2} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY0, mCb0, mCr0};	
					end	
			3'd3:	begin	//p1;	reg p3
					yuv_state <= 3'd4;	
					{mCr1, mY3} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY1, mCb0, mCr0};
					end	
			3'd4:	begin	//p2;	reg	p0
					yuv_state <= 3'd5;	
					{mCb0, mY0} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY2, mCb1, mCr1};	
					end		//p3;	reg p1
			3'd5:	begin	
					yuv_state <= 3'd2;	
					{mCr0, mY1} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY3, mCb1, mCr1};	
					end	
			endcase
		else
			begin
			yuv_state <= yuv_state;
			{mY0, mCb0, mCr0} <= {mY0, mCb0, mCr0};
			mY1 <= mY1;
			{mY2, mCb1, mCr1} <= {mY2, mCb1, mCr1};
			mY3	<= mY3;
			{post_img_Y, post_img_Cb, post_img_Cr} <= {post_img_Y, post_img_Cb, post_img_Cr};
			end
		end
	else
		begin
		yuv_state <= 3'd0;
		{mY0, mCb0, mCr0} <= {8'h0, 8'h0, 8'h0};
		{mY1, mCb1, mCr1} <= {8'h0, 8'h0, 8'h0};
		{post_img_Y, post_img_Cb, post_img_Cr} <= {8'h0, 8'h0, 8'h0};
		end
end

這裏給出上述 0~5 的狀態機轉移圖, 如下所示。可見從 0~1 爲寄存, 2~5 開始循環輸出, 直到一行數據的結束。

YUV444 轉 RGB888

//--------------------------------------------
/*********************************************
	R = 1.164(Y-16) + 1.596(Cr-128)
	G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
	B = 1.164(Y-16) + 2.018(Cb-128)
	->
	R = 1.164Y + 1.596Cr - 222.912
	G = 1.164Y - 0.391Cb - 0.813Cr + 135.488
	B = 1.164Y + 2.018Cb - 276.928
	->
	R << 9 = 596Y				+ 	817Cr	-	114131
	G << 9 = 596Y	-	200Cb	-	416Cr	+	69370
	B << 9 = 596Y	+	1033Cb				-	141787
**********************************************/

將該算法移植後, Bingo 發現圖像整體偏紫。 。。不可避免的懷疑是否是OV7725 的手冊, 不僅僅是上面加減法的錯誤。。。

 

OV7725 在手冊開頭說輸出輸出格式是 YCbCr422,如上圖所示,但在後續的寄存器等的介紹中又變成了 YUV。 。。 YUV 與 YCbCr 在轉換時,公式有略微的差異,主要是 YCbCr 是經過 Gamma 校正過的,如下:
 

因此,直接採用 OV7725 軟件應用手冊, 也許因爲 Gamma 矯正問題。切不在乎那麼多細節, Bingo通過實踐證明根據軟件配置,出現了偏色, 但是用上述的轉換方式,圖像非常的可以。因此轉換公式,如下:

針對上式而言,我們需要提取最後的 R、 G、 B, 這裏只需要進行三個步驟。 首先,計算每一個步中的分量,如下所示:

**********************************************/
reg	[19:0]	img_Y_r1; 				//8 + 9 + 1 = 18Bit
reg	[19:0]	img_Cb_r1, img_Cb_r2; 
reg	[19:0]	img_Cr_r1, img_Cr_r2;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_Y_r1 <= 0;
		img_Cb_r1 <= 0;	img_Cb_r2 <= 0;	
		img_Cr_r1 <= 0; img_Cr_r2 <= 0;
		end
	else
		begin
		img_Y_r1	<= 	per_img_Y * 18'd596;
		img_Cb_r1 	<= 	per_img_Cb * 18'd200;	
		img_Cb_r2	<=	per_img_Cb * 18'd1033;	
		img_Cr_r1 	<= 	per_img_Cr * 18'd817;	
		img_Cr_r2	<=	per_img_Cr * 18'd416;
		end
end

第二步, 計算 512 倍擴大、 9 次移位後的結果, 如下所示:

//--------------------------------------------
/**********************************************
	R << 9 = 596Y				+ 	817Cr	-	114131
	G << 9 = 596Y	-	200Cb	-	416Cr	+	69370
	B << 9 = 596Y	+	1033Cb				-	141787
**********************************************/
reg	[19:0]	XOUT; 	
reg	[19:0]	YOUT; 
reg	[19:0]	ZOUT;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		XOUT <= 0; 	
		YOUT <= 0; 
		ZOUT <= 0;
		end
	else
		begin
		XOUT <= (img_Y_r1 + img_Cr_r1 - 20'd114131)>>9; 	
		YOUT <= (img_Y_r1 - img_Cb_r1 - img_Cr_r2 + 20'd69370)>>9; 
		ZOUT <= (img_Y_r1 + img_Cb_r2 - 20'd141787)>>9;
		end
end

第三步,根據上述 XOUT、 YOUT、 ZOUT 的結果, 如果小於 0(Bit[10] ==1) ,則賦 0; 如果大於 255, 則賦 255; 如果在 0~255 之間,則保持原值, 具體的實現如下所示:

//------------------------------------------
//Divide 512 and get the result
//{xx[19:11], xx[10:0]}
reg	[7:0]	R, G, B;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		R <= 0;
		G <= 0;
		B <= 0;
		end
	else
		begin
		R <= XOUT[10] ? 8'd0 : (XOUT[9:0] > 9'd255) ? 8'd255 : XOUT[7:0];
		G <= YOUT[10] ? 8'd0 : (YOUT[9:0] > 9'd255) ? 8'd255 : YOUT[7:0];
		B <= ZOUT[10] ? 8'd0 : (ZOUT[9:0] > 9'd255) ? 8'd255 : ZOUT[7:0];
		end
end


5、灰度圖像的均值濾波算法
均值濾波算法介紹
首先要做的是最簡單的均值濾波算法。均值濾波是典型的線性濾波算法,它是指在圖像上對目標像素給一個模板,該模板包括了其周圍的臨近像素(以目標象素爲中心的周圍 8 個像素,構成一個濾波模板,即去掉目標像素本身),再用
模板中的全體像素的平均值來代替原來像素值。

中值濾波算法可以形象的用上述表格來描述, 即對於每個 3*3 的陣列而言,中間像素的值,等於邊緣 8 個像素的平均值。 算法的理論很簡單,對於 C 處理器而言,一幅 640*480 圖像的均值濾波, 可以很方便的通過數組獲得 3*3 的陣列,但對於我們的 Verilog HDL 而言,着實不易, 一開始想都想不明白!!!
 3*3 像素陣列的 HDL 實現

查詢過很多資料, 3*3 陣列的獲取,大概有以下三種方式:
(1) 通過 2 個或 3 個 RAM 的存儲,來實現 3*3 像素陣列
(2) 通過 2 個或 3 個 FIFO 的存儲,來實現 3*3 像素陣列

( 3) 通過 2 行或 3 行 Shift_RAM 的移位存儲,來實現 3*3 像素陣列

最方便的實現方式, 非 Shift_RAM 莫屬了,都感覺 Shift_RAM 甚至是爲實現 3*3 陣列而生的!

Shift_RAM 可定義數據寬度、 移位的行數、 每行的深度。 這裏我們固然需要8Bit, 640 個數據每行,同時移位寄存 2 行即可( 原因看後邊)。 同時選擇時鐘使能端口 clken。

看懂這個示意圖, 沒有任何繼續往下看手冊的意義!!!直接上戰場!!! Bingo的思路是這樣子的, Shift_RAM 中存 2 行數據,同時與當前輸入行的數據,組成3 行的陣列。

(1) 首先,將輸入的信號用像素使能時鐘同步一拍,以保證數據與Shift_RAM 輸出的數據保持同步,如下:

//Generate 3*3 matrix 
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//sync row3_data with per_frame_clken & row1_data & raw2_data
wire	[7:0]	row1_data;	//frame data of the 1th row
wire	[7:0]	row2_data;	//frame data of the 2th row
reg		[7:0]	row3_data;	//frame data of the 3th row
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		row3_data <= 0;
	else 
		begin
		if(per_frame_clken)
			row3_data <= per_img_Y;
		else
			row3_data <= row3_data;
		end	
end

(2) 接着, 例化並輸入 row3_data,此時可從 Modelsim 中觀察到 3 行數據同時存在了, HDL 如下
數據從 row3_data 輸入, 滿 3 行後剛好唯一 3 行陣列的第一。 從圖像第三行輸入開始, 到圖像的最後一行,我們均可從 row_data 得到完整的 3 行數據, 基爲實現3*3陣列奠定了基礎。 不過這樣做有2個不足之處, 即第一行與第二行不能得到完整的 3*3 陣列。 但從主到次,且不管算法的完美型,我們先驗證 3X3模板實現的正確性。 因此直接在行有效期間讀取 3*3 陣列,機器方便快捷的實現了我們的目的。

3) Row_data 讀取信號的分析及生成
這裏涉及到了一個問題,數據從Shift_RAM存儲耗費了一個時鐘,因此3*3陣列的讀取使能與時鐘,需要進行一個 clock 的偏移,如下所示:

//------------------------------------------
//lag 2 clocks signal sync  
reg	[1:0]	per_frame_vsync_r;
reg	[1:0]	per_frame_href_r;	
reg	[1:0]	per_frame_clken_r;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		per_frame_vsync_r <= 0;
		per_frame_href_r <= 0;
		per_frame_clken_r <= 0;
		end
	else
		begin
		per_frame_vsync_r 	<= 	{per_frame_vsync_r[0], 	per_frame_vsync};
		per_frame_href_r 	<= 	{per_frame_href_r[0], 	per_frame_href};
		per_frame_clken_r 	<= 	{per_frame_clken_r[0], 	per_frame_clken};
		end
end
//Give up the 1th and 2th row edge data caculate for simple process
//Give up the 1th and 2th point of 1 line for simple process
wire	read_frame_href		=	per_frame_href_r[0];	//RAM read href sync signal
wire	read_frame_clken	=	per_frame_clken_r[0];	//RAM read enable
assign	matrix_frame_vsync 	= 	per_frame_vsync_r[1];
assign	matrix_frame_href 	= 	per_frame_href_r[1];
assign	matrix_frame_clken 	= 	per_frame_clken_r[1];

4) Okay, 此時根據 read_frame_href 與 read_frame_clken 信號,直接讀取3*3 像素陣列。讀取的 HDL 實現如下( 請讀者詳細思考這一段代碼):

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
/******************************************************************************
					----------	Convert Matrix	----------
				[ P31 -> P32 -> P33 -> ]	--->	[ P11 P12 P13 ]	
				[ P21 -> P22 -> P23 -> ]	--->	[ P21 P22 P23 ]
				[ P11 -> P12 -> P11 -> ]	--->	[ P31 P32 P33 ]
******************************************************************************/
//---------------------------------------------------------------------------
//---------------------------------------------------
/***********************************************
	(1)	Read data from Shift_RAM
	(2) Caculate the Sobel
	(3) Steady data after Sobel generate
************************************************/
//wire	[23:0]	matrix_row1 = {matrix_p11, matrix_p12, matrix_p13};	//Just for test
//wire	[23:0]	matrix_row2 = {matrix_p21, matrix_p22, matrix_p23};
//wire	[23:0]	matrix_row3 = {matrix_p31, matrix_p32, matrix_p33};
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
		{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
		{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
		end
	else if(read_frame_href)
		begin
		if(read_frame_clken)	//Shift_RAM data read clock enable
			begin
			{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data};	//1th shift input
			{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};	//2th shift input
			{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};	//3th shift input
			end
		else
			begin
			{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
			{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
			{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
			end	
		end
	else
		begin
		{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
		{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
		{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
		end
end

注意: 這樣做我們快速方便地得到了 3*3 像素陣列,不過計算第一次與第二次得到的 3*3 行像素不完整,同時在計算中,最後一行的像素沒有參與。 同時每行的第一、第二、 最後個像素也是如此。這裏給出的只是一個 VIP算法 3X3 像素陣列生成的模板,更完美的方式可以通過鏡像方法實現,希望讀者自己加倍努力吧! !!

Mean_Filter 均值濾波算法的實現
我們例化Matrix_Generate_3X3_8Bit 模塊,直接得到了 3*3 像素陣列,不過相對於 3*3 像素陣列的生成而言,均值濾波的算法實現反而難度小的多,只是技巧性的問題。
繼續分析上面這個表格。 其實 HDL 完全有這個能力直接計算 8 個值相加的均值,不過爲了提升電路的速度,  建議我們需要通過以面積換速度的方式來實現。 So 這裏需要 3 個步驟:
(1) 分別計算 3 行中相關像素的和, 相關 HDL 代碼如下

//Add you arithmetic here
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//Mean Filter of 3X3 datas, need 2 clock
//Step 1
reg	[10:0]	mean_value1, mean_value2, mean_value3;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		mean_value1 <= 0;
		mean_value2 <= 0;
		mean_value3 <= 0;
		end
	else
		begin
		mean_value1 <= matrix_p11 + matrix_p12 + matrix_p13;
		mean_value2 <= matrix_p21 + 11'd0	   + matrix_p23;
		mean_value3 <= matrix_p31 + matrix_p32 + matrix_p33;
		end
end

(2) 計算(1) 中三個結果的和

//Step2
reg	[10:0]	mean_value4;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		mean_value4 <= 0;
	else
		mean_value4 <= mean_value1 + mean_value2 + mean_value3;
end

在(2) 運算後,我們不能急着用除法去實現均值的運算。記住, 能用移位替換的,絕對不用乘除法來實現。這裏 8 個像素, 即除以 8, 可以方便的用右移動 3Bit 來實現。不過這裏更方便的辦法是,直接提取 mean_value4[10:3]。


上圖(左) 爲前面 YCbCr 直接得到的 Y 灰度視頻顯示,上圖(右) 爲經過了均值濾波算法後的圖像。仔細一看,右圖與作圖相比,明顯喪失了部分細節,圖像變得模糊了。 這是由於均值濾波的效果/不足,濾除了部分噪聲的同步,缺失了更多的細節。

灰度圖像的中值濾波算法的實現

中值/均值濾波對比
無論是直接獲取的灰度圖像,還是由彩色圖像轉換得到的灰度圖像,裏面都有噪聲的存在,噪聲對圖像質量有很大的影響。進行中值濾波不僅可以去除孤點噪聲,而且可以保持圖像的邊緣特性,不會使圖像產生顯著的模糊,比較適合於實驗中的人臉圖像。

中值濾波算法與均值濾波非常的相似, 但濾波的效果卻有很大的差別, 區別
如下:
(1) 均值濾波相當於低通濾波, 有將圖像模糊化的趨勢,對椒鹽噪聲基本無能力。
(2) 中值濾波的有點事可以很好的過濾椒鹽噪聲,缺點是容易造成圖像的不連續。
這裏摳取 Bingo 在網上找到中值濾波與均值濾波的對比效果, 由於版權,尊重 原 創 , 感 謝 博 主 , 轉 載 必 注 :
http://www.360doc.com/content/13/0124/16/10086564_262170551.shtml

圖 1 爲含有椒鹽噪聲的 Lena,圖 2 爲均值濾波後的 Lena,可見效果並不明朗!!!圖 3 爲中值濾波後的 Lena,世界竟然可以如此的精彩!!! 因此設計實現中值濾波勢在必行,快馬加鞭啊!!!
中值濾波的算法非常簡單, 只要求得 3*3 像素陣列的中間值即可,這樣就有效的移植了最大值與最小值,圖像會變得均勻, 對椒鹽噪聲有很好的濾除效果!

中值濾波算法的 HDL 實現
我們現在的重點是如何快速求得 9 個值的均值, 該論文介紹了某種快速排序法,如下圖所示:

圖中非常形象的表示了取得中值的方法,只需要三個步驟,如下:
(1) 首先分別對每行 3 個像素進行排序, Verilog HDL 的實現,由於並行特性,我們只需要一個時鐘,實現如下:
 

//-----------------------------------
//Sort of 3 datas	
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		max_data <= 0;
		mid_data <= 0;
		min_data <= 0;
		end
	else
		begin
		//get the max value
		if(data1 >= data2 && data1 >= data3)
			max_data <= data1;
		else if(data2 >= data1 && data2 >= data3)
			max_data <= data2;
		else//(data3 >= data1 && data3 >= data2)
			max_data <= data3;

		//get the mid value
		if((data1 >= data2 && data1 <= data3) || (data1 >= data3 && data1 <= data2))
			mid_data <= data1;
		else if((data2 >= data1 && data2 <= data3) || (data2 >= data3 && data2 <= data1))
			mid_data <= data2;
		else//((data3 >= data1 && data3 <= data2) || (data3 >= data2 && data3 <= data1))
			mid_data <= data3;
			
		//ge the min value
		if(data1 <= data2 && data1 <= data3)
			min_data <= data1;
		else if(data2 <= data1 && data2 <= data3)
			min_data <= data2;
		else//(data3 <= data1 && data3 <= data2)
			min_data <= data3;
		
		end
end
//--------------------------------------------------------------------------------------
//FPGA Median Filter Sort order
//	  	   Pixel				 Sort1					  Sort2				 Sort3
//	[   P1  P2   P3 ]	[   Max1  Mid1   Min1 ]	
//	[   P4  P5   P6 ]	[   Max2  Mid2   Min2 ]	[Max_min, Mid_mid, Min_max]	mid_valid
//	[   P7  P8   P9 ]	[   Max3  Mid3   Min3 ]

//Step1
wire	[7:0]	max_data1, mid_data1, min_data1;
Sort3	u_Sort3_1
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(data11), 
	.data2		(data12), 
	.data3		(data13),
	
	.max_data	(max_data1),
	.mid_data	(mid_data1),
	.min_data   (min_data1)
);

wire	[7:0]	max_data2, mid_data2, min_data2;
Sort3	u_Sort3_2
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(data21), 
	.data2		(data22), 
	.data3		(data23),
	
	.max_data	(max_data2),
	.mid_data	(mid_data2),
	.min_data   (min_data2)
);

wire	[7:0]	max_data3, mid_data3, min_data3;
Sort3	u_Sort3_3
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(data31), 
	.data2		(data32), 
	.data3		(data33),
	
	.max_data	(max_data3),
	.mid_data	(mid_data3),
	.min_data	(min_data3)
);

 

( 2) 接着,對三行像素取得的排序進行處理,即提取三個最大值中的最小值,三個最小值中的最大值,以及三個中間值的中間值。
 

//Step2
wire	[7:0]	max_min_data, mid_mid_data, min_max_data;
Sort3	u_Sort3_4
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(max_data1), 
	.data2		(max_data2), 
	.data3		(max_data3),
	
	.max_data	(),
	.mid_data	(),
	.min_data   (max_min_data)
);

Sort3	u_Sort3_5
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(mid_data1), 
	.data2		(mid_data2), 
	.data3		(mid_data3),
	
	.max_data	(),
	.mid_data	(mid_mid_data),
	.min_data   ()
);

Sort3	u_Sort3_6
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(min_data1), 
	.data2		(min_data2), 
	.data3		(min_data3),
	
	.max_data	(min_max_data),
	.mid_data	(),
	.min_data   ()
);

( 3) 最後,將( 2) 中得到的三個值,再次取中值,求得最終 9 個像素的中值,如下:

//step3
Sort3	u_Sort3_7
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(max_min_data), 
	.data2		(mid_mid_data), 
	.data3		(min_max_data),
	
	.max_data	(),
	.mid_data	(target_data),
	.min_data   ()
);

Okay, 從( 1) -( 3) , 我們花費了 3 個 clock,完成了 3*3 像素陣列的中值提取。

上圖左爲原始灰度圖像,上圖右爲中值濾波後的圖像, 可見中值濾波後的圖像相對暗了一點,因爲最大值被捨去了。但相對於均值濾波而言,中值濾波在濾除噪聲的基礎上, 有效的保存了細節。此外,圖像處理中濾波算法有很多,包括高斯濾波, 非局部均勻等。掌握了最基本的圖像處理,更多的模板算法處理方式,也就是移植與實現的問題。

RAW→RGB→Gray→中值濾波
大部分機器視覺,都是在灰度域處理的,因此我們需要純淨的 Gray 圖像。對於機器視覺或者工業領域等對圖像要求高的,還需要做一定的預處理,此時肯定不會用 Sensor 本身輸出的 RGB565 圖像,因此本工程作爲所有後續算法升級演變的模型,首選將 Bayer 轉 RGB,繼而再通過 YUV 轉換提取出灰度圖像,做一步簡單的中值濾波了事。

(1) 優化 RAW2RGB 位 5*5 的模式,且考慮方向梯度
( 2) 優化濾波,修改爲高斯濾波,甚至非局部平均濾波、 BM3D 等 2D 濾波算法
RAW→RGB→Gray→Median→Sobel→中值濾波→腐蝕→膨脹







 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章