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。关于如何得到灰度图,可以参考另一篇文章: