赞
踩
以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
测试原图1:
对应灰度图1:
经过Sobel算法后:
测试原图2:
对应灰度图2:
经过Sobel算法进行边缘检测后:
野火开源图书资料
特权同学
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。