FPGA丨Sobel边沿检测算法实现

参考:小梅哥的《FPGA系统设计与验证实战指南》

一、算法介绍

Sobel 算法是像素图像边缘检测中最重要的算子之一,在机器学习、数字媒体、计算机视觉等信息科技领域起着举足轻重的作用。在技术上,它是一个离散的一阶差分算子,用来计算图像亮度函数的一阶梯度之近似值。在图像的任何一点使用此算子,将会产生该点对应的梯度矢量或是其法矢量。

Sobel 边缘检测算法比较简单,实际应用中效率比 canny 边缘检测效率要高,但是边缘不如 Canny 检测的准确,但是很多实际应用的场合,sobel 边缘却是首选,尤其是对效率要求较高,而对细纹理不太关心的时候。

Sobel 边缘检测通常带有方向性,可以只检测竖直边缘或垂直边缘或都检测。

在这里插入图片描述
在这里插入图片描述

二、程序设计

关于如何形成3*3的像素矩阵,参考另一篇文章:FPGA丨设计行缓存(linebuffer)生成像素矩阵

module  sobel(
	 input clk, //pixel clk
	 input rst_n,
	 
	 input [15:0] THRESHOLD,
	 input [15:0]data_in, //16 bit 灰度 pixel 
	 input data_in_en,
	 
	 output reg [15:0] data_out,
	 output reg data_out_en
);
//------------------------------------
// 三行像素缓存
//----------------------------------- 
wire [15:0] line0;
wire [15:0] line1;
wire [15:0] line2;
//-----------------------------------------
//3x3 像素矩阵中的像素点
//-----------------------------------------
reg [15:0] line0_data0;
reg [15:0] line0_data1;
reg [15:0] line0_data2;
reg [15:0] line1_data0;
reg [15:0] line1_data1;
reg [15:0] line1_data2;
reg [15:0] line2_data0;
reg [15:0] line2_data1;
reg [15:0] line2_data2;
//-----------------------------------------
//定义gx和gy的正负项中间变量
//-----------------------------------------
reg [17:0] sum0_gx;
reg [17:0] sum1_gx;
reg [17:0] sum0_gy;
reg [17:0] sum1_gy;

wire [15:0] res_sqrt;
wire [17:0] sobel_gx 
wire [17:0] sobel_gy 

wire   mat_flag; 
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; //这里假设开方用的SQRT IP核输出和输入的延迟是一个clk

always @(posedge clk)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

//---------------------------------------------
// 获取3*3的图像矩阵
//---------------------------------------------
matrix_3x3 matrix_3x3_inst(
    .clk (clk),
    .rst_n(rst_n),
    .din (data_in),
    .valid_in(data_in_en),
    .dout(),
    .dout_r0(line0),
    .dout_r1(line1),
    .dout_r2(line2),
    .mat_flag(mat_flag)
);

//--------------------------------------------------
// Form an image matrix of three multiplied by three
//--------------------------------------------------
always @(posedge clk or negedge rst_n) begin
 if(!rst_n) begin
	 line0_data0 <= 16'b0;
	 line0_data1 <= 16'b0;
	 line0_data2 <= 16'b0;
	 
	 line1_data0 <= 16'b0;
	 line1_data1 <= 16'b0;
	 line1_data2 <= 16'b0;
	 
	 line2_data0 <= 16'b0;
	 line2_data1 <= 16'b0;
	 line2_data2 <= 16'b0;

 end
 else if(data_in_en) begin //像素有效信号
	 line0_data0 <= line0;
	 line0_data1 <= line0_data0;
	 line0_data2 <= line0_data1;
	 
	 line1_data0 <= line1;
	 line1_data1 <= line1_data0;
	 line1_data2 <= line1_data1;
	 
	 line2_data0 <= line2;
	 line2_data1 <= line2_data0;
	 line2_data2 <= line2_data1; 
 end
end

//--------------------------------------------------------------------------
// 计算gx,gy
//--------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
 if(!rst_n)
 	sum0_gx <= 18'b0;
 	sum1_gx <= 18'b0;
 	sum0_gy <= 18'b0;
 	sum1_gy <= 18'b0;
 else if(data_in_en)
 //这里需要注意数据从左到右流进来,所以在3*3的像素矩阵中左右两侧像素是反的
 //sum0计算的是正权重部分,sum1计算的是负权重部分
 	sum0_gx  <= line0_data0 + line1_data0*2 + line2_data0;
 	sum1_gx  <= line0_data2 + line1_data2*2 + line2_data2;
 	
 	sum0_gy  <= line0_data0 + line0_data1*2 + line0_data2;
 	sum1_gy  <= line2_data2 + line2_data1*2 + line2_data0;
 else ;
end
//这里可能得到真实值的相反数,但不影响计算,只要绝对值相同即可
assign sobel_gx = (sum0_gx>=sum1_gx)?(sum0_gx-sum1_gx):(sum1_gx-sum0_gx);
assign sobel_gy = (sum0_gy>=sum1_gy)?(sum0_gy-sum1_gy):(sum1_gy-sum0_gy);

//-----------------------------------------------
// 得到 G,这里实现开方需要用到IP核,以后再具体解释吧!!!
//-----------------------------------------------
SQRT sqrt0 (
 .clk(clk),
 .radical(sobel_gx * sobel_gx + sobel_gy * sobel_gy ),
 .q(res_sqrt)
);
//--------------------------------------------------------------------------
// 阈值比较,像素值小于阈值赋值为0,大于阈值赋值为最大值,最后图像会变成了二值图像
//--------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
 if(!rst_n)
 	data_out <= 16'b0;
 else if(data_in_en)
 	data_out <= (res_sqrt> THRESHOLD) ? 16'hffff : 16'b0;
 else ;
end

always @(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
        data_out_en  <= 1'b0;
    else if(mat_flag_3 == 1'b1 && mat_flag_7 == 1'b1) 
        data_out_en  <= 1'b1;
    else
        data_out_en  <= 1'b0;

endmodule

需要注意的是:

上述代码的输入图像数据默认是灰度图像的数据,数据位宽为16。关于如何得到灰度图,可以参考另一篇文章:

FPGA丨RGB转Gray算法实现(rgb2ycbcr)

posted @ 2021-03-28 14:53  耐心的小黑  阅读(177)  评论(0编辑  收藏  举报