Sobel算法的FPGA实现
项目简洁
图像的边缘检测在图像处理中非常常见,因为可以去除一些冗余的信息,然后给后续的处理带来方便。本次实验的主要内容是,PC机通过千兆网发送一副1024*768分辨率的图片到FPGA开发板的DDR3缓存,然后FPGA进行边缘处理,最后FPGA经过USB3.0接口发送给上位机进行显示。本次实验所用到的软硬件环境如下:
1、VIVADO 2019.1软件环境
2、Modelsim 10.7c仿真环境
3、米联客MA7035FA(100T)开发板
4、米联客USB3.0上位机
图像边缘
简言之, 边缘就是图像灰度值突变的地方, 亦即图像在该部分的像素值变化速度非常之快,这就好比在坐标轴上一条曲线有刚开始的平滑突然来个大转弯,在变化处的导数非常大。
Sobel算法介绍
既然提到了导数,在咱们检测图像边缘的时候,也可以通过求该像素点的导数是否达到了一定值从而判断该像素点是否为图像的边缘。Sobel 算法正是基于求取图像某一像素点导数的近似值来判断该点是否为图像的边缘。如果以 A 代表原始图像, Gx 及 Gy 分别代表经横向及纵向边缘检测的图像灰
度值,其公式如下所示:
所以sobel两个方向的算子:
Sobel算法:
1、把图像每三行三列的数据分别乘上算子中对应位置的值再相加。然后进行如下运算,得到相应方向(x和y)的Dx和Dy。
Dx=(a3-a1)+(b3-b1)*2+c3-c1;
Dy=(a1-c1)+(a2-c2)*2+a3-c3;
2、对上面求得的Dx和Dy做平方和的平方根,再取近似值Dx和Dy的绝对值的和得到Dxy:
D x y = D x 2 + D y 2 = ( ∣ D x ∣ + ∣ D y ∣ ) Dxy = \sqrt{Dx^2+Dy^2} =(\left|Dx\right|+\left|Dy\right|) Dxy=Dx2+Dy2=(∣Dx∣+∣Dy∣)
3、如果Dxy的值大于一个阈值,表示该点为边界点,就让VGA显示一个白点,否则显示黑点。
4、把计算的结果通过vga显示,显示器会把是边界点的以白色像素显示,不是边界点的以黑色像素点显示,于是得到了一幅图像的轮廓。
Sobel算法的FPGA实现
3x3矩形窗的构建
从上面的分析,可以发现Sobel算法的难度最大的地方就是矩形窗的构建。这里说明一下,矩形窗的构建在图像处理中非常重要,比如我们之后要讲解的中值滤波、腐蚀膨胀等等操作都需要构建图像矩阵,相信学过图像处理的同学对这一点深有体会。
矩阵的构建我们为了方便起见使用了三个FIFO。与使用两个FIFO构建矩阵相比,该方案控制简单,但是多花费了一些硬件资源。整个矩阵构建的框图如下:
从上面可以看出我们使用FIFO便可以控制一个简单的矩阵。这里注意一下,Sobel算法是针对灰度图像而言,所以算法中,我们只对RGB中的一个分量进行算法操作。经过USB3.0传给上位机的数据也是Sobel处理后的灰度图像。
矩阵构建模块代码
这里因为控制很简单,不再给出详细的时序图,同学们可以根据下面的代码进行相应的学习。
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : nnzhang1996@foxmail.com
// Website :
// Module Name : mat_3x3.v
// Create Time : 2020-04-07 10:42:14
// Editor : sublime text3, tab size (2)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module mat_3x3(
//System Interfaces
input sclk ,
input rst_n ,
//Communication Interfaces
input [ 7:0] rx_data ,
input pi_flag ,
output wire [ 7:0] mat_row1 ,
output wire [ 7:0] mat_row2 ,
output wire [ 7:0] mat_row3 ,
output wire mat_flag
);
//========================================================================================\
//**************Define Parameter and Internal Signals**********************************
//========================================================================================/
parameter COL_NUM = 1024 ;
parameter ROW_NUM = 768 ;
reg [10:0] col_cnt ;
reg [10:0] row_cnt ;
wire wr_en2 ;
wire wr_en3 ;
wire rd_en1 ;
wire rd_en2 ;
wire [ 7:0] fifo1_rd_data ;
wire [ 7:0] fifo2_rd_data ;
wire [ 7:0] fifo3_rd_data ;
//========================================================================================\
//************** Main Code **********************************
//========================================================================================/
assign wr_en2 = row_cnt >= 11'd1 ? pi_flag : 1'b0;
assign rd_en1 = wr_en2;
assign wr_en3 = row_cnt >= 11'd2 ? pi_flag : 1'b0;
assign rd_en2 = wr_en3;
assign mat_flag = row_cnt >= 11'd3 ? pi_flag : 1'b0;
assign mat_row1 = fifo1_rd_data;
assign mat_row2 = fifo2_rd_data;
assign mat_row3 = fifo3_rd_data;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
col_cnt <= 11'd0;
else if(col_cnt == COL_NUM-1 && pi_flag == 1'b1)
col_cnt <= 11'd0;
else if(pi_flag == 1'b1)
col_cnt <= col_cnt + 1'b1;
else
col_cnt <= col_cnt;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
row_cnt <= 11'd0;
else if(row_cnt == ROW_NUM-1 && col_cnt == COL_NUM-1 && pi_flag == 1'b1)
row_cnt <= 11'd0;
else if(col_cnt == COL_NUM-1 && pi_flag == 1'b1)
row_cnt <= row_cnt + 1'b1;
fifo_generator_0 mat_fifo1 (
.clk (sclk ), // input wire clk
.srst (~rst_n ), // input wire srst
.din (rx_data ), // input wire [7 : 0] din
.wr_en (pi_flag ), // input wire wr_en
.rd_en (rd_en1 ), // input wire rd_en
.dout (fifo1_rd_data ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ) // output wire empty
);
fifo_generator_0 mat_fifo2 (
.clk (sclk ), // input wire clk
.srst (~rst_n ), // input wire srst
.din (fifo1_rd_data ), // input wire [7 : 0] din
.wr_en (wr_en2 ), // input wire wr_en
.rd_en (rd_en2 ), // input wire rd_en
.dout (fifo2_rd_data ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ) // output wire empty
);
fifo_generator_0 mat_fifo3 (
.clk (sclk ), // input wire clk
.srst (~rst_n ), // input wire srst
.din (fifo2_rd_data ), // input wire [7 : 0] din
.wr_en (wr_en3 ), // input wire wr_en
.rd_en (mat_flag ), // input wire rd_en
.dout (fifo3_rd_data ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ) // output wire empty
);
endmodule
Sobel算法实现及代码
前面算法介绍部分已经详细讲解了算法的流程,那么这部分我们只需要给出算法的流程写出相应的程序即可。这里需要注意的是绝对值在FPGA中实现的方法,每位取反然后加一,详细的查看如下代码:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : nnzhang1996@foxmail.com
// Website :
// Module Name : sobel.v
// Create Time : 2020-04-08 08:32:02
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module sobel(
//System Interfaces
input sclk ,
input rst_n ,
//Communication Interfaces
input [ 7:0] rx_data ,
input pi_flag ,
output reg [ 7:0] tx_data ,
output reg po_flag
);
//========================================================================================\
//**************Define Parameter and Internal Signals**********************************
//========================================================================================/
parameter COL_NUM = 1024 ;
parameter ROW_NUM = 768 ;
parameter VALUE = 80 ;
wire [ 7:0] mat_row1 ;
wire [ 7:0] mat_row2 ;
wire [ 7:0] mat_row3 ;
wire mat_flag ;
reg [ 7:0] mat_row1_1 ;
reg [ 7:0] mat_row2_1 ;
reg [ 7:0] mat_row3_1 ;
reg [ 7:0] mat_row1_2 ;
reg [ 7:0] mat_row2_2 ;
reg [ 7:0] mat_row3_2 ;
reg mat_flag_1 ;
reg mat_flag_2 ;
reg mat_flag_3 ;
reg mat_flag_4 ;
reg mat_flag_5 ;
reg mat_flag_6 ;
reg mat_flag_7 ;
reg [10:0] row_cnt ;
reg [ 7:0] dx ;
reg [ 7:0] dy ;
reg [ 7:0] abs_dx ;
reg [ 7:0] abs_dy ;
reg [ 7:0] abs_dxy ;
//========================================================================================\
//************** Main Code **********************************
//========================================================================================/
always @(posedge sclk)
begin
mat_row1_1 <= mat_row1;
mat_row2_1 <= mat_row2;
mat_row3_1 <= mat_row3;
mat_row1_2 <= mat_row1_1;
mat_row2_2 <= mat_row2_1;
mat_row3_2 <= mat_row3_1;
end
always @(posedge sclk)
begin
mat_flag_1 <= mat_flag;
mat_flag_2 <= mat_flag_1;
mat_flag_3 <= mat_flag_2;
mat_flag_4 <= mat_flag_3;
mat_flag_5 <= mat_flag_4;
mat_flag_6 <= mat_flag_5;
mat_flag_7 <= mat_flag_6;
end
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
row_cnt <= 11'd0;
else if(row_cnt == ROW_NUM-1 && mat_flag == 1'b1)
row_cnt <= 11'd0;
else if(mat_flag == 1'b1)
row_cnt <= row_cnt + 1'b1;
else
row_cnt <= row_cnt;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
dx <= 8'd0;
else
dx <= mat_row1_2-mat_row1+((mat_row2_2-mat_row2)<<1)+mat_row3_2-mat_row3;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
dy <= 8'd0;
else
dy <= mat_row1-mat_row3+((mat_row1_1-mat_row3_1)<<1)+mat_row1_2-mat_row3_2;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
abs_dx <= 8'd0;
else if(dx[7] == 1'b1)
abs_dx <= (~dx)+1'b1;
else
abs_dx <= dx;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
abs_dy <= 8'd0;
else if(dy[7] == 1'b1)
abs_dy <= (~dy)+1'b1;
else
abs_dy <= dy;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
abs_dxy <= 8'd0;
else
abs_dxy <= abs_dx + abs_dy;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
tx_data <= 8'd0;
else if(abs_dxy >= VALUE)
tx_data <= 8'd0;
else
tx_data <= 8'd255;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
po_flag <= 1'b0;
else if(mat_flag_3 == 1'b1 && mat_flag_5 == 1'b1)
po_flag <= 1'b1;
else
po_flag <= 1'b0;
mat_3x3 mat_3x3_inst(
//System Interfaces
.sclk (sclk ),
.rst_n (rst_n ),
//Communication Interfaces
.rx_data (rx_data ),
.pi_flag (pi_flag ),
.mat_row1 (mat_row1 ),
.mat_row2 (mat_row2 ),
.mat_row3 (mat_row3 ),
.mat_flag (mat_flag )
);
endmodule
为了与我们前面千兆网接收模块相结合,我们有写了下面的连接模块。下面模块的作用是,千兆网传来的数据是8位,一个图像点占三个时钟,那么传入Sobel模块的使能信号将是离散的。但是,我们要求的使能信号需要是连续的,所以加了个FIFO,代码如下:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : nnzhang1996@foxmail.com
// Website :
// Module Name : conver_bit.v
// Create Time : 2020-03-18 17:39:59
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module conver_bit(
//System Interfaces
input sclk ,
input rst_n ,
//Gigbit Interfaces
input [ 7:0] image_data ,
input image_data_en ,
//Communication Interfaces
output wire [31:0] rgb_data ,
output wire rgb_data_en
);
//========================================================================================\
//**************Define Parameter and Internal Signals**********************************
//========================================================================================/
parameter COL_NUM = 1024 ;
reg [ 1:0] image_cnt ;
reg [23:0] data ;
wire [ 7:0] sobel_data ;
reg sobel_data_en ;
reg rd_en ;
wire [ 7:0] dout ;
reg [10:0] data_cnt ;
reg [10:0] rd_cnt ;
//========================================================================================\
//************** Main Code **********************************
//========================================================================================/
assign rgb_data = {
8'h00,sobel_data,sobel_data,sobel_data};
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
image_cnt <= 2'b0;
else if(image_cnt == 2'd2 && image_data_en == 1'b1)
image_cnt <= 2'd0;
else if(image_data_en == 1'b1)
image_cnt <= image_cnt + 1'b1;
else
image_cnt <= image_cnt;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
data <= 24'd0;
else if(image_data_en == 1'b1)
data <= {
data[15:0],image_data};
else
data <= data;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
sobel_data_en <= 1'b0;
else if(image_cnt == 2'd2 && image_data_en == 1'b1)
sobel_data_en <= 1'b1;
else
sobel_data_en <= 1'b0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
data_cnt <= 11'd0;
else if(sobel_data_en == 1'b1 && data_cnt == COL_NUM-1)
data_cnt <= 11'd0;
else if(sobel_data_en == 1'b1)
data_cnt <= data_cnt + 1'b1;
else
data_cnt <= data_cnt;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
rd_cnt <= 11'd0;
else if(rd_en == 1'b1 && rd_cnt == COL_NUM-1)
rd_cnt <= 11'd0;
else if(rd_en == 1'b1)
rd_cnt <= rd_cnt + 1'b1;
else
rd_cnt <= rd_cnt;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
rd_en <= 1'b0;
else if(rd_en == 1'b1 && rd_cnt == COL_NUM-1)
rd_en <= 1'b0;
else if(sobel_data_en == 1'b1 && data_cnt == COL_NUM-1)
rd_en <= 1'b1;
else
rd_en <= rd_en;
sobel sobel_inst(
//System Interfaces
.sclk (sclk ),
.rst_n (rst_n ),
//Communication Interfaces
.rx_data (dout ),
.pi_flag (rd_en ),
.tx_data (sobel_data ),
.po_flag (rgb_data_en )
);
fifo_generator_1 fifo_generator_5_inst(
.clk (sclk ), // input wire clk
.srst (~rst_n ), // input wire srst
.din (data[7:0] ), // input wire [7 : 0] din
.wr_en (sobel_data_en ), // input wire wr_en
.rd_en (rd_en ), // input wire rd_en
.dout (dout ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ) // output wire empty
);
endmodule
Sobel模块的测试代码
为了验证我们模块的正确性,我们将给出相应的代码如下:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : nnzhang1996@foxmail.com
// Website :
// Module Name : tb_sobel.v
// Create Time : 2020-04-08 09:19:44
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module tb_sobel();
//System Interfaces
reg sclk ;
reg rst_n ;
//Communication Interfaces
reg [ 7:0] rx_data ;
reg pi_flag ;
wire [31:0] tx_data ;
wire po_flag ;
reg [ 1:0] cnt ;
initial begin
sclk = 1'b0;
rst_n <= 1'b0;
pi_flag <= 1'b0;
rx_data <= 8'd0;
#(1000);
rst_n <= 1'b1;
#(10000);
gendata();
#(10000);
gendata();
end
always #5 sclk = ~sclk;
task gendata();
integer i ;
integer j ;
begin
for(j = 0;j < 768;j = j+1)
begin
for(i = 0;i < 1024;i = i+1)
begin
pi_flag = 1'b1;
#10;
pi_flag = 1'b0;
#20;
end
pi_flag = 1'b0;
#1000;
end
end
endtask
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt <= 2'd0;
else if(pi_flag == 1'b1 && cnt == 2'd2)
cnt <= 2'd0;
else if(pi_flag == 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= cnt;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
rx_data <= 8'd0;
else if(cnt == 2'd2 && pi_flag == 1'b1 && rx_data == 1024-1)
rx_data <= 8'd0;
else if(cnt == 2'd2 && pi_flag == 1'b1)
rx_data <= rx_data + 1'b1;
else
rx_data <= rx_data;
//sobel sobel_inst(
// //System Interfaces
// .sclk (sclk ),
// .rst_n (rst_n ),
// //Communication Interfaces
// .rx_data (rx_data ),
// .pi_flag (pi_flag ),
// .tx_data (tx_data ),
// .po_flag (po_flag )
//);
conver_bit conver_bit_inst(
//System Interfaces
.sclk (sclk ),
.rst_n (rst_n ),
//Gigbit Interfaces
.image_data (rx_data ),
.image_data_en (pi_flag ),
//Communication Interfaces
.rgb_data (tx_data ),
.rgb_data_en (po_flag )
);
endmodule
仿真结果
进行上面程序的仿真,结果如下:
项目程序
我们前面已经对我们的这次项目进行了描述,如果同学们是严格跟着我博客学习的,那么这个程序只需要学习上面的算法就行。具体这个项目的其他部分请参考前面的博客。这里我们为了方便同学们的学习,给出相应项目的代码:
top模块:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : nnzhang1996@foxmail.com
// Website :
// Module Name : top.v
// Create Time : 2020-03-01 20:33:42
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module top(
//System Interfaces
input sclk ,
input rst_n ,
//DDR3 Interfaces
output wire [13:0] ddr3_addr ,
output wire [ 2:0] ddr3_ba ,
output wire ddr3_cas_n ,
output wire ddr3_ck_n ,
output wire ddr3_ck_p ,
output wire ddr3_cke ,
output wire ddr3_ras_n ,
output wire ddr3_reset_n ,
output wire ddr3_we_n ,
inout [31:0] ddr3_dq ,
inout [ 3:0] ddr3_dqs_n ,
inout [ 3:0] ddr3_dqs_p ,
output wire [ 0:0] ddr3_cs_n ,
output wire [ 3:0] ddr3_dm ,
output wire [ 0:0] ddr3_odt ,
//Gigbit Interfaces
output wire phy_rst_n ,
input [ 3:0] rx_data ,
input rx_ctrl ,
input rx_clk ,
//USB3 Interfaces
output wire USBSS_EN ,
input USB_clk ,
inout [15:0] data ,
inout [ 1:0] be ,
input rxf_n ,
input txf_n ,
output wire oe_n ,
output wire wr_n ,
output wire siwu_n ,
output wire rd_n ,
output wire wakeup ,
output wire [ 1:0] gpio
);
//========================================================================================\
//**************Define Parameter and Internal Signals**********************************
//========================================================================================/
//clk_wiz_0_inst
wire clk_200m ;
wire locked ;
wire clk_125m ;
//ddr3_drive_inst
wire init_calib_complete ;
wire c3_p0_cmd_clk ;
wire c3_p0_cmd_en ;
wire [ 2:0] c3_p0_cmd_instr ;
wire [27:0] c3_p0_cmd_byte_addr ;
wire [ 6:0] c3_p0_cmd_bl ;
wire c3_p0_wr_clk ;
wire c3_p0_wr_en ;
wire [31:0] c3_p0_wr_mask ;
wire [255:0] c3_p0_wr_data ;
wire [10:0] c3_p0_wr_count ;
wire c3_p1_cmd_clk ;
wire c3_p1_cmd_en ;
wire [ 2:0] c3_p1_cmd_instr ;
wire [27:0] c3_p1_cmd_byte_addr ;
wire [ 6:0] c3_p1_cmd_bl ;
wire c3_p1_rd_clk ;
wire c3_p1_rd_en ;
wire [255:0] c3_p1_rd_data ;
wire [10:0] c3_p1_rd_count ;
//sensor_data_gen_inst
wire clk_24m ;
wire data_wr_en ;
wire [31:0] data_wr ;
//usb3_drive_inst
wire [15:0] data_in ;
wire data_req ;
wire [ 7:0] image_data ;
wire image_data_en ;
wire [31:0] rlst ;
wire rlst_flag ;
//========================================================================================\
//************** Main Code **********************************
//========================================================================================/
clk_wiz_0 clk_wiz_0_inst(
// Clock out ports
.clk_out1 (clk_200m ), // output clk_out1
.clk_out2 (clk_125m ),
.clk_out3 (clk_50m ),
// Status and control signals
.reset (~rst_n ), // input reset
.locked (locked ), // output locked
// Clock in ports
.clk_in1 (sclk )
); // input clk_in1
ddr3_top ddr3_top_inst(
//System Interfaces
.rst_n (rst_n ),
.locked (locked ),
.clk_200m (clk_200m ),
//DDR3 Interfaces
.ddr3_addr (ddr3_addr ),
.ddr3_ba (ddr3_ba ),
.ddr3_cas_n (ddr3_cas_n ),
.ddr3_ck_n (ddr3_ck_n ),
.ddr3_ck_p (ddr3_ck_p ),
.ddr3_cke (ddr3_cke ),
.ddr3_ras_n