基于Sobel算法的边沿检测设计与实现
1. 边缘检测
边缘检测,针对的是灰度图像,顾名思义,检测图像的边缘,是针对图像像素点的一种计算,目的是标识数据图像中灰度变化明显的点,图像的边缘检测,在保留了图像的重要结构信息的同时,剔除了可以认为不相关的信息,大幅度减少了数据量,便于图像的传输和处理
边缘检测的实现方式:基于查找的方式、零穿越
Sobel算法属于基于查找的方式,两个sobel算子是固定的
先转灰度图像,之后的边缘显示看VGA输出
2. 实战演练
将图像用软件转成灰度图像,然后将灰度图像的高三位取出进行保存,通过串口将图像数据传给FPGA,随后FPGA通过Sobel算法检测图像轮廓,然后将处理后的图片通过VGA显示640*480@60,图像大小为100 * 100
时钟生成模块调用IP核,rx、tx模块实现过,vga显示和控制模块也实现过,需要实现的就是sobel算法模块,已经顶层模块的编写
2.1. matlab进行灰度图像生成
clc; %清理命令行窗口
clear all; %清理工作区
image = imread('logo.png'); %使用imread函数读取图片数据
figure; %创建一个窗口
imshow(image); %窗口显示图片
R = image(:,:,1); %提取图片中的红色层生成灰度图像
figure;
imshow(R); %窗口显示灰色图像
[ROW,COL] = size(R); %灰色图像大小参数
data = zeros(1,ROW*COL); %定义一个初值为0的数组,存储转换后的图片数据
for r = 1:ROW
for c = 1 : COL
data((r-1)*COL+c) = bitshift(R(r,c),-5); %红色层数据右移5位
end
end
fid = fopen('logo.txt','w+'); %打开或新建一个txt文件
for i = 1:ROW*COL;
fprintf(fid,'%02x ',data(i)); %写入图片数据
end
fclose(fid);
2.2. sobel_ctrl控制模块
这里用到的算法就是sobel算法,其中两个sobel算子是固定的参数,图像数据是通过串口发送给fpga的
图像数据是以数据流的方式传送的,在图片数据流中就要提取三行三列的数据参与运算,数据提取的方式就可以参考之前的FIFO求和,用FIFO进行数据的缓存和提取
module sobel_ctrl (
input wire sys_clk ,
input wire sys_rst_n ,
input wire pi_flag ,
input wire [ 7: 0] pi_data ,
output reg po_flag ,
output reg [ 7: 0] po_data
);
// 100行100列的数据矩阵
parameter CNT_COL_MAX = 8'd100;
parameter CNT_ROW_MAX = 8'd100;
// 阈值参数
parameter THR = 8'b000_011_00;
// 颜色参数RGB332格式
parameter BLACK = 8'b000_000_00;
parameter WHITE = 8'b111_111_11;
reg [ 7: 0] cnt_col ;
reg [ 7: 0] cnt_row ;
reg wr_en_1 ;
reg [ 7: 0] wr_data_1 ;
reg wr_en_2 ;
reg [ 7: 0] wr_data_2 ;
reg rd_en ;
wire [ 7: 0] dout_1 ;
wire [ 7: 0] dout_2 ;
reg dout_flag ;
reg [ 7: 0] cnt_rd ;
reg [ 7: 0] dout_1_reg ;
reg [ 7: 0] dout_2_reg ;
reg [ 7: 0] pi_data_reg ;
reg rd_en_reg ;
reg rd_en_reg1 ;
reg [ 7: 0] a1 ;
reg [ 7: 0] a2 ;
reg [ 7: 0] a3 ;
reg [ 7: 0] b1 ;
reg [ 7: 0] b2 ;
reg [ 7: 0] b3 ;
reg [ 7: 0] c1 ;
reg [ 7: 0] c2 ;
reg [ 7: 0] c3 ;
reg gx_gy_flag ;
reg [ 8: 0] gx ; // 最高位为符号位
reg [ 8: 0] gy ;
reg gxy_flag ;
reg [ 7: 0] gxy ;
reg com_flag ;
// cnt_col:列计数器
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt_col <= 8'd0;
else if ((cnt_col == CNT_COL_MAX - 1'b1) && (pi_flag == 1'b1))
cnt_col <= 8'b0;
else if (pi_flag == 1'b1)
cnt_col <= cnt_col + 1'b1;
// cnt_row:行计数器
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt_row <= 8'd0;
else if ((cnt_row == CNT_ROW_MAX - 1'b1) && (cnt_col == CNT_COL_MAX - 1'b1) && (pi_flag == 1'b1))
cnt_row <= 8'd0;
else if ((cnt_col == CNT_COL_MAX - 1'b1) && (pi_flag == 1'b1))
cnt_row <= cnt_row + 1'b1;
// wr_en_1:FIFO_1写使能
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
wr_en_1 <= 1'b0;
else if ((cnt_row == 8'd0) && (pi_flag == 1'b1))
wr_en_1 <= 1'b1;
else
wr_en_1 <= dout_flag;
// wr_data_1:FIFO_1写数据
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
wr_data_1 <= 8'd0;
else if ((cnt_row == 8'd0) && (pi_flag == 1'b1))
wr_data_1 <= pi_data;
else if (dout_flag == 1'b1)
wr_data_1 <= dout_2;
// wr_en_2:FIFO_2写使能
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
wr_en_2 <= 1'b0;
else if ((cnt_row >= 8'd1) && (cnt_row <= CNT_ROW_MAX - 2) && (pi_flag == 1'b1)) // 第1、2、3行写入FIFO_2
wr_en_2 <= 1'b1;
else
wr_en_2 <= 1'b0;
// wr_data_2:FIFO_2写数据
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
wr_data_2 <= 8'd0;
else if ((cnt_row >= 8'd1) && (cnt_row <= CNT_ROW_MAX - 2) && (pi_flag == 1'b1))
wr_data_2 <= pi_data;
else
wr_data_2 <= wr_data_2;
// rd_en:读使能
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if ((cnt_row >= 8'd2) && (cnt_row <= CNT_ROW_MAX - 1'b1) && (pi_flag == 1'b1))
rd_en <= 1'b1;
else
rd_en <= 1'b0;
// dout_flag:FIFO读出标志信号,用于产生FIFO_1写使能信号
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
dout_flag <= 1'b0;
else if ((wr_en_2 == 1'b1) && (rd_en == 1'b1))
dout_flag <= 1'b1;
else
dout_flag <= 1'b0;
// cnt_rd:读数据计数器
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt_rd <= 8'd0;
else if ((cnt_rd == CNT_COL_MAX - 1'b1) && (rd_en == 1'b1)) // 一行数据读出
cnt_rd <= 8'd0;
else if (rd_en == 1'b1)
cnt_rd <= cnt_rd + 1'b1;
// dout_1_reg:
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
dout_1_reg <= 8'd0;
else if (rd_en_reg == 1'b1)
dout_1_reg <= dout_1;
// dout_2_reg:
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
dout_2_reg <= 8'd0;
else if (rd_en_reg == 1'b1)
dout_2_reg <= dout_2;
// pi_data_reg:
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
pi_data_reg <= 8'd0;
else if (rd_en_reg == 1'b1)
pi_data_reg <= pi_data;
// rd_en_reg:
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
rd_en_reg <= 1'b0;
else if (rd_en == 1'b1)
rd_en_reg <= 1'b1;
else
rd_en_reg <= 1'b0;
// rd_en_reg1:a、b、c赋值标志信号
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
rd_en_reg1 <= 1'b0;
else if (rd_en_reg == 1'b1)
rd_en_reg1 <= 1'b1;
else
rd_en_reg1 <= 1'b0;
// a1、a2、a3、b1、b2、b3、c1、c2、c3
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
begin
a1 <= 8'd0;
a2 <= 8'd0;
a3 <= 8'd0;
b1 <= 8'd0;
b2 <= 8'd0;
b3 <= 8'd0;
c1 <= 8'd0;
c2 <= 8'd0;
c3 <= 8'd0;
end
else if (rd_en_reg1 == 1'b1)
begin
a1 <= a2;
a2 <= a3;
a3 <= dout_1_reg;
b1 <= b2;
b2 <= b3;
b3 <= dout_1_reg;
c1 <= c2;
c2 <= c3;
c3 <= pi_data_reg;
end
// gx_gy_flag:gx、gy计算标志信号
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
gx_gy_flag <= 1'b0;
else if ((rd_en_reg1 == 1'b1) && ((cnt_rd >= 8'd3) || (cnt_rd == 8'd0)))
gx_gy_flag <= 1'b1;
else
gx_gy_flag <= 1'b0;
// gx:Gx = (a3-a1) + (b3-b1)*2 + (c3-c1)
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
gx <= 9'd0;
else if (gx_gy_flag == 1'b1)
gx <= (a3-a1) + ((b3-b1)<<1) + (c3-c1); // 左移1位,扩大二倍
else
gx <= gx;
// gy:Gy = (a1-c1) + (a2-c2)*2 + (a3-c3)
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
gy <= 9'd0;
else if (gx_gy_flag == 1'b1)
gy <= (a1-c1) + ((a2-c2)<<1) + (a3-c3); // 左移1位,扩大二倍
else
gy <= gy;
// gxy_flag:gxy计算标志信号
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
gxy_flag <= 1'b0;
else if (gx_gy_flag == 1'b1)
gxy_flag <= 1'b1;
else
gxy_flag <= 1'b0;
// gxy:Gxy = √[(Gx)^2+(Gy)^2] ≈ (|Gx| + |Gy|)
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
gxy <= 8'd0;
else if ((gx[8] == 1'b1) && (gy[8] == 1'b1) && (gxy_flag == 1'b1)) // gx为负,gy为负
gxy <= (~gx[7:0] + 1'b1) + (~gy[7:0] + 1'b1);
else if ((gx[8] == 1'b1) && (gy[8] == 1'b0) && (gxy_flag == 1'b1)) // gx为负,gy为正
gxy <= (~gx[7:0] + 1'b1) + (gy[7:0]);
else if ((gx[8] == 1'b0) && (gy[8] == 1'b1) && (gxy_flag == 1'b1)) // gx为正,gy为负
gxy <= (gx[7:0]) + (~gy[7:0] + 1'b1);
else if ((gx[8] == 1'b0) && (gy[8] == 1'b0) && (gxy_flag == 1'b1)) // gx为正,gy为正
gxy <= (gx[7:0]) + (gy[7:0]);
// com_flag:阈值比较信号
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
com_flag <= 1'b0;
else if (gxy_flag == 1'b1)
com_flag <= 1'b1;
else
com_flag <= 1'b0;
// po_data:
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
po_data <= 8'd0;
else if ((com_flag == 1'b1) && (gxy > THR))
po_data <= BLACK;
else if (com_flag == 1'b1)
po_data <= WHITE;
// po_flag:
always @ (posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= com_flag;
fifo fifo_1 (
.clk (sys_clk ), // input wire clk
.rst (~sys_rst_n ), // input wire rst
.din (wr_data_1 ), // input wire [7 : 0] din
.wr_en (wr_en_1 ), // input wire wr_en
.rd_en (rd_en ), // input wire rd_en
.dout (dout_1 ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ) // output wire empty
);
fifo fifo_2 (
.clk (sys_clk ), // input wire clk
.rst (~sys_rst_n ), // input wire rst
.din (wr_data_2 ), // input wire [7 : 0] din
.wr_en (wr_en_2 ), // input wire wr_en
.rd_en (rd_en ), // input wire rd_en
.dout (dout_2 ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ) // output wire empty
);
endmodule
2.3. vga显示模块
实例化之前实现过的vga_ctrl模块和vga_pic模块
vga_pic模块需要重新确定图片显示的区域:
100x100大小的图像,经过sobel算法之后的大小为【98x98】
列求和实验:n×m大小 ——> p×q大小 p = n-(x-1) q = m
eg. 5x5大小的矩阵,经过列求和会变成3x5的新矩阵
sobel算法是三行三列进行求和,
进行三行的列求和会少两行,进行三行的行求和会少两列
parameter H_PIC = 10'd98,
V_PIC = 10'd98;
parameter PIC_SIZE= 14'd9604; // 98x98=9604
module vga (
input wire sys_clk , // 50MHz
input wire vga_clk , // 25MHz
input wire sys_rst_n ,
input wire [ 7: 0] pi_data ,
input wire pi_flag ,
output wire [11: 0] rgb ,
output wire hsync ,
output wire vsync
);
wire [ 9: 0] pix_x ;
wire [ 9: 0] pix_y ;
wire [ 7: 0] pix_data;
wire [ 7: 0] rgb_332 ; // 332
vga_ctrl vga_ctrl_inst (
.vga_clk (vga_clk ), // 25MHz
.sys_rst_n (sys_rst_n ),
.pix_data (pix_data ),
.pix_x (pix_x ),
.pix_y (pix_y ),
.hsync (hsync ),
.vsync (vsync ),
.rgb (rgb_332 )
);
vga_pic vga_pic_inst (
.rx_clk (sys_clk ), // 50MHz
.vga_clk (vga_clk ), // 25MHz
.sys_rst_n (sys_rst_n ),
.pix_x (pix_x ),
.pix_y (pix_y ),
.pi_data (pi_data ),
.pi_flag (pi_flag ),
.pix_data (pix_data )
);
assign rgb = {2'b0, rgb_332[7:6], 1'b0, rgb_332[5:3], 1'b0, rgb_332[2:0]};
endmodule
2.4. 顶层模块实例化
module sobel (
input wire sys_clk ,
input wire sys_rst_n ,
input wire rx ,
output wire hsync ,
output wire vsync ,
output wire [11: 0] rgb ,
output wire tx
);
parameter CLK_FREQ = 'd50_000_000;
wire clk_25M ; // vga
wire clk_50M ; // uart
wire locked ;
wire [ 7: 0] rx_data ;
wire rx_flag ;
wire [ 7: 0] po_data ;
wire po_flag ;
wire rst_n = sys_rst_n && locked;
clk_gen instance_name
(
// Clock out ports
.clk_out1 (clk_25M ), // output clk_out1
.clk_out2 (clk_50M ), // output clk_out2
// Status and control signals
.reset (~sys_rst_n ), // input reset
.locked (locked ), // output locked
// Clock in ports
.clk_in1 (sys_clk ) // input clk_in1
);
uart_rx
#(
.UART_BPS ('d9600 ),
.CLK_FREQ (CLK_FREQ )
)
uart_rx_inst
(
.sys_clk (clk_50M ),
.sys_rst_n (rst_n ),
.rx (rx ),
.po_data (rx_data ),
.po_flag (rx_flag )
);
uart_tx
#(
.UART_BPS ('d9600 ),
.CLK_FREQ (CLK_FREQ )
)
uart_tx_inst
(
.sys_clk (clk_50M ),
.sys_rst_n (rst_n ),
.pi_data (po_data ),
.pi_flag (po_flag ),
.tx (tx )
);
vga vga_inst (
.sys_clk (clk_50M ),
.vga_clk (clk_25M ),
.sys_rst_n (rst_n ),
.pi_data (po_data ),
.pi_flag (po_flag ),
.rgb (rgb ),
.hsync (hsync ),
.vsync (vsync )
);
sobel_ctrl sobel_ctrl_inst (
.sys_clk (clk_50M ),
.sys_rst_n (rst_n ),
.pi_flag (rx_flag ),
.pi_data (rx_data ),
.po_flag (po_flag ),
.po_data (po_data )
);
endmodule
2.5. 仿真与下板测试
tb模块的代码复用之前串口发送数据到FPGA然后由VGA显示的工程代码,只需要修改添加输出信号tx和读文件地址
`timescale 1ns / 1ns
module sobel_tb ();
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
wire hsync ;
wire vsync ;
wire [11: 0] rgb ;
wire tx ;
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #5 sys_clk = ~sys_clk;
reg [ 7: 0] data_mem[9999: 0]; // 存储器,用来产生模拟图片数据
initial begin
// $readmemh ("C:/Users/123/Desktop/Vivado_test/VGA_uart_pic/matlab/mine/bird_RGB332.txt", data_mem);
$readmemh ("C:/Users/123/Desktop/Vivado_test/Sobel/matlab/logo.txt", data_mem);
end
initial begin
rx = 1'b1;
#200
rx_byte ();
end
// 17ms
sobel sobel_inst (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.hsync (hsync ),
.vsync (vsync ),
.rgb (rgb ),
.tx (tx )
);
// defparam vga_uart_pic_inst.uart_rx_inst.BAUD_CNT_MAX = 52;
// parameter可用作在顶层模块中例化底层模块时传递参数的接口,
// localparam的作用域仅仅限于当前module,不能作为参数传递的接口
// 为了仿真简单,可以去uart_tx把BAUD_CNT_MAX改成52
// 或者重定义
defparam sobel_inst.CLK_FREQ = 'd50_000_0;
task rx_byte ();
integer j;
begin
for (j = 0; j < 10000; j = j + 1)
rx_bit(data_mem[j]);
end
endtask
task rx_bit;
input [ 7: 0] data ;
integer i;
begin
for (i = 0; i < 10; i = i + 1) begin
case (i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
default: rx <= 1'b1;
endcase
// #(5208 * 20)
#(52 * 20)
// #(5 * 20)
;
end
end
endtask
endmodule
下板成功