一 概述
圖像處理算法一般是用matla或OpenCV實現的,若是用FPGA實現,設計思路差別極大。matlab和opencv的優勢:這些工具的優勢在於可以方便地載入圖像文件,或輸出數據到圖像文件,同時提供了大量的API函數,便於使用者快速實現想要的功能,同時又能通過查看圖像文件直觀地看到預想結果。將算法直接在FPGA實現是有難度和漫長的,在matlab中,一個直方圖處理和雙邊濾波器,引導圖像濾波,僅僅一行代碼即可,有現成的函數調用,十分簡單。而在FPGA實現則需要考慮幀緩存,算法的設計結構與硬件相符合,時序等問題。很有必要對FPGA實現圖像處理算法的基本思路和方法進行學習!
二 FPGA實現圖像處理算法的基本思路
(一) 需求分析及問題描述
問題描述應該清楚地描述問題而不是解決方法。為使描述更具體,至少需要討論三個方面。第一是系統功能,也就是系統需要做什么。在一個圖像處理應用中,需要詳細說明圖像處理后的預期結果。第二,必須討論系統的性能,即說明系統完成這些功能的指標是什么。對 千實時圖像處理來說,允許的最大延時和每秒需要處理的幀數是兩個很重要的指標。第三個需要考慮的方面是系統將要運行的環境。應用圖像處理不僅僅包含圖像處理算法,它是一個需要對整個系統進行考慮和說明的系統工程問題。其他需要 考慮的重要方面包括照明、光學及所支持的硬件和機械接口。圖像處理系統之間及其與整個工程系統其他部分之間的聯系也需要認真地說明和定義。比如,要做一個分辨率為640*480@60Hz的視頻處理系統,要求提升每一幀圖像的亮度和對比度。這就是明確的需求:即明確輸入圖像或視頻的要求和最終的評價指標。
(二)軟件算法設計及驗證
軟件開發及驗證會適當地在硬件設計之前進行,比如在用matlab或OpenCV等工具驗證算法的視覺效果和客觀指標,這主要是由千在FPGA上調試算法周期過長,即使僅僅做仿真工作所消耗的時間也遠遠比軟件多。如果在硬件上進行映射,其綜合、編譯和布局布線的時間花費更是無法令人接受。大部分情況下,FPGA更多的是僅僅作為一個映射工具。一般實現某個算法如紅外圖像細節增強,都是先花兩周看經典的SCI論文,選兩三篇經典的(一是權威的作者:如知名企業三星,學術機構IEEE的院士,知名大學中科院教授;二是看被引量,如60次以上,這是很多相關算法的基礎,很完善成熟。三是發表時間五年之前的比較靠譜的,新的論文很少有人驗證。四是看論文的實驗仿真,設計是否嚴謹和多種指標衡量。),在對這兩三篇經典的論文算法理解透了,用matlab復現。通過自己matlab仿真不同算法,從實驗結果選擇一種最佳的作為硬件實現。算法的精度,涉及到浮點轉換為定點運算,FPGA不支持小數運算。
(三) 硬件平台設計
硬件平台的設計往往會和軟件開發同時進行。通常 個算法的測試及改進是 個周期很長的任務。硬件平台的設計在算法開 發基本功能驗證之后就可以對其進行整體評估。
- 軟件與硬件的划分
硬件平台設計的第 步是合理地划分硬件和軟件。 這里的硬件是指算法由 FPGA邏輯實現,軟件是指算法由 DSP、ARM 或MCU軟件編程實現。規則的底層圖像處理操作(如形態學濾波、 Sobel 算子、均值濾波等)具有計算數據量大、 結構規則並行等特點,非常適合千用 FPGA 硬件實現。 不規則的底層圖像處理操作(如具有動態可變長度循環的算法) 和串行頂層圖像處理操作(如彈道計算、任務判決融合等)用 FPGA實現會非常繁瑣且效率較低, 此類操作用軟件實現效率較高, 開發難度較低。無論怎么划分層級,清楚地定義軟件與硬件之間的接口與通信機制是基本的要求。尤其有必要設計同步和數據交換機制來促進數據流的平滑。如常見的LCD屏顯示,通常是FPGA處理像素數據后通過SPI總線發送給MCU驅動顯示。
2. 資源評估與FPGA選型
在硬件方案確定之后, 在確定具體的FPGA型號之前, 對整個系統所消耗的資源 進行預估是十分必要的。對千圖像處理系統來講, 比較敏感的資源是存儲器資源。此外,FPGA 所擁有的些高速接口資源也是重要的考慮因素, 這主要考慮到視頻處理的高帶寬特點。如系統需求為2560*1600,幀率為90Hz的RGB888視頻流輸入,則幀緩存的帶寬為2560*1600*90*24/8=1.105GB/s,而一般DDR3的帶寬為800M,需要兩片DDR3才行。
(四) FPGA映射
FPGA映射是將軟件算法轉換為FPGA設計的過程。這個在書籍<<基於FPGA的數字圖像處理原理及應用>>的第四章,有講解到映射技術,如下圖所示,需要時可以看書籍進一步了解
(五) 仿真驗證
在FPGA映射之后,接下來的重點工作是對設計的系統進行仿真和驗證。在FPGA代碼撰寫完畢時對其進行功能測試是十分有必要的。一般是搭建視頻的modelsim仿真平台,即編寫一個Verilog文件模擬符合標准視頻時序的視頻輸入源,提供給我們的設計模塊,仿真觀察波形,將算法處理的結果用txt文本存儲,再用matlab觀察對比效果。本文的后面會介紹一個vga的模擬輸入。在硬件中的在線調試也是十分必要的。最簡單的方法是將主信號布線到不用的IO,口上, 使得它們從FPGA外部是可觀測的, 在外部使用 個示波器或是邏輯分析儀來監控信號。 此外, Xlinx和Altera廠商提供的IDE中也提供了虛擬的邏輯分析儀來輔助調試。 不過, 輔助調試手段需要占用片內的存儲器資源。
三 VGA
上面介紹圖像處理算法的fpga實現的基本思路,現在通過搭建一個vga時序來實現搭建圖像仿真平台的第一步。vga時序模擬圖像算法的視頻輸入,第二步就是設計圖像處理算法,如直方圖統計。第三步把算法對圖像數據的處理結果用matlab直觀顯示,也可以和matlab實現算法的處理結果對比。通過這三步就很直觀顯示圖像輸入輸出的效果,對於驗證圖像算法的有效性很方便。
(一) 外部接口
1.VGA原理圖及端口
上面是vga接口的電路圖,可以看出總共五個信號,分成兩類,一是控制VGA驅動的行同步信號VGA_HS(HSync) 和場同步信號(VSync);二是控制像素數據輸出的RGB信號,根據vga接口和RGB的組合,常見RGB格式有RGB888和RGB565.
2. VGA掃描方式
顯示是用逐行掃描的方式解決,陰極射線槍發出電子束打在塗有熒光粉的熒光屏上,產生RGB 三基色,合成一個彩色像素。掃描從屏幕的左上方開始,從左到右,從上到下,進行掃描,每掃完一行,電子束回到屏幕的左邊下一行的起始位置,在這其間CRT 對電子束進行消隱。每行結束時,用行同步信號進行同步;掃描完所有行,用場同步信號進行同步,並使掃描回到屏幕左上方,同時進行場消隱,預備下一場的掃描。
(二) 640*480@60Hz的VGA實現
1.VESA顯示標准
下面我們以實現640*480@60Hz的vga驅動來掌握VGA的實現參數和驅動。對於vga的驅動,首先有一個官方的標准,VESA視頻標准顯示手冊,這是做視頻顯示的權威指南,下面我們給出這個分辨率的vesa標准:
首先是紅色方框的圖像有效顯示的分辨率和幀數(一秒內顯示的圖像數),即640*480為有效區域和屏幕的總分辨率800*525區別,大致理解有效區域為總屏幕的一部分,這是理解重點,牽涉到后面的像素有效信號test_dvalid(de)的理解。像素的時鍾由分辨率和幀率決定,pixel clock = (1/幀數)/屏幕分辨率(800*525)=25M左右
2. VGA屏幕一幀的時序參數圖
其他的時序參數如行場同步,場前肩,場后肩,行左邊界,行右邊界。要結合下面的兩張圖才能深刻理解,光看上面的vesa標准還是很困惑這些時序參數的含義。
上面的第二張為從<VESA Display Monitor Timing Standard>手冊中截取的屏幕一幀的時序參數圖,從圖中可知紅框表示圖像的有效區域640*480對應着它上面圖片的圖像顯示區。綠框為整個顯示屏幕,分辨率大小800*525為標准手冊的Hor Total Time=800 pixels和Ver Total Time = 525 lines,分別表示總行時間,總列時間。分別以像素時鍾的周期和行的時間為單位。這個分辨率對應着它上面圖2的整個屏幕的大小800*525.故后面代碼中的像素有效標志信號test_dvalid為圖像像素在紅框的有效區域時,行同步信號有效的值,表示輸出的像素有效。
時序參數可以參考上面的解釋。
3. VGA 顯示的時序圖
(三) VGA時序模擬的設計思路及代碼
1.設計思路
上面是vga行場掃描的時序圖,通過這個時序圖結合我們上面的分析,就能編寫Verilog代碼驅動vga顯示。行同步信號(Hsync)與場同步信號都可以每行/幀一開始拉高/低,場同步信號為每一幀的起始點。行同步信號則為每一行像素的起始點。設計思路,結合手冊,先編寫行計數器cnt_h和垂直計數器cnt_v,分別是計數到800-1,525-1,垂直計數器cnt_v是以每行計數完加1的。接着是像素有效信號test_dvalid,它是在有效區域640*480內,在像素時鍾的驅動下拉高。場同步信號test_vsync_temp根據時序參數知為2lines的高電平,故在垂直計數器cnt_v在0-2范圍則拉高,其余時間為零。故場同步信號表示每一幀的開始,即新幀的到來,它滿足每一秒60幀,即場同步信號的周期為1/60秒,約為16.6毫秒,故可判讀時序是否正確。
2. 代碼
1 `timescale 1 ns/1 ns 2 3 `define SEEK_SET 0 4 `define SEEK_CUR 1 5 `define SEEK_END 2 6 //the simulation of image conform to the VESA stand 7 //so modified the parameters 8 //Timing Name = 640 x 480 @ 60Hz; 9 //acquired from vesa 10 module ima_src( 11 reset_l, 12 clk, 13 src_sel, 14 test_vsync, 15 test_dvalid,//pixel valid 16 test_data, 17 clk_out 18 ); 19 20 parameter iw = 640;//640*2 2 bytes per pixels 21 parameter ih = 480;//512 plus one command line 22 parameter dw = 8; 23 24 parameter h_total = 800;//Hor Total Time = 800 pixel 25 parameter v_total = 525 ;//Ver Total Time = 16.683; // (msec) = 525 lines 26 27 parameter sync_b = 2;//V Front Porch/ Ver Sync Time=2 lines 28 parameter sync_e = 2;//Ver Sync 29 parameter vld_b = (2+25+8);//V Back Porch=35 30 //Ver Addr Time =480 lines 31 parameter h_b = (96+40+8);//Hor Sync Time(96)+H Back Porch(40)+H Left Border(8) =144 32 input reset_l,clk;//clock,reset 33 input [3:0]src_sel;//to select the input file 34 output test_vsync,test_dvalid,clk_out; 35 output [dw-1:0]test_data; 36 37 reg [dw-1:0]test_data_reg; 38 reg test_vsync_temp; 39 reg test_dvalid_tmp; 40 reg [1:0]test_dvalid_r; 41 42 reg [10:0] h_cnt; 43 reg [10:0] v_cnt; 44 45 integer fp_r; 46 integer cnt=0; 47 48 assign clk_out = clk;//output the dv clk 49 50 assign test_data = test_data_reg;//test data output 51 52 //read data from file 53 54 always @(posedge clk or posedge test_vsync_temp ) 55 56 if ((((~test_vsync_temp))) == 1'b0) 57 cnt<=0;//clear file pointer when a new frame comes 58 else 59 begin 60 if (test_dvalid_tmp == 1'b1) 61 begin 62 case (src_sel) 63 4'b0000 :fp_r = $fopen("../poc/ln.txt","r"); 64 4'b0001 :fp_r = $fopen("../poc/ln.txt","r");//very error 65 4'b0010 :fp_r = $fopen("../poc/recovery/e_640x480_hex.txt","r"); 66 4'b0011 :fp_r = $fopen("txt_source/test_src3.txt", "r"); 67 4'b0100 :fp_r = $fopen("txt_source/test_src4.txt", "r"); 68 4'b0101 :fp_r = $fopen("txt_source/test_src5.txt", "r"); 69 4'b0110 :fp_r = $fopen("txt_source/test_src6.txt", "r"); 70 4'b0111 :fp_r = $fopen("txt_source/test_src7.txt", "r"); 71 4'b1000 :fp_r = $fopen("txt_source/test_src8.txt", "r"); 72 4'b1001 :fp_r = $fopen("txt_source/test_src9.txt", "r"); 73 4'b1010 :fp_r = $fopen("txt_source/test_src10.txt", "r"); 74 4'b1011 :fp_r = $fopen("txt_source/test_src11.txt", "r"); 75 4'b1100 :fp_r = $fopen("txt_source/test_src12.txt", "r"); 76 4'b1101 :fp_r = $fopen("txt_source/test_src13.txt", "r"); 77 4'b1110 :fp_r = $fopen("txt_source/test_src14.txt", "r"); 78 4'b1111 :fp_r = $fopen("txt_source/test_src15.txt", "r"); 79 default :fp_r = $fopen("../poc/ln.txt","r"); 80 endcase 81 82 $fseek(fp_r,cnt,0); 83 $fscanf(fp_r, "%02x\n", test_data_reg); 84 cnt <= cnt + 4 ; 85 $fclose(fp_r); 86 //$display("%02x",test_data_reg); //for debug use 87 end 88 end 89 90 //horizon counter 91 always @(posedge clk or posedge reset_l) 92 if (((~(reset_l))) == 1'b1) 93 h_cnt <= #1 {11{1'b0}}; 94 else 95 begin 96 if (h_cnt == ((h_total - 1))) 97 h_cnt <= #1 {11{1'b0}}; 98 else 99 h_cnt <= #1 h_cnt + 11'b00000000001; 100 end 101 102 //vertical counter 103 always @(posedge clk or posedge reset_l) 104 if (((~(reset_l))) == 1'b1) 105 v_cnt <= #1 {11{1'b0}}; 106 else 107 begin 108 if (h_cnt == ((h_total - 1))) 109 begin 110 if (v_cnt == ((v_total - 1))) 111 v_cnt <= #1 {11{1'b0}}; 112 else 113 v_cnt <= #1 v_cnt + 11'b00000000001; 114 end 115 end 116 117 //field sync 118 always @(posedge clk or posedge reset_l) 119 if (((~(reset_l))) == 1'b1) 120 test_vsync_temp <= #1 1'b1; 121 else 122 begin 123 if (v_cnt >= 0 & v_cnt < (sync_b )) 124 test_vsync_temp <= #1 1'b1; 125 else 126 test_vsync_temp <= #1 1'b0; 127 end 128 129 assign test_vsync = (test_vsync_temp); 130 131 //horizon sync 132 always @(posedge clk or posedge reset_l) 133 if (((~(reset_l))) == 1'b1) 134 test_dvalid_tmp <= #1 1'b0; 135 else 136 begin 137 if (v_cnt >= vld_b & v_cnt < ((vld_b + ih)-1)) 138 begin 139 if (h_cnt >= h_b & h_cnt < ((h_b + iw)-1)) 140 test_dvalid_tmp <= #1 1'b1; 141 else 142 test_dvalid_tmp <= #1 1'b0; 143 end 144 end 145 assign test_dvalid = test_dvalid_tmp; 146 147 always @(posedge clk or posedge reset_l) 148 if (((~(reset_l))) == 1'b1) 149 test_dvalid_r <= #1 2'b00; 150 else 151 test_dvalid_r <= #1 ({test_dvalid_r[0], test_dvalid_tmp}); 152 153 endmodule
明顯的差別是數據的輸出,這次的設計,通過文件操作來獲得圖像的數據,為了和matlab聯合仿真。在硬件描述語言仿真平台中簡單地載入圖像文件和輸出圖像文件,那么對圖像類處理的仿真將會帶來極大的方便。方式通過用matlab把圖像轉換為txt文本,用Verilog的$fopen,$fclose,$fscanf,$fread,$fwrite等進行文件操作,注意點是只能用來仿真,不可綜合。后面Verilog算法處理后存儲為txt文本還能用來作為matlab的圖像輸入進行顯示。這樣很方便能驗證算法的處理效果,不要很麻煩到硬件平台去觀察。
3.MATLAB代碼
通過matlab代碼將圖像轉換為8比特的16進制形式作為設計的輸入數據。MATLAB處理結果如下:
matlab功能:把640*480的lean原圖轉換640*480=307200個8比特的像素數據,存儲為txt文本。后面modelsim仿真,打開這個文件作為vga模擬視頻源的輸入數據。
clc; clear; %% 數據獲取 RGB = imread('lean640_480.bmp'); %rgb原始圖像 GRAY = rgb2gray(RGB); %Matlab變換灰度圖像 fid = fopen('./lean640_480_hex.txt','wt'); for i = 1:size(RGB,1) for j = 1:size(RGB,2) fprintf(fid,'%2x\n',RGB(i,j));%每個數據之間用空格分開 end end %% 畫圖顯示 figure(1); subplot(1,3,1); imshow(RGB); title('lena原始圖像'); subplot(1,3,2); imshow(GRAY); title('Matlab變換灰度圖像');
再附上lean原圖,可以調整為640*480,供圖像處理算法使用,這是圖像領域的經典,好好收藏!
(四) 仿真
1.testbench代碼設計

