大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。“煮酒言歡”進(jìn)入IC技術(shù)圈,這里有近100個IC技術(shù)公眾號。
今天給大俠帶來基于 FPGA 的圖像邊緣檢測設(shè)計,話不多說,上貨。
設(shè)計流程如下:mif文件的制作→?調(diào)用 ip 核生成rom以及仿真注意問題→?灰度處理→?均值濾波:重點是3*3 像素陣列的生成→?sobel邊緣檢測→?圖片的顯示→?結(jié)果展示?。
一、mif文件的制作
受資源限制,將圖片像素定為 160 * 120,將圖片數(shù)據(jù)制成 mif 文件,對 rom ip 核進(jìn)行初始化。mif文件的制作方法網(wǎng)上有好多辦法,因此就不再敘述了,重點說mif文件的格式。?mif文件的格式為:
WIDTH=16 ; //數(shù)據(jù)位寬
DEPTH=19200 ; // rom 深度即圖片像素點的個數(shù)
ADDRESS_RADIX=UNS?;???//地址數(shù)據(jù)格式
DATA_RADIX=BIN ; //數(shù)據(jù)格式
CONTENT
BEGIN
0:1010110011010000?;?????//?地址?:數(shù)據(jù)?;注意格式要和上面定義的保持統(tǒng)一
1:1010110011010000?;
2:1010010010110000?;
......
19198:1110011011111001?;
19199:1110011011011000?;
END;
二、ip 核生成 rom 及仿真時需要注意的問題
ip?核生成 rom
1、Tools -> MegaWizard Plug-In Manager
2、Create a new custom megafuction variation
3、Memory Compier -> ROM -> Verilog HDL -> 自定義名稱
???
仿真注意問題:
1、仿真時要注意是否有 altera_mf 庫文件,否則會報錯。Module 'altsyncram' is not define解決方案:
(1).下載 altera_mf 庫文件;
(2).?仿真時將 altera_mf.v 與其他文件一起加入到 project 中。
2、要將 .mif 文件放在仿真工程目錄下,即與 .mpf 文件在一起,否則將不會有數(shù)據(jù)輸出。
三、灰度處理
任何顏色都由紅、綠、藍(lán)三原色組成,假如原來某點的顏色為( R,G,B )那么,我們可以通過下面幾種方法,將其轉(zhuǎn)換為灰度:
浮點算法:Gray=0.299R+0.587G+0.114B
平均值法:Gray=(R+G+B)/3;
僅取單色(如綠色):Gray=G;
將計算出來的Gray值同時賦值給 RGB 三個通道即RGB為(Gray,Gray,Gray),此時顯示的就是灰度圖。通過觀察調(diào)色板就能看明了。?通過觀察可知,當(dāng)RGB三個通道的值相同時即為灰色,Gray的值越大,顏色越接近白色,反之越接近黑色(這是我自己的理解,不嚴(yán)謹(jǐn)錯誤之處請大神指正)。這是在線調(diào)色板網(wǎng)址,可以進(jìn)去自己研究一下。
站長工具顏色代碼查詢、RGB顏色值:
http://tool.chinaz.com/tools/selectcolor.aspx
此次采用是浮點算法來實現(xiàn)灰度圖的,我的圖片數(shù)據(jù)是RGB565 格式 ,難點: 如何進(jìn)行浮點運算。思路:先將數(shù)據(jù)放大,然后再縮小。例如:
Gray=0.299R+0.587G+0.114B轉(zhuǎn)化為 Gray=(77R+150G+29B)>>8 即可,這里有一個技巧,若 a 為 16 位即 a [15:0],那么 a>>8 與 a [15:8]是一樣的。核心代碼如下:
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
red_r1 <= 0 ;
green_r1 <= 0 ;
blue_r1 <= 0 ;
end
else begin
red_r1 <= red * 77 ; //放大后的值
green_r1 <= green * 150;
blue_r1 <= blue * 29 ;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
Gray <= 0; // 三個數(shù)之和
end
else begin
Gray <= red_r1 + green_r1 + blue_r1;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
post_data_in <= 0; //輸出的灰度數(shù)據(jù)
end
else begin
post_data_in <= { Gray[13:9], Gray[13:8], Gray[13:9] };//將Gray值賦值給RGB三個通道
end
end
四、均值濾波
均值濾波的原理做圖像處理,?“把每個像素都用周圍的8個像素來做均值操作 ”, 比如:
圖通常是最能說明問題的東西, 非常明顯的, 這個3*3區(qū)域像素的顏色值分別是5,3,6,2,1,9,8,4,7那么中間的1這個像素的過濾后的值就是這些值的平均值, 也就是前面的計算方法:(5+3+6+2+1+9+8+4+7)/9=5一目了然。那么這個均值濾波有什么用處呢?主要還是平滑圖像的用處, 有的圖像的銳度很高,用這樣的均值算法,可以把銳度降低。使得圖像看上去更加自然,下面就有幾幅圖我們可以看出一些端倪:原圖:
平滑處理后:
這里還是可以明顯的感覺到不同的, 沒有好壞之分,就是第二幅圖片看上去更為平滑。繼續(xù)我們的問題, 那這里均值平滑是否具有去除噪聲的功能呢?我們搞來了椒鹽噪聲(就是隨機(jī)的白點,黑點)來試試手:
噪聲圖(5%):? ? ? ? ? ? ? ? ? ??
平滑處理之后:
首先這里的噪聲還是比較小的, 只有5%,從均值的效果來看的話, 我可以說幾乎沒有用,其實直觀的想也可以判斷, 因為這里的處理并沒有剔除這些噪聲點, 而只是微弱地降低了噪聲,所以效果可以想見的。?最后的時候還是貼上一段處理的代碼:
void?meanFilter?(unsigned?char*?corrupted,?unsigned?char*?smooth,?int?width,?int?height)
{
memcpy ( smooth, corrupted, width*height*sizeof(unsigned char) );
for (int j=1;j<height-1;j++)
{
for (int i=1;i<width-1;i++)
{
smooth [ j*width+i ] = ( corrupted [ (j-1)*width+(i-1) ] + corrupted [ (j-1)*width+i] + corrupted [ (j-1)*width+(i+1) ] +
corrupted [ j*width+(i-1) ] + corrupted [ j*width+i] + corrupted [ j*width+(i+1) ] +
corrupted [ (j+1)*width+(i-1) ] + corrupted [ (j+1)*width+i] + corrupted [ (j+1)*width+(i+1) ] ) / 9;
}
}
}
簡單的從1...width-1來處理, 所以第一個和最后一個像素就簡單的拋掉了, 如果只是簡單的看看效果還是沒有問題的。
如何生成 3*3 的像素陣列。可以利用 ip 核生成移位寄存器 ,方法與 ip 核生成 rom 一樣,詳情見?ip 核 生成 rom?操作?。
仿真波形如下 row_1 , row_2 , row_3 是指圖像的第一、二、三行的數(shù)據(jù),Per_href 是行有效信號(受VGA時序的啟發(fā),從 rom 中讀取數(shù)據(jù)時設(shè)計了行有效和場有效的控制信號,事半功倍,有了利于仿真查錯和數(shù)據(jù)的控制)。從 3 開始就出現(xiàn)了3*3 的像素陣列,這時候就可以求取周圍 8 個像素點的平均值,進(jìn)行均值濾波。
下面這個圖表示的是FPGA 如何將矩陣數(shù)據(jù)處理成并行的像素點,可以結(jié)合下面的代碼好好理解,這也是精華所在。正方形紅框框起來的是第一個完整的 3*3 矩陣,長方形紅框框起來的是并行的像素點,在此基礎(chǔ)上就可以求得平均值,進(jìn)行均值濾波。從下圖也能看到 3*3 矩陣從左往右滑動。第一個3*3 陣列。0 ?1 ?2 ? -- > ?p11 p12 p133 ?4 ?5 ? -- > ?p21 p22 p236 ?7 ?8 ? -- > ?p31 p32 p33
核心代碼如下:
reg [5:0]p_11,p_12,p_13; // 3 * 3 卷積核中的像素點
reg [5:0]p_21,p_22,p_23;
reg [5:0]p_31,p_32,p_33;
reg [8:0]mean_value_add1,mean_value_add2,mean_value_add3;//每一行之和
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
{p_11,p_12,p_13} <= {5'b0,5'b0,5'b0} ;
{p_21,p_22,p_23} <= {15'b0,15'b0,15'b0};
{p_31,p_32,p_33} <= {15'b0,15'b0,15'b0};
end
else begin
if(per_href_ff0==1&&flag_do==1)begin
{p_11,p_12,p_13}<={p_12,p_13,row_1};
{p_21,p_22,p_23}<={p_22,p_23,row_2};
{p_31,p_32,p_33}<={p_32,p_33,row_3};
end
else begin
{p_11,p_12,p_13}<={5'b0,5'b0,5'b0};
{p_21,p_22,p_23}<={5'b0,5'b0,5'b0}
{p_31,p_32,p_33}<={5'b0,5'b0,5'b0}
end
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
mean_value_add1<=0;
mean_value_add2<=0;
mean_value_add3<=0;
end
else if(per_href_ff1)begin
mean_value_add1<=p_11+p_12+p_13;
mean_value_add2<=p_21+ 0 +p_23;
mean_value_add3<=p_31+p_32+p_33;
end
end
wire [8:0]mean_value;//8位數(shù)之和
wire [5:0]fin_y_data; //平均數(shù),除以8,相當(dāng)于左移三位。
assign mean_value=mean_value_add1+mean_value_add2+mean_value_add3;
assign?fin_y_data=mean_value[8:3];?
五、sobel?邊緣檢測
邊緣檢測的原理該算子包含兩組 3x3 的矩陣,分別為橫向及縱向,將之與圖像作平面卷積,即可分別得出橫向及縱向的亮度差分近似值。A代表原始圖像的 3*3 像素陣列,Gx及Gy分別代表經(jīng)橫向及縱向邊緣檢測的圖像,其公式如下:
圖像的每一個像素的橫向及縱向梯度近似值可用以下的公式結(jié)合,來計算梯度的大小。
如果梯度G大于某一閥值則認(rèn)為該點(x,y)為邊緣點。用的是邊緣檢測算法。難點:(1)掌握了 3*3 像素陣列,Gx 與 Gy 就很好計算了。注意問題:為了避免計算過程中出現(xiàn)負(fù)值,所以將正負(fù)值分開單獨計算,具體見代碼)(2)G的計算需要開平方,如何進(jìn)行開平方運算Quartus ii 提供了開平方 ip 核,因此我們直接調(diào)用就好了 。
代碼如下:
reg [8:0] p_x_data ,p_y_data ; // x 和 y 的正值之和
reg [8:0] n_x_data ,n_y_data ; // x 和 y 的負(fù)值之和
reg [8:0] gx_data ,gy_data ; //最終結(jié)果
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
p_x_data <=0;
n_x_data <=0;
gx_data <=0;
end
else if(per_href_ff1==1) begin
p_x_data <= p_13 + (p_23<<1) + p_33 ;
n_x_data <= p_11 + (p_12<<1 )+ p_13 ;
gx_data <= (p_x_data >=n_x_data)? p_x_data - n_x_data : n_x_data - p_x_data ;
end
else begin
p_x_data<=0;
n_x_data<=0;
gx_data <=0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
p_y_data <=0;
n_y_data <=0;
gy_data <=0;
end
else if(per_href_ff1==1) begin
p_y_data <= p_11 + (p_12<<1) + p_13 ;
n_y_data <= p_31 + (p_32<<1) + p_33 ;
gy_data <= (p_y_data >=n_y_data)? p_y_data - n_y_data : n_y_data - p_y_data ;
end
else begin
p_y_data <=0;
n_y_data <=0;
gy_data <=0;
end
end
//求平方和,調(diào)用ip核開平方
reg [16:0] gxy; // Gx 與 Gy 的平方和
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
gxy<=0;
end
else begin
gxy<= gy_data* gy_data + gx_data* gx_data ;
end
end
wire [8:0] squart_out ;
altsquart u1_altsquart ( //例化開平方的ip核
.radical (gxy),
.q (squart_out), //輸出的結(jié)果
.remainder()
);
//與閾值進(jìn)行比較
reg [15:0] post_y_data_r;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
post_y_data_r<=16'h00;
end
else if(squart_out>=threshold)
post_y_data_r<=16'h00 ;
else
post_y_data_r<=16'hffff ;
???
end
六、圖片的顯示
本來是想用 VGA 來顯示圖片的,由于條件的限制沒能實現(xiàn),最終只能將處理完的數(shù)據(jù)輸出保存在 .txt 文件中,然后借助網(wǎng)頁進(jìn)行顯示。難點:(1) 如何將數(shù)據(jù)流輸出保存到 .txt 文件中。(2) 網(wǎng)頁的使用及注意事項。在testbench里加入下面所示代碼即可將圖片數(shù)據(jù)保存到 .txt 文本。
代碼如下:
integer w_file;
initial
w_file = $fopen("data_out_3.txt"); //保存數(shù)據(jù)的文件名
always @(posedge clk or negedge rst_n)
begin
if(flag_write==1&&post_href==1)//根據(jù)自己的需求定義
$fdisplay(w_file,"%b",post_y_data);
??????end??????
網(wǎng)頁的界面如下,將參數(shù)設(shè)置好以后就可以顯示圖片。
下載鏈接:
鏈接:https://pan.baidu.com/s/1pwkJHtAxVHWWijSLczH0Ng
提取碼:e87j
注意:由于此網(wǎng)站是量身定做的,所以只能顯示數(shù)據(jù)格式為RGB565的16位二進(jìn)制的數(shù)才能正確顯示,注意不能有分號,正確格式示例如下,必須嚴(yán)格遵守。
七、結(jié)果展示
小結(jié):均值濾波處理后的圖片有明顯的黑邊,產(chǎn)生這一現(xiàn)象的原因就是生成 3*3 像素矩陣和取像素值時數(shù)據(jù)有損失造成的,但是這也是可以優(yōu)化的,后續(xù)我會繼續(xù)努力不斷完善。本次只是簡單對一幅圖像進(jìn)行邊緣檢測,我的后續(xù)目標(biāo)是實現(xiàn)圖片的實時處理,這又需要學(xué)習(xí)很多東西了,SDRAM、攝像頭驅(qū)動等等等,越學(xué)習(xí)越發(fā)現(xiàn)自己知道的實在是太少了,永遠(yuǎn)在路上,學(xué)無止境。希望我的分享能夠幫助一些和我一樣熱愛 FPGA 圖像處理的朋友。