正点原子 发表于 2021-10-29 16:24:54

《新起点V2之FPGA开发指南》第五十三章 OV5640摄像头Sobel边缘检测

本帖最后由 正点原子 于 2021-10-30 10:55 编辑

1)实验平台:正点原子新起点V2FPGA开发板
2)章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-328002-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122









第五十三章 基于OV5640摄像头的Sobel边缘检测实验
       边缘检测是一种常用的图像分割技术,常用的边缘检测算子有Roberts Cross算子、Prewitt算子、Sobel算子和 Kirsch算子等。Sobel算子是根据像素点上下左右邻点灰度加权差在边缘处达到极值这一现象来检测边缘。其对噪声具有平滑作用,提供较为精确的边缘方向信息,当对精度要求不是很高时,是一种较为常用的边缘检测方法。在本章实验中将进行基于SOBEL算子的边缘检测实验。
       本章包括以下几个部分:
       1.1简介
       1.2实验任务
       1.3硬件设计
       1.4程序设计
       1.5下载验证

1.1简介
       图像分割就是把图像分成若干个特定的、具有独特性质的区域并提出有意义目标的技术和过程。它是由图像处理到图像分析的关键步骤。现有的图像分割方法主要分以下几类:基于阈值的分割方法、基于区域的分割方法、基于边缘的分割方法以及基于特定理论的分割方法等。从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。图像分割的过程也是一个标记过程,即把属于同一区域的像素赋予相同的编号。
       图像分割的一种重要方法就是边缘检测,即检测图像灰度级或者结构发生突变的像素点,表明一个区域的终结,也是另一个区域开始的地方。这种一副图像中灰度级或结构突变的像素点的集合称为边缘。不同的图像灰度变化不同,在边界处一般有明显的灰度变化,检测图像的灰度变化是一种常用的边缘检测方法。
       图像的灰度值具有不连续性,图像局部的灰度突变可以用微分来检测,因为这种突变时间十分短促,因此一阶微分和二阶微分十分适合这种突变的检测。但使用二阶微分对噪声的响应更加强烈,其对图像的质量要求较高。
       为了对有意义的边缘点进行分类,与这个点相联系的灰度级的变化必须比在这一点的背景上的变化更有效(灰度变化更剧烈)。由于本次实验用局部计算处理,因此决定一个值是否有效的方法就是设定一个阈值。当一个点的二维一阶导数(图像是一个二维函数)比指定阈值大,就认为这个点是一个边缘点。
       一幅图像可以看做是一个二维函数,图像的的一阶导数可以用梯度表示(梯度的本意是一个向量(矢量),表示某一函数在某一点处的方向导数沿着该方向取得最大值。我们用∇f表示梯度,如下图所示为像素点(x,y)的梯度表达式:

图 53.1.1 点(x,y)处的梯度向量
       ∇f它指出了f(图像)在点(x,y)处最大变化率的方向。
       向量∇f的大小(长度)表示为M(x,y),M(x,y)的表达式如下:

图 53.1.2 M(x,y)的表达式
       它是梯度向量方向变化率的值。值得注意的是这些求和、平方及开平方都是阵列操作。
       梯度的方向可以用图 53.1.3的公式来表示,它是以x轴为度量基准表示的,它与图像的边缘垂直。

图 53.1.3 梯度向量的方向
       本次实验要计算梯度的大小,首先要计算梯度关于x和y方向的分量gy和gx,比较常用的计算梯度的分量的方法是使用一些空间域的模板。大家通常把用于计算梯度偏导数的滤波器模板称为梯度算子(也称为差分算子、边缘算子和边缘检测子),例如用于边缘检测的Sobel算子、Roberts算子和Prewitt算子等。如下图就是几种常用的边缘检测模板:

图 53.1.4 几种边缘检测算子
       罗伯特交叉梯度算子是最早尝试具有对角优势的模板之一,它是一个2x2模板。2x2的模板在概念上很简单,但它是一个偶数模板,偶数模板对于中心点的确认很不便,因此一般情况下大家使用奇数的模板,最小的奇数模板是3x3的模板。这些奇数模板考虑了中心点对端数据的性质,并携带有关于边缘方向的更多信息,能够更好的表达图像的边缘。Sobel算子和Prewitt算子都是实际应用中常见的算子,但相比用Prewitt算子,Sobel算子具有更好的抑制噪声性能。下面给出使用Sobel算子计算梯度分量的算术表达式:

图 53.1.5 Sobel算子求取梯度分量
       在这些公式中,3x3区域的第三行与第一行的差近似为x方向的偏导数,第三列与第一列的差近似为y方向的偏导数。上式用Verilog实现可以用下图的计算方法表示:

图 53.1.6 用Verilog对梯度分量的表示
       在上图的算式中,gy1和gy3分别表示第一行和第三列行的值。gx1和gx3分别表示第一行列第三列的值。
       在进行边缘检测时首先需要求出每一个像素在x和y方向的偏导数gy 和gx,再求出像素点的梯度大小M(x,y),然后把M(x,y)与本次实验设置的阈值进行比较,大于阈值则表示该像素点为边缘,反之,则表示该像素点不是边缘。
1.2实验任务
       本次实验任务是通过摄像头采集的RGB565数据转化为YCbCr数据(也可直接配置摄像头采集yuv数据),然后再进行灰度的Sobel边缘检测, 最后在HDMI显示器上显示。
1.3硬件设计
      本章节中硬件设计与“OV5640摄像头HDMI显示实验”完全相同,在此就不在赘述。
1.4程序设计
       根据实验任务,首先设计如图 53.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头HDMI灰度显示实验”的整体架构。本次实验包括以下模块:时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、摄像头采集模块、图像处理模块和HDMI顶层模块。其中时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、摄像头采集模块和HDMI顶层模块本次实验没有做任何修改,这些模块在“OV5640摄像头HDMI显示实验”中已经说明过,这里不再详述,本次实验只是修改了图像处理模块。
ov5640的sobel边沿检测实验系统框图如下图所示:

图 53.4.1 顶层系统框图
       由上图可知,时钟模块(pll和pll_hdmi)为HDMI顶层模块、SDRAM控制模块以及IIC驱动模块提供驱动时钟。IIC驱动模块和IIC配置模块控制着传感器初始化的开始与结束,传感器初始化完成后将采集到的数据写入摄像头采集模块。数据在摄像头采集模块处理完成后写入图像处理模块,图像处理模块将摄像头数据进行处理后存入SDRAM控制模块。顶层模块从SDRAM控制模块中读出数据并驱动显示器显示,这时整个系统才完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在SDRAM和传感器都初始化完成之后才开始输出数据的,避免了在SDRAM初始化过程中向里面写入数据。
       顶层模块代码如下所示:
1   module ov5640_hdmi_sobel(   
2       input         sys_clk    ,//系统时钟
3       input         sys_rst_n,//系统复位,低电平有效
4       //摄像头
5       input         cam_pclk   ,//cmos 数据像素时钟
6       input         cam_vsync,//cmos 场同步信号
7       input         cam_href   ,//cmos 行同步信号
8       inputcam_data   ,//cmos 数据
9       output      cam_rst_n,//cmos 复位信号,低电平有效
10      output      cam_pwdn   ,//cmos 电源休眠模式选择信号
11      output      cam_scl    ,//cmos SCCB_SCL线
12      inout         cam_sda    ,//cmos SCCB_SDA线
13      //SDRAM
14      output      sdram_clk,//SDRAM 时钟
15      output      sdram_cke,//SDRAM 时钟有效
16      output      sdram_cs_n ,//SDRAM 片选
17      output      sdram_ras_n,//SDRAM 行有效
18      output      sdram_cas_n,//SDRAM 列有效
19      output      sdram_we_n ,//SDRAM 写有效
20      output sdram_ba   ,//SDRAM Bank地址
21      output sdram_dqm,//SDRAM 数据掩码
22      output sdram_addr ,//SDRAM 地址
23      inout sdram_data ,//SDRAM 数据   
24      //HDMI接口
25      output      tmds_clk_p,   // TMDS 时钟通道
26      output      tmds_clk_n,
27      output tmds_data_p,// TMDS 数据通道
28      output tmds_data_n
29      );
30
31//parameter define
32parameter SLAVE_ADDR    = 7'h3c          ; //OV5640的器件地址7'h3c
33parameter BIT_CTRL      = 1'b1         ; //OV5640的字节地址为16位0:8位 1:16位
34parameter CLK_FREQ      = 27'd50_000_000 ; //i2c_dri模块的驱动时钟频率
35parameter I2C_FREQ      = 18'd250_000    ; //I2C的SCL时钟频率,不超过400KHz
36parameter V_CMOS_DISP   = 11'd800      ; //CMOS分辨率--行
37parameter H_CMOS_DISP   = 11'd1280       ; //CMOS分辨率--列
38parameter TOTAL_H_PIXEL = 13'd2570       ; //CMOS分辨率--行
39parameter TOTAL_V_PIXEL = 13'd980      ;
40
41//wire define
42wire      clk_100m         ;//100mhz时钟,SDRAM操作时钟
43wire      clk_100m_shift   ;//100mhz时钟,SDRAM相位偏移时钟
44wire      clk_50m            ;
45wire      hdmi_clk         ;
46wire      hdmi_clk_5         ;
47wire      locked             ;
48wire      locked_hdmi      ;
49wire      rst_n            ;
50wire      sys_init_done      ;//系统初始化完成(sdram初始化+摄像头初始化)
51wire      i2c_exec         ;//I2C触发执行信号
52wire i2c_data         ;//I2C要配置的地址与数据(高8位地址,低8位数据)         
53wire      i2c_done         ;//I2C寄存器配置完成信号
54wire      i2c_dri_clk      ;//I2C操作时钟
55wire [ 7:0] i2c_data_r         ;//I2C读出的数据
56wire      i2c_rh_wl          ;//I2C读写控制信号
57wire      cam_init_done      ;//摄像头初始化完成                        
58wire      wr_en            ;//sdram_ctrl模块写使能
59wire wr_data            ;//sdram_ctrl模块写数据
60wire      rd_en            ;//sdram_ctrl模块读使能
61wire rd_data            ;//sdram_ctrl模块读数据
62wire      sdram_init_done    ;//SDRAM初始化完成
63wire pixel_xpos_w       ;//HDMI横坐标
64wire pixel_ypos_w       ;//HDMI纵坐标
65wire      post_frame_vsync   ;//处理后的场信号
66wire      post_frame_hsync   ;//处理后的行信号
67wire      post_frame_de      ;//处理后的数据使能
68wire post_rgb         ;//处理后的数据
69
70//*****************************************************
71//**                  main code
72//*****************************************************
73
74assignrst_n = sys_rst_n & locked & locked_hdmi;
75//系统初始化完成:SDRAM和摄像头都初始化完成
76//避免了在SDRAM初始化过程中向里面写入数据
77assignsys_init_done = sdram_init_done & cam_init_done;
78//电源休眠模式选择 0:正常模式 1:电源休眠模式
79assigncam_pwdn= 1'b0;
80assigncam_rst_n = 1'b1;
81
82//锁相环
83pll u_pll(
84      .areset             (~sys_rst_n),
85      .inclk0             (sys_clk),            
86      .c0               (clk_100m),
87      .c1               (clk_100m_shift),
88      .c2               (clk_50m),   
89      .locked             (locked)
90      );
91
92pll_hdmi    pll_hdmi_inst (
93      .areset             ( ~sys_rst_n),
94      .inclk0             ( sys_clk   ),
95      .c0               ( hdmi_clk    ),//hdmi pixel clock 71Mhz
96      .c1               ( hdmi_clk_5),//hdmi pixel clock*5 355Mhz
97      .locked             ( locked_hdmi )
98      );
99      
100 //I2C配置模块
101 i2c_ov5640_rgb565_cfg u_i2c_cfg(
102   .clk                (i2c_dri_clk),
103   .rst_n            (rst_n      ),
104            
105   .i2c_exec         (i2c_exec   ),
106   .i2c_data         (i2c_data   ),
107   .i2c_rh_wl          (i2c_rh_wl),      //I2C读写控制信号
108   .i2c_done         (i2c_done   ),
109   .i2c_data_r         (i2c_data_r ),   
110               
111   .cmos_h_pixel       (H_CMOS_DISP),    //CMOS水平方向像素个数
112   .cmos_v_pixel       (V_CMOS_DISP) ,   //CMOS垂直方向像素个数
113   .total_h_pixel      (TOTAL_H_PIXEL),    //水平总像素大小
114   .total_v_pixel      (TOTAL_V_PIXEL),    //垂直总像素大小
115         
116   .init_done          (cam_init_done)
117   );   
118
119 //I2C驱动模块
120 i2c_dri #(
121   .SLAVE_ADDR         (SLAVE_ADDR    ),    //参数传递
122   .CLK_FREQ         (CLK_FREQ      ),            
123   .I2C_FREQ         (I2C_FREQ      )
124   )
125 u_i2c_dr(
126   .clk                (clk_50m       ),
127   .rst_n            (rst_n         ),
128
129   .i2c_exec         (i2c_exec      ),   
130   .bit_ctrl         (BIT_CTRL      ),   
131   .i2c_rh_wl          (i2c_rh_wl   ),   //固定为0,只用到了IIC驱动的写操作   
132   .i2c_addr         (i2c_data),   
133   .i2c_data_w         (i2c_data ),   
134   .i2c_data_r         (i2c_data_r    ),   
135   .i2c_done         (i2c_done      ),   
136   .scl                (cam_scl       ),   
137   .sda                (cam_sda       ),
138   .dri_clk            (i2c_dri_clk   )       //I2C操作时钟
139   );
140
141 //CMOS图像数据采集模块
142 cmos_capture_data u_cmos_capture_data(         //系统初始化完成之后再开始采集数据
143   .rst_n            (rst_n & sys_init_done),
144   
145   .cam_pclk         (cam_pclk ),
146   .cam_vsync          (cam_vsync),
147   .cam_href         (cam_href ),
148   .cam_data         (cam_data ),         
149   
150   .cmos_frame_vsync   (cmos_frame_vsync),
151   .cmos_frame_href    (cmos_frame_href),
152   .cmos_frame_valid   (wr_en    ),      //数据有效使能信号
153   .cmos_frame_data    (wr_data)       //有效数据
154   );
155      
156//图像处理模块
157 vip u_vip(
158   //module clock
159   .clk            (cam_pclk),         // 时钟信号
160   .rst_n            (rst_n    ),          // 复位信号(低有效)
161   //图像处理前的数据接口
162   .pre_frame_vsync(cmos_frame_vsync   ),
163   .pre_frame_hsync(cmos_frame_href   ),
164   .pre_frame_de   (wr_en   ),
165   .pre_rgb          (wr_data),
166   .xpos             (pixel_xpos_w   ),
167   .ypos             (pixel_ypos_w   ),
168   //图像处理后的数据接口
169   .post_frame_vsync (post_frame_vsync ),// 场同步信号
170   .post_frame_hsync ( ),                  // 行同步信号
171   .post_frame_de    (post_frame_de ),   // 数据输入使能
172   .post_rgb         (post_rgb)            // RGB565颜色数据
173
174 );      
175
176 //SDRAM 控制器顶层模块,封装成FIFO接口
177 //SDRAM 控制器地址组成: {bank_addr,row_addr,col_addr}
178 sdram_top u_sdram_top(
179   .ref_clk            (clk_100m),         //sdram 控制器参考时钟
180   .out_clk            (clk_100m_shift),   //用于输出的相位偏移时钟
181   .rst_n            (rst_n),            //系统复位
182                                             
183   //用户写端口                              
184   .wr_clk             (cam_pclk),         //写端口FIFO: 写时钟
185   .wr_en            (post_frame_de),    //写端口FIFO: 写使能
186   .wr_data            (post_rgb),         //写端口FIFO: 写数据   
187      
188   .wr_min_addr      (24'd0),            //写SDRAM的起始地址
189   .wr_max_addr      (V_CMOS_DISP*H_CMOS_DISP-1),   //写SDRAM的结束地址
190   .wr_len             (10'd512),          //写SDRAM时的数据突发长度
191   .wr_load            (~rst_n),         //写端口复位: 复位写地址,清空写FIFO
192                                             
193   //用户读端口                              
194   .rd_clk             (hdmi_clk),         //读端口FIFO: 读时钟
195   .rd_en            (rd_en),            //读端口FIFO: 读使能
196   .rd_data            (rd_data),          //读端口FIFO: 读数据
197   .rd_min_addr      (24'd0),            //读SDRAM的起始地址
198   .rd_max_addr      (V_CMOS_DISP*H_CMOS_DISP-1),   //读SDRAM的结束地址
199   .rd_len             (10'd512),          //从SDRAM中读数据时的突发长度
200   .rd_load            (~rst_n),         //读端口复位: 复位读地址,清空读FIFO
201                                                
202   //用户控制端口                              
203   .sdram_read_valid   (1'b1),             //SDRAM 读使能
204   .sdram_pingpang_en(1'b1),             //SDRAM 乒乓操作使能
205   .sdram_init_done    (sdram_init_done),//SDRAM 初始化完成标志
206                                             
207   //SDRAM 芯片接口                              
208   .sdram_clk          (sdram_clk),      //SDRAM 芯片时钟
209   .sdram_cke          (sdram_cke),      //SDRAM 时钟有效
210   .sdram_cs_n         (sdram_cs_n),       //SDRAM 片选
211   .sdram_ras_n      (sdram_ras_n),      //SDRAM 行有效
212   .sdram_cas_n      (sdram_cas_n),      //SDRAM 列有效
213   .sdram_we_n         (sdram_we_n),       //SDRAM 写有效
214   .sdram_ba         (sdram_ba),         //SDRAM Bank地址
215   .sdram_addr         (sdram_addr),       //SDRAM 行/列地址
216   .sdram_data         (sdram_data),       //SDRAM 数据
217   .sdram_dqm          (sdram_dqm)         //SDRAM 数据掩码
218   );
219
220 //例化HDMI顶层模块
221 hdmi_top u_hdmi_top(
222   .hdmi_clk       (hdmi_clk   ),
223   .hdmi_clk_5   (hdmi_clk_5 ),
224   .rst_n          (rst_n      ),
225               
226   .rd_data      (rd_data    ),
227   .rd_en          (rd_en      ),
228   .h_disp         (),
229   .v_disp         (),
230   .pixel_xpos   (pixel_xpos_w),
231   .pixel_ypos   (pixel_ypos_w),
232   .video_vs       (),
233   .tmds_clk_p   (tmds_clk_p ),
234   .tmds_clk_n   (tmds_clk_n ),
235   .tmds_data_p    (tmds_data_p),
236   .tmds_data_n    (tmds_data_n)
237   );   
238
239 endmodule
       FPGA顶层模块(ov5640_hdmi_sobel)例化了以下八个模块:时钟模块1(pll)、时钟模块2(pll_hdmi)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov5640_rgb565_cfg)、图像采集模块(cmos_capture_data)、图像处理模块(vip)、SDRAM控制模块(sdram_top)和HDMI顶层模块(hdmi_top)。
       时钟模块:时钟模块通过调用PLL IP核实现,共输出5个时钟,频率分别为100M时钟、100M偏移-75度时钟、50M时钟、71Mhz时钟和355M时钟(HDMI像素时钟的5倍频)。其中pll 产生了50M时钟、100M时钟和100M偏移-75度时钟,pll_hdmi 产生了71Mhz时钟和355M时钟,这里之所以用两个锁相环是因为HDMI所用的时钟71Mhz与SDRAM控制模块使用的100M时钟不是整数倍,使用一个锁相环不符合设计要求。100Mhz时钟作为SDRAM控制模块的驱动时钟,100M偏移-75度时钟用来输出给外部SDRAM芯片使用,50Mhz时钟作为I2C驱动模块的驱动时钟,71Mhz时钟和355M时钟(HDMI像素时钟的5倍频)负责驱动HDMI顶层模块。
       I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV5640 SCCB接口总线,用户可根据该模块提供的用户接口可以很方便的对OV5640的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
       I2C配置模块(i2c_ov5640_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV5640的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV5640传感器的初始化。
       图像采集模块(cmos_capture_data):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。OV5640和OV7725图像输出时序非常相似,有关该模块的详细介绍请大家参考“OV7725摄像头LCD显示实验”章节。
       图像处理模块(vip):对采集后的图像数据进行处理,并将处理后的数据存入SDRAM控制模块。
       SDRAM控制模块(sdram_top):SDRAM读写控制器模块负责驱动SDRAM片外存储器,缓存图像传感器输出的图像数据。有关该模块的详细介绍请大家参考“SDRAM读写测试实验”章节。
       HDMI顶层模块(hdmi_top):HDMI顶层模块负责驱动HDMI显示器的驱动信号的输出,同时为其他模块提供显示器参数、场同步信号和数据请求信号。关HDMI顶层模块的详细介绍请大家参考“OV5640摄像头HDMI显示实验”章节。
       vip模块框图如下图所示:

图 53.4.2 vip模块框图
       vip模块例化了RGB转YCbCr模块(rgb2ycbcr)和sobel边缘检测模块(vip_sobel_edge_detector)。RGB转YCbCr模块负责将摄像头采集的RGB565格式数据到转换为YUV格式的数据。sobel边缘检测模块负责将YUV格式的视频图像进行边缘检测后输出。有关RGB转YCbCr模块的详细介绍请大家参考“OV5640摄像头HDMI灰度显示实验”章节。
       vip模块原理图如下图所示:

图 53.4.3 vip模块原理图
       如上图所示,摄像头采集到16位rgb565输入vip模块,经过“rgb2ycbcr”模块转化为8位的yuv444数据,然后在将转化后的灰度数据(img_y)作为“vip_sobel_edge_detector”模块的输入,对灰度进行边缘检测处理,最后输出处理后的灰度数据“post_img_bit”。
       图像处理模块负责图像数据的格式转换,代码如下:
1module vip(
2      //module clock
3      input         clk            ,    // 时钟信号
4      input         rst_n          ,    // 复位信号(低有效)
5
6      //图像处理前的数据接口
7      input         pre_frame_vsync,
8      input         pre_frame_hsync,
9      input         pre_frame_de   ,
10   input    pre_rgb      ,
11   input    xpos         ,
12   input    ypos         ,
13
14   //图像处理后的数据接口
15   output          post_frame_vsync,   // 场同步信号
16   output          post_frame_hsync,   // 行同步信号
17   output          post_frame_de   ,   // 数据输入使能
18   output    post_rgb            // RGB565颜色数据
19 );
20
21 parameterSOBEL_THRESHOLD = 128; //sobel阈值
22
23 //wire define
24 wire   [ 7:0]         img_y;
25 wire   [ 7:0]         post_img_y;
26 wire                  pe_frame_vsync;
27 wire                  pe_frame_href;
28 wire                  pe_frame_clken;
29 wire                  ycbcr_vsync;
30 wire                  ycbcr_hsync;
31 wire                  ycbcr_de;
32
33 //*****************************************************
34 //**                  main code
35 //*****************************************************
36
37 //RGB转YCbCr模块
38 rgb2ycbcr u_rgb2ycbcr(
39   //module clock
40   .clk             (clk    ),            // 时钟信号
41   .rst_n         (rst_n),            // 复位信号(低有效)
42   //图像处理前的数据接口
43   .pre_frame_vsync (pre_frame_vsync),    // vsync信号
44   .pre_frame_hsync (pre_frame_hsync),    // href信号
45   .pre_frame_de    (pre_frame_de   ),    // data enable信号
46   .img_red         (pre_rgb ),
47   .img_green       (pre_rgb ),
48   .img_blue      (pre_rgb[ 4:0 ] ),
49   //图像处理后的数据接口
50   .post_frame_vsync(pe_frame_vsync),   // vsync信号
51   .post_frame_hsync(pe_frame_href),      // href信号
52   .post_frame_de   (pe_frame_clken),   // data enable信号
53   .img_y         (img_y),            //灰度数据
54   .img_cb          (),
55   .img_cr          ()
56 );
57
58 vip_sobel_edge_detector
59   #(
60   .SOBEL_THRESHOLD(SOBEL_THRESHOLD)    //sobel阈值
61   )
62 u_vip_sobel_edge_detector(
63   .clk (clk),   
64   .rst_n (rst_n),
65   
66   //处理前数据
67   .per_frame_vsync (pe_frame_vsync),    //处理前帧有效信号
68   .per_frame_href(pe_frame_href),   //处理前行有效信号
69   .per_frame_clken (pe_frame_clken),    //处理前图像使能信号
70   .per_img_y       (img_y),             //处理前输入灰度数据
71   
72   //处理后的数据
73   .post_frame_vsync (post_frame_vsync), //处理后帧有效信号
74   .post_frame_href(post_frame_hsync), //处理后行有效信号
75   .post_frame_clken (post_frame_de),    //输出使能信号
76   .post_img_bit   (post_rgb)          //输出像素
77         
78 );
79
80 endmodule
       代码的第21行表示对边缘检测的阈值的设定,本次实验设定的值为256级灰度的中间值128。
       代码的第38行至56行是对灰度转换模块的例化,在该模块以摄像头采集的16位RGB565红、绿、蓝三原色数据作为输入数据,通过算法实现RGB到YCbCr的转换,并输出8位灰度数据,并输出数据输出使能信号。有关RGB转YCbCr模块的详细介绍请大家参考“OV5640摄像头HDMI灰度显示实验”章节。
       代码的第58行至78行是对sobel边缘检测模块的例化,前面已经介绍过Sobel算子是3x3的模板,因此在实现边缘检测时也需要生成3x3的阵列,有关3x3阵列的生成在“基于OV5640的中值滤波实验”有过介绍,在此就不再赘述。
       sobel边缘检测模块如下:
1   module vip_sobel_edge_detector
2       #(
3       parameterSOBEL_THRESHOLD = 250 //Sobel 阈值
4       )
5       (
6       input         clk,             //cmos 像素时钟
7       input         rst_n,
8       //处理前数据
9       input         per_frame_vsync,
10      input         per_frame_href,
11      input         per_frame_clken,
12      input    per_img_y,      
13      //处理后的数据
14      output          post_frame_vsync,
15      output          post_frame_href,
16      output          post_frame_clken,
17      output    post_img_bit   
18);
19//reg define
20reg gx_temp2; //第三列值
21reg gx_temp1; //第一列值
22reg gx_data;//x方向的偏导数
23reg gy_temp1; //第一行值
24reg gy_temp2; //第三行值
25reg gy_data;//y方向的偏导数
26reg gxy_square;
27reg per_frame_vsync_r;
28reg per_frame_href_r;
29reg per_frame_clken_r;
30
31//wire define
32wire      matrix_frame_vsync;
33wire      matrix_frame_href;
34wire      matrix_frame_clken;
35wire dim;
36//输出3X3 矩阵
37wire matrix_p11;
38wire matrix_p12;
39wire matrix_p13;
40wire matrix_p21;
41wire matrix_p22;
42wire matrix_p23;
43wire matrix_p31;
44wire matrix_p32;
45wire matrix_p33;
46
47//*****************************************************
48//**                  main code
49//*****************************************************
50
51assign post_frame_vsync = per_frame_vsync_r;
52assign post_frame_href= per_frame_href_r ;
53assign post_frame_clken = per_frame_clken_r;
54assign post_img_bit   = post_frame_href ? {16{~post_img_bit_r}} : 16'd0;
55
56//3x3矩阵
57vip_matrix_generate_3x3_8bit u_vip_matrix_generate_3x3_8bit(
58      .clk               (clk),   
59      .rst_n               (rst_n),
60      //预处理数据
61      .per_frame_vsync   (per_frame_vsync),
62      .per_frame_href      (per_frame_href),
63      .per_frame_clken   (per_frame_clken),
64      .per_img_y         (per_img_y),      
65      
66      //处理后的数据
67      .matrix_frame_vsync(matrix_frame_vsync),
68      .matrix_frame_href   (matrix_frame_href),
69      .matrix_frame_clken(matrix_frame_clken),
70      .matrix_p11          (matrix_p11),
71      .matrix_p12          (matrix_p12),
72      .matrix_p13          (matrix_p13), //输出 3X3 矩阵
73      .matrix_p21          (matrix_p21),
74      .matrix_p22          (matrix_p22),
75      .matrix_p23          (matrix_p23),
76      .matrix_p31          (matrix_p31),
77      .matrix_p32          (matrix_p32),
78      .matrix_p33          (matrix_p33)
79);
80
81//Sobel 算子
82//         gx                  gy                  像素点
83// [   -10   +1]   [   +1+2   +1 ]   [   P11P12   P13 ]
84// [   -20   +2]   [   0   0    0]   [   P21P22   P23 ]
85// [   -10   +1]   [   -1-2   -1 ]   [   P31P32   P33 ]
86
87//Step 1 计算x方向的偏导数
88always@(posedge clk or negedge rst_n)begin
89      if(!rst_n)begin
90          gy_temp1 <= 10'd0;
91          gy_temp2 <= 10'd0;
92          gy_data <=10'd0;
93      end
94      else begin
95          gy_temp1 <= matrix_p13 + (matrix_p23 << 1) + matrix_p33;
96          gy_temp2 <= matrix_p11 + (matrix_p21 << 1) + matrix_p31;
97          gy_data <= (gy_temp1 >= gy_temp2) ? gy_temp1 - gy_temp2 :
98                     (gy_temp2 - gy_temp1);
99      end
100 end
101
102 //Step 2 计算y方向的偏导数
103 always@(posedge clk or negedge rst_n)begin
104   if(!rst_n)begin
105         gx_temp1 <= 10'd0;
106         gx_temp2 <= 10'd0;
107         gx_data <=10'd0;
108   end
109   else begin
110         gx_temp1 <= matrix_p11 + (matrix_p12 << 1) + matrix_p13;
111         gx_temp2 <= matrix_p31 + (matrix_p32 << 1) + matrix_p33;
112         gx_data <= (gx_temp1 >= gx_temp2) ? gx_temp1 - gx_temp2 :
113                  (gx_temp2 - gx_temp1);
114   end
115 end
116
117 //Step 3 计算平方和
118 always@(posedge clk or negedge rst_n)begin
119   if(!rst_n)
120         gxy_square <= 21'd0;
121   else
122         gxy_square <= gx_data * gx_data + gy_data * gy_data;
123 end
124
125 //Step 4 开平方(梯度向量的大小)
126 SQRTu_SQRT
127 (
128   .radical   (gxy_square),
129   .q         (dim),
130   .remainder ()
131 );
132
133 //Step 5 将开平方后的数据与预设阈值比较
134 reg post_img_bit_r;
135 always@(posedge clk or negedge rst_n)begin
136   if(!rst_n)
137         post_img_bit_r <= 1'b0; //初始值
138   else if(dim >= SOBEL_THRESHOLD)
139         post_img_bit_r <= 1'b1; //检测到边缘1
140   else
141   post_img_bit_r <= 1'b0; //不是边缘 0
142 end
143
144 //延迟5个周期同步
145 always@(posedge clk or negedge rst_n)begin
146   if(!rst_n)begin
147         per_frame_vsync_r <= 0;
148         per_frame_href_r<= 0;
149         per_frame_clken_r <= 0;
150   end
151   else begin
152         per_frame_vsync_r<={per_frame_vsync_r,matrix_frame_vsync};
153         per_frame_href_r   <={per_frame_href_r,matrix_frame_href};
154         per_frame_clken_r<={per_frame_clken_r,matrix_frame_clken};
155   end
156 end
157
158 endmodule       在上述代码中,首先求x、y方向的偏导数,如代码第88至115行;在代码第118至123行,计算了偏导数的平方和;代码第126行至131行,对上一步计算的平方和进行开平方,所得的值就是梯度向量的大小,在此处调用了开方函数,稍后会展示关于开方函数IP的配置界面;最后将开方所得的值与预设阈值进行比较,若大于阈值则post_img_bit_r赋为1,表示该像素点是边缘像素,否则赋为0,表示该像素点不是边缘像素,如代码第135行至142行;前面step1到5一共消耗了5个时钟周期,因此需要对post_frame_vsync、post_frame_href 和post_frame_clken延迟5个时钟周期进行同步,如代码第145行至156行。
       有关阈值的设置,本实验中设为128,如顶层代码第3行所示。理论上讲阈值可以为0到255中的任意值(灰度值为0到255),但实际上,阈值过小或过大都会影响实验结果。阈值过大会导致边缘间断;阈值过小,会导致伪边缘。
       关于开方函数IP的调用,我们先在搜索框输入“SQRT”,选择“ALTSQRT”,如下图所示:

图 53.4.4 选择“SQRT”
       点击“Next”进入配置界面,如下图:

图 53.4.5 “SQRT”参数配置界面
       位宽设为21,点击“Finish”,这样就完成了开方函数的配置。
1.5下载验证
       编译完工程之后就可以开始下载程序了。将OV5640摄像头模块插在新起点开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,下载完成后观察HDMI显示器显示图案。如下图所示:

图 53.5.1 HDMI实时显示边缘检测图像
页: [1]
查看完整版本: 《新起点V2之FPGA开发指南》第五十三章 OV5640摄像头Sobel边缘检测