OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 10959|回复: 0
打印 上一主题 下一主题

[XILINX] 【正点原子FPGA连载】第五十二章基于OV5640的中值滤波实验--摘自【正点原子】超越者之FPGA开发指南

[复制链接]

1308

主题

1324

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5598
金钱
5598
注册时间
2019-5-8
在线时间
1493 小时
跳转到指定楼层
楼主
发表于 2021-1-26 11:37:03 | 只看该作者 |只看大图 回帖奖励 |正序浏览 |阅读模式
1)实验平台:正点原子超越者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=631660290421
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
4)本章实例源码下载: 45_ov5640_hdmi_median_filter.rar (11.18 MB, 下载次数: 386)


5)正点原子FPGA交流群:905624739点击加入:
6)关注正点原子公众号,获取最新资料更新



第五十二章基于OV5640的中值滤波实验




在数字图像处理中,无论是直接获取的灰度图像,还是由彩色图像转换得到的灰度图像,里面都有噪点的存在,噪点对图像质量有很大的影响。空间滤波是一种常用的降噪方法,进行中值滤波(一种空间滤波)不仅可以去除孤立噪点,而且可以保持图像的边缘特性,不会使图像产生显著的模糊,比较适合于实验中的人脸图像。本章实验将进行OV5640摄像头采集RGB565数据,转化为YUV数据,然后进行中值滤波的实验。
本章包括以下几个部分:
5252.1  简介
52.2  实验任务
52.3  硬件设计
52.4  程序设计
52.5  下载验证


52.1简介
滤波是指接收(通过)或过滤掉信号中一定的频率分量,例如,通过低频率的滤波器称为低通滤波器。空间滤波是图像处理领域应用非常广泛的工具之一,它可以改善图像质量,包括去除高频噪点的干扰、图像平滑等。大家常见的空间滤波有中值滤波和均值滤波。
图像可以看成是一个定义在二维平面上的信号,该信号的幅值对应像素的灰度(彩色图像对应RGB三个分量)。图像的频率指的是空间频率,它和平常认知的物理频率是不同的。图像的频率是表征图像中灰度变化剧烈程度的指标,是灰度在平面空间上的梯度。不同频率信息在图像结构中有不同的作用。图像的主要成分是低频信息,它形成了图像的基本灰度等级,对图像结构的决定作用较小;中频信息决定了图像的基本结构,形成了图像的主要边缘结构;高频信息形成了图像的边缘和细节,是在中频信息上对图像内容的进一步强化。
大家也可以通过空间滤波器(也称为空间掩模、模板或窗口)直接作用于图像本身从而对图像进行滤波处理。空间滤波器由两部分组成:
(1)邻域,
(2)对该邻域包围的图像像素执行的预定义操作。
领域是指一个像素点及其附近像素点所组成的空间。滤波会产生一个新像素,像素的坐标就是邻域中心的坐标,像素的值就是滤波操作的结果。
中值滤波就是一种很常见的空间滤波,它是一种非线性平滑技术。它将每一像素点及该像素点的邻域作为一个滤波模板,计算出模板中所有像素点灰度值的中值,然后用它代替模板中心像素点的值。图 52.1.1为像素点P及其周围8个像素点所组成的3x3滤波模板:

图 52.1.1 中值滤波模板

中值滤波是一种基于排序统计理论的非线性信号处理技术,它可以消除孤立的噪点,从而让图像中的像素值更接近真实值。红外图像中的盲元就是一种孤立噪点的例子。由于红外探测器制造过程中的缺陷,传感器中某些像元的输出可能会非常大,导致图像中对应的像素点非常亮,通常称之为盲元,如下图中红色箭头所示:

图 52.1.2 红外图像中的盲元

中值滤波对类似于上图中的脉冲噪点有良好的滤除作用,特别是在滤除噪点的同时,能够保护信号的边缘,使之不被模糊。这些优良特性是线性滤波方法所不具备的。此外,中值滤波的算法比较简单,也易于用硬件实现。所以,中值滤波方法一经提出后,便在数字信号处理领域得到广泛的应用。
关于中值滤波如何快速求得中值,有多种方法实现,例如冒泡排序法、选择排序法等方法。但是用Verilog实现这些排序算法不仅很复杂而且运算速率同时也会大大降低。在本章实验中采用流水线操作的方式,在图像的3x3矩阵中实现快速排序。下面是算法流程框图:

图 52.1.3 中值滤波算法框图

首先生成一个3x3的像素阵列,然后在分别对每行3个像素进行排序,得出每行的最大、中值和最小值(如上图Max1、Med1和Min1)。接着,对排序后的矩阵进行处理,即提取三个最大值中的最小值(Minz_of_Max),三个中间值的中间值(Med_of_Med),以及三个最小值中的最大值(Max_of_Min)。最后,将得到的三个值,再次取中值,最终求得9个像素的中值。
52.2实验任务
本次实验任务是利用OV5640摄像头采集RGB565数据,使用FPGA将OV5640采集到的彩色图像数据转换为灰度图像数据,然后对灰度图像数据进行中值滤波处理,最后在HDMI显示器上实时显示出来。
52.3硬件设计
本章节中硬件设计与“OV5640摄像头HDMI显示实验”完全相同,此处不再赘述。
52.4程序设计
根据实验任务,可以设计出如图 52.4.1所示的系统框图。本章实验的系统框架延续了“OV5640摄像头HDMI灰度显示实验”的整体架构。本次实验包括以下模块:时钟模块、DDR控制器模块、摄像头驱动模块、图像处理模块和HDMI顶层模块。其中时钟模块、DDR控制器模块、HDMI顶层模块和摄像头驱动模块没有做任何修改,这些模块在“OV5640摄像头HDMI显示实验”中已经介绍过,这里不再赘述,本次实验只对图像处理模块做了修改。
顶层系统框图如下所示:

图 52.4.1 顶层系统框图

