大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。
今天給大俠簡單帶來FPGA verilog HDL實現(xiàn)中值濾波,話不多說,上貨。
一、實現(xiàn)步驟:
1、查看了中值濾波實現(xiàn)相關(guān)的網(wǎng)站和paper;
2、按照某篇paper的設(shè)計思想進行編程實現(xiàn);
3、對各個模塊進行語法檢查、波形仿真、時序設(shè)計、調(diào)試驗證;
4、與matlab的中值濾波結(jié)果進行比較。
二、實現(xiàn)過程:
1、查看了中值濾波實現(xiàn)相關(guān)的網(wǎng)站和paper;
在網(wǎng)上看了很多中值濾波的設(shè)計,也有一些代碼可以下載,也有一片講解的,只是感覺講解的比較模糊而且不完整,最后看了幾篇碩士論文,論文竟然主要做了中值濾波的工作,發(fā)現(xiàn)了一些設(shè)計思路,然后就按照自己的想法進行設(shè)計。
2、按照某篇paper的設(shè)計思想進行編程實現(xiàn);
整個中值濾波模塊分為幾個小的模塊:3*3窗口生成模塊、計數(shù)器控制模塊、3*3中值濾波模塊、頂層模塊以及最后的測試模塊testbench的編寫。整個框架的設(shè)計如下圖所示(使用visio畫的框架圖):
各個模塊的設(shè)計:1)ROM IP核的生成,用于存儲原始灰度圖像的數(shù)據(jù)。
使用matlab生成.coe圖像數(shù)據(jù)文件,然后使用Xilinx ISE工具將.coe文件添加到ROM核進行數(shù)據(jù)初始化,按步驟得到ROM模塊,參考生成的.v文件在頂層模塊直接調(diào)用即可。
rom_512by512 rom_512by512_inst
(
.clka(CLK), //input clka;
.addra(rom_addr), //input-from
.douta(rom_data) //output-to
);
注意ROM的存儲空間的大??;
2)3*3窗口生成模塊,用于生成濾波的滑動窗口,得到窗口內(nèi)的所有元素數(shù)據(jù)。
功能:
(1)根據(jù)中心像素點得到所在其所在的行、列位置;
(2)根據(jù)該模塊的開始信號設(shè)計得到獲取數(shù)據(jù)的有效時間序列;
(3)在讀取數(shù)據(jù)的有效時序內(nèi),得到窗口內(nèi)的所有元素數(shù)據(jù);
(4)窗口數(shù)據(jù)的獲取按照一定的時序順序來獲得,類似于黑金推薦的“仿順序操作”,這個比較適合my style;不過后來發(fā)現(xiàn)調(diào)試的過程中被項目組的硬件人員改動了一些,甚至說不好,感覺可能是本人還沒有理解掌握吃透“仿順序操作”的精髓吧。
(5)根據(jù)中心像素點的行、列位置信息得到每個窗口元素的ROM地址,根據(jù)某一時刻ROM地址,下一時刻調(diào)用ROM模塊得到對應(yīng)的元素數(shù)據(jù),下一時刻將數(shù)據(jù)鎖存,然后再讀取該地址的數(shù)據(jù);所以要注意地址和數(shù)據(jù)的獲取不是在同一時刻,而是需要延遲兩個時刻;
(6)還需要注意的是圖像的邊界問題的特殊化處理;一般圖像處理都會遇到邊界問題,這個需要謹慎;
(7)對matlab的中值濾波函數(shù)medfilt2原理的深入掌握對我們編寫這一模塊非常重要。matlab并沒有主要過程的代碼,看注釋默認情況下邊界元素設(shè)置為0,這也可以通過結(jié)果反推回去發(fā)現(xiàn)的。
3)計數(shù)器控制模塊,主要用于獲得中心像素點的地址信息。
(1)系統(tǒng)模塊開始信號之后開始獲取第一個中心像素點,注意初始化信號值和系統(tǒng)開始的信號值的區(qū)別;
(2)該時刻得到的的數(shù)據(jù)將在下一個時刻產(chǎn)生結(jié)果,該時刻的數(shù)據(jù)并沒有改變;
(3)注意中心像素點的行、列位置信息的計算;
4) 3*3中值濾波模塊
功能:得到某一中心像素點的3*3滑窗區(qū)域的灰度值的中值,作為中心像素點的值;
中值濾波原理,網(wǎng)上有很多,大家可以查看一下。
本項目采用的是快速中值濾波的方法。
(1)若是3*3窗口生成模塊完成之后就計算下一個中心像素點,需要將該中心像素點的窗口元素鎖存起來,以防計算過程中將這些元素掩蓋,不能正確進行中值濾波的計算;
(2)需要在時序的有效區(qū)域內(nèi)進行計算,怎么設(shè)計信號的有效性;
(3)仿順序操作可以分開進行;每一個時刻只進行一個操作,這樣可能更明了(代碼中沒有這樣做);
(4)verilog編程調(diào)用函數(shù)的方法,指出輸入信號,函數(shù)內(nèi)可以使用其他定義聲明的信號,最后的輸出信號作為調(diào)用函數(shù)的結(jié)果(突然想起來,如果輸出信號有多個元素呢,又該怎么辦呢?大家可以想想);
該模塊的代碼:
5)頂層模塊
用于將低層的各個功能/控制模塊銜接起來,得到結(jié)果;注意輸入輸出信號,以及不同模塊之間是如何進行連線的。信號的名稱盡量有其特別的意義,不要重復(fù)使用同一個信號名稱,容易造成混亂;
區(qū)別wire和reg類型數(shù)據(jù)的使用情況;
6)測試模塊
如何將數(shù)據(jù)寫入文件,需要定義文件的名稱和類型;
integer fouti;
需要在初始化部分打開文件:
fouti = $fopen("medfilter2_re.txt");
代碼如下:
整體的代碼就是這樣的。
3、對各個模塊進行語法檢查、波形仿真、時序設(shè)計、調(diào)試驗證;
本人覺得原理清楚之后按部就班的編寫代碼還好,只是剛接觸波形仿真和調(diào)試的時候是真心不順心,還好有其他人幫忙調(diào)試;在調(diào)試的過程中其實會學(xué)習(xí)到很多東西,很多經(jīng)驗,以及很簡單的但你之前就是不知道的知識,這就是一個實踐的過程,有時候你根本不知道錯誤在哪里,這怎么會是錯誤的呢,為什么不可以這樣寫,我覺得這樣寫才是正確的,這些就是在調(diào)試過程中本人的真實心情寫照呀。可是,沒有那么多為什么,verilog就是這樣編程的,只是你不知道而已!這才是最傷人的,因為你不知道!
仿真調(diào)試的過程中遇到的問題以及解決方法有空專門寫一篇,調(diào)試的過程中最好是一個一個模塊的測試,特別是關(guān)鍵信號的數(shù)值,最好搞懂整體模塊和各個模塊的時序設(shè)計過程,推薦使用TimeDesigner進行波形的設(shè)計;另外還需要有關(guān)聯(lián)的兩個甚至多個不同模塊信號的交叉仿真驗證。
4、與matlab的中值濾波結(jié)果進行比較
使用matlab編程基于自帶的中值濾波函數(shù)得到處理之后的圖像與數(shù)據(jù),并將verilog得到的濾波數(shù)據(jù)轉(zhuǎn)換為圖像,將二者進行比較
使用matlab自帶的中值濾波函數(shù)medfilt2生成原圖像的灰度圖像的濾波數(shù)據(jù);
% mcode to median filter for one jpg image, and create a image data file
src = imread('lena.jpg');
gray = rgb2gray(src);
medfilt2im = medfilt2( gray );
[m, n] = size( medfilt2im ); % m行 n列
N = m*n; %%數(shù)據(jù)的長度,即存儲器深度。
word_len = 8; %%每個單元的占據(jù)的位數(shù),需自己設(shè)定
lena_gray = reshape(gray', 1, N);% 1行N列
lena_medfilt = reshape(medfilt2im', 1, N);% 1行N列
fid_gray=fopen('lena_gray.txt', 'wt'); %打開文件
fid_medfilt=fopen('lena_medfilt.txt', 'wt'); %打開文件
% fprintf(fid, 'MEMORY_INITIALIZATION_RADIX=16;n');
% fprintf(fid, 'MEMORY_INITIALIZATION_VECTOR=n');
for i = 1 : N-1
fprintf(fid_gray, '%d,n', lena_gray(i));%使用%x表示十六進制數(shù)
end
fprintf(fid_gray, '%d;n', data(N)); %%輸出結(jié)尾,每個數(shù)據(jù)后面用逗號或者空格或者換行符隔開,最后一個數(shù)據(jù)后面加分號
fclose(fid_gray); %%關(guān)閉文件
for i = 1 : N-1
fprintf(fid_medfilt, '%d,n', lena_medfilt(i));%使用%x表示十六進制數(shù)
end
fprintf(fid_medfilt, '%d;n', lena_medfilt(N)); %%輸出結(jié)尾,每個數(shù)據(jù)后面用逗號或者空格或者換行符隔開,最后一個數(shù)據(jù)后面加分號
fclose(fid_medfilt); %%關(guān)閉文件
將medfilt2函數(shù)和verilog產(chǎn)生的濾波數(shù)據(jù)轉(zhuǎn)換為圖像,并與matlab直接產(chǎn)生的濾波圖像進行對比,代碼如下:
% code to create image data from txt file
clc;
clear all;
close all;
I_rgb = imread('lena.jpg');
subplot(2, 3, 1), imshow(I_rgb), title('lena-rgb')
I_gray = rgb2gray(I_rgb);
subplot(2, 3, 2), imshow(I_gray), title('lena-gray')
medfilt_m_load = load('.lena_medfilt.txt');
%medfilt_m_load = load('.lena.coe');
medfilt_v_load = load('.medfilter2_reV1.txt'); % verilog 產(chǎn)生的中值濾波之后數(shù)據(jù)
medfilt2im = medfilt2( I_gray );
subplot(2, 3, 3), imshow(medfilt2im), title('lena-medfilt2')
m = 512;
n = 512;
medfilt_m = reshape(medfilt_m_load, m, n);
medfilt_v = reshape(medfilt_v_load, m, n);
medfilt_m = uint8(medfilt_m');
medfilt_v = uint8(medfilt_v');
aa = medfilt2im - medfilt_m;
bb = medfilt2im - medfilt_v;
cc = medfilt_m - medfilt_v;
subplot(2, 3, 5), imshow(medfilt_m), title('medfilt-matlab');
subplot(2, 3, 6), imshow(medfilt_v), title('medfilt-verilog');
顯示的結(jié)果如下圖所示:
結(jié)果:兩種濾波產(chǎn)生的圖像數(shù)據(jù)完全一致,不過感覺函數(shù)直接產(chǎn)生的圖像顏色更深一些,不知道為什么。
這里需要了解一下medfilt2這個函數(shù)的原理。結(jié)果數(shù)據(jù)表明,默認情況下該函數(shù)對圖像邊界采用的是補0的方法進行處理的。
結(jié)論
中值濾波終于告一段落了!簡單的問題還是需要深入進去研究的,實踐的過程中你才會發(fā)現(xiàn)自己之前了解的東西是多么的淺薄,對已知的知識掌握的是多么的流于表面!最后結(jié)果的數(shù)據(jù)還是很讓人開心的!