查看: 137|回复: 0

[教程] 用FPGA 实现图像边缘检测

[复制链接]

61

主题

0

好友

1223

积分

进士

Rank: 5Rank: 5

  • TA的每日心情
    开心
    2015-7-14 09:10
  • 签到天数: 9 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2017-7-18 09:45:38 |显示全部楼层
    本文主要内容是实现图像的边缘检测功能

    目录
    • mif文件的制作
    • 调用 ip 核生成rom以及在 questasim 仿真注意问题
    • 灰度处理
    • 均值滤波:重点是3*3 像素阵列的生成
    • sobel边缘检测
    • 图片的显示
    • 结果展示

    mif文件的制作受资源限制,将图片像素定为 160 * 120,将图片数据制成 mif 文件,对 rom ip 核进行初始化。mif文件的制作方法网上有好多办法,因此就不再叙述了,重点说mif文件的格式。
    1、mif文件的格式为:
    1. 1 WIDTH=16 ;    //数据位宽
    2. 2 DEPTH=19200 ;   // rom 深度即图片像素点的个数
    3. 3 ADDRESS_RADIX=UNS ;   //地址数据格式
    4. 4 DATA_RADIX=BIN ;   //数据格式
    5. 5 CONTENT
    6. 6 BEGIN
    7. 7 0:1010110011010000 ;     // 地址 :数据 ;注意格式要和上面定义的保持统一
    8. 8 1:1010110011010000 ;
    9. 9 2:1010010010110000 ;
    10. 10 ......
    11. 11 19198:1110011011111001 ;
    12. 12 19199:1110011011011000 ;
    13. 13 END;
    复制代码
    调用ip 核生成 rom 以及在 questasim 仿真注意问题这部分内容见上篇链接:http://www.eeboard.com/bbs/thread-96158-1-1.html

    灰度处理任何颜色都由红、绿、蓝三原色组成,假如原来某点的颜色为( R,G,B )那么,我们可以通过下面几种方法,将其转换为灰度:
    • 浮点算法:Gray=0.299R+0.587G+0.114B

    • 平均值法:Gray=(R+G+B)/3;

    • 仅取单色(如绿色):Gray=G;

    将计算出来的Gray值同时赋值给 RGB 三个通道即RGB为(Gray,Gray,Gray),此时显示的就是灰度图。通过观察调色板就能看明了。 通过观察可知,当RGB三个通道的值相同时即为灰色,Gray的值越大,颜色越接近白色,反之越接近黑色(这是我自己的理解,不严谨错误之处请大神指正)。

    TIM截图20170718090424.png

    此次我采用是浮点算法来实现灰度图的,我的图片数据是RGB565 格式 ,
    难点: 如何进行浮点运算。
    思路:先将数据放大,然后再缩小。

    例如:
    Gray=0.299R+0.587G+0.114B转化为 Gray=(77R+150G+29B)>>8 即可,这里有一个技巧,若 a 为 16 位即 a [15:0],那么 a>>8 与 a [15:8]是一样的。
    核心代码如下:
    1. always  @(posedge clk or negedge rst_n)begin
    2.     if(rst_n==1'b0)begin
    3.        red_r1   <= 0 ;  
    4.        green_r1 <= 0 ;
    5.        blue_r1  <= 0 ;
    6.     end
    7.     else begin
    8.        red_r1   <= red   * 77 ;        //放大后的值
    9.        green_r1 <= green * 150;
    10.        blue_r1  <= blue  * 29 ;
    11.     end
    12. end

    13. always  @(posedge clk or negedge rst_n)begin
    14.     if(rst_n==1'b0)begin
    15.         Gray <= 0;    // 三个数之和
    16.     end
    17.     else begin
    18.         Gray <= red_r1 + green_r1 + blue_r1;        
    19.     end
    20. end

    21. always  @(posedge clk or negedge rst_n)begin
    22.     if(rst_n==1'b0)begin
    23.        post_data_in <= 0;  //输出的灰度数据
    24.     end
    25.     else begin
    26.        post_data_in <= { Gray[13:9], Gray[13:8], Gray[13:9] };//将Gray值赋值给RGB三个通道
    27.     end
    28. end
    复制代码
    均值滤波

    均值滤波的原理链接:http://www.eeboard.com/bbs/thread-96163-1-1.html

    难点:如何生成 3*3 的像素阵列。
    我们可以利用 ip 核生成移位寄存器 ,方法与 ip 核 生成 rom 一样,详情见目录 2 因此不再赘述 。

    TIM截图20170718090501.png


    仿真波形如下 row_1 , row_2 , row_3 是指图像的第一、二、三行的数据,Per_href 是行有效信号(受VGA时序的启发,从 rom 中读取数据时设计了行有效和场有效的控制信号,事半功倍,有了利于仿真查错和数据的控制)。从 3 开始就出现了3*3 的像素阵列,这时候就可以求取周围 8 个像素点的平均值,进行均值滤波。

    TIM截图20170718090520.png


    下面这个图是我自己画的 FPGA 如何将矩阵数据处理成并行的像素点,可以结合下面的代码好好理解,这也是精华所在。
    正方形红框框起来的是第一个完整的 3*3 矩阵,长方形红框框起来的是并行的像素点,在此基础上就可以求得平均值,进行均值滤波。
    从下图也能看到 3*3 矩阵从左往右滑动。
    第一个3*3 阵列。
    0  1  2   -- >  p11 p12 p13
    3  4  5   -- >  p21 p22 p23
    6  7  8   -- >  p31 p32 p33

    TIM截图20170718090542.png


    核心代码如下:
    1. reg [5:0]p_11,p_12,p_13;  // 3 * 3 卷积核中的像素点
    2. reg [5:0]p_21,p_22,p_23;
    3. reg [5:0]p_31,p_32,p_33;
    4. reg [8:0]mean_value_add1,mean_value_add2,mean_value_add3;//每一行之和


    5. always  @(posedge clk or negedge rst_n)begin
    6.     if(rst_n==1'b0)begin
    7.         {p_11,p_12,p_13} <= {5'b0,5'b0,5'b0}   ;
    8.         {p_21,p_22,p_23} <= {15'b0,15'b0,15'b0};
    9.         {p_31,p_32,p_33} <= {15'b0,15'b0,15'b0};
    10.     end
    11.     else  begin
    12.      if(per_href_ff0==1&&flag_do==1)begin
    13.         {p_11,p_12,p_13}<={p_12,p_13,row_1};
    14.         {p_21,p_22,p_23}<={p_22,p_23,row_2};
    15.         {p_31,p_32,p_33}<={p_32,p_33,row_3};
    16.      end
    17.      else begin
    18.          {p_11,p_12,p_13}<={5'b0,5'b0,5'b0};
    19.          {p_21,p_22,p_23}<={5'b0,5'b0,5'b0}
    20.          {p_31,p_32,p_33}<={5'b0,5'b0,5'b0}
    21.      end
    22.    end
    23. end

    24. always  @(posedge clk or negedge rst_n)begin
    25.     if(rst_n==1'b0)begin
    26.         mean_value_add1<=0;
    27.         mean_value_add2<=0;
    28.         mean_value_add3<=0;
    29.     end
    30.     else if(per_href_ff1)begin
    31.         mean_value_add1<=p_11+p_12+p_13;
    32.         mean_value_add2<=p_21+   0   +p_23;
    33.         mean_value_add3<=p_31+p_32+p_33;
    34.     end
    35. end

    36. wire [8:0]mean_value;//8位数之和
    37. wire [5:0]fin_y_data; //平均数,除以8,相当于左移三位。

    38. assign mean_value=mean_value_add1+mean_value_add2+mean_value_add3;
    39. assign fin_y_data=mean_value[8:3];
    复制代码
    sobel 边缘检测
    边缘检测的原理
    该算子包含两组 3x3 的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。A代表原始图像的 3*3 像素阵列,Gx及Gy分别代表经横向及纵向边缘检测的图像,其公式如下:
    TIM截图20170718090606.png

    图像的每一个像素的横向及纵向梯度近似值可用以下的公式结合,来计算梯度的大小。
    TIM截图20170718092524.png

    如果梯度G大于某一阀值则认为该点(x,y)为边缘点。

    用的是 边缘检测算法。
    难点:(1)掌握了 3*3 像素阵列,Gx 与 Gy 就很好计算了 (注意问题:为了避免计算过程中出现负值,所以将正负值分开单独计算,具体见代码)
            (2)G的计算需要开平方,如何进行开平方运算
    Quartus 提供了开平方 ip 核,因此我们直接调用就好了 。

    TIM截图20170718092729.png


    代码:
    1. reg [8:0] p_x_data ,p_y_data ;  // x 和 y 的正值之和
    2. reg [8:0] n_x_data ,n_y_data ; // x 和 y 的负值之和
    3. reg [8:0] gx_data  ,gy_data  ; //最终结果

    4. always  @(posedge clk or negedge rst_n)begin
    5.     if(rst_n==1'b0)begin
    6.        p_x_data <=0;
    7.        n_x_data <=0;
    8.        gx_data   <=0;
    9.     end
    10.     else if(per_href_ff1==1) begin
    11.         p_x_data <= p_13 + (p_23<<1) + p_33 ;
    12.         n_x_data <= p_11 + (p_12<<1 )+ p_13 ;
    13.         gx_data   <= (p_x_data >=n_x_data)? p_x_data - n_x_data : n_x_data - p_x_data ;
    14.     end
    15.     else begin
    16.          p_x_data<=0;
    17.          n_x_data<=0;
    18.          gx_data <=0;
    19.     end  
    20. end

    21. always  @(posedge clk or negedge rst_n)begin
    22.     if(rst_n==1'b0)begin
    23.        p_y_data <=0;
    24.        n_y_data <=0;
    25.        gy_data   <=0;
    26.     end
    27.     else if(per_href_ff1==1) begin
    28.         p_y_data <= p_11 + (p_12<<1) + p_13 ;
    29.         n_y_data <= p_31 + (p_32<<1) + p_33 ;
    30.         gy_data   <= (p_y_data >=n_y_data)? p_y_data - n_y_data : n_y_data - p_y_data ;
    31.     end
    32.     else begin
    33.         p_y_data <=0;
    34.         n_y_data <=0;
    35.         gy_data   <=0;
    36.    end
    37. end

    38. //求平方和,调用ip核开平方
    39. reg [16:0] gxy; // Gx 与 Gy 的平方和
    40. always  @(posedge clk or negedge rst_n)begin
    41.     if(rst_n==1'b0)begin
    42.         gxy<=0;
    43.     end
    44.     else begin
    45.         gxy<= gy_data* gy_data + gx_data* gx_data ;
    46.     end
    47. end

    48. wire [8:0] squart_out ;
    49. altsquart  u1_altsquart (     //例化开平方的ip核
    50.     .radical (gxy),
    51.     .q       (squart_out),  //输出的结果
    52.     .remainder()
    53.                        );

    54. //与阈值进行比较
    55. reg [15:0] post_y_data_r;
    56. always  @(posedge clk or negedge rst_n)begin
    57.     if(rst_n==1'b0)begin
    58.         post_y_data_r<=16'h00;
    59.     end
    60.     else if(squart_out>=threshold)
    61.          post_y_data_r<=16'h00  ;
    62.     else
    63.          post_y_data_r<=16'hffff  ;
    64.    
    65. end
    复制代码
    图片的显示本来是想用 VGA 来显示图片的,由于条件的限制没能实现,最终只能将处理完的数据输出保存在 .txt 文件中,然后借助好友写的网页进行显示。
    难点:(1) 如何将数据流输出保存到 .txt 文件中。
            (2) 网页的使用及注意事项
    在testbench里加入下面所示代码即可将图片数据保存到 .txt 文本
    代码如下:
    1. integer w_file;  
    2.      initial
    3.      w_file = $fopen("data_out_3.txt");   //保存数据的文件名

    4.      always @(posedge clk or negedge rst_n)  
    5.      begin  
    6.       if(flag_write==1&&post_href==1)//根据自己的需求定义
    7.         $fdisplay(w_file,"%b",post_y_data);   
    8.       end      
    复制代码
    网页的界面如下,将参数设置好以后就可以显示图片。
    下载:
    aggregrate.html (1.96 KB, 下载次数: 0)

    TIM截图20170718090639.png

    注意:由于此网站是量身定做的,所以只能显示数据格式为RGB565的16位二进制的数才能正确显示,注意不能有分号,正确格式示例如下,必须严格遵守
    TIM截图20170718090718.png


    data.txt (124 Bytes, 下载次数: 0)

    结果展示原图
    994469-20160817105936765-167854052.jpg


    灰度图
    994469-20160817112934375-1859976771.jpg


    均值滤波
    994469-20160817113848812-1577131824.jpg


    边缘检测(阈值为5)
    994469-20160817114300546-1331816294.png


    边缘检测(阈值为10)
    994469-20160817114109734-259853572.png


    边缘检测(阈值为16)
    994469-20160817114414593-1620726324.png


    小结:均值滤波处理后的图片有明显的黑边,产生这一现象的原因就是生成 3*3 像素矩阵和取像素值时数据有损失造成的,但是这也是可以优化的,后续我会继续努力不断完善。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    关闭

    站长推荐上一条 /5 下一条

    手机版|爱板网 |网站地图  

    GMT+8, 2017-8-20 00:46 , Processed in 0.136051 second(s), 12 queries , Memcache On.

    苏公网安备 32059002001056号

    Powered by Discuz!

    回顶部