【正点原子FPGA连载】第五十一章 基于OV5640摄像头的中值滤波实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

1)实验平台:正点原子新起点V2开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:994244016
5)关注正点原子公众号,获取最新资料更新
在这里插入图片描述

第五十一章 基于OV5640摄像头的中值滤波实验

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

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

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

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

图 51.4.1 顶层系统框图
由上图可知,时钟模块(pll和pll_hdmi)为HDMI顶层模块、SDRAM控制模块以及IIC驱动模块提供驱动时钟。IIC驱动模块和IIC配置模块控制着传感器初始化的开始与结束,传感器初始化完成后将采集到的数据写入摄像头采集模块。数据在摄像头采集模块处理完成后写入图像处理模块,图像处理模块将摄像头数据进行处理后存入SDRAM控制模块。顶层模块从SDRAM控制模块中读出数据并驱动显示器显示,这时整个系统才完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在SDRAM和传感器都初始化完成之后才开始输出数据的,避免了在SDRAM初始化过程中向里面写入数据。
顶层模块代码如下所示:

1   module ov5640_hdmi_median_filter(    
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       input  [7:0]  cam_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 [1:0]  sdram_ba   ,  //SDRAM Bank地址
21      output [1:0]  sdram_dqm  ,  //SDRAM 数据掩码
22      output [12:0] sdram_addr ,  //SDRAM 地址
23      inout  [15:0] sdram_data ,  //SDRAM 数据    
24      //HDMI接口
25      output        tmds_clk_p,   // TMDS 时钟通道
26      output        tmds_clk_n,
27      output [2:0]  tmds_data_p,  // TMDS 数据通道
28      output [2:0]  tmds_data_n
29;
30  
31  //parameter define
32  parameter SLAVE_ADDR    = 7'h3c          ; //OV5640的器件地址7'h3c
33  parameter BIT_CTRL      = 1'b1           ; //OV5640的字节地址为16位  0:8位 1:16位
34  parameter CLK_FREQ      = 27'd50_000_000 ; //i2c_dri模块的驱动时钟频率 
35  parameter I2C_FREQ      = 18'd250_000    ; //I2C的SCL时钟频率,不超过400KHz
36  parameter V_CMOS_DISP   = 11'd800        ; //CMOS分辨率--行
37  parameter H_CMOS_DISP   = 11'd1280       ; //CMOS分辨率--列 
38  parameter TOTAL_H_PIXEL = 13'd2570       ; //CMOS分辨率--行
39  parameter TOTAL_V_PIXEL = 13'd980        ;
40  
41  //wire define
42  wire        clk_100m           ;  //100mhz时钟,SDRAM操作时钟
43  wire        clk_100m_shift     ;  //100mhz时钟,SDRAM相位偏移时钟
44  wire        clk_50m            ;
45  wire        hdmi_clk           ;  
46  wire        hdmi_clk_5         ;  
47  wire        locked             ;
48  wire        locked_hdmi        ;
49  wire        rst_n              ;
50  wire        sys_init_done      ;  //系统初始化完成(sdram初始化+摄像头初始化)
51  wire        i2c_exec           ;  //I2C触发执行信号
52  wire [23:0] i2c_data           ;  //I2C要配置的地址与数据(高8位地址,低8位数据)          
53  wire        i2c_done           ;  //I2C寄存器配置完成信号
54  wire        i2c_dri_clk        ;  //I2C操作时钟
55  wire [ 7:0] i2c_data_r         ;  //I2C读出的数据
56  wire        i2c_rh_wl          ;  //I2C读写控制信号
57  wire        cam_init_done      ;  //摄像头初始化完成                         
58  wire        wr_en              ;  //sdram_ctrl模块写使能
59  wire [15:0] wr_data            ;  //sdram_ctrl模块写数据
60  wire        rd_en              ;  //sdram_ctrl模块读使能
61  wire [15:0] rd_data            ;  //sdram_ctrl模块读数据
62  wire        sdram_init_done    ;  //SDRAM初始化完成
63  wire [10:0] pixel_xpos_w       ;  //HDMI横坐标
64  wire [10:0] pixel_ypos_w       ;  //HDMI纵坐标
65  wire        post_frame_vsync   ;  //处理后的场信号
66  wire        post_frame_hsync   ;  //处理后的行信号
67  wire        post_frame_de      ;  //处理后的数据使能  
68  wire [15:0] post_rgb           ;  //处理后的数据 
69  
70  //*****************************************************
71  //**                    main code
72  //*****************************************************
73  
74  assign  rst_n = sys_rst_n & locked & locked_hdmi;
75  //系统初始化完成:SDRAM和摄像头都初始化完成
76  //避免了在SDRAM初始化过程中向里面写入数据
77  assign  sys_init_done = sdram_init_done & cam_init_done;
78  //电源休眠模式选择 0:正常模式 1:电源休眠模式
79  assign  cam_pwdn  = 1'b0;
80  assign  cam_rst_n = 1'b1;
81  
82  //锁相环
83  pll 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  
92  pll_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      ) 
124125 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[23:8]),   
133     .i2c_data_w         (i2c_data[7:0] ),   
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[1:0],row_addr[12:0],col_addr[8:0]}
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_median_filter)例化了以下八个模块:时钟模块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模块框图如下图所示:

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

