16. 基于sobel算法的边缘检测设计与实现

上一章节中,我们通过实验学习了使用FIFO实现3行数据求和的具体方法,本章节中我们会以此为基础学习基于Sobel算法的边缘检测,边缘检测在计算机视觉、图像分析和图像处理等应用中起着重要作用,Sobel算法又是其中比较重要的一个方法,读者务必理解掌握。

16.1. 理论学习

16.1.1. 边沿检测

边缘是图像的基本特征,包含了用于图像识别的有用信息,在计算机视觉、图像分析和图像处理等应用中起着重要作用。

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

边缘检查的方法大致可以分为两类:基于查找的一类,通过寻找图像一阶导数中最大值和最小值来检测边界,包括Sobel算法、Roberts Cross算法等;基于零穿越的一类,通过寻找图像二阶导数零穿越来寻找边界,包括Canny算法,Laplacian算法等。读者读到此处,如不理解无需深究,若感兴趣可自行查阅相关资料。

16.1.2. Sobel算法简介

在本实验中,我们用到的是第一类方法中的Sobel算法, Sobel边缘检测算法比较简单,虽然准确度较低,但在实际应用中效率较高,在很多实际应用场合,Sobel算法却是首选,尤其是对效率要求较高,对纹理不太关心的时候。

Soble算法的核心就是Sobel算子,该算子包含两组3x3的矩阵,具体见图 45‑1。

Sobel002

图 45‑1 卷积因子

对于图像而言,取3行3列的图像数据,将图像数据与对应位置的算子的值相乘再相加,得到x方向的Gx,和y方向的Gy,将得到的Gx和Gy,平方后相加,再取算术平方根,得到Gxy,近似值为Gx和Gy绝对值之和,将计算得到的Gxy与我们设定的阈值相比较,Gxy如果大于阈值,表示该点为边界点,此点显示黑点,否则 显示白点。具体见图 45‑2。

Sobel003

图 45‑2 Gxy计算公式

16.2. 实战演练

16.2.1. sobel算法实现

在上一章节中我们通过双FIFO的3行数据求和实验对FIFO求和的方法进行了详细讲解;在本章的理论学习中,我们对边缘检测和Sobel算法进行了简单介绍,了解了Sobel算法在图像的边缘检测中的用法以及相关计算公式。结合这两个部分的内容,想要实现Sobel算法就比较简单了。

我们将Sobel算法在图像边缘检查中的实现分为4步,第1步,通过Gx、Gy的计算公式结合FIFO求和算法求取Gx、Gy的值;第2步,求得Gx、Gy的绝对值;第3步,将Gx、Gy带入Gxy计算公式,求得Gxy的值;第4步,将求得的Gxy与设定的阈值相比较,当Gxy大于等于阈值,赋值rgb为黑色,否则, rgb赋值为白色。

读者要注意的是,图片正在经过Sobel算法之后,输出的图片相比于输入时的图片会少2行2列数据,这是因为在求取Gx、Gy时,要使用FIFO求和算法,该算法只有在第2行或第2列数据输入时才开始执行,第0、1行或第0、1列不会进行求和运算,更无数据输出,所以会缺失的2行2列。

16.2.2. 实验目标

上一小节我们简单介绍了Sobel算法在图像边缘检测中实现的具体步骤,下面我们通过实验,来具体实现一下图像边缘检测中的Sobel算法。

实验目标:使用Matlab软件将图片转换为灰度图像,并且将灰度图像的高 3 位取出存放为txt 文本 ,PC机通过串口RS232传输图片数据给FPGA,FPGA通过Sobel算法检测出图片的边缘轮廓,将处理后的图片在VGA显示器上显示出来。

实验要求:VGA显示模式:640x480@60;传入图片分辨率大小100x100。

16.2.3. 硬件资源

详见“VGA显示器驱动设计与验证”章节 “硬件资源”介绍。

16.3. 程序设计

16.3.1. 图片预处理

在进行Sobel算法之前,先要将图片进行一下预处理,将彩色图片转换成灰度图像,并且将灰度图像的高 3 位取出存放为txt 文本,图解和代码,具体见图 45‑3及代码清单 45‑1。

Sobel004

图 45‑3 预处理图解

代码清单 45‑1 图片预处理代码(sobel.m)

1 clc; %清理命令行窗口

2 clear all; %清理工作区

3 image = imread(‘logo.png’); %使用imread函数读取图片数据

