1.图像边缘检测算法的简介:
1.1 在本学期的实践中用FPGA实现了图像的边缘检测算法,边缘检测是分析视频图像的重要手段之一, 并且应用领域广泛。
1.2 在现在的图像处理研究中是非常重要的基础研究,在安全监控,计算机图形处理,机器识别等领域越来越成为研究的重点。
1.3 边缘检测是确定一幅图像在哪些区域上亮度发生突变。 这些亮度突变的区域通常就是物体的边缘。
2. 设计的具体实现要求:
2.1 图像边沿滤波器,执行sobel算法。
2.2 原始图像亮度保存在MEM中。
2.3 启动算法后,滤波器将亮度图像转变为导数图像,保存在MEM中。
2.4 导数的计算避免开方根,采用偏导数绝对值之和近似计算。
边缘检测算法的数学公式推导过程:
分辨率为 MxN 的图像是由 MxN 个像素组成。对于灰度图像,每个像素点一般由 8比特来表示该像素的亮度值。我们以灰度图像为例,假设图像按行存储在内存中,并且每行里从左到右连续的像素点占据着内存中连续的存贮单元。像素值是无符号整数,范围从 0(黑色)到 255(白色) 。这里,我们采用一种相对简单的算法来完成图像边缘检测,叫做Sobel 边缘检测法。它的机理是计算 x 和 y 方向亮度信号的导数值并且寻找导数中的最大值和最小值。这些区域就是亮度变化最剧烈的区域,即图像边缘。Sobel 检测法通过一个叫做卷积的过程来估计每个像素点每个方向上的导数值。把中心像素点和离它最近的八个像素点每个乘以一个系数后相加。该系数通常用一个卷积表(convolution mask) 来表示。 分别用于计算 x 和 y 方向导数值的 Sobel 卷积表 Gx 和 Gy 如下所示。
-1 0 +1
-2 0 +2
-1 0 +1
Gx
+1 +2 +1
0 0 0
-1 -2 -1
Gy
我们把每个像素值分别乘以卷积表中对应的系数, 再把相乘得到的九个数相加就得到了x 方向和 y 方向的偏导数值 Dx 和 Dy。 然后, 利用这两个偏导数值计算中心像素点的导数。
计算公式如下:
由于我们只想找到导数幅值的最大值和最小值,对上式作如下简化:
这样近似能够满足计算要求, 因为开平方和平方函数都是单调的, 实际计算幅度的最大值、最小值与近似以后计算的最大值、最小值发生在图像的同一个地方。并且,与计算平方和开平方相比,计算绝对值所用的硬件资源少得多。我们需要重复地计算图像中每个像素位置的导数幅值。 但是, 注意到环绕图像边缘的像素点并没有一个完整的相邻像素组来计算偏导数和导数, 所以我们需要对这些像素进行单独处理。最简单的方法就是把图像中边缘像素点的导数值值 |D|设置为 0。
3. world技术文档如下
3.1 顶层设计
3.2顶层架构
3.3 sobel计算器的架构
3.4具体节拍分析如下图(流水线方式)
4. 根据设计的技术文档设计verilog代码
4.1 首先例化存储器模块(MEM)
存储器模块, 只需当外部电路产生既定读写时序时, 其能将数据送往MEM进行数据的存储。存储器读写时序与 Sobel 从机接口读写
时序一致。 此外, 存储器模型中, 在初始化阶段便将一幅原始图像的像素值导入到存储器中,模拟摄像头图像采集功能; 而当原始图像完成边缘检测后, 我们没有将导数像素值真正地存入存储器中,而是将其直接存为文件,以供验证
module memory(clk, rst_n, data_out, data_in, addr, write, read);
input clk, rst_n;
input [31:0] data_out;
output reg [31:0] data_in;
input [21:0] addr;
input write, read;
reg [7:0] mem [22'h3fffff:0];
integer DATAFILE;
initial begin
$readmemh("bmp1.txt", mem);
end
initial begin
DATAFILE = $fopen("post1.txt");
end
always @ (posedge clk)
begin
if(write)
$fdisplay(DATAFILE, "", data_out[31:24], data_out[23:16], data_out[15:8], data_out[7:0]);
else if(read)
data_in <= {mem[addr], mem[addr+1], mem[addr+2], mem[addr+3]};
end
endmodule
4.2实现检测算法的顶层模块verilog代码
根据技术文档,分别由 地址模块(addr_gen) , (计算模块)computer ,(控制器模块) sfz_fsm 构成,代码如下:
module sobel_filter_zx1704(clk, rst_n, src_addr, dest_addr, start, done, data_out, data_in, addr, write, read);
parameter WIDTH = 600;
parameter HIGH = 400;
input clk, rst_n;
input [21:0] src_addr, dest_addr;
input start;
output done;
output [31:0] data_out;
input [31:0] data_in;
output [21:0] addr;
output write, read;
wire pr_send, cr_send, nr_send, dr_send, pr_load, cr_load, nr_load, shift_en, set_zero, row_buf_load;
addr_gen
#(.WIDTH(WIDTH), .HIGH(HIGH))
AG(
.clk(clk),
.rst_n(rst_n),
.src_addr(src_addr),
.dest_addr(dest_addr),
.pr_send(pr_send),
.cr_send(cr_send),
.nr_send(nr_send),
.dr_send(dr_send),
.addr(addr),
.start(start)
);
computer COM(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.pr_load(pr_load),
.cr_load(cr_load),
.nr_load(nr_load),
.shift_en(shift_en),
.data_out(data_out),
.row_buf_load(row_buf_load),
.set_zero(set_zero)
);
sfz_fsm
#(.WIDTH(WIDTH), .HIGH(HIGH))
SFSM(
.clk(clk),
.rst_n(rst_n),
.start(start),
.done(done),
.write(write),
.read(read),
.pr_send(pr_send),
.cr_send(cr_send),
.nr_send(nr_send),
.dr_send(dr_send),
.pr_load(pr_load),
.cr_load(cr_load),
.nr_load(nr_load),
.shift_en(shift_en),
.row_buf_load(row_buf_load),
.set_zero(set_zero)
);
endmodule
4.3 地址模块(addr_gen)
地址模块主要作用是对(MEM)中的地址进行控制
这是一个简单的有限自动机。可以直接写代码:
1.1 start后,AG必须捕获src_addr和dest_addr至内部寄存器(例如内部22位的src_pr, src_cr, src_nr和dest)
1.2 因此,start后,必须将src_addr装到src_pr
1.3 将src_addr+WIDTH装到src_cr
1.4 将src_addr+2*WIDTH装到src_nr
1.5 将dest_addr装到dest
1.6 之后,在沿敏感中用if语句,检测pr_send为真,则将src_pr装配到addr,并且src_pr加4
1.7 之后,用else if语句,检测cr_send为真,则将src_cr装配到addr,并且src_cr加4
1.8 之后,用else if语句,检测nr_send为真,则将src_nr装配到addr,并且src_nr加4
1.9 之后,用else if语句,检测dr_send为真,则将dest装配到addr