前言
項目算法需求,需要將RGB彩色圖像轉換為灰度圖像,算法原理是很簡單的,但是對於剛接觸FPGA的寶寶來說,進行時序的設計和調試還是不那么容易的,為了省事兒,就按照上一篇中值濾波(http://www.cnblogs.com/happyamyhope/p/5577898.html)的結構進行設計。開始的開始,只能根據已經做好的設計照葫蘆畫瓢,否則調試還是很繁瑣的,主要是因為目前還是掌握不了時序設計的精髓和思路,慢慢來吧。
實驗步驟:
1.實驗原理介紹;
2.編寫各模塊的代碼;
3.調試仿真,並與matlab中rgb2gray函數的結果進行比較;
實驗過程:
1.實驗原理介紹;
matlab中rgb2gray函數的原理還是比較簡單的,最后輸出的灰度圖像是RGB三種顏色通道的加權和;
rgb2gray converts RGB values to grayscale values by forming a weighted sum of the R, G, and B components: 0.2989 * R + 0.5870 * G + 0.1140 * B
上面就是MATLAB中rgb2gray函數的算法原理;
本模塊輸入的是8bits的三通道彩色圖像數據,輸出的也是8bits的數據,至於整體算法模塊的小數,有待到時候進行一下整合,改變數據的位數。
為了不涉及到小數,我們將數據整體左移16位即乘以65536,根據matlab中的算法原理進行設計:
gray <= 19589 * red + 38469 * green + 7471 * blue; //gray <= 19595 * red + 38469 * green + 7472 * blue;
查看了一些資料,比如: http://www.cnblogs.com/diewcs/archive/2010/10/03/1841744.html
2.編寫個模塊的代碼;
先將計數器控制模塊和rgb2gray進行調試仿真,再將兩個模塊綜合到一起進行調試仿真,得到最終的結果。
主要是計數器控制模塊,如何正確得到正確時序的地址數據。
1)計數器控制模塊:

module counter_ctrl( CLK, RSTn, iCall, iNxt_pix, oAddr, oDone ); input CLK; input RSTn; input iCall; input iNxt_pix; output [17:0] oAddr; output oDone; reg [17:0] imk; reg isDone ; reg start_sig_d; wire start_sig_rising_vld; always @ (posedge CLK or negedge RSTn) //Asynchronous reset if ( !RSTn ) start_sig_d <= 0; else start_sig_d <= iCall; assign start_sig_rising_vld = iCall & (~start_sig_d); always @ ( posedge CLK or negedge RSTn ) if ( !RSTn ) begin imk <= 18'd0; isDone <= 1'b0; end else if ( start_sig_rising_vld ) begin imk <= 18'b1; isDone <= 1'b1; end else if ( iNxt_pix ) // & ( imk != 166222 ) begin imk <= imk + 1'b1; isDone <= 1'b1; end else isDone <= 1'b0; assign oAddr = imk; assign oDone = isDone; endmodule
模塊通過計數控制得到將要讀取圖像數據的地址,需要注意的是何時開始獲取數據初始地址,何時開始地址開始進行加一計數。
testbench模塊:
module ctrl_tb; // Inputs reg CLK; reg RSTn; reg iCall; // Outputs wire [17:0] oAddr; wire oDone; // Instantiate the Unit Under Test (UUT) counter_ctrl uut ( .CLK(CLK), .RSTn(RSTn), .iCall(iCall), .oAddr(oAddr), .oDone(oDone) ); initial begin // Initialize Inputs CLK = 0; RSTn = 0; iCall = 0; // Wait 100 ns for global reset to finish #100; RSTn = 1; iCall = 1; // Add stimulus here end always #10 CLK = ~CLK; always @ ( posedge CLK or RSTn ) if ( oDone ) $display("%d\n", oAddr ); always @ ( posedge CLK or RSTn ) if ( oAddr == 18'd200 ) begin iCall <= 0; $stop; end endmodule
2)rgb2gray算法模塊;
// matlab algorithm:gray = 0.2989 * R + 0.5870 * G + 0.1140 * B; // move left by 16bits: gray = 19589 * R + 38469 * G + 7471 * B; module rgb2gray( CLK, RSTn, iCall, iRed, iGreen, iBlue, oGray, oDone ); input CLK; input RSTn; input iCall; //input [23:0] iRGB; input [7:0] iRed; input [7:0] iGreen; input [7:0] iBlue; output [7:0] oGray; output oDone; reg [7:0] red; reg [7:0] green; reg [7:0] blue; reg [23:0] gray; reg [2:0] i; reg isDone; /********************************************************************************/ reg get_pix_vld; always @ ( posedge CLK or negedge RSTn ) if (!RSTn) get_pix_vld <= 1'b0; else if ( iCall ) get_pix_vld <= 1'b1; else if ( i==3'd2 ) get_pix_vld <= 1'b0; always @ ( posedge CLK or negedge RSTn ) if ( !RSTn ) begin red <= 8'd0; green <= 8'd0; blue <= 8'd0; end else if ( iCall ) begin red <= iRed ; green <= iGreen; blue <= iBlue ; end always @ ( posedge CLK or negedge RSTn ) if ( !RSTn ) begin i <= 3'd0; gray <= 24'd0; isDone <= 1'b0; end else if ( get_pix_vld ) case ( i ) 0: begin //gray <= 19595 * iRGB[23:16] + 38469 * iRGB[15:8] + 7472 * iRGB[7:0]; gray <= 19589 * red + 38469 * green + 7471 * blue; //gray <= 19595 * red + 38469 * green + 7472 * blue; i <= i + 1'b1; end 1: begin isDone <= 1'b1; i <= i + 1'b1; end 2: begin isDone <= 1'b0; i <= 3'd0; end endcase assign oGray = ( gray >> 16 ) ; assign oDone = isDone ; endmodule
將輸入的三色通道圖像數據轉換為灰度圖像。
3)創建IP ROM核,將彩色圖象數據分通道儲存在ROM中,具體操作步驟請參考
http://www.cnblogs.com/happyamyhope/p/5498745.html
圖像大小是383*434,共R、G、B三個通道。
4)頂層模塊:
將低層各個模塊聯系起來,得到rgb2gray的整體模塊。