4 figure;

5 imshow(image); %窗口显示图片

6 R = image(:,:,1); %提取图片中的红色层生成灰度图像

7 figure;

8 imshow(R); %窗口显示灰色图像

9 [ROW,COL] = size(R); %灰色图像大小参数

10 data = zeros(1,ROW*COL); %定义一个初值为0的数组,存储转换后的图片数据

11 for r = 1:ROW

12 for c = 1 : COL

13 data((r-1)*COL+c) = bitshift(R(r,c),-5); %红色层数据右移5位

14 end

15 end

16 fid = fopen(‘logo.txt’,’w+’); %打开或新建一个txt文件

17 for i = 1:ROW*COL;

18 fprintf(fid,’%02x ‘,data(i)); %写入图片数据

19 end

20 fclose(fid);

代码中含有详细注释,在此不再讲解。

16.3.1.1. 整体说明

在本小节中,我们对实验工程的整体框图进行讲解。实验工程整体框图,具体见图 45‑4。

Sobel005

图 45‑4 sobel算法工程整体框图

由整体框图可知,本实验工程包括8个子模块,各模块简介,具体见表格 45‑1。

表格 45‑1 sobel算法工程模块简介

模块名称

功能描述

sobel

顶层模块

clk_gen

时钟生成模块

uart_rx

串口数据接收模块

uart_tx

串口数据发送模块

sobel_ctrl

数据求和模块

vga

vga显示顶层模块

vga_pic

图像数据生成模块

vga_ctrl

vga显示驱动模块

结合图表,我们来介绍一下sobel算法工程的具体具体工作流程。

  1. 系统上电后,板卡传入系统时钟(sys_clk)和复位信号(sys_rst_n)到顶层模块;

(2) 系统时钟由顶层模块传入时钟生成模块(clk_gen),分频产生25MHz、50MHz时钟。其中50MHz时钟作为串口数据收发模块(uart_rx、uart_tx)和数据求和模块(sobel_ctrl)的工作时钟,同时也作为图像数据生成模块(vga_pic)内部实例化RAM的数据写入时钟;25 MHz时钟,作为图像数据生成模块(vga_pic)和VGA时序控制模块(vga_ctrl)的工作时钟;

(3) PC机将图片数据通过串口RS232传输给FPGA,数据在uart_rx模块中完成拼接传给sobel_ctrl模块进行Sobel运算,输出结果同时传给vga模块和uart_tx模块;vga模块将接收到的经sobel算法处理后的图像数据存入图像数据生成模块中的RAM中;uart_tx模块将输出结 果回传给PC机验证数据完整性。

  1. 图像数据生成模块以VGA时序控制模块传入的像素点坐标(pix_x,pix_y)为约束条件,生成待显示图像的色彩信息(pix_data),图像色彩信息包括彩条背景和经sobel算法处理后的图片信息;

  2. 图像数据生成模块生成的图像色彩信息传入VGA时序控制模块,在模块内部使用使能信号滤除掉非图像显示有效区域的图像数据,产生RGB色彩信息(rgb),在行、场同步信号(hsync、vsync)的同步作用下,将RGB色彩信息扫描显示到VGA显示器,显示出经sobel算法处理过的图像。

实验工程的整体说明完毕后,接下来我们会对工程的各子功能模块做详细介绍。串口数据收发模块、VGA显示相关模块在前面章节均已详细介绍过,在下文中不再重复说明 ;时钟生成模块为调用IP和生成,且前文也有相关介绍,不再赘述;后文中只对数据求和模块、顶层模块做一下详细说明。

16.3.1.2. 数据求和模块

模块框图

数据求和模块sobel_ctrl是本实验工程的核心模块,负责Sobel算法的实现,模块包含4路输入信号和2路输出信号,内部还调用了2个fifo,参与sobel运算。

输入信号中除了输入系统时钟sys_clk和系统复位信号sys_rst_n,还有自uart_rx模块输入的数据信号pi_data和与之对应的数据标志信号pi_flag,数据输入后通过sobel运算后得出Gx、Gy,进而求出Gxy,求出结果与阈值比较,根据比较结果给输出信号po_data赋值,输出信号除 了po_data之外还有与之同步的数据标志信号po_flag。 sobel_ctrl模块框图,具体见图 45‑5;输入输出信号功能描述,具体见表格 45‑2。

Sobel006

图 45‑5 sobel_ctrl模块框图

