一 概述
影像處理演算法一般是用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/