文章源於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
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→中值濾波→腐蝕→膨脹