表格 45‑2 sobel_ctrl模块输入输出信号功能描述

·

位宽

类型

功能描述

sys_clk

1Bit

Input

工作时钟,频率50MHz

sys_rst_n

1Bit

Input

复位信号,低电平有效

pi_flag

1Bit

Input

输入数据标志信号

pi_data

8Bit

Input

输入拼接后的图像数据

po_flag

1Bit

Output

输出数据标志信号

po_data

8Bit

Output

输出经sobel算法处理后的图像数据

波形图绘制

在上一小节,我们对数据求和模块的具体功能做了说明,对输入输出信号做了简单介绍,在本小节,我们将通过波形图的绘制,对各信号做详细讲解,利用模块输入信号,实现模块功能。

sobel_ctrl模块波形图,具体见图 45‑6、图 45‑7。

Sobel007

图 45‑6 sobel_ctrl模块波形图(一)

Sobel008

图 45‑7 sobel_ctrl模块波形图(二)

由于波形图篇幅较大,我们将他分为两部分,图 45‑6为图片数据自输入模块到数据写入fifo这一过程的波形图,图 45‑7为数据从fifo读出经过Sobel运算,到将结果输出的过程的波形图。虽然sobel_ctrl模块整体波形图看起来较为复杂,但读者不必担心,下文中,我们将对其进行分部详细讲解,方便读者理解。

第一部分:输入信号

由前文可知,本模块有4路输入信号,包括系统时钟sys_clk(频率50MHz)、复位信号sys_rst_n(低电平有效)和由uart_rx模块输入的8位宽的数据信号pi_data,以及和它相匹配的pi_flag输入数据有效标志信号。输入信号波形图如下。

Sobel009

图 45‑8 输入信号波形图

第二部分:图像行列计数器cnt_h、cnt_v信号波形图设计与实现

本模块的作用是要对传入图像进行sobel算法处理,为了对图像进行sobel算法处理,模块内部调用两个FIFO进行数据缓存,我们需要将图片的不同行的图片信息按照要求缓存到不同的FIFO之中,图像数据是由串口接收模块传入,且每次传入一个像素点图像信息,为了满足sobel算法的要求,我们可以利用计数器对传 入图像像素点个数进行计数,计满一行数据后,按要求缓存到对应FIFO。

所以模块内部声明两个计数器:行计数器cnt_h,列计数器cnt_v。

串口传入的图片大小为100*100,串口每传入一个像素点数据,与之同步出入的数据标志信号pi_flag拉高一个时钟周期。可以将pi_flag信号作为行计数器cnt_h信号的约束条件,将行计数器cnt_h赋初值为0,计数范围0-99, pi_flag信号每拉高1次自加1,计数一行数据个数,计数到最大值归0,重新计数,一个计数周期计数100次,与图片一行像素点个数对应。

同理,将行计数器cnt_h和pi_flag作为列计数器cnt_v的约束条件,列计数器cnt_v赋初值为0,计数范围0-99,cnt_h行计数器每计满1个周期且pi_flag信号拉高,列计数器cnt_v自加1,计数一帧图片数据的行个数,技术到最大值归0。

行计数器cnt_h,列计数器cnt_v信号波形图如下。

Sobel010

图 45‑9 cnt_h、cnt_v信号波形图

第三部分: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使能信号。

上述各信号波形图如下。

Sobel011

图 45‑10 FIFO相关信号波形图

第四部分:sobel算法相关信号波形图设计与实现

前文提到,实现sobel算法就要求出Gxy,Gxy由Gx、Gy运算得到,Gx、Gy由sobel算子与图像数据运算得到。参与运算的图像数据要包含图像3行3列的像素信息。

这就表示只有在图像的第2行的第2个数据传入模块时,才能开始Gx、Gy的运算。要准确定位运算开始的时刻,我们需要声明计数器,用来计数读出数据个数,判断Gx、Gy的运算时刻。

声明读出数据计数器cnt_rd,初值为0,计数范围0-99,共计数100次,rd_en读使能信号有效时,cnt_rd自加1,计数到最大值归0,重新计数,用来判断何时求解gx、gy。信号波形图如下。

Sobel012

图 45‑11 cnt_rd信号波形图

同时,为了保证之前传入的数据和自FIFO读取数据不会丢失,我们需要声明若干数据寄存器,用以寄存输入和读出的图片数据。声明输入数据寄存器pi_data_dly、fifo1读出数据寄存data_out1_dly、fifo2读出数据寄存data_out2_dly、数据寄存标志信号rd_en_dly1。