图 51.4.3 vip模块原理图
如上图所示,摄像头采集到16位rgb565输入vip模块,经过“rgb2ycbcr”模块转化为8位的yuv444数据,然后在将转化后的灰度数据(img_y)作为“vip_gray_median_jilter”模块的输入,对灰度进行中值滤波处理,最后输出处理后的灰度数据“pos_img_y”。
图像处理模块负责图像数据的格式转换,代码如下:

1  module 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    [15:0] pre_rgb           ,
11     input    [10:0] xpos              ,
12     input    [10:0] ypos              ,
13 
14     //图像处理后的数据接口
15     output          post_frame_vsync  ,   // 场同步信号
16     output          post_frame_hsync  ,   // 行同步信号
17     output          post_frame_de     ,   // 数据输入使能
18     output   [15:0] post_rgb              // RGB565颜色数据
19 
20;
21 
22 //wire define
23 wire   [ 7:0]         img_y;
24 wire   [ 7:0]         post_img_y;
25 wire                  pe_frame_vsync;
26 wire                  pe_frame_href;
27 wire                  pe_frame_clken;
28 
29 //*****************************************************
30 //**                    main code
31 //*****************************************************
32 
33 assign  post_rgb = {post_img_y[7:3],post_img_y[7:2],post_img_y[7:3]};
34 
35 //RGB转YCbCr模块
36 rgb2ycbcr u_rgb2ycbcr(
37     //module clock
38     .clk             (clk    ),            // 时钟信号
39     .rst_n           (rst_n  ),            // 复位信号(低有效)
40     //图像处理前的数据接口
41     .pre_frame_vsync (pre_frame_vsync),    // vsync信号
42     .pre_frame_hsync (pre_frame_hsync),    // href信号
43     .pre_frame_de    (pre_frame_de   ),    // data enable信号
44     .img_red         (pre_rgb[15:11] ),
45     .img_green       (pre_rgb[10:5 ] ),
46     .img_blue        (pre_rgb[ 4:0 ] ),
47     //图像处理后的数据接口
48     .post_frame_vsync(pe_frame_vsync),     // vsync信号
49     .post_frame_hsync(pe_frame_href),      // href信号
50     .post_frame_de   (pe_frame_clken),     // data enable信号
51     .img_y           (img_y),              //灰度数据
52     .img_cb          (),
53     .img_cr          ()
54;
55 
56 //灰度图中值滤波
57 vip_gray_median_filter u_vip_gray_median_filter(
58     .clk    (clk),   
59     .rst_n  (rst_n), 
60     
61     //处理前图像数据
62     .pe_frame_vsync (pe_frame_vsync),      // vsync信号
63     .pe_frame_href  (pe_frame_href),       // href信号
64     .pe_frame_clken (pe_frame_clken),      // data enable信号
65     .pe_img_y       (img_y),
66     
67     //处理后的图像数据
68     .pos_frame_vsync (post_frame_vsync),   // vsync信号
69     .pos_frame_href  (post_frame_hsync),   // href信号
70     .pos_frame_clken (post_frame_de),      // data enable信号
71     .pos_img_y       (post_img_y)          //中值滤波后的灰度数据
72;
73
74 endmodule

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

图 51.4.4 中值滤波模块的层次结构
前面介绍过,通过对图像的3x3矩阵进行排序操作来实现中值滤波的,那么在实现中值滤波时,首先要生成3x3的矩阵。“vip_matrix_generate_3x3_8bit”模块实现的是对图像生成一个3x3数据矩阵。“median_filter_3x3”模块实现的是对图像生成的3x3数据矩阵进行中值滤波处理。
中值滤波模块代码如下:

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

在vip_gray_median_filter模块调用了vip_matrix_generate_3x3_8bit、median_filter_3x3两个模块,他们分别用于生成3x3矩阵和求得矩阵的中值。
vip_matrix_generate_3x3_8bit模块代码如下:

1   module  vip_matrix_generate_3x3_8bit
23       input             clk,  
4       input             rst_n,
5   
6       input             per_frame_vsync,
7       input             per_frame_href,
8       input             per_frame_clken,
9       input      [7:0]  per_img_y,
10      
11      output            matrix_frame_vsync,
12      output            matrix_frame_href,
13      output            matrix_frame_clken,
14      output reg [7:0]  matrix_p11,
15      output reg [7:0]  matrix_p12, 
16      output reg [7:0]  matrix_p13,
17      output reg [7:0]  matrix_p21, 
18      output reg [7:0]  matrix_p22, 
19      output reg [7:0]  matrix_p23,
20      output reg [7:0]  matrix_p31, 
21      output reg [7:0]  matrix_p32, 
22      output reg [7:0]  matrix_p33
23;
24  
25  //wire define
26  wire [7:0] row1_data;  
27  wire [7:0] row2_data;  
28  wire       read_frame_href;
29  wire       read_frame_clken;
30  
31  //reg define
32  reg  [7:0] row3_data;  
33  reg  [1:0] per_frame_vsync_r;
34  reg  [1:0] per_frame_href_r;
35  reg  [1:0] per_frame_clken_r;
36  
37  //*****************************************************
38  //**                    main code
39  //*****************************************************
40  
41  assign read_frame_href    = per_frame_href_r[0] ;
42  assign read_frame_clken   = per_frame_clken_r[0];
43  assign matrix_frame_vsync = per_frame_vsync_r[1];
44  assign matrix_frame_href  = per_frame_href_r[1] ;
45  assign matrix_frame_clken = per_frame_clken_r[1];
46  
47  //当前数据放在第3行
48  always@(posedge clk or negedge rst_n) begin
49      if!rst_n)
50          row3_data <= 0;
51      else begin
52          if(per_frame_clken)
53              row3_data <= per_img_y ;
54          else
55              row3_data <= row3_data ;
56      end
57  end
58  
59  //用于存储列数据的RAM
60  line_shift_ram_8bit  u_line_shift_ram_8bit
6162      .clock          (clk),
63      .clken          (per_frame_clken),
64      .per_frame_href (per_frame_href),
65      
66      .shiftin        (per_img_y),   
67      .taps0x         (row2_data),   
68      .taps1x         (row1_data)    
69;
70  
71  //将同步信号延迟两拍,用于同步化处理
72  always@(posedge clk or negedge rst_n) begin
73      if!rst_n) begin
74          per_frame_vsync_r <= 0;
75          per_frame_href_r  <= 0;
76          per_frame_clken_r <= 0;
77      end
78      else begin
79          per_frame_vsync_r <= { per_frame_vsync_r[0], per_frame_vsync };
80          per_frame_href_r  <= { per_frame_href_r[0],  per_frame_href  };
81          per_frame_clken_r <= { per_frame_clken_r[0], per_frame_clken };
82      end
83  end
84  
85  //在同步处理后的控制信号下,输出图像矩阵
86  always@(posedge clk or negedge rst_n) begin
87      if!rst_n) begin
88          {matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
89          {matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
90          {matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
91      end
92      else if(read_frame_href) begin
93          if(read_frame_clken) begin
94              {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data};
95              {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};
96              {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};
97          end
98          else begin
99              {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
100             {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
101             {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
102         end
103     end
104     else begin
105         {matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
106         {matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
107         {matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
108     end
109 end
110 
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  module line_shift_ram_8bit(
2      input          clock,   
3      input          clken,
4      input          per_frame_href,
5      
6      input   [7:0]  shiftin,  
7      output  [7:0]  taps0x,   
8      output  [7:0]  taps1x    
9;
10 
11 //reg define
12 reg  [2:0]  clken_dly;
13 reg  [9:0]  ram_rd_addr;
14 reg  [9:0]  ram_rd_addr_d0;
15 reg  [9:0]  ram_rd_addr_d1;
16 reg  [7:0]  shiftin_d0;
17 reg  [7:0]  shiftin_d1;
18 reg  [7:0]  shiftin_d2;
19 reg  [7:0]  taps0x_d0;
20 
21 //*****************************************************
22 //**                    main code
23 //*****************************************************
24 
25 //在数据来到时,ram地址累加
26 always@(posedge clock)begin
27     if(per_frame_href)
28         if(clken)
29             ram_rd_addr <= ram_rd_addr + 1 ;
30         else
31             ram_rd_addr <= ram_rd_addr ;
32     else
33         ram_rd_addr <= 0 ;
34 end
35 
36 //时钟使能信号延迟三拍
37 always@(posedge clock) begin
38     clken_dly <= { clken_dly[1:0] , clken };
39 end
40 
41 
42 //将ram地址延迟二拍
43 always@(posedge clock ) begin
44     ram_rd_addr_d0 <= ram_rd_addr;
45     ram_rd_addr_d1 <= ram_rd_addr_d0;
46 end
47 
48 //输入数据延迟三拍
49 always@(posedge clock)begin
50     shiftin_d0 <= shiftin;
51     shiftin_d1 <= shiftin_d0;
52     shiftin_d2 <= shiftin_d1;
53 end
54 
55 //用于存储前一行图像的RAM
56 blk_mem_gen_0  u_ram_1024x8_0(
57     .data      (shiftin_d2),     //在延迟的第三个时钟周期,当前行的数据写入RAM0
58     .clock     (clock),
59     .wraddress (ram_rd_addr_d1),
60     .wren      (clken_dly[2]),
61     .rdaddress (ram_rd_addr),
62     .q         (taps0x)          //延迟一个时钟周期,输出RAM0中前一行图像的数据
63;  
64 
65 //用于存储前前一行图像的RAM
66 blk_mem_gen_0  u_ram_1024x8_1(
67     .data      (taps0x),         //在延迟的第二个时钟周期,将前一行图像的数据写入RAM1
68     .clock     (clock     ),
69     .wraddress (ram_rd_addr_d0),
70     .wren      (clken_dly[1]),    
71     
72     .rdaddress (ram_rd_addr),
73     .q         (taps1x)           //延迟一个时钟周期,输出RAM1中前前一行图像的数据
74; 
75 
76 endmodule 

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

图 51.4.5 RAM中存储的两行图像
在读出两个RAM中前两行的图像数据之后,还要将RAM0中的数据写入RAM1,如代码中第67和70行所示;然后将新行图像数据写入RAM0,如代码第57和60行所示,从而不断更新两个RAM中的图像数据。
从RAM中读取数据,以及向RAM1和RAM0中更新数据各需要花费一个时钟周期,因此在代码的第37至39行将输入的clken信号延时了三个时钟周期。并使用延迟之后的clken信号作为两个RAM中的写使能信号,如代码的第60和70行所示。
下面我们将介绍中值算法模块,median_filter_3x3模块的代码如下:

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

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

图 51.4.6 取中值快速算法
图 51.4.6中所示的算法在模块中的实现共分为三步:第一步(step1),例化了三次sort3模块,用以对矩阵的每一行数据进行排序,分别求出矩阵每一行的最小值、中值和最大值,如程序第58到95行;第二步(step2),再例化三次sort3模块,与之前不同的是,此处sort3模块的输入是step1得到的三行数据每一行的三个最小值、三个中值和三个最大值,并输出三个最小值的最大值,三个中值的中间值以及三个最大值的最小值,如代码第98到135行;第三步(step3),再次例化sort3,并以step2中得到的三个最小值、中值及最大值作为输入,取三个值的中值,如代码第139行到150行。
经过以上三步排序操作,就能得到3x3模板的中值。由于在求得中值过程中,step1、step2和step3一共需要消耗三个时钟周期,因此需要将median_frame_vsync、median_frame_href和median_frame_clken三个信号延迟三个时钟周期以作同步,如代码第152到163行。
在median_filter_3x3模块多次调用了sort3模块,sort3模块是一个针对三个数据进行排序操作的模块,它的代码如下:

1  module sort3(
2      input            clk,
3      input            rst_n,
4      input [7:0]      data1, 
5      input [7:0]      data2, 
6      input [7:0]      data3,
7      
8      output reg [7:0] max_data, 
9      output reg [7:0] mid_data, 
10     output reg [7:0] min_data
11;
12 
13 //-----------------------------------
14 //对三个数据进行排序
15 always@(posedge clk or negedge rst_n)begin
16     if!rst_n)begin
17         max_data <= 0;
18         mid_data <= 0;
19         min_data <= 0;
20     end
21     else begin
22         //取最大值
23         if(data1 >= data2 && data1 >= data3)
24             max_data <= data1;
25         else if(data2 >= data1 && data2 >= data3)
26             max_data <= data2;
27         else//(data3 >= data1 && data3 >= data2)
28             max_data <= data3;
29         //取中值
30         if((data1 >= data2 && data1 <= data3) || (data1 >= data3 && data1 <= data2))
31             mid_data <= data1;
32         else if((data2 >= data1 && data2 <= data3) || (data2 >= data3 && data2 <= data1))
33             mid_data <= data2;
34         else//((data3 >= data1 && data3 <= data2) || (data3 >= data2 && data3 <= data1))
35             mid_data <= data3;
36         //取最小值
37         if(data1 <= data2 && data1 <= data3)
38             min_data <= data1;
39         else if(data2 <= data1 && data2 <= data3)
40             min_data <= data2;
41         else//(data3 <= data1 && data3 <= data2)
42             min_data <= data3;
43         
44      end
45 end
46 
47 endmodule 

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

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

图 51.5.1 HDMI实时显示图像

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值