实现图像的边沿检测算法设计

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 顶层设计

实现图像的边沿检测算法设计_第1张图片

3.2顶层架构

实现图像的边沿检测算法设计_第2张图片

3.3 sobel计算器的架构

实现图像的边沿检测算法设计_第3张图片

3.4具体节拍分析如下图(流水线方式)

实现图像的边沿检测算法设计_第4张图片

实现图像的边沿检测算法设计_第5张图片

实现图像的边沿检测算法设计_第6张图片

实现图像的边沿检测算法设计_第7张图片

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部分偏导数的计算:

  1. 从无符号类型的Z1~Z9,根据公式(见Gonzalez教材260页公式),注意这里Z1至Z9组成的图像卷积模板,其Z1是图像的左上角,Z9是右下角(注意不要平方,使用偏导数的绝对值之和)
  2. 由于有负数参与运算,故这里要用有符号加法器
  3. 因此,模板中系数为+1的部分,就是加上Zi的正数
  4. 模板中系数为-1的部分,就是加上Zi的负数
  5. 模板中系数为+2的部分,就是Zi左移一位后,加上其正数
  6. 模板中系数为-2的部分,就是Zi左移一位后,加上其负数
  7. 有符号加法器的最后和,就是偏导数GX和GY,它们必须定义成11位的有符号数:
    reg signed [10:0] gx, gy;
  8. 绝对值的计算,可以这样:
    reg signed [10:0] abs_gx, abs_gy;

    if (gx > 0)
    abs_gx = gx;
    else
    abs_gx = -gx;
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 

仿真波形:

实现图像的边沿检测算法设计_第8张图片

结果分析:

1.根据波形观测数据输入之后,经过程序运算之后,数据输出正常。

2.用一幅图像进行检测如下:

    原始图像

实现图像的边沿检测算法设计_第9张图片

    处理过后的图像

实现图像的边沿检测算法设计_第10张图片

3.对比两幅图像可知设计的算法基本实现边沿检测的效果,经过推导简化的sobel边缘检测算法实现方法更加简单易行,满足了传统算法最优化的准则,具有较好的精确性和方便使用性的特点。

4.边缘检测对于图像理解,图像识别, 图像分析来说是一个基础性的课题,是图像分割视觉匹配的基础,也是我们实验室研究的领域重要的基础内容,将继续研究算法的优化,提高运行效率。

你可能感兴趣的:(实现图像的边沿检测算法设计)