1 `timescale 1ns/1ps 2 3 module tb_top; 4 5 //======================================================== 6 //parameters 7 parameter CLK_FREQ = 25.200;//ddr reference clock frequency, unit: MHz 8 parameter CLK_PERIOD = 1000.0/CLK_FREQ; //unit: ns 9 //pixel_total=h_total*v_total*60=25_200_000 10 // parameter FREQ = 100_000_000 ; 11 parameter sim_num = 5_000_000 ; 12 // parameter BAUDRATE = 115200 ; 13 //ima parameter 14 parameter iw = 640;//640*2 2 bytes per pixels 15 parameter ih = 480;//512 plus one command line 16 parameter dw = 8; 17 18 parameter h_total = 800;//Hor Total Time = 800 pixel 19 parameter v_total = 525 ;//Ver Total Time = 16.683; // (msec) = 525 lines 20 21 parameter sync_b = 2;//V Front Porch 22 parameter sync_e = 2;//Ver Sync 23 parameter vld_b = 25+8;//V Back Porch+V top Borch 24 25 //======================================================= 26 reg clk; // 50M 27 reg rst_n ; 28 reg [3:0] de ; 29 30 31 wire test_vsync; 32 wire test_dvalid; 33 wire[7:0] test_data; 34 wire clk_out; 35 36 37 //======================================================== 38 GSR GSR(.GSRI(1'b1)); 39 40 41 initial 42 begin 43 rst_n = 1'b0; 44 de =4'd0; 45 #200; 46 de = 4'd1; 47 rst_n = 1'b1; 48 49 50 51 end 52 53 //---------------------------------------------------- 54 //ref clk 55 initial 56 begin 57 clk = 1'b0; 58 end 59 60 always #(CLK_PERIOD/2.0) clk = ~clk; 61 62 //================================================== 63 //ima_src 64 //parameter must be connected to constant 65 ima_src inst_ima_src ( 66 .reset_l (rst_n), 67 .clk (clk), 68 .src_sel (de), 69 .test_vsync (test_vsync), 70 .test_dvalid (test_dvalid), 71 .test_data (test_data), 72 .clk_out (clk_out) 73 ); 74 75 endmodule
2.波形
從仿真波形看出一幀的時間為16.6ms左右,即場同步信號的周期, 符合每秒60幀的時序。並且像素數據在像素有效信號拉高是輸出。
四 總結
一是至少看三種以上的參考資料。如本次學習搭建圖像仿真平台,就是<<基於FPGA的數字圖像處理原理及應用>>,然后是網上博客如咸魚FPGA;V3學院就業辦的基礎課程第17講VGA,OpensLee, 開源騷客,vesa標准手冊等。
二是,循序漸進搭仿真。如剛開始是直接給時鍾復位,讓仿真跑起來。其次是理解別人的設計邏輯。這時候通過上面的各種資料,這一步可能花兩三天,最后就是修改邏輯和不斷仿真調試,直到符合vesa時序圖。
三是,在實踐過程中記錄。遇到問題及解決過程。
參考資料:
1.<<基於FPGA的數字圖像處理原理及應用>>
2.V3學院--第十七講、VGA 接口驅動
3. vesa標准手冊
4.https://www.cnblogs.com/huangwei0521/