由上图可知,时钟模块(clock)为HDMI顶层模块、DDR控制模块以及摄像头驱动模块提供驱动时钟。摄像头驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入图像处理模块,图像处理模块将处理后的数据存入DDR3,HDMI顶层模块从DDR控制模块中读出数据并驱动显示器显示,这时整个系统才完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在DDR3和传感器都初始化完成之后才开始输出数据的,避免了在DDR3初始化过程中向里面写入数据。
顶层模块的代码如下:
  1. 1   module ov5640_hdmi_median_filter(   
  2. 2       input                 sys_clk,         //系统时钟
  3. 3       input                 sys_rst_n,       //系统复位,低电平有效
  4. 4       //摄像头接口                          
  5. 5       input                 cam_pclk,        //cmos 数据像素时钟
  6. 6       input                 cam_vsync,       //cmos 场同步信号
  7. 7       input                 cam_href,        //cmos 行同步信号
  8. 8       input   [7:0]         cam_data,        //cmos 数据
  9. 9       output                cam_rst_n,       //cmos 复位信号,低电平有效
  10. 10      output                cam_pwdn ,       //电源休眠模式选择 0:正常模式 1:电源休眠模式
  11. 11      output                cam_scl,         //cmos SCCB_SCL线
  12. 12      inout                 cam_sda,         //cmos SCCB_SDA线      
  13. 13    //ddr3 port
  14. 14      inout   [15:0]        mcb3_dram_dq,     
  15. 15      output  [13:0]        mcb3_dram_a,      
  16. 16      output  [2:0]         mcb3_dram_ba,     
  17. 17      output                mcb3_dram_ras_n,  
  18. 18      output                mcb3_dram_cas_n,   
  19. 19      output                mcb3_dram_we_n,   
  20. 20      output                mcb3_dram_reset_n,
  21. 21      output                mcb3_dram_cke,   
  22. 22      output                mcb3_dram_dm,     
  23. 23      inout                 mcb3_dram_udqs,   
  24. 24      inout                 mcb3_dram_udqs_n,
  25. 25      inout                 mcb3_rzq,         
  26. 26      inout                 mcb3_zio,         
  27. 27      output                mcb3_dram_udm,   
  28. 28      inout                 mcb3_dram_dqs,   
  29. 29      inout                 mcb3_dram_dqs_n,  
  30. 30      output                mcb3_dram_ck,     
  31. 31      output                mcb3_dram_ck_n,      
  32. 32  
  33. 33      output                init_calib_complete,   
  34. 34      //hdmi接口                              
  35. 35      output                tmds_clk_p,         // TMDS 时钟通道
  36. 36      output                tmds_clk_n,
  37. 37      output  [2:0]         tmds_data_p,        // TMDS 数据通道
  38. 38      output  [2:0]         tmds_data_n  
  39. 39      );
  40. 40  
  41. 41  //parameter define
  42. 42  parameter  V_CMOS_DISP = 11'd768;                  //CMOS分辨率--行
  43. 43  parameter  H_CMOS_DISP = 11'd1024;                 //CMOS分辨率--列
  44. 44  parameter  TOTAL_H_PIXEL = H_CMOS_DISP + 12'd1216;
  45. 45  parameter  TOTAL_V_PIXEL = V_CMOS_DISP + 12'd504;   
  46. 46                                                                     
  47. 47  //wire define                          
  48. 48  wire         clk_50m                   ;  //50mhz时钟
  49. 49  wire         locked_100m               ;  //时钟锁定信号
  50. 50  wire         locked_300m               ;  //时钟锁定信号
  51. 51  wire         rst_n                     ;  //全局复位                                                               
  52. 52  wire         wr_en                     ;  //DDR3控制器模块写使能
  53. 53  wire  [15:0] wr_data                   ;  //DDR3控制器模块写数据
  54. 54  wire         rdata_req                 ;  //DDR3控制器模块读使能
  55. 55  wire  [15:0] rd_data                   ;  //DDR3控制器模块读数据
  56. 56  wire         cmos_frame_valid          ;  //数据有效使能信号
  57. 57  wire         init_calib_complete       ;  //DDR3初始化完成init_calib_complete
  58. 58  wire         sys_init_done             ;  //系统初始化完成(DDR初始化+摄像头初始化)
  59. 59  wire         cmos_frame_vsync          ;  //输出帧有效场同步信号
  60. 60  wire         lcd_de                    ;  //LCD 数据输入使能
  61. 61  wire         cmos_frame_href           ;  //输出帧有效行同步信号
  62. 62  wire  [10:0] h_disp                    ;  //水平分辨率
  63. 63  wire  [10:0] v_disp                    ;  //垂直分辨率     
  64. 64  wire  [29:0] ddr3_addr_max             ;  //存入DDR3的最大读写地址
  65. 65  wire  [15:0] post_rgb                  ;  //处理后的图像数据
  66. 66  wire         post_frame_vsync          ;  //处理后的场信号
  67. 67  wire         post_frame_de             ;  //处理后的数据有效使能
  68. 68  wire  [10:0] pixel_xpos                ;  //像素点横坐标
  69. 69  wire  [10:0] pixel_ypos                ;  //像素点纵坐标  
  70. 70  
  71. 71  //*****************************************************
  72. 72  //**                    main code
  73. 73  //*****************************************************
  74. 74  //待时钟锁定后产生复位结束信号
  75. 75  assign  rst_n = sys_rst_n & locked_100m & locked_300m;
  76. 76  
  77. 77  //系统初始化完成:DDR3初始化完成
  78. 78  assign  sys_init_done = init_calib_complete;
  79. 79  
  80. 80  //DDR3的最大读写地址
  81. 81  assign  ddr3_addr_max = V_CMOS_DISP * H_CMOS_DISP;
  82. 82     
  83. 83   //ov5640 驱动
  84. 84  ov5640_dri u_ov5640_dri(
  85. 85      .clk               (clk_50m),
  86. 86      .rst_n             (rst_n),
  87. 87  
  88. 88      .cam_pclk          (cam_pclk ),
  89. 89      .cam_vsync         (cam_vsync),
  90. 90      .cam_href          (cam_href ),
  91. 91      .cam_data          (cam_data ),
  92. 92      .cam_rst_n         (cam_rst_n),
  93. 93      .cam_pwdn          (cam_pwdn ),
  94. 94      .cam_scl           (cam_scl  ),
  95. 95      .cam_sda           (cam_sda  ),
  96. 96      
  97. 97      .capture_start     (init_calib_complete),
  98. 98      .cmos_h_pixel      (H_CMOS_DISP),
  99. 99      .cmos_v_pixel      (V_CMOS_DISP),
  100. 100     .total_h_pixel     (TOTAL_H_PIXEL),
  101. 101     .total_v_pixel     (TOTAL_V_PIXEL),
  102. 102     .cmos_frame_vsync  (cmos_frame_vsync),
  103. 103     .cmos_frame_href   (cmos_frame_href),
  104. 104     .cmos_frame_valid  (cmos_frame_valid),
  105. 105     .cmos_frame_data   (wr_data)
  106. 106     );
  107. 107
  108. 108  //图像处理模块
  109. 109 vip u_vip(
  110. 110     //module clock
  111. 111     .clk              (cam_pclk),          // 时钟信号
  112. 112     .rst_n            (rst_n ),            // 复位信号(低有效)
  113. 113     //图像处理前的数据接口
  114. 114     .pre_frame_vsync  (cmos_frame_vsync),
  115. 115     .pre_frame_hsync  (cmos_frame_href),
  116. 116     .pre_frame_de     (cmos_frame_valid),
  117. 117     .pre_rgb          (wr_data),
  118. 118     .xpos             (pixel_xpos),
  119. 119     .ypos             (pixel_ypos),
  120. 120     //图像处理后的数据接口
  121. 121     .post_frame_vsync (post_frame_vsync),  // 处理后的场信号
  122. 122     .post_frame_hsync ( ),                 // 处理后的行信号
  123. 123     .post_frame_de    (post_frame_de),     // 处理后的数据有效使能
  124. 124     .post_rgb         (post_rgb)           // 处理后的图像数据
  125. 125
  126. 126 );  
  127. 127     
  128. 128 //ddr控制器
  129. 129 ddr3_control
  130. 130  #(
  131. 131    .C3_NUM_DQ_PINS         (16),
  132. 132    .C3_MEM_ADDR_WIDTH      (14),
  133. 133    .C3_MEM_BANKADDR_WIDTH  (3)
  134. 134    )
  135. 135  u_ddr3_control(
  136. 136   .clk_ddr                (clk_ddr),                              
  137. 137   .clk_100m               (clk_100m),                             
  138. 138   .rst_n                  (sys_rst_n),                           
  139. 139   .mcb3_dram_dq           (mcb3_dram_dq     ),                    
  140. 140   .mcb3_dram_a            (mcb3_dram_a      ),                    
  141. 141   .mcb3_dram_ba           (mcb3_dram_ba     ),                    
  142. 142   .mcb3_dram_ras_n        (mcb3_dram_ras_n  ),                    
  143. 143   .mcb3_dram_cas_n        (mcb3_dram_cas_n  ),                    
  144. 144   .mcb3_dram_we_n         (mcb3_dram_we_n   ),                    
  145. 145   .mcb3_dram_reset_n      (mcb3_dram_reset_n),                    
  146. 146   .mcb3_dram_cke          (mcb3_dram_cke    ),                    
  147. 147   .mcb3_dram_dm           (mcb3_dram_dm     ),                    
  148. 148   .mcb3_dram_udqs         (mcb3_dram_udqs   ),                    
  149. 149   .mcb3_dram_udqs_n       (mcb3_dram_udqs_n ),                    
  150. 150   .mcb3_rzq               (mcb3_rzq         ),                    
  151. 151   .mcb3_zio               (mcb3_zio         ),                    
  152. 152   .mcb3_dram_udm          (mcb3_dram_udm    ),                    
  153. 153   .mcb3_dram_dqs          (mcb3_dram_dqs    ),                    
  154. 154   .mcb3_dram_dqs_n        (mcb3_dram_dqs_n  ),                    
  155. 155   .mcb3_dram_ck           (mcb3_dram_ck     ),                    
  156. 156   .mcb3_dram_ck_n         (mcb3_dram_ck_n   ),                    
  157. 157   .c3_calib_done          (init_calib_complete),                  
  158. 158                                                                  
  159. 159   .ddr3_read_valid        (1'b1 ),                                
  160. 160   .ddr3_pingpang_en       (1'b1),                                 
  161. 161                                                                  
  162. 162   //摄像头                                                        
  163. 163   .clk_write              (cam_pclk),              //写时钟                          
  164. 164   .wr_en                  (post_frame_de),         //写使能                        
  165. 165   .wr_data                (post_rgb),              //写数据  
  166. 166   .wr_load                (post_frame_vsync),      //写端更新信号   
  167. 167   .wr_length              (H_CMOS_DISP[10:3]),     //一次写长度                          
  168. 168   .wr_max_addr            (ddr3_addr_max),         //写最大地址   
  169. 169   .wr_min_addr            (0),                     //写最小地址                        
  170. 170                                                                             
  171. 171   .clk_read               (pixel_clk),             //读时钟   
  172. 172   .rd_load                (video_vs),              //读端更新信号   
  173. 173   .rd_en                  (rdata_req),             //读使能                        
  174. 174   .rd_max_addr            (ddr3_addr_max),         //读最大地址   
  175. 175   .rd_min_addr            (0),                     //读最小地址                        
  176. 176   .rd_length              (H_CMOS_DISP[10:3]),     //一次读长度                        
  177. 177   .rd_data                (rd_data)                //读数据                        
  178. 178                                                                  
  179. 179   );
  180. 180
  181. 181 //时钟模块
  182. 182 clock u_clock(
  183. 183     .sys_clk      (sys_clk),       //系统输入时钟
  184. 184
  185. 185     .clk_ddr      (clk_ddr),       //ddr运行时钟
  186. 186     .clk_50m      (clk_50m),       //50m时钟
  187. 187     .clk_100m     (clk_100m),      //100m时钟
  188. 188     .locked_100m  (locked_100m),   //时钟稳定信号
  189. 189     .locked_300m  (locked_300m)    //时钟稳定信号
  190. 190     
  191. 191 );
  192. 192
  193. 193 //HDMI驱动显示模块   
  194. 194 hdmi_top u_hdmi_top(
  195. 195     .clk_50m              (clk_50m),  
  196. 196     .pixel_clk            (pixel_clk),  
  197. 197     .sys_rst_n            (sys_init_done & rst_n),
  198. 198     //hdmi接口                  
  199. 199     .tmds_clk_p           (tmds_clk_p   ),   // TMDS 时钟通道
  200. 200     .tmds_clk_n           (tmds_clk_n   ),
  201. 201     .tmds_data_p          (tmds_data_p  ),   // TMDS 数据通道
  202. 202     .tmds_data_n          (tmds_data_n  ),
  203. 203     //用户接口
  204. 204     .h_disp               (h_disp),          //HDMI屏水平分辨率
  205. 205     .v_disp               (v_disp),          //HDMI屏垂直分辨率
  206. 206     .video_vs             (video_vs),   
  207. 207     .pixel_xpos           (pixel_xpos),
  208. 208     .pixel_ypos           (pixel_ypos),      
  209. 209     .data_in              (rd_data),         //数据输入     
  210. 210     .data_req             (rdata_req)        //请求数据输入   
  211. 211 );
  212. 212
  213. 213 endmodule
复制代码

FPGA顶层模块(ov5640_hdmi_median_filter)例化了以下五个模块:时钟模块(clock)、OV5640驱动模块(ov5640_dri)、图像处理模块(vip)、DDR控制模块(ddr3_control)和HDMI顶层模块(hdmi_top)。
时钟模块(clock):时钟模块通过调用DCM_CLKGEN IP核实现,共输出3个时钟,频率分别为300Mhz、50Mhz时钟和100Mhz时钟。300Mhz时钟作为DDR控制模块的运行时钟;100Mhz时钟作为DDR控制模块的驱动时钟;50Mhz时钟作为OV5640驱动模块和HDMI顶层模块的驱动时钟。
OV5640驱动模块(ov5640_dri):OV5640驱动模块负责驱动OV5640 SCCB接口总线,将像素时钟驱动下的传感器输出的场同步信号、行同步信号以及8位数据转换成DDR读写控制模块的写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。
图像处理模块(vip):对采集后的图像数据进行处理,并将处理后的数据存入DDR控制模块。
DDR控制模块(ddr3_control):DDR读写控制器模块负责驱动DDR片外存储器,缓存图像传感器输出的图像数据。该模块将MIG IP核复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。有关DDR控制模块的详细介绍请大家参考“DDR读写测试实验”章节。
HDMI顶层模块(hdmi_top):HDMI顶层模块负责驱动HDMI显示器的驱动信号的输出,同时为其他模块提供参数和数据请求信号。HDMI顶层模块例化了HDMI驱动模块(video_driver)和RGB转DVI模块(rgbtodvi_top)。HDMI驱动模块负责产生行场信号和数据有效使能信号和像素点的横纵坐标,并将内部信号data_req(数据请求信号)输出至端口,方便从DDR控制器中读取数据。RGB转DVI模块负责将RGB888格式的视频图像转换成TMDS数据输出。有关HDMI驱动模块、RGB转DVI模块的详细介绍请大家参考“HDMI彩条显示实验”章节。
VIP模块框图如下图所示:

图 52.4.2 vip模块框图

vip模块例化了RGB转YCbCr模块(rgb2ycbcr)和中值滤波模块(vip_gray_median_jilter)。RGB转YCbCr模块负责将摄像头采集的RGB565格式的彩色图像转换为灰度图像。中值滤波模块负责对灰度图像进行中值滤波后输出。有关RGB转YCbCr模块的详细介绍请大家参考“OV5640摄像头HDMI灰度显示实验”章节。
vip模块原理图如下图所示:

图 52.4.3 vip模块原理图

如上图所示,摄像头采集到16位rgb565输入vip模块,经过“rgb2ycbcr”模块转化为8位的灰度值img_y(YUV中的亮度Y),然后在将转化后的灰度数据(img_y)作为“vip_gray_median_filter”模块的输入,对灰度值进行中值滤波处理,最后输出处理后的灰度数据“pos_img_y”。
图像处理模块负责图像数据的格式转换,代码如下:
  1. 1  module vip(
  2. 2      //module clock
  3. 3      input           clk               ,  // 时钟信号
  4. 4      input           rst_n             ,  // 复位信号(低有效)
  5. 5  
  6. 6      //图像处理前的数据接口
  7. 7      input           pre_frame_vsync   ,
  8. 8      input           pre_frame_hsync   ,
  9. 9      input           pre_frame_de      ,
  10. 10     input    [15:0] pre_rgb           ,
  11. 11     input    [10:0] xpos              ,
  12. 12     input    [10:0] ypos              ,
  13. 13
  14. 14     //图像处理后的数据接口
  15. 15     output          post_frame_vsync  ,   // 场同步信号
  16. 16     output          post_frame_hsync  ,   // 行同步信号
  17. 17     output          post_frame_de     ,   // 数据输入使能
  18. 18     output   [15:0] post_rgb              // RGB565颜色数据
  19. 19
  20. 20 );
  21. 21
  22. 22 //wire define
  23. 23 wire   [ 7:0]         img_y;
  24. 24 wire   [ 7:0]         post_img_y;
  25. 25 wire   [15:0]         post_rgb;
  26. 26 wire                  post_frame_vsync;
  27. 27 wire                  post_frame_hsync;
  28. 28 wire                  post_frame_de;
  29. 29 wire                  pe_frame_vsync;
  30. 30 wire                  pe_frame_href;
  31. 31 wire                  pe_frame_clken;
  32. 32
  33. 33 //*****************************************************
  34. 34 //**                    main code
  35. 35 //*****************************************************
  36. 36
  37. 37 assign  post_rgb = {post_img_y[7:3],post_img_y[7:2],post_img_y[7:3]};
  38. 38
  39. 39
  40. 40 //RGB转YCbCr模块
  41. 41 rgb2ycbcr u_rgb2ycbcr(
  42. 42     //module clock
  43. 43     .clk             (clk    ),            // 时钟信号
  44. 44     .rst_n           (rst_n  ),            // 复位信号(低有效)
  45. 45     //图像处理前的数据接口
  46. 46     .pre_frame_vsync (pre_frame_vsync),    // vsync信号
  47. 47     .pre_frame_hsync (pre_frame_hsync),    // href信号
  48. 48     .pre_frame_de    (pre_frame_de   ),    // data enable信号
  49. 49     .img_red         (pre_rgb[15:11] ),
  50. 50     .img_green       (pre_rgb[10:5 ] ),
  51. 51     .img_blue        (pre_rgb[ 4:0 ] ),
  52. 52     //图像处理后的数据接口
  53. 53     .post_frame_vsync(pe_frame_vsync),     // vsync信号
  54. 54     .post_frame_hsync(pe_frame_href),      // href信号
  55. 55     .post_frame_de   (pe_frame_clken),     // data enable信号
  56. 56     .img_y           (img_y),              //灰度数据
  57. 57     .img_cb          (),
  58. 58     .img_cr          ()
  59. 59 );
  60. 60
  61. 61 //灰度图中值滤波
  62. 62 vip_gray_median_filter u_vip_gray_median_filter(
  63. 63     .clk    (clk),   
  64. 64     .rst_n  (rst_n),
  65. 65     
  66. 66     //处理前图像数据
  67. 67     .pe_frame_vsync (pe_frame_vsync),      // vsync信号
  68. 68     .pe_frame_href  (pe_frame_href),       // href信号
  69. 69     .pe_frame_clken (pe_frame_clken),      // data enable信号
  70. 70     .pe_img_y       (img_y),
  71. 71     
  72. 72     //处理后的图像数据
  73. 73     .pos_frame_vsync (post_frame_vsync),   // vsync信号
  74. 74     .pos_frame_href  (post_frame_hsync),   // href信号
  75. 75     .pos_frame_clken (post_frame_de),      // data enable信号
  76. 76     .pos_img_y       (post_img_y)          //中值滤波后的灰度数据
  77. 77 );
  78. 78 endmodule
复制代码

代码的第37行表示对滤波后的8bit灰度数据进行位拼接,形成16bit的RGB565格式的数据输出。
代码的第41行至59行是对灰度转换模块的例化。该模块以摄像头采集的16位RGB565红、绿、蓝三原色数据作为输入数据,通过算法实现RGB到YCbCr的转换,并输出8位灰度数据以及数据输出使能信号。有关RGB转YCbCr模块的详细介绍请大家参考“OV5640摄像头HDMI灰度显示实验”章节。
代码的第62行至77行是对中值滤波模块的例化,该模块负责将YUV格式的视频图像进行中值滤波后输出。
中值滤波模块的层次结构如下图:

图 52.4.4 中值滤波模块的层次结构

前面介绍过,通过对图像的3x3矩阵进行排序操作来实现中值滤波,那么在实现中值滤波时,首先要生成3x3的矩阵。“vip_matrix_generate_3x3_8bit”模块实现的是对图像生成一个3x3数据矩阵。“median_filter_3x3”模块实现的是对图像生成的3x3数据矩阵进行中值滤波处理。
中值滤波模块代码如下:
  1. 1  module vip_gray_median_filter(
  2. 2      //时钟
  3. 3      input       clk,             //50MHz
  4. 4      input       rst_n,
  5. 5      
  6. 6      //处理前图像数据
  7. 7      input       pe_frame_vsync,  //处理前图像数据场信号
  8. 8      input       pe_frame_href,   //处理前图像数据行信号
  9. 9      input       pe_frame_clken,  //处理前图像数据输入使能效信号
  10. 10     input [7:0] pe_img_y,        //灰度数据            
  11. 11     
  12. 12     //处理后的图像数据
  13. 13     output       pos_frame_vsync, //处理后的图像数据场信号   
  14. 14     output       pos_frame_href,  //处理后的图像数据行信号  
  15. 15     output       pos_frame_clken, //处理后的图像数据输出使能效信号
  16. 16     output [7:0] pos_img_y        //处理后的灰度数据           
  17. 17 );
  18. 18
  19. 19 //wire define
  20. 20 wire        matrix_frame_vsync;
  21. 21 wire        matrix_frame_href;
  22. 22 wire        matrix_frame_clken;
  23. 23 wire [7:0]  matrix_p11; //3X3 阵列输出
  24. 24 wire [7:0]  matrix_p12;
  25. 25 wire [7:0]  matrix_p13;
  26. 26 wire [7:0]  matrix_p21;
  27. 27 wire [7:0]  matrix_p22;
  28. 28 wire [7:0]  matrix_p23;
  29. 29 wire [7:0]  matrix_p31;
  30. 30 wire [7:0]  matrix_p32;
  31. 31 wire [7:0]  matrix_p33;
  32. 32 wire [7:0]  mid_value;
  33. 33
  34. 34 //*****************************************************
  35. 35 //**                    main code
  36. 36 //*****************************************************
  37. 37 //在延迟后的行信号有效,将中值赋给灰度输出值
  38. 38 assign pos_img_y = pos_frame_href ? mid_value : 8'd0;
  39. 39
  40. 40 vip_matrix_generate_3x3_8bit u_vip_matrix_generate_3x3_8bit(
  41. 41     .clk        (clk),
  42. 42     .rst_n      (rst_n),
  43. 43     
  44. 44     //处理前图像数据
  45. 45     .per_frame_vsync    (pe_frame_vsync),
  46. 46     .per_frame_href     (pe_frame_href),
  47. 47     .per_frame_clken    (pe_frame_clken),
  48. 48     .per_img_y          (pe_img_y),
  49. 49     
  50. 50     //处理后的图像数据
  51. 51     .matrix_frame_vsync (matrix_frame_vsync),
  52. 52     .matrix_frame_href  (matrix_frame_href),
  53. 53     .matrix_frame_clken (matrix_frame_clken),
  54. 54     .matrix_p11         (matrix_p11),   
  55. 55     .matrix_p12         (matrix_p12),   
  56. 56     .matrix_p13         (matrix_p13),
  57. 57     .matrix_p21         (matrix_p21),   
  58. 58     .matrix_p22         (matrix_p22),   
  59. 59     .matrix_p23         (matrix_p23),
  60. 60     .matrix_p31         (matrix_p31),   
  61. 61     .matrix_p32         (matrix_p32),   
  62. 62     .matrix_p33         (matrix_p33)
  63. 63 );
  64. 64
  65. 65 //3x3阵列的中值滤波,需要3个时钟
  66. 66 median_filter_3x3 u_median_filter_3x3(
  67. 67     .clk        (clk),
  68. 68     .rst_n      (rst_n),
  69. 69     
  70. 70     .median_frame_vsync (matrix_frame_vsync),
  71. 71     .median_frame_href  (matrix_frame_href),
  72. 72     .median_frame_clken (matrix_frame_clken),
  73. 73     
  74. 74     //第一行
  75. 75     .data11           (matrix_p11),
  76. 76     .data12           (matrix_p12),
  77. 77     .data13           (matrix_p13),
  78. 78     //第二行              
  79. 79     .data21           (matrix_p21),
  80. 80     .data22           (matrix_p22),
  81. 81     .data23           (matrix_p23),
  82. 82     //第三行              
  83. 83     .data31           (matrix_p31),
  84. 84     .data32           (matrix_p32),
  85. 85     .data33           (matrix_p33),
  86. 86     
  87. 87     .pos_median_vsync (pos_frame_vsync),
  88. 88     .pos_median_href  (pos_frame_href),
  89. 89     .pos_median_clken (pos_frame_clken),
  90. 90     .target_data      (mid_value)
  91. 91 );
  92. 92
  93. 93 endmodule
复制代码

在vip_gray_median_filter模块中调用了vip_matrix_generate_3x3_8bit、median_filter_3x3两个模块,它们分别用于生成3x3矩阵和求得矩阵的中值。
vip_matrix_generate_3x3_8bit模块代码如下:
  1. 1   module  vip_matrix_generate_3x3_8bit
  2. 2   (
  3. 3       input             clk,  
  4. 4       input             rst_n,
  5. 5   
  6. 6       input             per_frame_vsync,
  7. 7       input             per_frame_href,
  8. 8       input             per_frame_clken,
  9. 9       input      [7:0]  per_img_y,
  10. 10      
  11. 11      output            matrix_frame_vsync,
  12. 12      output            matrix_frame_href,
  13. 13      output            matrix_frame_clken,
  14. 14      output reg [7:0]  matrix_p11,
  15. 15      output reg [7:0]  matrix_p12,
  16. 16      output reg [7:0]  matrix_p13,
  17. 17      output reg [7:0]  matrix_p21,
  18. 18      output reg [7:0]  matrix_p22,
  19. 19      output reg [7:0]  matrix_p23,
  20. 20      output reg [7:0]  matrix_p31,
  21. 21      output reg [7:0]  matrix_p32,
  22. 22      output reg [7:0]  matrix_p33
  23. 23  );
  24. 24  
  25. 25  //wire define
  26. 26  wire [7:0] row1_data;  
  27. 27  wire [7:0] row2_data;  
  28. 28  wire       read_frame_href;
  29. 29  wire       read_frame_clken;
  30. 30  
  31. 31  //reg define
  32. 32  reg  [7:0] row3_data;  
  33. 33  reg  [1:0] per_frame_vsync_r;
  34. 34  reg  [1:0] per_frame_href_r;
  35. 35  reg  [1:0] per_frame_clken_r;
  36. 36  
  37. 37  //*****************************************************
  38. 38  //**                    main code
  39. 39  //*****************************************************
  40. 40  
  41. 41  assign read_frame_href    = per_frame_href_r[0] ;
  42. 42  assign read_frame_clken   = per_frame_clken_r[0];
  43. 43  assign matrix_frame_vsync = per_frame_vsync_r[1];
  44. 44  assign matrix_frame_href  = per_frame_href_r[1] ;
  45. 45  assign matrix_frame_clken = per_frame_clken_r[1];
  46. 46  
  47. 47  //当前数据放在第3行
  48. 48  always@(posedge clk or negedge rst_n) begin
  49. 49      if(!rst_n)
  50. 50          row3_data <= 0;
  51. 51      else begin
  52. 52          if(per_frame_clken)
  53. 53              row3_data <= per_img_y ;
  54. 54          else
  55. 55              row3_data <= row3_data ;
  56. 56      end
  57. 57  end
  58. 58  
  59. 59  //用于存储列数据的RAM
  60. 60  line_shift_ram_8bit  u_line_shift_ram_8bit
  61. 61  (
  62. 62      .clock          (clk),
  63. 63      .clken          (per_frame_clken),
  64. 64      .per_frame_href (per_frame_href),
  65. 65      
  66. 66      .shiftin        (per_img_y),   
  67. 67      .taps0x         (row2_data),   
  68. 68      .taps1x         (row1_data)   
  69. 69  );
  70. 70  
  71. 71  //将同步信号延迟两拍,用于同步化处理
  72. 72  always@(posedge clk or negedge rst_n) begin
  73. 73      if(!rst_n) begin
  74. 74          per_frame_vsync_r <= 0;
  75. 75          per_frame_href_r  <= 0;
  76. 76          per_frame_clken_r <= 0;
  77. 77      end
  78. 78      else begin
  79. 79          per_frame_vsync_r <= { per_frame_vsync_r[0], per_frame_vsync };
  80. 80          per_frame_href_r  <= { per_frame_href_r[0],  per_frame_href  };
  81. 81          per_frame_clken_r <= { per_frame_clken_r[0], per_frame_clken };
  82. 82      end
  83. 83  end
  84. 84  
  85. 85  //在同步处理后的控制信号下,输出图像矩阵
  86. 86  always@(posedge clk or negedge rst_n) begin
  87. 87      if(!rst_n) begin
  88. 88          {matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
  89. 89          {matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
  90. 90          {matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
  91. 91      end
  92. 92      else if(read_frame_href) begin
  93. 93          if(read_frame_clken) begin
  94. 94              {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data};
  95. 95              {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};
  96. 96              {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};
  97. 97          end
  98. 98          else begin
  99. 99              {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
  100. 100             {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
  101. 101             {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
  102. 102         end
  103. 103     end
  104. 104     else begin
  105. 105         {matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
  106. 106         {matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
  107. 107         {matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
  108. 108     end
  109. 109 end
  110. 110
  111. 111 endmodule
复制代码

为了获得3x3的滤波模板,本次实验需要使用RAM来存储图像前两行的数据,而当前输入的图像数据作为第三行,如代码中第55行所示。而在代码的第59至69行,当第三行数据到达时,通过调用line_shift_ram_8bit模块,读出寄存在RAM中的前两行数据,从而获得一个“三行一列”的像素数据。三行数据分别位于row1_data、row2_data和row3_data三个变量中,其中row3_data表示当前行(第三行)图像数据。
接下来,将“三行一列”的像素数据,连续寄存三次,从而获取一个“三行三列”的像素阵列,如代码中的第99至101行所示。其中,matrix_p11、matrix_p12、 matrix_p13代表阵列中第一行中的三列像素数据,而matrix_p21、matrix_p22、matrix_p23代表阵列中第二行中的三列像素数据,以此类推。这个“三行三列”的矩阵就是本次实验所需要的3x3模板。
前面获取“三行一列”和获取“三行三列”的操作分别需要一个时钟周期,即该模块生成3x3模板共消耗两个时钟周期。因此,还要对场有效信号、数据有效信号和时钟使能信号延迟两个周期以作同步,如代码第79至81行所示。
代码的第60行调用了“line_shift_ram_8bit”模块,其代码如下:
  1. 1  module line_shift_ram_8bit(
  2. 2      input          clock,   
  3. 3      input          clken,
  4. 4      input          per_frame_href,
  5. 5      
  6. 6      input   [7:0]  shiftin,  
  7. 7      output  [7:0]  taps0x,   
  8. 8      output  [7:0]  taps1x   
  9. 9  );
  10. 10
  11. 11 //reg define
  12. 12 reg  [2:0]  clken_dly;
  13. 13 reg  [9:0]  ram_rd_addr;
  14. 14 reg  [9:0]  ram_rd_addr_d0;
  15. 15 reg  [9:0]  ram_rd_addr_d1;
  16. 16 reg  [7:0]  shiftin_d0;
  17. 17 reg  [7:0]  shiftin_d1;
  18. 18 reg  [7:0]  shiftin_d2;
  19. 19 reg  [7:0]  taps0x_d0;
  20. 20
  21. 21 //*****************************************************
  22. 22 //**                    main code
  23. 23 //*****************************************************
  24. 24
  25. 25 //在数据来到时,ram地址累加
  26. 26 always@(posedge clock)begin
  27. 27     if(per_frame_href)
  28. 28         if(clken)
  29. 29             ram_rd_addr <= ram_rd_addr + 1 ;
  30. 30         else
  31. 31             ram_rd_addr <= ram_rd_addr ;
  32. 32     else
  33. 33         ram_rd_addr <= 0 ;
  34. 34 end
  35. 35
  36. 36 //时钟使能信号延迟三拍
  37. 37 always@(posedge clock) begin
  38. 38     clken_dly <= { clken_dly[1:0] , clken };
  39. 39 end
  40. 40
  41. 41
  42. 42 //将ram地址延迟二拍
  43. 43 always@(posedge clock ) begin
  44. 44     ram_rd_addr_d0 <= ram_rd_addr;
  45. 45     ram_rd_addr_d1 <= ram_rd_addr_d0;
  46. 46 end
  47. 47
  48. 48 //输入数据延迟三拍
  49. 49 always@(posedge clock)begin
  50. 50     shiftin_d0 <= shiftin;
  51. 51     shiftin_d1 <= shiftin_d0;
  52. 52     shiftin_d2 <= shiftin_d1;
  53. 53 end
  54. 54
  55. 55 //用于存储前一行图像的RAM
  56. 56 blk_mem_gen_0  u_ram_1024x8_0(   
  57. 57     .clka   (clock),
  58. 58     .wea   (clken_dly[2]),    //在延迟的第三个时钟周期,当前行的数据写入RAM0
  59. 59     .addra (ram_rd_addr_d1),
  60. 60     .dina  (shiftin_d2),
  61. 61     .clkb  (clock),
  62. 62     .addrb (ram_rd_addr),
  63. 63     .doutb (taps0x)           //延迟一个时钟周期,输出RAM0中前一行图像的数据
  64. 64 );
  65. 65
  66. 66 //寄存一次前一行图像的数据
  67. 67 always@(posedge clock ) begin
  68. 68     taps0x_d0 <= taps0x;
  69. 69 end
  70. 70
  71. 71 blk_mem_gen_0  u_ram_1024x8_1(   
  72. 72     .clka   (clock),           
  73. 73     .wea   (clken_dly[1]),    //在延迟的第二个时钟周期,将前一行图像的数据写入RAM1
  74. 74     .addra (ram_rd_addr_d0),
  75. 75     .dina  (taps0x_d0),
  76. 76     .clkb  (clock),
  77. 77     .addrb (ram_rd_addr),
  78. 78     .doutb (taps1x)           //延迟一个时钟周期,输出RAM1中前前一行图像的数据
  79. 79 );
  80. 80
  81. 81 endmodule
复制代码

line_shift_ram_8bit模块中例化了两个RAM,分别用于存储图像前两行的数据。
在上述代码中,当数据有效信号和时钟使能信号同时为高时,RAM地址开始累加,如代码第26到34行所示。由于RAM地址在per_frame_href信号为低电平时清零;而当新的一行到达时,per_frame_href信号为高电平,RAM地址开始累加,所以RAM的地址等于每行图像像素的横坐标。因此就可以根据RAM地址从而读出当前行像素点对应的前两行的图像,如代码的第62和63行,以及77和78行所示。读出的数据直接传递到模块的输出端口,用于上层模块生成“三行一列”的像素数据。
在该模块中,RAM1(u_ram_1024x8_1)中存储的是第一行(前前一行)的数据,RAM0(u_ram_1024x8_0)中存储的是第二行(前一行)的数据,而输入的图像数据则作为第三行。如下图所示:

图 52.4.5 RAM中存储的两行图像

在读出两个RAM中前两行的图像数据之后,还要将RAM0中的数据写入RAM1,如代码中第73和75行所示;然后将新行图像数据写入RAM0,如代码第58和60行所示,从而不断更新两个RAM中的图像数据。
从RAM中读取数据,以及向RAM1和RAM0中更新数据各需要花费一个时钟周期,因此在代码的第37至39行将输入的clken信号延时了三个时钟周期。并使用延迟之后的clken信号作为两个RAM中的写使能信号,如代码的第58和73行所示。
下面我们将介绍中值算法模块。median_filter_3x3模块的代码如下:
  1. 1   module median_filter_3x3(
  2. 2       input       clk,
  3. 3       input       rst_n,
  4. 4       input       median_frame_vsync,
  5. 5       input       median_frame_href,
  6. 6       input       median_frame_clken,
  7. 7      
  8. 8       input [7:0]  data11,
  9. 9       input [7:0]  data12,
  10. 10      input [7:0]  data13,
  11. 11      input [7:0]  data21,
  12. 12      input [7:0]  data22,
  13. 13      input [7:0]  data23,
  14. 14      input [7:0]  data31,
  15. 15      input [7:0]  data32,
  16. 16      input [7:0]  data33,
  17. 17     
  18. 18      output [7:0] target_data,
  19. 19      output       pos_median_vsync,
  20. 20      output       pos_median_href,
  21. 21      output       pos_median_clken
  22. 22  );
  23. 23  
  24. 24  //--------------------------------------------------------------------------------------
  25. 25  //FPGA Median Filter Sort order
  26. 26  //       Pixel -- Sort1 -- Sort2 -- Sort3
  27. 27  // [ P1  P2  P3 ]   [   Max1  Mid1   Min1 ]
  28. 28  // [ P4  P5  P6 ]   [   Max2  Mid2   Min2 ] [Max_min, Mid_mid, Min_max] mid_valid
  29. 29  // [ P7  P8  P9 ]   [   Max3  Mid3   Min3 ]
  30. 30  
  31. 31  //reg define
  32. 32  reg [2:0]   median_frame_vsync_r;
  33. 33  reg [2:0]   median_frame_href_r;
  34. 34  reg [2:0]   median_frame_clken_r;
  35. 35  //wire define
  36. 36  wire [7:0] max_data1;
  37. 37  wire [7:0] mid_data1;
  38. 38  wire [7:0] min_data1;
  39. 39  wire [7:0] max_data2;
  40. 40  wire [7:0] mid_data2;
  41. 41  wire [7:0] min_data2;
  42. 42  wire [7:0] max_data3;
  43. 43  wire [7:0] mid_data3;
  44. 44  wire [7:0] min_data3;
  45. 45  wire [7:0] max_min_data;
  46. 46  wire [7:0] mid_mid_data;
  47. 47  wire [7:0] min_max_data;
  48. 48  
  49. 49  //*****************************************************
  50. 50  //**                    main code
  51. 51  //*****************************************************
  52. 52  
  53. 53  assign pos_median_vsync = median_frame_vsync_r[2];
  54. 54  assign pos_median_href  = median_frame_href_r[2];
  55. 55  assign pos_median_clken = median_frame_clken_r[2];
  56. 56  
  57. 57  //Step1 对stor3进行三次例化操作
  58. 58  sort3  u_sort3_1(     //第一行数据排序
  59. 59      .clk      (clk),
  60. 60      .rst_n    (rst_n),
  61. 61      
  62. 62      .data1    (data11),
  63. 63      .data2    (data12),
  64. 64      .data3    (data13),
  65. 65      
  66. 66      .max_data (max_data1),
  67. 67      .mid_data (mid_data1),
  68. 68      .min_data (min_data1)
  69. 69  );
  70. 70  
  71. 71  sort3  u_sort3_2(      //第二行数据排序
  72. 72      .clk      (clk),
  73. 73      .rst_n    (rst_n),
  74. 74         
  75. 75      .data1    (data21),
  76. 76      .data2    (data22),
  77. 77      .data3    (data23),
  78. 78      
  79. 79      .max_data (max_data2),
  80. 80      .mid_data (mid_data2),
  81. 81      .min_data (min_data2)
  82. 82  );
  83. 83  
  84. 84  sort3  u_sort3_3(      //第三行数据排序
  85. 85      .clk      (clk),
  86. 86      .rst_n    (rst_n),
  87. 87         
  88. 88      .data1    (data31),
  89. 89      .data2    (data32),
  90. 90      .data3    (data33),
  91. 91      
  92. 92      .max_data (max_data3),
  93. 93      .mid_data (mid_data3),
  94. 94      .min_data (min_data3)
  95. 95  );
  96. 96  
  97. 97  //Step2 对三行像素取得的排序进行处理
  98. 98  sort3 u_sort3_4(        //取三行最大值的最小值
  99. 99      .clk      (clk),
  100. 100     .rst_n    (rst_n),
  101. 101           
  102. 102     .data1    (max_data1),
  103. 103     .data2    (max_data2),
  104. 104     .data3    (max_data3),
  105. 105     
  106. 106     .max_data (),
  107. 107     .mid_data (),
  108. 108     .min_data (max_min_data)
  109. 109 );
  110. 110
  111. 111 sort3 u_sort3_5(        //取三行中值的最小值
  112. 112     .clk      (clk),
  113. 113     .rst_n    (rst_n),
  114. 114           
  115. 115     .data1    (mid_data1),
  116. 116     .data2    (mid_data2),
  117. 117     .data3    (mid_data3),
  118. 118     
  119. 119     .max_data (),
  120. 120     .mid_data (mid_mid_data),
  121. 121     .min_data ()
  122. 122 );
  123. 123
  124. 124 sort3 u_sort3_6(        //取三行最小值的最大值
  125. 125     .clk      (clk),
  126. 126     .rst_n    (rst_n),
  127. 127           
  128. 128     .data1    (min_data1),
  129. 129     .data2    (min_data2),
  130. 130     .data3    (min_data3),
  131. 131     
  132. 132     .max_data (min_max_data),
  133. 133     .mid_data (),
  134. 134     .min_data ()
  135. 135 );
  136. 136
  137. 137 //step3 将step2 中得到的三个值,再次取中值
  138. 138 sort3 u_sort3_7(
  139. 139     .clk      (clk),
  140. 140     .rst_n    (rst_n),
  141. 141           
  142. 142     .data1    (max_min_data),
  143. 143     .data2    (mid_mid_data),
  144. 144     .data3    (min_max_data),
  145. 145     
  146. 146     .max_data (),
  147. 147     .mid_data (target_data),
  148. 148     .min_data ()
  149. 149 );
  150. 150
  151. 151 //延迟三个周期进行同步
  152. 152 always@(posedge clk or negedge rst_n)begin
  153. 153     if(!rst_n)begin
  154. 154         median_frame_vsync_r <= 0;
  155. 155         median_frame_href_r  <= 0;
  156. 156         median_frame_clken_r <= 0;
  157. 157     end
  158. 158     else begin
  159. 159         median_frame_vsync_r <= {median_frame_vsync_r[1:0],median_frame_vsync};
  160. 160         median_frame_href_r  <= {median_frame_href_r [1:0], median_frame_href};
  161. 161         median_frame_clken_r <= {median_frame_clken_r[1:0],median_frame_clken};
  162. 162     end
  163. 163 end
  164. 164
  165. 165 endmodule
复制代码

在median_3x3模块实现了简介中所介绍的取中值的快速算法,如下图所示:

图 52.4.6 取中值快速算法

图 52.4.6中所示的算法在模块中的实现共分为三步:
第一步(step1),例化了三次sort3模块,用以对矩阵的每一行数据进行排序,分别求出矩阵每一行的最小值、中值和最大值,如程序第58到95行;
第二步(step2),再例化三次sort3模块,与之前不同的是,此处sort3模块的输入是step1得到的三行数据每一行的三个最小值、三个中值和三个最大值,并输出三个最小值的最大值,三个中值的中间值以及三个最大值的最小值,如代码第98到135行;
第三步(step3),再次例化sort3,并以step2中得到的三个最小值、中值及最大值作为输入,取三个值的中值,如代码第138行到149行。
经过以上三步排序操作,就能得到3x3模板的中值。由于在求得中值过程中,step1、step2和step3一共需要消耗三个时钟周期,因此需要将median_frame_vsync、median_frame_href和median_frame_clken三个信号延迟三个时钟周期以作同步,如代码第152到163行。
在median_filter_3x3模块多次调用了sort3模块,sort3模块是一个针对三个数据进行排序操作的模块,它的代码如下:
  1. 1  module sort3(
  2. 2      input            clk,
  3. 3      input            rst_n,
  4. 4      input [7:0]      data1,
  5. 5      input [7:0]      data2,
  6. 6      input [7:0]      data3,
  7. 7      
  8. 8      output reg [7:0] max_data,
  9. 9      output reg [7:0] mid_data,
  10. 10     output reg [7:0] min_data
  11. 11 );
  12. 12
  13. 13 //-----------------------------------
  14. 14 //对三个数据进行排序
  15. 15 always@(posedge clk or negedge rst_n)begin
  16. 16     if(!rst_n)begin
  17. 17         max_data <= 0;
  18. 18         mid_data <= 0;
  19. 19         min_data <= 0;
  20. 20     end
  21. 21     else begin
  22. 22         //取最大值
  23. 23         if(data1 >= data2 && data1 >= data3)
  24. 24             max_data <= data1;
  25. 25         else if(data2 >= data1 && data2 >= data3)
  26. 26             max_data <= data2;
  27. 27         else//(data3 >= data1 && data3 >= data2)
  28. 28             max_data <= data3;
  29. 29         //取中值
  30. 30         if((data1 >= data2 && data1 <= data3) || (data1 >= data3 && data1 <= data2))
  31. 31             mid_data <= data1;
  32. 32         else if((data2 >= data1 && data2 <= data3) || (data2 >= data3 && data2 <= data1))
  33. 33             mid_data <= data2;
  34. 34         else//((data3 >= data1 && data3 <= data2) || (data3 >= data2 && data3 <= data1))
  35. 35             mid_data <= data3;
  36. 36         //取最小值
  37. 37         if(data1 <= data2 && data1 <= data3)
  38. 38             min_data <= data1;
  39. 39         else if(data2 <= data1 && data2 <= data3)
  40. 40             min_data <= data2;
  41. 41         else//(data3 <= data1 && data3 <= data2)
  42. 42             min_data <= data3;
  43. 43         
  44. 44      end
  45. 45 end
  46. 46
  47. 47 endmodule
复制代码

我们对模块median_filter_3x3进行中值提取的结果进行了仿真,仿真结果如下图所示:

图 52.4.7 Median_Filter_3X3仿真图

如上图所示,红色标记部分,矩阵的三行数据分别为{100,140,30}、{140,30,70}和{30,70,110}按本文中所介绍的中值滤波算法可以求得中值为70。由于median_filter模块的中值提取操作共消耗了三个时钟周期,所以中值(mid_value)会在三个时钟周期后输出,上图仿真结果图也表明中值是在三个周期后输出。如图中红色圆圈所指示,模块输出的中值也是70,与本次实验计算的相同,这说明中值提取成功。
52.5下载验证
编译完工程之后就可以开始下载程序了。将OV5640摄像头模块插在超越者开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,下载完成后HDMI显示器上会显示OV5640摄像头采集到的经FPGA中值滤波处理后的灰度图像。如下图所示,大家可以看到中值滤波处理后的图像与原图几乎没有差别,这是因为现如今的彩色摄像头的采集的图像质量都很高,含有的干扰很少,因此在中值滤波前后图像差别不明显,中值滤波结果可以用仿真来验证。

图 52.5.1 中值滤波后的HDMI实时显示图像


回复

使用道具 举报

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

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-4-18 02:02

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表