module rgb_gray( CLK, RSTn, Start, Gray, Done ); input CLK; input RSTn; input Start; output [7:0] Gray; output Done; /*****************************************************************************/ wire [17:0] addra ; wire [7:0 ] red ; wire [7:0 ] green ; wire [7:0 ] blue ; imLr imLr_inst( .clka ( CLK ), .addra ( addra ), .douta ( red ) ); imLg imLg_inst( .clka ( CLK ), .addra ( addra ), .douta ( green ) ); imLb imLb_inst( .clka ( CLK ), .addra ( addra ), .douta ( blue ) ); /*****************************************************************************/ wire done_ctrl; wire done_gray; counter_ctrl counter_ctrl_inst( .CLK ( CLK ), .RSTn ( RSTn ), .iCall ( Start ), .iNxt_pix( done_gray ), .oAddr ( addra ), .oDone ( done_ctrl ) ); wire [7:0] gray; rgb2gray rgb2gray_inst( .CLK ( CLK ) , .RSTn ( RSTn ) , .iCall ( done_ctrl ) , .iRed ( red ) , .iGreen ( green ) , .iBlue ( blue ) , .oGray ( gray ) , .oDone ( done_gray ) ); /*****************************************************************************/ assign Gray = gray; assign Done = done_gray; endmodule
頂層模塊需要注意不同模塊之間數據的連接,以數據線的形式相連,故要使用wire類型。
5)testbench模塊:
module rgb_gray_tb; // Inputs reg CLK; reg RSTn; reg Start; reg [17:0] pix_cnt; // Outputs wire [7:0] Gray; wire Done ; integer fout ; // Instantiate the Unit Under Test (UUT) rgb_gray rgb_gray_inst ( .CLK(CLK), .RSTn(RSTn), .Start(Start), .Gray(Gray), .Done(Done) ); initial begin // Initialize Inputs CLK = 0; RSTn = 1; Start = 0; pix_cnt = 0; fout = $fopen( "rgb2gray_re.txt" ); // Wait 100 ns for global reset to finish #100; RSTn = 0; Start = 1; pix_cnt = 0; // Add stimulus here #100; // To start the system // Add stimulus here RSTn = 1; pix_cnt = 1; end always #10 CLK = ~CLK; always @ ( posedge CLK ) if ( Done ) pix_cnt <= pix_cnt + 1'b1; always @ ( posedge CLK ) if ( pix_cnt == 18'd166223 ) begin Start <= 0; $display("Image rgb2gray Completed!\n"); $display("The all time is %d \n",$time); $stop; end always @ ( posedge CLK ) if ( Done ) begin $fwrite(fout, "%d", Gray, "\n"); $display("%d, %d ",pix_cnt, Gray); end endmodule
結果圖片:
前一張圖片,在最后兩個像素的時候代碼出現了警告,不知道是什么原因,不過最后的結果沒有錯誤。
后一張圖片可以看出前幾個像素點得到的算法結果。
3.調試仿真,並與matlab中rgb2gray函數的結果進行比較:
matlab的比較代碼:
% code to create image data from rgb2gray txt file clc; clear all; close all; I_rgb = imread('imL.png'); subplot(2, 2, 1), imshow(I_rgb), title('imL-rgb') I_gray = rgb2gray(I_rgb); [m, n] = size(I_gray); subplot(2, 2, 3), imshow(I_gray), title('imL-rgb2gray-mfunc') rgb2gray_v_load = load('.\rgb2gray_re.txt'); % verilog 產生的rgb2gray數據 rgb2gray_v = reshape(rgb2gray_v_load, n, m); rgb2gray_v = uint8(rgb2gray_v'); diff = I_gray - rgb2gray_v; subplot(2, 2, 4), imshow(rgb2gray_v), title('rgb2gray-verilog');
最后的數據結果顯示,verilog的算法結果與matlab函數的處理結果相比較,差值在1個像素之內。
顯示結果:
可以看出,最后的結果相差無幾,Good!
實驗結論:
因為有中值濾波的基礎,基於此,實現rgb2gray的算法設計還是比較簡單的,不過也還是弄得有點久,最重要的是時序的設計,在調試仿真的過程中讓寶寶比較煩惱的就是時序問題,是在是搞不懂FPGA的時序到底是怎么回事兒,就算明白了時序的問題還會有一些該有的延時或者該考慮的時序沒有考慮到,還有,就算考慮到了,可是編寫代碼還是硬傷。
加油吧,少年!
完