data_out1和data_out2分别是fifo1和fifo2 数据输出信号;data_out1_dly和data_out2_dly分别是data_out1和data_out2的数据寄存,延后1个时钟周期;pi_data_dly是输入数据pi_data的数据寄存,延后1个时钟周期;信号rd_en _dly1滞后rd_en读使能信号1个时钟周期,作用是作为缓存fifo读出数据和pi_data输入数据的使能信号。各信号波形如下。

Sobel013

图 45‑12 输入数据、读取数据寄存信号波形图

求解Gx、Gy,需要使用sobel算子与3行3列图片数据进行相关运算。所以我们需要9个变量来寄存3行3列图片数据参与运算。声明a1、a2、a3、b1、b2、b3、c1、c2、c3寄存3行3列图片数据,声明rd_en_dly2信号作为图像数据寄存标志信号。

a1-c3,这9个变量初值为0,当rd_en_dly2信号有效时,将data_out1_dly、data_out2_dly、pi_data_dly分别赋值给a1、b1、c1,将a1、b1、c1赋值给a2、b2、c2,将a2、b2、c2赋值给a3、b3、c3;信号rd_en_dly2滞后rd_en_d ly1信号1个时钟周期,作为a1-c3的赋值条件。各信号波形如下。

Sobel014

图 45‑13 a1-c3信号波形图

开始Gx、Gy的求解,声明Gx、Gy计算标志信号gx_gy_flag。 gx_gy_flag有效时,计算Gx、Gy,当rd_en_dly2信号有效且cnt_rd不等于1或2时,标志信号gx_gy_flag有效,其他时刻gx_gy_flag无效;当gx_gy_flag信号有效时,结合a1-c3按照公式求解出Gx、Gy。

声明Gxy计算标志信号gxy_flag,求解Gxy。gxy_flag有效时,按照计算公式求解Gxy,gxy_flag由gx_gy_flag赋值,滞后其1个时钟周期;

声明阈值比较信号compare_flag,compare_flag信号由gxy_flag信号赋值,滞后gxy_flag信号1个时钟周期;compare_flag信号有效时,将求解出的Gxy与设定阈值相比较,当Gxy大于等于设定阈值时,po_data输出数据赋值为黑色,否者赋值为白色;输出数据标志信号 po_flag延后compare_flag信号1个时钟周期,与po_data信号同步输出。上述各信号波形图如下。

Sobel015

图 45‑14 Gx、Gy、Gxy、输出信号波形图

代码编写

波形图绘制完毕,参照波形图进行代码编写。sobel_ctrl模块参考代码,具体见代码清单 45‑2。

