zoukankan      html  css  js  c++  java
  • 14图像边缘检测的sobel_ctrl控制模块

    一设计功能

    计算得到的dxy,再通过和阈值比较大小,输出po_sum作为VGA的输入,在显示器器上显示图像的轮廓。

    二设计思路

       根据前一篇博客对sobel算法的介绍,先通过FIFO的双流水线操作采集到三行三列的九个数,再得到dx  和dy,再求dx 和dy的绝对值和给Dxy,最后把Dxy 和阈值比较大小得到输出po_sum。

    (一)双FIFO的流水线操做

    根据FIFO的时序图:写的读写控制代码如下

    注意在实际过程中加一个wr_cnt来计算数据的个数。一般先设置一个FIFOIP核,再自己写一个控制程序。根据上面的读写时序,控制程序如下

    module fifo_ctrl(

           input      wire              clk,

           input      wire              rst_n

           );

    reg                              wr_en;

    reg         [8:0]              wr_cnt;

    reg         [7:0]              wr_data;

    wire                                   full,empty;

    reg                              rd_en;

    reg         [1:0]              empty_dly;

    reg                              read_start;

    reg                [8:0]              rd_cnt;

    wire              [7:0]              rd_data;

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  wr_en <= 1'b0;

           end

           else if (wr_cnt == 'd0) begin

                  wr_en <= 1'b1;

           end

           else if (wr_cnt== 'd257) begin

                  wr_en <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  wr_cnt <= 'd0;

           end

           else if (wr_cnt != 'd257) begin

                  wr_cnt <= wr_cnt + 1'b1;

           end

    end

    always @(posedge clk or  negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  wr_data <='d0;

           end

           else if (wr_en == 1'b1) begin

                  wr_data <= wr_data + 1'b1;

           end

           else begin

                  wr_data <= 'd0;

           end

    end

    always @(posedge clk) begin

           empty_dly <= {empty_dly[0],empty};//shift reg empty_dly == 2'b10 negedge

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  read_start <= 1'b0;

           end

           else if (empty_dly == 2'b10) begin

                  read_start <= 1'b1;

           end

           else begin

                  read_start <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  rd_en <= 1'b0;

           end

           else if(rd_cnt == 256)begin

                  rd_en <= 1'b0;

           end

           else if (read_start == 1'b1) begin

                  rd_en <= 1'b1;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  rd_cnt <= 'd0;

           end

           else if (rd_en == 1'b1) begin

                  rd_cnt <= rd_cnt + 1'b1;

           end

    end

    asfifo_w256x8_r256x8 fifo_inst (

      .wr_clk(clk), // input wr_clk

      .rd_clk(clk), // input rd_clk

      .din(wr_data), // input [7 : 0] din

      .wr_en(wr_en), // input wr_en

      .rd_en(rd_en), // input rd_en

      .dout(rd_data), // output [7 : 0] dout

      .full(full), // output full

      .empty(empty) // output empty

    );

    endmodule

         1. sobel_ctrl控制模块的关键点一:双FIFO流水线操作

    有了上面一个FIFO的读写控制代码基础,现在来实现怎么设计实现双FIFO的流水线操作

    /根据时序图写代码时,一定得懂设计思路

    //FIFO1的写使能怎么产生,写数据来源于什么

    //FIFO2的写使能怎么产生,写数据来源于什么

    //读信号怎么产生,读出的数据给谁,读信号有效才加

    来回答上面的问题:

      第一部分:写使能信号的产生

    FIFO1的写使能信号:在0行时,来一个pi_flag 就拉高。对于2~84行的数据,利用wr_en1_pre1 和wr_en1_pre2进行打一拍操作,在2~84行,只要有pi_flag 就拉高wr_en1_pre2。

    FIFO2的写使能怎么产生:在1~84行且来一个pi_flag 就拉高wr_en2

    第二部分:写数据的产生

    FIFO1:在第0行,fifo1的数据来源于串口接收端。对于2~84行的数据,FIFO1的数据来源于FIFO2的读出端数据。

    FIFO2:在1~84行,FIFO2的数据来源于串口的数据(来一个pi_flag)

       第三部分:读信号和读出的数据输出给谁

    对于FIFO1和FIFO2的读出端数据dout1和dout2.对于2~85行的数据,在读信号的控制下,读出后直接在加标志信号flag_add控制下,将FIFO1 和FIFO2的读出端数据和串口接收端的数据加起来输出给PO_SUM

       读信号rd_en,在大于第二行,且来一个pi_flag 就拉高rd_en

    根据上面双FIFO流水线设计思路的要点,编写代码如下:

    parameter VALUE = 10 ; //阈值

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  wr_en1 <= 1'b0;

           end

           else if (cnt_row == 'd0) begin

                  wr_en1 <= pi_flag;

           end

           else begin

                  wr_en1 <= wr_en1_pre1;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  wr_en1_pre2 <= 1'b0;

           end

           else if (cnt_row >=2 && cnt_row <=198 && pi_flag == 1'b1) begin

                  wr_en1_pre2 <= 1'b1;

           end

           else begin

                  wr_en1_pre2 <= 1'b0;

           end

    end

    always @(posedge clk) begin

           wr_en1_pre1 <= wr_en1_pre2;

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  rd_en <= 1'b0;

           end

           else if (cnt_row >=2 && cnt_row <=199 && pi_flag == 1'b1) begin

                  rd_en <= 1'b1;

           end

           else begin

                  rd_en <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  data_in1 <= 'd0;

           end

           else if (cnt_row == 'd0) begin

                  data_in1 <= pi_data;

           end

           else  begin//if (cnt_row >=2 && cnt_row <=84)

                  data_in1 <= dout2;

           end

    end

    always @(posedge clk or negedge rst_n ) begin

           if (rst_n  == 1'b0) begin

                  // reset

                  wr_en2 <= 1'b0;

           end

           else if (cnt_row >=1 && cnt_row <=198 && pi_flag == 1'b1) begin

                  wr_en2 <= 1'b1;

           end

           else begin

                  wr_en2 <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  data_in2 <= 'd0;

           end

           else if ( cnt_row >=1 && cnt_row <=198) begin

                  data_in2 <= pi_data;

           end

    end

    always @(posedge clk) begin

           add_flag <= flag_shift;

    end

    2. sobel_ctrl控制模块的关键点二:怎么采集三行三列的9个数

    关键是怎么采集三行三列的9个数,我的想法是,比如第零行用FIFO1存储,然后再加三个寄存器,打拍操作。每来一个串口接收标志信号就送FIFO1的数据到如寄存器1中,然后是寄存器2,再是寄存器3。

    怎么采集三行三列的9个数

    //注意打拍的寄存器越往后是越早的数据

    //如pi_data是最新的数据,dout1_tt是前两个数据

    reg flag_shift;

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  flag_shift <= 1'b0;

           end

           else begin

                  flag_shift <= rd_en;

           end

    end

     //打拍操作,三个寄存器在标志信号下打一拍存一个数

    always@(posedge clk)begin

           if(flag_shift == 1'b1)begin

           {dout1_tt,dout1_t} <= {dout1_t,dout1};

           {dout2_tt,dout2_t} <= {dout2_t,dout2};

           {rx_data_tt,rx_data_t}<={rx_data_t,pi_data};

           end

    end

    3. sobel_ctrl控制模块的关键点三:怎么得到横向的DX和纵向的DY?

    always @(posedge clk) begin

           add_flag <= flag_shift;

    end

    //对这三行三列的数进行加减乘运算

    //((dout2-dout2_tt)<<1)实现乘2操作

    reg [7:0]dx;

    always@(posedge clk or negedge rst_n)

           if(rst_n==0)

           dx<=0;

    else if(add_flag)

           dx<=(dout1-dout1_tt)+((dout2-dout2_tt)<<1)+(pi_data-rx_data_tt);

           //对这三行三列的数进行加减乘运算

    reg [7:0]dy;

    always@(posedge clk or negedge rst_n)

           if(rst_n==0)

           dy<=0;

    else if(add_flag)

           dy<=(dout1_tt-rx_data_tt)+((dout1_t-rx_data_t)<<1)+(dout1-pi_data);

    4. sobel_ctrl控制模块的关键点四:怎么得到横向的DX和纵向的DY的绝对值和?

    利用原码反码,补码的关系。即最高位为符号位,1表示负数,绝对值为取反加一。0表示正数,绝对值等于本身。值得注意的是进行绝对值前,利用flag_d_pre,flag_d, flag_abs延迟三个时钟周期,打了三拍。再求绝对值的和,又利用flag_dxy打了一拍。

    //对dx和dy进行绝对值求和

           //补码原码反码的关系

    //flag_d_pre

    reg flag_d_pre;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_d_pre<=0;

    else if(cnt_row>=2 && cnt_col>=2 && pi_flag)

           flag_d_pre<=1;

    else

           flag_d_pre<=0;

    //flag_d;

    reg flag_d;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_d<=0;

    else

           flag_d<=flag_d_pre;

           //falg_abs

    reg flag_abs;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_abs<=0;

    else

           flag_abs<=flag_d;

    //abs_dx

    reg [7:0]abs_dx;

    reg [7:0]abs_dy;

    always@(posedge clk or negedge rst_n)

    if(!rst_n)

           abs_dx<=0;

    else if(flag_abs & dx[7]==1'b1)

           abs_dx<=(~dx)+1;

    else if(flag_abs & dx[7]==1'b0)

           abs_dx<=dx;

    //abs_dy 

    always@(posedge clk or negedge rst_n)

    if(!rst_n)

           abs_dy<=0;

    else if(flag_abs & dy[7]==1'b1)

           abs_dy<=(~dy)+1;

    else if(flag_abs & dy[7]==1'b0)

           abs_dy<=dy;

    //取了绝对值进行打拍

    reg flag_dxy;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_dxy<=0;

    else

           flag_dxy<=flag_abs;

    reg [7:0]dxy;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           dxy<=0;

    else if(flag_dxy)

           dxy<=abs_dy+abs_dx;

    5. sobel_ctrl控制模块的关键点五:怎么阈值比较得到边缘,大于阈值白色显示,否则黑色?

    //判断相对于阈值大小输出不同的图像

    reg flag_rgb;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_rgb<=0;

    else

           flag_rgb<=flag_dxy;

    //rgb

    always@(posedge clk or negedge rst_n)

    if(!rst_n)

           po_sum<=0;

    else if(flag_rgb & dxy>=VALUE)

           po_sum<=8'hff;

    else if(flag_rgb & dxy<=VALUE)

           po_sum<=8'h00;  

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           po_flag<=0;

    else

           po_flag<=flag_rgb;

    三,仿真验证

    到这里,终于把图像边缘检测的算法关键点讲清楚了,而我的仿真是先利用80X4的数据,即用sublime的重复语句产生一个0~320的数,然后再为仿真的输入,进行波形的验证。

    在sublime中利用敲出{:08b},然后再选中这个代码,再右键选择重复代码,之后在设置起始位和停止位及步长,,敲回车即可。

    如0~320,1

    RAM一般最大为9K,即1024X9个数据。

    刚开始我写的仿真程序包含了串口发送有点复杂,所以在这里提高vga_ram模块的仿真,两者很相似,只需要改哈端口就行,设计思想相同。把上面产生的数据保存在一个txt文件里,在仿真里用mem读取这个文本,再用一个task函数把这数据赋值给我们功能模块vga_ram的输入pi_data,如程序所写。b第二个注意点,如要产生300X3的数据,不用写一个矩阵的仿真程序,直接并转串,用一个900X1的数据表示,结果是一样,还很方便。

    `timescale 1ns / 1ps

    module tb_vga_ram;

           // Inputs

           reg sclk;

           reg rst_n;

           reg pi_flag;

           reg [7:0]pi_data;

           reg [7:0] mem[899:0];

           // Outputs

           wire hsync ;

           wire vsync  ;

           wire [7:0]vga_rgb;

           vga_ram  inst_vga_ram (

                         .clk     (sclk),

                         .rst_n   (rst_n),

                         .pi_flag (pi_flag),

                         .pi_data (pi_data),

                         .hsync   (hsync),

                         .vsync   (vsync),

                         .vga_rgb (vga_rgb)

                  );

           // Instantiate the Unit Under Test (UUT)

           initial begin

                  // Initialize Inputs

                  sclk = 0;

                  rst_n = 0;

                  pi_flag=0;

                  pi_data=0;

                  // Wait 100 ns for global reset to finish

                  #100;

            rst_n =1;

                  // Add stimulus here

           end

           initial begin

                  $readmemb("./data.txt",mem);

           end

           always #10 sclk = ~sclk;

           always #100 pi_flag = ~pi_flag;

           initial begin

                  #200;

                  rx_byte();

           end

           task rx_byte;

                  integer i;

                  integer j;

                  begin

                         for(j=0;j<900;j=j+1)begin

                                for (i=0;i<900;i=i+1)begin

                                       pi_data=mem[i];

                                #199;

                                end

                         end

                  end

           endtask

       

    endmodule

    波形如下:

  • 相关阅读:
    ADF中遍历VO中的行数据(Iterator)
    程序中实现两个DataTable的Left Join效果(修改了,网上第二个DataTable为空,所处的异常)
    ArcGIS api for javascript——鼠标悬停时显示信息窗口
    ArcGIS api for javascript——查询,然后单击显示信息窗口
    ArcGIS api for javascript——查询,立刻打开信息窗口
    ArcGIS api for javascript——显示多个查询结果
    ArcGIS api for javascript——用图表显示查询结果
    ArcGIS api for javascript——查询没有地图的数据
    ArcGIS api for javascript——用第二个服务的范围设置地图范围
    ArcGIS api for javascript——显示地图属性
  • 原文地址:https://www.cnblogs.com/Xwangzi66/p/12968016.html
Copyright © 2011-2022 走看看