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,并且dest加4
module addr_gen(clk, rst_n, src_addr, dest_addr, pr_send, cr_send, nr_send, dr_send, start, addr);
parameter WIDTH = 600;
parameter HIGH = 400;
input clk, rst_n;
input [21:0] src_addr, dest_addr;
input pr_send, cr_send, nr_send, dr_send, start;
output reg [21:0] addr;
reg [21:0] src_pr, src_cr, src_nr, dest;
always @ (posedge clk)
begin
if (!rst_n)
begin
src_pr <= 0;
src_cr <= 0;
src_nr <= 0;
dest <= 0;
addr <= 0;
end
else if (start)
begin
src_pr <= src_addr;
src_cr <= src_addr + WIDTH;
src_nr <= src_addr + 2*WIDTH;
dest <= dest_addr;
end
else if (pr_send)
begin
addr <= src_pr;
src_pr <= src_pr + 22'd4;
end
else if (cr_send)
begin
addr <= src_cr;
src_cr <= src_cr + 22'd4;
end
else if (nr_send)
begin
addr <= src_nr;
src_nr <= src_nr + 22'd4;
end
else if (dr_send)
begin
addr <= dest;
dest <= dest + 22'd4;
end
end
endmodule
4.4 算法运算模块(computer)
主要负责边缘检测算法的数学运算,关于computer部分偏导数的计算:
module computer(clk, rst_n, data_in, pr_load, cr_load, nr_load, shift_en, data_out, row_buf_load, set_zero);
input clk, rst_n;
input row_buf_load, set_zero;
input [31:0] data_in;
input pr_load, cr_load, nr_load, shift_en;
output [31:0] data_out;
reg [31:0] pr, cr, nr;
reg [31:0] prb, crb, nrb;
reg [7:0] z1, z2, z3, z4, z5, z6, z7, z8, z9;
reg signed [10:0] dx, dy;
reg [7:0] abs;
reg [47:0] res_reg;
always @(posedge clk)
begin : PR
if(!rst_n)
pr <= 0;
else if(pr_load)
pr <= data_in;
end
always @(posedge clk)
begin : CR
if(!rst_n)
cr <= 0;
else if(cr_load)
cr <= data_in;
end
always @(posedge clk)
begin : NR
if(!rst_n)
nr <= 0;
else if(nr_load)
nr <= data_in;
end
always @(posedge clk)
begin : PRB
if(row_buf_load)
prb <= pr;
else if(shift_en)
prb[31:8] <= prb[23:0];
end
always @(posedge clk)
begin : CRB
if(row_buf_load)
crb <= cr;
else if(shift_en)
crb[31:8] <= crb[23:0];
end
always @(posedge clk)
begin : NRB
if(row_buf_load)
nrb <= nr;
else if(shift_en)
nrb[31:8] <= nrb[23:0];
end
always @(posedge clk)
begin : Z1_Z9
if(shift_en) begin
z3 <= prb[31:24];
z6 <= crb[31:24];
z9 <= nrb[31:24];
z2 <= z3;
z5 <= z6;
z8 <= z9;
z1 <= z2;
z4 <= z5;
z7 <= z8;
end
end
always @(posedge clk)
begin : DXY
if(shift_en) begin
dx <= -$signed({3'b000, z1}) + $signed({3'b000, z3}) - ($signed({3'b000, z4})<<1)
+ ($signed({3'b000, z6})<<1) - $signed({3'b000, z7}) + $signed({3'b000, z9});
dy <= $signed({3'b000, z1}) + ($signed({3'b000, z2})<<1) + $signed({3'b000, z3})
- $signed({3'b000, z7}) - ($signed({3'b000, z8})<<1) - $signed({3'b000, z9});
end
end
function [10:0] absd (input signed [10:0] x);
absd = (x > 0) ? x : -x;
endfunction
always @(posedge clk)
begin : ABSD
if(set_zero)
abs <= 0;
else if(shift_en)
abs <= (absd(dx) + absd(dy)) >> 3;
end
always @(posedge clk)
begin : RR
if(shift_en) begin
res_reg[47:8] <= res_reg[39:0];
res_reg[7:0] <= abs;
end
end
assign data_out = res_reg[47:16];
endmodule
4.5 控制模块(sfz_fsm)
根据状态转移图设计控制模块,控制驱动各个模块的执行顺序和过程
module sfz_fsm(clk, rst_n, start, done, write, read, pr_send, cr_send, nr_send, dr_send, pr_load, cr_load, nr_load, shift_en, row_buf_load,
set_zero);
parameter WIDTH = 600;
parameter HIGH = 400;
input clk, rst_n;
input start;
output reg pr_send, cr_send, nr_send, dr_send, pr_load, cr_load, nr_load, shift_en;
output reg done, write, read;
output reg row_buf_load, set_zero;
reg [1:0] beat;
reg [7:0] col;
reg [8:0] row;
always @ (posedge clk)
begin : LSM_1S
if(!rst_n)
begin
row <= 0;
col <= 0;
beat <= 0;
end
else
casex({row, col, beat})
{9'd0, 8'd0, 2'd0} : if(start) beat <= 1;
{9'dx, 8'dx, 2'd3} : if(row == 398 && col == 3)
begin
beat <= 0;
col <= 0;
row <= 0;
end
else if(col == 149)
begin
beat <= 0;
col <= 0;
row <= row + 1;
end
else
begin
beat <= 0;
col <= col + 1;
end
default : beat <= beat + 1;
endcase
end
always @ (posedge clk)
begin : LSM_2S
if(!rst_n)
task_reset;
else if(row == 0 && col == 0)
task_r0c0;
else if(row == 0 && col == 1)
task_r0c1;
else if(row == 0 && col == 2)
task_r0c2;
else if(row == 0 && col == 3)
task_r0c3;
else if(col == 2)
task_c2;
else if(row == 398 && col == 3)
task_end;
else
task_c4;
end
task task_reset;
begin
read <= 0;
write <= 0;
pr_send <= 0;
cr_send <= 0;
nr_send <= 0;
dr_send <= 0;
pr_load <= 0;
cr_load <= 0;
nr_load <= 0;
row_buf_load <= 0;
set_zero <= 0;
shift_en <= 0;
done <= 1;
end
endtask
task task_r0c0;
case(beat)
0 : if(start) begin pr_send <= 1; done <= 0; end
1 : begin read <= 1; cr_send <= 1; pr_send <= 0; end
2 : begin pr_load <= 1; read <= 1; nr_send <= 1; cr_send <= 0; end
3 : begin cr_load <= 1; read <= 1; pr_load <= 0; nr_send <= 0; end
endcase
endtask
task task_r0c1;
case(beat)
0 : begin pr_send <= 1; nr_load <= 1; cr_load <= 0; read <= 0; end
1 : begin read <= 1; cr_send <= 1; row_buf_load <= 1; pr_send <= 0; nr_load <= 0; end
2 : begin pr_load <= 1; read <= 1; nr_send <= 1; cr_send <= 0; row_buf_load <= 0; shift_en <= 1; end
3 : begin cr_load <= 1; read <= 1; pr_load <= 0; nr_send <= 0; end
endcase
endtask
task task_r0c2;
case(beat)
0 : begin pr_send <= 1; nr_load <= 1; cr_load <= 0; read <= 0; end
1 : begin read <= 1; cr_send <= 1; row_buf_load <= 1; pr_send <= 0; nr_load <= 0; set_zero <= 1; end
2 : begin pr_load <= 1; read <= 1; nr_send <= 1; cr_send <= 0; row_buf_load <= 0; set_zero <= 0; end
3 : begin cr_load <= 1; read <= 1; pr_load <= 0; nr_send <= 0; end
endcase
endtask
task task_r0c3;
case(beat)
0 : begin pr_send <= 1; nr_load <= 1; cr_load <= 0; read <= 0; end
1 : begin read <= 1; cr_send <= 1; row_buf_load <= 1; pr_send <= 0; nr_load <= 0; end
2 : begin pr_load <= 1; read <= 1; nr_send <= 1; cr_send <= 0; row_buf_load <= 0; end
3 : begin cr_load <= 1; read <= 1; dr_send <= 1; pr_load <= 0; nr_send <= 0; end
endcase
endtask
task task_c4;
case(beat)
0 : begin pr_send <= 1; nr_load <= 1; write <= 1; cr_load <= 0; read <= 0; dr_send <= 0; end
1 : begin read <= 1; cr_send <= 1; row_buf_load <= 1; pr_send <= 0; nr_load <= 0; write <= 0; end
2 : begin pr_load <= 1; read <= 1; nr_send <= 1; cr_send <= 0; row_buf_load <= 0; end
3 : begin cr_load <= 1; read <= 1; dr_send <= 1; pr_load <= 0; nr_send <= 0; end
endcase
endtask
task task_c2;
case(beat)
0 : begin pr_send <= 1; nr_load <= 1; write <= 1; cr_load <= 0; read <= 0; dr_send <= 0; set_zero <= 1; end
1 : begin read <= 1; cr_send <= 1; row_buf_load <= 1; pr_send <= 0; nr_load <= 0; write <= 0; set_zero <= 1; end
2 : begin pr_load <= 1; read <= 1; nr_send <= 1; cr_send <= 0; row_buf_load <= 0; set_zero <= 0; end
3 : begin cr_load <= 1; read <= 1; dr_send <= 1; pr_load <= 0; nr_send <= 0; end
endcase
endtask
task task_end;
case(beat)
0 : begin pr_send <= 1; nr_load <= 1; write <= 1; cr_load <= 0; read <= 0; dr_send <= 0; end
1 : begin read <= 1; cr_send <= 1; row_buf_load <= 1; pr_send <= 0; nr_load <= 0; write <= 0; end
2 : begin pr_load <= 1; read <= 1; nr_send <= 1; cr_send <= 0; row_buf_load <= 0; end
3 : begin cr_load <= 1; read <= 1; dr_send <= 1; pr_load <= 0; nr_send <= 0; done <= 1; end
endcase
endtask
endmodule
4.6 各大模块之下小模块代码
module row_shift(clk, rst_n, data_in, load, shift, shift_num);
input clk, rst_n;
input [31:0] data_in;
input load, shift;
output reg [7:0] shift_num;
reg [31:0] shift_temp;
always @ (posedge clk)
begin
if (!rst_n)
begin
shift_num <= 0;
shift_temp <= 0;
end
else if (load)
shift_temp <= data_in;
else if (shift)
begin
shift_num <= shift_temp[31:24];
shift_temp <= shift_temp << 8;
end
end
endmodule
module unit(clk, rst_n, shift_en, num_in, num_out);
input clk, rst_n;
input shift_en;
input [7:0] num_in;
output reg [7:0] num_out;
always @ (posedge clk)
begin
if (!rst_n)
num_out <= 0;
else if (shift_en)
num_out <= num_in;
end
endmodule
module dx_dy(clk, rst_n, shift_en, z1, z2, z3, z4, z5, z6, z7, z8, z9, dx, dy);
input clk, rst_n;
input shift_en;
input [7:0] z1, z2, z3, z4, z5, z6, z7, z8, z9;
output reg signed [10:0] dx, dy;
reg signed [10:0] dx_s, dy_s;
always @ (posedge clk)
begin
if (!rst_n)
begin
dx_s <= 0;
dy_s <= 0;
end
else if (shift_en)
begin
dx_s <= -$signed({3'b000, z1}) + $signed({3'b000, z3}) - ($signed({3'b000, z4}) << 1)
+ ($signed({3'b000, z6}) << 1) - $signed({3'b000, z7}) + $signed({3'b000, z9});
dy_s <= -$signed({3'b000, z7}) + $signed({3'b000, z1}) - ($signed({3'b000, z8}) << 1)
+ ($signed({3'b000, z2}) << 1) - $signed({3'b000, z9}) + $signed({3'b000, z3});
end
end
always @ (*)
begin
if (dx_s > 0)
dx = dx_s;
else
dx = -dx_s;
end
always @ (*)
begin
if (dy_s > 0)
dy = dy_s;
else
dy = -dy_s;
end
endmodule
module abs_d(clk, rst_n, shift_en, dx, dy, abs);
input clk, rst_n;
input shift_en;
input [10:0] dx, dy;
output [7:0] abs;
reg [10:0] abs_t;
assign abs = abs_t[7:0];
always @ (posedge clk)
begin
if (!rst_n)
abs_t <= 0;
else if (shift_en)
abs_t <= (dx + dy) >> 3;
end
endmodule
module res_reg(clk, rst_n, shift_en, abs, data_out);
input clk, rst_n;
input shift_en;
input [7:0] abs;
output reg [31:0] data_out;
always @ (posedge clk)
begin
if (!rst_n)
data_out <= 0;
else if (shift_en)
data_out <= {data_out[23:0], abs};
end
endmodule
4.7 仿真测试程序如下
`timescale 1ns/1ps
module sobel_filter_zx1704_tb;
reg clk, rst_n;
reg start;
reg [21:0] src_addr, dest_addr;
wire done, write, read;
wire [31:0] data_out, data_in;
wire [21:0] addr;
sobel_filter_zx1704 DUT(
.clk(clk),
.rst_n(rst_n),
.src_addr(src_addr),
.dest_addr(dest_addr),
.start(start),
.done(done),
.data_out(data_out),
.data_in(data_in),
.addr(addr),
.write(write),
.read(read)
);
memory MEM(
.clk(clk),
.rst_n(rst_n),
.data_out(data_out),
.data_in(data_in),
.addr(addr),
.write(write),
.read(read)
);
initial begin
clk = 1;
rst_n = 0;
start = 0;
src_addr = 0;
dest_addr = 0;
#200
@(posedge clk)
rst_n = 1;
#200
@(posedge clk)
start = 1;
dest_addr = 22'h100000;
src_addr = 0;
@(posedge clk)
start = 0;
#200
@(posedge done)
#200
$stop;
end
always #10 clk = ~clk;
endmodule
仿真波形:
1.根据波形观测数据输入之后,经过程序运算之后,数据输出正常。
2.用一幅图像进行检测如下:
原始图像
处理过后的图像
3.对比两幅图像可知设计的算法基本实现边沿检测的效果,经过推导简化的sobel边缘检测算法实现方法更加简单易行,满足了传统算法最优化的准则,具有较好的精确性和方便使用性的特点。
4.边缘检测对于图像理解,图像识别, 图像分析来说是一个基础性的课题,是图像分割视觉匹配的基础,也是我们实验室研究的领域重要的基础内容,将继续研究算法的优化,提高运行效率。