基于Sobel算法的边沿检测设计与实现

基于Sobel算法的边沿检测设计与实现

  • 1. 边缘检测
  • 2. 实战演练
    • 2.1. matlab进行灰度图像生成
    • 2.2. sobel_ctrl控制模块
    • 2.3. vga显示模块
    • 2.4. 顶层模块实例化
    • 2.5. 仿真与下板测试

1. 边缘检测

边缘检测,针对的是灰度图像,顾名思义,检测图像的边缘,是针对图像像素点的一种计算,目的是标识数据图像中灰度变化明显的点,图像的边缘检测,在保留了图像的重要结构信息的同时,剔除了可以认为不相关的信息,大幅度减少了数据量,便于图像的传输和处理

边缘检测的实现方式:基于查找的方式、零穿越

Sobel算法属于基于查找的方式,两个sobel算子是固定的
基于Sobel算法的边沿检测设计与实现_第1张图片
基于Sobel算法的边沿检测设计与实现_第2张图片
先转灰度图像,之后的边缘显示看VGA输出

2. 实战演练

将图像用软件转成灰度图像,然后将灰度图像的高三位取出进行保存,通过串口将图像数据传给FPGA,随后FPGA通过Sobel算法检测图像轮廓,然后将处理后的图片通过VGA显示640*480@60,图像大小为100 * 100

基于Sobel算法的边沿检测设计与实现_第3张图片
时钟生成模块调用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进行数据的缓存和提取
基于Sobel算法的边沿检测设计与实现_第4张图片
基于Sobel算法的边沿检测设计与实现_第5张图片
基于Sobel算法的边沿检测设计与实现_第6张图片

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模块
基于Sobel算法的边沿检测设计与实现_第7张图片

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

基于Sobel算法的边沿检测设计与实现_第8张图片
下板成功
基于Sobel算法的边沿检测设计与实现_第9张图片

你可能感兴趣的:(FPGA,matlab)