以FIFO实现3行数据求和为基础,学习图像处理方面的基于Sobel算法的边缘检测,边缘检测在计算机视觉、图像分析和图像处理等应用中起着重要作用。
输入图片,将图片中内容的边缘进行提取显示。
开发板:特权同学xilinx spartan6开发板
开发软件:ISE14.7、modelsim10.5
编程语言:verilog
边缘是图像的基本特征,包含了用于图像识别的有用信息,在计算机视觉、图像分析和图像处理等应用中起着重要作用。边缘检测,针对的是灰度图像,顾名思义,检测图像的边缘,是针对图像像素点的一种计算,目的是标识数字图像中灰度变化明显的点,图像的边缘检测,在保留了图像的重要结构信息的同时,剔除了可以认为不相关的信息,大幅度减少了数据量,便于图像的传输和处理。
边缘检查的方法大致可以分为两类:基于查找的一类,通过寻找图像一阶导数中最大值和最小值来检测边界,包括 Sobel 算法、Roberts Cross 算法等;基于零穿越的一类,通过寻找图像二阶导数零穿越来寻找边界,包括 Canny 算法,Laplacian 算法等。
本次学习主要专注于Sobel这种方法。
Sobel检测算法比较简单,虽然准确度比较低但是在实际应用中效率较高,在一些对纹理要求不严格的场景下,这一算法是我们的首选。
Sobel算法的核心是Sobel算子,该算子包含两组3×3的矩阵,如下图所示
对于图像而言,取 3 行 3 列的图像数据,将图像数据与对应位置的算子的值相乘再相加,得到 x 方向的 Gx,和 y 方向的 Gy,将得到的 Gx 和 Gy,平方后相加,再取算术平方根,得到 Gxy,近似值为 Gx 和 Gy 绝对值之和,将计算得到的 Gxy 与我们设定的阈值相比较,Gxy 如果大于阈值,表示该点为边界点,此点显示黑点,否则显示白点。具体见下图。
要实现Sobel算法首先要利用FIFO3行数据求和,其次掌握上述的边缘检测和算法的具体理论,结合这两个部分来实现算法就比较简单了。我们把整个过程分为下面四个步骤:
需要注意的是,图片经过Sobel算法之后,输出的图片相比输入的图片会少2行2列数据,这是因为使用FIFO求和算法时这一算法只有当第2行或第2列数据输入时才开始执行。所以会丢失。
在Sobel算法之前我们首先要把彩色图片转化为灰度图,之后将灰度图的高三位取出来存放到txt中,这一个预处理选择用matlab来实现,相关.m文件代码如下
clc; %清理命令行窗口
clear all; %清理工作区
image = imread('cat.png'); %使用imread函数读取图片数据
figure;
imshow(image); %窗口显示图片
R = image(:,:,2); %提取图片中的红色层生成灰度图像
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('cat.txt','w+'); %打开或新建一个txt文件
for i = 1:ROW*COL;
fprintf(fid,'%02x ',data(i)); %写入图片数据
end
fclose(fid);
(首先打开一个文件夹的工作区,在文件夹中放好图片,并且命名与代码相对应,之后运行即可产生一个我们所需的txt文件)
Sobel算法进行边缘检测的整个实验工程如下图所示,其中clk_gen为PLL IP核产生25MHz和50MHz的时钟,rx和tx为uart串口通信部分,VGA为显示部分,sobel_ctrl为整个工程的核心部分包含了Sobel算法的具体实现。
具体工作流程:
整体的工程已经做了一个简单的介绍,其中的串口收发模块、VGA驱动模块、PLL IP核等均为子模块直接按照需求调用即可。本文主要介绍顶层模块和Sobel求和模块
信号 | 位宽 | 类型 | 功能描述 |
---|---|---|---|
sys_clk | 1Bit | Input | 工作时钟,频率 |
sys_rst_n | 1Bit | Input | 复位信号,低电平有效 |
pi_flag | 1Bit | Input | 输入数据标志信号 |
pi_data | 8Bit | Input | 输入拼接后的图像数据 |
po_flag | 1Bit | Output | 输出数据标志信号 |
po_data | 8Bit | Output | 输出经sobel算法处理后的图像数据 |
这个模块的设计主要包含两个部分:
第一个部分(参考FIFO三行求和的工程)
本功能模块的作用是对输入的图片进行 sobel 算法处理并输出处理后的数据,由前文可知,要实现 sobel 算法的求解,要使用 sobel 算子求出 Gx、Gy,进而求出 Gxy,求解后的 Gxy 与设定阈值比较,确定图像边界,完成 sobel 算法处理。
Gx、Gy 的求解分别是对图形 3 行、3 列图形数据的处理,我们可以参考上一章节的FIFO 求和实验对 3 行数据的处理方式,实现 Gx、Gy 数据的求解。
与 FIFO 求和实验类似,sobel_ctrl 模块内部同样调用两个 FIFO 用作数据缓存。使用同样的方式将串口接收模块传入的图片数据按要求暂存到两个 FIFO 中。
两 FIFO 的时钟信号为系统时钟 sys_clk 与串口接收模块时钟相同;我们需要在模块内部声明 FIFO 写使能信号,声明 fifo1 写使能信号为 wr_en1,数据输入信号为 data_in1,声明 fifo2 写使能信号为 wr_en2,数据输入信号为 data_in2;声明两 FIFO 共用读使能信号rd_en。
wr_en1:当第 0 行数据输入,wr_en1 写使能信号由数据标志信号 pi_flag 赋值,滞后
pi_flag 信号 1 个时钟周期,第 1 行数据输入时,wr_en1 写使能信号保持无效,自第 2 行数据输入到数据输入结束,wr_en1 写使能信号由数据标志信号 dout_flag 赋值,滞后dout_flag 信号 1 个时钟周期;
data_in1:当第 0 行数据输入且写使能有效时, 将第 0 行数据写入 fifo1:当第 2-98 行数据写入且写使能有效时,将 fifo2 读出的 1-97 行数据写入 fifo1;
wr_en2:当第 1-98 行数据输入时,wr_en2 写使能信号由数据标志信号 pi_flag 赋值,滞后 pi_flag 信号 1 个时钟周期,其他时刻写使能信号 wr_en2 均无效;
data_in2:当 fifo2 的写使能信号 wr_en2 有效时,将传入的 pi_data 赋值给 data_in2,数据写入 fifo2,写使能无效时,data_in2 保持原有状态;
rd_en:fifo1 和 fifo2 共用读使能信号,该使能信号在第 0 行和第 1 行数据输入是始终保持无效状态,自第 2 行数据开始输入到数据输入完成,读使能信号 rd_en 由 pi_flag 赋值,滞后 pi_flag 信号 1 个时钟周期;
dout_flag 信号只有在 wr_en2 信号和 rd_en 信号均有效时才有效,其他时刻均无效,目的是赋值给在第 2 行数据输入后的 wr_en1 使能信号。
时序图如下
第二个部分
实现 sobel 算法就要求出 Gxy,Gxy 由 Gx、Gy 运算得到,Gx、Gy 由 sobel
算子与图像数据运算得到。参与运算的图像数据要包含图像 3 行 3 列的像素信息。
这就表示只有在图像的第 2 行的第 2 个数据传入模块时,才能开始 Gx、Gy 的运算。
要准确定位运算开始的时刻,我们需要声明计数器,用来计数读出数据个数,判断 Gx、Gy 的运算时刻,之后根据公式来进行运算,具体逻辑不在此处赘述,对应代码已经附在下面,读者可以自行理解设计。
module sobel_ctrl
(
input wire sys_clk , //输入系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [7:0] pi_data , //rx传入的数据信号
input wire pi_flag , //rx传入的标志信号
output reg [7:0] po_data , //fifo加法运算后的信号
output reg po_flag //输出标志信号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter LENGTH_P = 10'd100 , //图片长度
WIDE_P = 10'd100 ; //图片宽度
parameter THRESHOLD = 8'b000_011_00 ; //比较阈值
parameter BLACK = 8'b0000_0000 , //黑色
WHITE = 8'b1111_1111 ; //白色
//wire define
wire [7:0] data_out1 ; //fifo1数据输出
wire [7:0] data_out2 ; //fifo2数据输出
//reg define
reg [7:0] cnt_h ; //行计数
reg [7:0] cnt_v ; //场计数
reg [7:0] pi_data_dly ; //pi_data数据寄存
reg wr_en1 ; //fifo1写使能
reg wr_en2 ; //fifo2写使能
reg [7:0] data_in1 ; //fifo1写数据
reg [7:0] data_in2 ; //fifo2写数据
reg rd_en ; //fifo1,fifo2共用读使能
reg [7:0] data_out1_dly ; //fifo1数据输出寄存
reg [7:0] data_out2_dly ; //fifo2数据输出寄存
reg dout_flag ; //使能信号
reg rd_en_dly1 ; //输出数据标志信号,延后rd_en一拍
reg rd_en_dly2 ; //a,b,c赋值标志信号
reg gx_gy_flag ; //gx,gy计算标志信号
reg gxy_flag ; //gxy计算标志信号
reg compare_flag; //阈值比较标志信号
reg [7:0] cnt_rd ; //读出数据计数器
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 [8:0] gx ;
reg [8:0] gy ; //gx,gy
reg [7:0] gxy ; //gxy
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_h:行数据个数计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_h <= 8'd0;
else if((cnt_h == (LENGTH_P - 1'b1)) && (pi_flag == 1'b1))
cnt_h <= 8'd0;
else if(pi_flag == 1'b1)
cnt_h <= cnt_h + 1'b1;
//cnt_v:场计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_v <= 8'd0;
else if((cnt_v == (WIDE_P - 1'b1)) && (pi_flag == 1'b1)
&& (cnt_h == (LENGTH_P - 1'b1)))
cnt_v <= 8'd0;
else if((cnt_h == (LENGTH_P - 1'b1)) && (pi_flag == 1'b1))
cnt_v <= cnt_v + 1'b1;
//cnt_rd:fifo数据读出个数计数,用来判断何时对gx,gy进行运算
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_rd <= 8'd0;
else if((cnt_rd == (LENGTH_P - 1'b1)) && (rd_en == 1'b1))
cnt_rd <= 8'd0;
else if(rd_en == 1'b1)
cnt_rd <= cnt_rd + 1'b1;
//wr_en1:fifo1写使能,高电平有效
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en1 <= 1'b0;
else if((cnt_v == 8'd0) && (pi_flag == 1'b1))
wr_en1 <= 1'b1; //第0行写入fifo1
else
wr_en1 <= dout_flag; //2-198行写入fifo1
//wr_en2,fifo2的写使能,高电平有效
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en2 <= 1'b0;
else if((cnt_v >= 8'd1)&&(cnt_v <= ((WIDE_P - 1'b1) - 1'b1))
&& (pi_flag == 1'b1))
wr_en2 <= 1'b1; //2-199行写入fifo2
else
wr_en2 <= 1'b0;
//data_in1:fifo1的数据写入
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_in1 <= 8'b0;
else if((pi_flag == 1'b1) && (cnt_v == 8'b0))
data_in1 <= pi_data;
else if(dout_flag == 1'b1)
data_in1 <= data_out2;
else
data_in1 <= data_in1;
//data_in2:fifo2的数据写入
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_in2 <= 8'b0;
else if((pi_flag == 1'b1) && (cnt_v >= 8'd1)
&& (cnt_v <= ((WIDE_P - 1'b1) - 1'b1)))
data_in2 <= pi_data;
else
data_in2 <= data_in2;
//rd_en:fifo1和fifo2的共用读使能,高电平有效
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if((pi_flag == 1'b1) && (cnt_v >= 8'd2)
&& (cnt_v <= (WIDE_P - 1'b1)))
rd_en <= 1'b1;
else
rd_en <= 1'b0;
//dout_flag:控制fifo1写使能wr_en1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dout_flag <= 1'b0;
else if((wr_en2 == 1'b1) && (rd_en == 1'b1))
dout_flag <= 1'b1;
else
dout_flag <= 1'b0;
//rd_en_dly1:输出数据标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en_dly1 <= 1'b0;
else if(rd_en == 1'b1)
rd_en_dly1 <= 1'b1;
else
rd_en_dly1 <= 1'b0;
//data_out1_dly:data_out1数据寄存
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_out1_dly <= 8'b0;
else if(rd_en_dly1 == 1'b1)
data_out1_dly <= data_out1;
//data_out2_dly:data_out2数据寄存
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_out2_dly <= 8'b0;
else if(rd_en_dly1 == 1'b1)
data_out2_dly <= data_out2;
//pi_data_dly:输入数据pi_data寄存
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data_dly <= 8'b0;
else if(rd_en_dly1 == 1'b1)
pi_data_dly <= pi_data;
//rd_en_dly2:a,b,c赋值标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en_dly2 <= 1'b0;
else if(rd_en_dly1 == 1'b1)
rd_en_dly2 <= 1'b1;
else
rd_en_dly2 <= 1'b0;
//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_dly2 == 1'b1) && ((cnt_rd >= 8'd3) || (cnt_rd == 8'd0)))
gx_gy_flag <= 1'b1;
else
gx_gy_flag <= 1'b0;
//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;
//compare_flag,阈值比较标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
compare_flag <= 1'b0;
else if(gxy_flag == 1'b1)
compare_flag <= 1'b1;
else
compare_flag <= 1'b0;
//a,b,c赋值
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_dly2==1)
begin
a1 <= data_out1_dly;
b1 <= data_out2_dly;
c1 <= pi_data_dly;
a2 <= a1;
b2 <= b1;
c2 <= c1;
a3 <= a2;
b3 <= b2;
c3 <= c2;
end
//gx:计算gx
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;
else
gx <= gx;
//gy:计算gy
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;
else
gy <= gy;
//gxy:gxy计算
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
gxy <= 0;
else if((gx[8] == 1'b1) && (gy[8] == 1'b1) && (gxy_flag == 1'b1))
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))
gxy <= (~gx[7:0] + 1'b1) + (gy[7:0]);
else if((gx[8] == 1'b0) && (gy[8] == 1'b1) && (gxy_flag == 1'b1))
gxy <= (gx[7:0]) + (~gy[7:0] + 1'b1);
else if((gx[8] == 1'b0) && (gy[8] == 1'b0) && (gxy_flag == 1'b1))
gxy <= (gx[7:0]) + (gy[7:0]);
//po_data:通过gxy与阈值比较,赋值po_data
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if((gxy >= THRESHOLD) && (compare_flag == 1'b1))
po_data <= BLACK;
else if(compare_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 if(compare_flag == 1'b1)
po_flag <= 1'b1;
else
po_flag <= 1'b0;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//-------------fifo_pic_inst1--------------
fifo_pic fifo_pic_inst1
(
.clk (sys_clk ), // input clk
.din (data_in1 ), // input [7 : 0] din
.wr_en (wr_en1 ), // input wr_en
.rd_en (rd_en ), // input rd_en
.dout (data_out1 ) // output [7 : 0] dout
);
//-------------fifo_pic_inst2--------------
fifo_pic fifo_pic_inst2
(
.clk (sys_clk ), // input clk
.din (data_in2 ), // input [7 : 0] din
.wr_en (wr_en2 ), // input wr_en
.rd_en (rd_en ), // input rd_en
.dout (data_out2 ) // output [7 : 0] dout
);
endmodule
测试原图2:
对应灰度图2:
经过Sobel算法进行边缘检测后:
野火开源图书资料
特权同学