代码清单 45‑2 sobel_ctrl模块参考代码(sobel_ctrl.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
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
 (
 .clock (sys_clk ), // input sys_clk
 .data (data_in1 ), // input [7 : 0] din
 .wrreq (wr_en1 ), // input wr_en
 .rdreq (rd_en ), // input rd_en

 .q (data_out1 ) // output [7 : 0] dout
 );

 //-------------fifo_pic_inst2--------------
 fifo_pic fifo_pic_inst2
 (
 .clock (sys_clk ), // input sys_clk
 .data (data_in2 ), // input [7 : 0] din
 .wrreq (wr_en2 ), // input wr_en
 .rdreq (rd_en ), // input rd_en

 .q (data_out2 ) // output [7 : 0] dout
 );

 endmodule

模块参考代码是参照绘制波形图进行编写的,在波形图绘制小节已经对模块各信号有了详细的说明,本小节不再过多叙述。

对于本模块的仿真验证不再单独进行,待顶层模块介绍完毕,直接对实验进行整体仿真,到时再对本模块进行仿真波形分析。

16.3.1.3. 顶层模块

代码编写

顶层模块sobel代码,包含各子模块的实例化,连接各模块对应信号,较为容易理解,在此只列出代码,不再讲解,具体见代码清单 45‑3。

代码清单 45‑3 顶层模块sobel代码(sobel.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
module sobel
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //系统复位
input wire rx , //串口接收数据

output wire tx , //串口发送数据
output wire hsync , //输出行同步信号
output wire vsync , //输出场同步信号
 output wire [7:0] rgb //输出像素信息
 );

 ////
 //\* Parameter and Internal Signal \//
 ////
 //wire define
 wire vga_clk ;
 wire [7:0] pi_data ;
 wire pi_flag ;
 wire [7:0] po_data ;
 wire po_flag ;
 wire locked ;
 wire rst_n ;
 wire clk_50m ;

 //rst_n:VGA模块复位信号
 assign rst_n = (sys_rst_n & locked);

 ////
 //\* Instantiation \//
 ////
 //------------- clk_gen_inst -------------
 clk_gen clk_gen_inst
 (
 .areset (~sys_rst_n ), //输入复位信号,高电平有效,1bit
 .inclk0 (sys_clk ), //输入50MHz晶振时钟,1bit

 .c0 (vga_clk ), //输出VGA工作时钟,频率25MHz,1bit
 .c1 (clk_50m ), //输出串口工作时钟,频率50MHz,1bit
 .locked (locked ) //输出pll locked信号,1bit
 );

 //------------- uart_rx_inst --------------
 uart_rx uart_rx_inst
 (
 .sys_clk (clk_50m ), //系统时钟50MHz
 .sys_rst_n (rst_n ), //全局复位
 .rx (rx ), //串口接收数据

 .po_data (pi_data ), //串转并后的数据
 .po_flag (pi_flag ) //串转并后的数据有效标志信号
 );

 //------------- sobel_ctrl_inst --------------
 sobel_ctrl sobel_ctrl_inst
 (
 .sys_clk (clk_50m ), //输入系统时钟,频率50MHz
 .sys_rst_n (rst_n ), //复位信号,低有效
 .pi_data (pi_data ), //rx传入的数据信号
 .pi_flag (pi_flag ), //rx传入的标志信号

 .po_data (po_data ), //fifo加法运算后的信号
 .po_flag (po_flag ) //输出标志信号
 );

 //------------- vga_ctrl_inst -------------
 vga vga_inst
 (
 .vga_clk (vga_clk ), //输入工作时钟,频率50MHz
 .sys_clk (clk_50m ), //输入工作时钟,频率25MHz
 .sys_rst_n (rst_n ), //输入复位信号,低电平有效
 .pi_data (po_data ), //输入数据
 .pi_flag (po_flag ), //输入数据标志信号

 .hsync (hsync ), //输出行同步信号
 .vsync (vsync ), //输出场同步信号
 .rgb (rgb ) //输出像素信息
 );

 //------------- uart_tx_inst --------------
 uart_tx uart_tx_inst
 (
 .sys_clk (clk_50m ), //系统时钟50MHz
 .sys_rst_n (rst_n ), //全局复位
 .pi_data (po_data ), //并行数据
 .pi_flag (po_flag ), //并行数据有效标志信号

 .tx (tx ) //串口发送数据
 );

 endmodule

RTL视图

实验工程通过仿真验证后,使用 Quartus 软件对实验工程进行编译,编译完成后,我们查看一下 RTL 视图, RTL 视图展示信息与顶层模块框图一致,各信号连接正确,具体见图 45‑15。

Sobel016

图 45‑15 RTL视图

16.3.1.4. 仿真验证

仿真代码编写

顶层模块参考代码介绍完毕,开始对顶层模块进行仿真,对顶层模块的仿真就是对实
验工程的整体仿真。顶层模块仿真参考代码,具体见代码清单 45‑4。

代码清单 45‑4 顶层模块仿真参考代码(tb_sobel.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
\`timescale 1ns/1ns
module tb_sobel();
//wire define
wire tx ;
wire hsync ;
wire vsync ;
wire [7:0] rgb;

//reg define
reg clk ;
reg rst_n ;
reg rx ;
reg [7:0] data_mem [9999:0] ; //data_mem是一个存储器,相当于一个ram

//读取sim文件夹下面的data.txt文件,并把读出的数据定义为data_mem
initial
$readmemh
("E:/GitLib/Altera/EP4CE10/base_code/9_sobel/matlab/data_logo.txt",data_mem);

//时钟、复位信号
initial
begin
clk = 1'b1 ;
rst_n <= 1'b0 ;
#200
rst_n <= 1'b1 ;
end

always #10 clk = ~clk;


initial
begin
rx <= 1'b1;
#200
rx_byte();
end

task rx_byte();
integer j;
for(j=0;j<10000;j=j+1)
rx_bit(data_mem[j]);
endtask

task rx_bit(input[7:0] data); //data是data_mem[j]的值。
integer i;
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]; //上面8个发送的是数据位
9: rx <= 1'b1 ; //停止位
endcase
#1040; //一个波特时间=sclk周期*波特计数器
end
endtask

//重定义defparam,用于修改参数,缩短仿真时间
defparam sobel_inst.uart_rx_inst.BAUD_CNT_END = 52;
defparam sobel_inst.uart_rx_inst.BAUD_CNT_END_HALF = 26;
defparam sobel_inst.uart_tx_inst.BAUD_CNT_END = 52;

//-------------sobel_inst-------------
sobel sobel_inst(
.sys_clk (clk ), //input sys_clk
.sys_rst_n (rst_n ), //input sys_rst_n
.rx (rx ), //input rx

.hsync (hsync ), //output hsync
.vsync (vsync ), //output vsync
.rgb (rgb ), //output [7:0] rgb
.tx (tx ) //output tx
);

endmodule

仿真波形分析

仿真代码编写完成后,通过Quartus软件联合Modelsim进行仿真,串口数据收发模块、VGA显示模块和顶层模块的仿真波形我们不再讲解,在此重点说一下sobel_ctrl模块的各信号仿真波形。

对于sobel_ctrl模块的波形,我们参照波形图绘制小结,将其分为两个部分讲解。第一部分为图片数据自输入模块到数据写入fifo这一过程,包括4路输入信号和8个寄存器变量,输入信号不必再说,我们重点分析一下这8个寄存器变量。

  1. 由波形图可知,行计数器cnt_h初值为0,pi_flag信号每拉高1次,行计数器cnt_h自加,计数到最大值99,计数器归0,重新计数,仿真波形与绘制波形图信号一致,具体见图 45‑16。

Sobel017

图 45‑16 cnt_h信号波形图

  1. 列计数器cnt_v初值为0,行计数器cnt_h计满1个时钟周期且pi_flag信号拉高,cnt_v自加1,计数最大值为99,计满归0,具体见图 45‑17。

Sobel018

图 45‑17 cnt_v信号波形图

(3) 当数据第0行输入,即cnt_v为0时,fifo1写使能信号wr_en1有pi_data信号赋值,滞后其1个时钟周期,fifo1数据写入data_in1数据由pi_data传入;当数据第0行传入后,即cnt_v为1-99时,wr_en1信号有dout_flag信号赋值,dout_flag信号有 效时,fifo2读出数据data_out2写入fifo1的data_in1,具体见图 45‑18。

Sobel019

图 45‑18 fifo1写使能、写数据波形

  1. fifo2写使能信号wr_en2,只在cnt_v计数在1-98区域且pi_flag信号为高电平时,写使能有效,其他时刻均无效;写使能信号有效时,将传入数据pi_data写入fifo1写数据端口data_in1 ,写使能wr_en无效无数据写入,具体见图 45‑19。

Sobel020

图 45‑19 fifo2写使能、写数据波形

  1. fifo共用读使能信号rd_en,列计数器cnt_v计数在2-99范围且pi_flag信号为高电平,读使能信号rd_en有效,其他时刻无效,具体见图 45‑20。

Sobel021

图 45‑20 fifo共用读使能信号波形图

  1. fifo1读使能信号wr_en1在cnt_v计数器大于0时,受控于信号dout_flag,dout_flag信号只有在fifo2写使能信号wr_en2和读使能信号rd_en均有效时才有效,其他其他时刻,dout_flag信号无效,具体见图 45‑21。

Sobel022

图 45‑21 信号dout_flag仿真波形图

第一部分信号仿真波形分析完毕,接下来进入第二部分的信号波形分析。

  1. 读数据计数器cnt_rd,计数读出数据个数,初值为0,读使能信号拉高1次,cnt_rd自加1,计数到最大值,即一行数据个数99,计数器归0,重新计数,具体见图 45‑22。

Sobel023

图 45‑22 读数据计数器波形

  1. 使能信号rd_en_dly1滞后rd_en读使能1拍,作为fifo读出数据data_out1、data_out2和输入数据pi_data的数据寄存使能信号;使能信号rd_en_dly2滞后rd_en读使能2拍,作为gx_gy_flag信号的约束条件之一,具体见图 45‑23。

Sobel024

图 45‑23 读使能打拍信号波形

  1. 读使能信号rd_en有效时,两个fifo有数据读出,具体见图 45‑24。

Sobel025

图 45‑24 fifo读出数据波形

  1. 以信号rd_en_dly1为使能,将fifo的读出数据data_out1、data_out2分别赋值给data_out_dly1、data_out2_dly2,数据寄存,用于sobel运算,具体见图 45‑25。

Sobel026

图 45‑25 fifo读出数据寄存波形

  1. 以信号rd_en_dly1为使能,将输入数据pi_data赋值给pi_data_dly,数据寄存,用于sobel运算,具体见图 45‑26。

Sobel027

图 45‑26 输入数据基础波形

(6) a1-c3是要进行sobel运算的像素点,初值为0,当使能信号rd_en_dly2有效时,将data_out1_dly、data_out2_dly、pi_data_dly分别赋值给a1、b1、c1,a1、b1、c1赋值给a2、b2、c2,a2、b2、c2赋值给a3、b3、c3,具体见图 45‑27。

Sobel028

图 45‑27 sobel运算像素点赋值

(7) 信号gx_gy_flag是计算gx、gy的使能信号,当信号gx_gy_flag有效时,根据gx、gy计算公式求出gx、gy,该信号滞后rd_en_dly2信号1个时钟周期;信号gxy_flag是计算gy的使能信号,当信号gxy_flag有效时,根据gxy计算公式求出gxy,该信号滞后gx_g y_flag信号1个时钟周期;compare_flag是阈值比较信号,此信号滞后gxy_flag信号1个时钟周期,信号有效时,将求得的gxy与设定阈值比较,根据比较结果给po_data赋值,具体见图 45‑28及图 45‑29。

Sobel029

图 45‑28 sobel 运算使能信号波形

Sobel030

图 45‑29 gx、gy、gxy运算

  1. compare_flag信号有效时,将求得的gxy与设定阈值比较,若gxy大于等于设定阈值,给输出数据po_data赋值黑色指令,否者赋值白色指令;输出数据标志信号po_flag滞后compare_flag信号1个时钟周期,为的是与输出数据po_data保持同步,具体见图 45‑30。

Sobel031

图 45‑30 输出数据、输出标志信号

16.4. 上板验证

16.4.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 45‑3所示。

表格 45‑3 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

输入系统时钟

sys_rst_n

Input

M15

复位信号

rx

Output

N6

串口接收数据

tx

Output

N5

串口发送数据

hsync

Output

C2

行同步信号

vsync

Output

D1

场同步信号

rgb[7]

Output

A5

RGB色彩信息(红)

rgb[6]

Output

E6

RGB色彩信息(红)

rgb[5]

Output

E7

RGB色彩信息(红)

rgb[4]

Output

F8

RGB色彩信息(绿)

rgb[3]

Output

E8

RGB色彩信息(绿)

rgb[2]

Output

B7

RGB色彩信息(绿)

rgb[1]

Output

B6

RGB色彩信息(蓝)

rgb[0]

Output

A6

RGB色彩信息(蓝)

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 45‑31所示。

Sobel032

图 45‑31 管脚分配

16.4.1.1. 结果验证

如图 45‑32所示,开发板连接12V直流电源、USB-Blaster下载器JTAG端口、USB数据线以及VGA显示器。线路正确连接后,打开开关为板卡上电。

Sobel033

图 45‑32 程序下载连线图

如图 45‑33所示,使用“Programmer”为开发板下载程序。

Sobel034

图 45‑33 程序下载窗口

程序下载完成后,使用串口助手向板卡发送转换后的图像数据,如图 45‑34所示;随后VGA显示器显示出彩条背景,并在中央扫描打印出sobel算法处理后的图片,如图 45‑35所示;实际效果和预期效果一致,上板验证成功。

Sobel035

图 45‑34 串口助手发送图片数据

Sobel036

图 45‑35 VGA显示实验效果图

16.5. 章末总结

边缘检测在计算机视觉、图像分析和图像处理等应用中起着重要作用,Sobel算法又是边缘检测的重要算法之一,通过本章节的学习,读者要了解Sobel算法的具体流程,掌握Sobel算法的实现方法,如对边缘检测的其他算法感兴趣,也可自行查找相关资料加以学习。

16.6. 拓展训练

  1. 更改代码中设置的阈值参数,编译并上板验证,分析阈值参数的大小对边缘信息提取的影响。

  2. 自行查找其他边缘检测的算法,编写工程,设计并实现图片边缘信息的提取。