发 帖  
原厂入驻New
[资料] 【正点原子FPGA连载】第二十七章基于OV5640的中值滤波实验-领航者 ZYNQ 之嵌入式开发指南
2020-9-7 15:46:37  54 正点原子FPGA
分享
1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/FPGA/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
5)关注正点原子公众号,获取最新资料


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

在数字图像处理中,无论是直接获取的灰度图像,还是由彩色图像转换得到的灰度图像,里面都有噪声的存在,噪声对图像质量有很大的影响。而中值滤波是一种常用的降噪方法,它不仅可以去除孤点噪声,而且可以保持图像的边缘特性,不会使图像产生显著的模糊。本章实验我们将进行基于OV5640摄像头的中值滤波的实验。本章包括以下几个部分:
2727.1简介
27.2实验任务
27.3硬件设计
27.4软件设计
27.5下载验证
27.1简介
滤波是指接收(通过)或过滤掉信号中一定的频率分量,例如,通过低频率的滤波器称为低通滤波器。空间滤波是图像处理领域应用非常广泛的工具之一,它可以改善图像质量,包括去除高频噪声与干扰、图像平滑等。我们常见的空间滤波有中值滤波和均值滤波。
图像可以看成是一个定义在二维平面上的信号,该信号的幅值对应像素的灰度(彩色图像对应RGB三个分量)。图像的频率指的是空间频率,它和我们认知的物理频率是不同的。图像的频率是表征图像中灰度变化剧烈程度的指标,是灰度在平面空间上的梯度。不同频率信息在图像结构中有不同的作用。图像的主要成分是低频信息,它形成了图像的基本灰度等级,对图像结构的决定作用较小;中频信息决定了图像的基本结构,形成了图像的主要边缘结构;高频信息形成了图像的边缘和细节,是在中频信息上对图像内容的进一步强化。
我们也可以通过空间滤波器(也称为空间掩模、模板或窗口)直接作用于图像本身而对图像进行滤波处理。空间滤波器由两部分组成:(1)邻域,(2)对该邻域包围的图像像素执行的预定义操作。领域是指一个像素点及其附近像素点所组成的空间。滤波会产生一个新像素,像素的坐标就是邻域中心的坐标,像素的值就是滤波操作的结果。
中值滤波就是一种很常见的空间滤波,它一种非线性平滑技术。它将每一像素点及该像素点的邻域作为一个滤波模板,计算出模板中所有像素点的灰度值的中值,然后用它代替模板中心点像素的值。图 27.1.1为像素点P及其周围8个像素点所组成的3x3滤波模板:



图 27.1.1 中值滤波模板

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



图 27.1.2 红外图像中的盲元

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



图 27.1.3 中值滤波算法框图

首先我们生成一个3x3的像素阵列,然后在分别对每行3个像素进行排序,得出每行的最大、 中值和最小值(如上图Max1、Med1和Min1)。接着,对排序后的矩阵进行处理,即提取三个最大值中的最小值(Minz_of_Max),三个中间值的中间值(Med_of_Med),以及三个最小值中的最大值(Max_of_Min)。最后,将得到的三个值,再次取中值,最终求得9个像素的中值。
27.2实验任务
本章我们利用OV5640摄像头采集RGB565数据,将采集的数据转换为YUV数据,然后对灰度数据进行中值滤波处理,最后通过LCD显示。
27.3硬件设计
本次实验的硬件电路、管脚分配与“OV5640摄像头LCD显示”实验完全相同,有关这一部分内容请读者参考“OV5640摄像头LCD显示”实验。 PL 端的硬件系统框架与“OV5640摄像头LCD显示”实验基本相同,但不同点在于,我们添加了VIP(video_image_process)模块,该模块的功能是将 OV5640 摄像头采集的 RGB 格式的图像数据转换成Ycbcr格式,然后进行灰度中值滤波操作,最后将处理后的数据送入 Video In to AXI4-Stream IP 核。
本次实验的系统框图如下:



图 27.3.1 实验系统框图

VIP模块的IP核在Block Design中的连接图如下:



图 27.3.2 VIP模块在Block Design中的连接图

图 27.3.2中的video_image_process_0就是本次实验中完成的视频图像处理模块(VIP),它在摄像头数据采集模块ov5640_capture_data_0和IP核v_vid_in_axi4s_0之间负责完成灰度转换和中值滤波等图像处理。
VIP(video_image_process)模块包含“RGB转Ycbcr”和“中值滤波”两个模块。VIP内部两个模块的连接图如下所示:



图 27.3.3 VIP内部模块连接图

图 27.3.3中u_rgb2ycbcr模块负责完成灰度转换,而u_gray_median_filter则负责将转换得到的灰度图像进行中值滤波。
VIP模块顶层例化代码如下:

  • 1 module Video_Image_Processor(
  • 2 input clk, //cmos 像素时钟
  • 3 input rst_n,
  • 4
  • 5 //预处理图像
  • 6 input pre_image_vsync, //预处理图像场同步信号
  • 7 input pre_image_clken, //预处理图像时钟使能信号
  • 8 input pre_data_valid, //预处理图像数据有效信号
  • 9 input [23:0] pre_image_data, //预处理图像数据
  • 10
  • 11 //处理后图像
  • 12 output pos_image_vsync, //处理后图像场同步信号
  • 13 output pos_image_clken, //处理后图像时钟使能信号
  • 14 output pos_data_valid, //处理后图像数据有效信号
  • 15 output [23:0] pos_image_data //处理后图像数据
  • 16
  • 17 );
  • 18
  • 19 //wire define
  • 20 wire [7:0] gray_data ;
  • 21 wire ycbcb_vsync;
  • 22 wire ycbcbr_clken;
  • 23 wire ycbcr_valid;
  • 24
  • 25 //*****************************************************
  • 26 //** main code
  • 27 //*****************************************************
  • 28 //rgb转ycbcr模块
  • 29 rgb2ycbcr u_rgb2ycbcr(
  • 30 .clk (clk),
  • 31 .rst_n (rst_n),
  • 32
  • 33 .rgb_vsync (pre_image_vsync),
  • 34 .rgb_clken (pre_image_clken),
  • 35 .rgb_valid (pre_data_valid),
  • 36 .rgb_data (pre_image_data),
  • 37
  • 38
  • 39 .ycbcb_vsync (ycbcb_vsync),
  • 40 .ycbcbr_clken (ycbcbr_clken),
  • 41 .ycbcr_valid (ycbcr_valid),
  • 42 .gray_data (gray_data)
  • 43 );
  • 44
  • 45 //中值滤波模块
  • 46 gray_median_filter u_gray_median_filter(
  • 47 .clk (clk),
  • 48 .rst_n (rst_n),
  • 49
  • 50 //预处理图像
  • 51 .pre_gray_vsync (ycbcb_vsync),
  • 52 .pe_gray_valid (ycbcr_valid),
  • 53 .pe_gray_clken (ycbcbr_clken),
  • 54 .pre_gray_data (gray_data),
  • 55
  • 56 //处理后图像
  • 57 .pos_gray_vsync (pos_image_vsync),
  • 58 .pos_gray_valid (pos_data_valid),
  • 59 .pos_gray_clken (pos_image_clken),
  • 60 .pos_pixel_data (pos_image_data)
  • 61 );
  • 62
  • 63 endmodule

在VIP顶层模块调用了“rgb2ycbcr”和“gray_median_filter”两个模块,有关“rgb2ycbcr”模块我们在“OV5640摄像头灰度图显示”实验已经有过介绍,需要了解的朋友可以参考“OV5640摄像头灰度图显示”实验相关内容。本章节我们将讲解gray_median_filter模块,即中值滤波模块。
gray_median_filter模块的代码如下:

  • 1 module gray_median_filter(
  • 2 input clk,
  • 3 input rst_n,
  • 4
  • 5 //预处理灰度数据
  • 6 input pre_gray_vsync, //预处理灰度场同步
  • 7 input pe_gray_valid, //预处理灰度数据有效信号
  • 8 input pe_gray_clken, //预处理灰度时钟使能信号
  • 9 input [7:0] pre_gray_data, //预处理灰度数据
  • 10
  • 11 //处理后灰度数据
  • 12 output pos_gray_vsync, //处理后灰度场同步信号
  • 13 output pos_gray_valid, //处理后灰度数据有效信号
  • 14 output pos_gray_clken, //处理后灰度时钟使能信号
  • 15 output [23:0] pos_pixel_data //处理后灰度数据
  • 16 );
  • 17
  • 18 //wire define
  • 19 wire matrix_frame_vsync;
  • 20 wire matrix_frame_href;
  • 21 wire matrix_frame_clken;
  • 22 wire [7:0] matrix_p11; //3X3 矩阵数据
  • 23 wire [7:0] matrix_p12;
  • 24 wire [7:0] matrix_p13;
  • 25 wire [7:0] matrix_p21;
  • 26 wire [7:0] matrix_p22;
  • 27 wire [7:0] matrix_p23;
  • 28 wire [7:0] matrix_p31;
  • 29 wire [7:0] matrix_p32;
  • 30 wire [7:0] matrix_p33;
  • 31 wire [7:0] mid_value ;
  • 32 wire [7:0] pos_img_Y;
  • 33
  • 34 //*****************************************************
  • 35 //** main code
  • 36 //*****************************************************
  • 37
  • 38 assign pos_img_Y = pos_gray_valid ? mid_value : 8'd0;
  • 39 assign pos_pixel_data = {pos_img_Y,pos_img_Y,pos_img_Y};
  • 40
  • 41 VIP_matrix_generate_3x3_8Bit u_VIP_matrix_generate_3x3_8bit(
  • 42 .clk (clk),
  • 43 .rst_n (rst_n),
  • 44
  • 45 //预处理灰度数据
  • 46 .per_frame_vsync (pre_gray_vsync),
  • 47 .per_frame_href (pe_gray_valid),
  • 48 .per_frame_clken (pe_gray_clken),
  • 49 .per_img_Y (pre_gray_data),
  • 50
  • 51 //输出3x3矩阵
  • 52 .matrix_frame_vsync (matrix_frame_vsync),
  • 53 .matrix_frame_href (matrix_frame_href),
  • 54 .matrix_frame_clken (matrix_frame_clken),
  • 55 .matrix_p11 (matrix_p11),
  • 56 .matrix_p12 (matrix_p12),
  • 57 .matrix_p13 (matrix_p13),
  • 58 .matrix_p21 (matrix_p21),
  • 59 .matrix_p22 (matrix_p22),
  • 60 .matrix_p23 (matrix_p23),
  • 61 .matrix_p31 (matrix_p31),
  • 62 .matrix_p32 (matrix_p32),
  • 63 .matrix_p33 (matrix_p33)
  • 64 );
  • 65
  • 66 //3x3矩阵中值提取
  • 67 median_filter u_median_filter(
  • 68 .clk (clk),
  • 69 .rst_n (rst_n),
  • 70
  • 71 .median_frame_vsync (matrix_frame_vsync),
  • 72 .median_frame_href (matrix_frame_href),
  • 73 .median_frame_clken (matrix_frame_clken),
  • 74
  • 75 //矩阵第一行数据
  • 76 .data11 (matrix_p11),
  • 77 .data12 (matrix_p12),
  • 78 .data13 (matrix_p13),
  • 79 //矩阵第二行数据
  • 80 .data21 (matrix_p21),
  • 81 .data22 (matrix_p22),
  • 82 .data23 (matrix_p23),
  • 83 //矩阵第三行数据
  • 84 .data31 (matrix_p31),
  • 85 .data32 (matrix_p32),
  • 86 .data33 (matrix_p33),
  • 87
  • 88 .pos_frame_vsync (pos_gray_vsync),
  • 89 .pos_frame_href (pos_gray_valid),
  • 90 .pos_frame_clken (pos_gray_clken),
  • 91 .target_data (mid_value)
  • 92 );
  • 93
  • 94 endmodule

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

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

为了获得3x3的滤波模板,我们需要使用RAM来存储图像前两行的数据,而当前输入的图像数据作为第三行,如代码中第55行所示。而在代码的第61至71行,当第三行数据到达时,我们通过调用line_shift_RAM_8bit模块,读出寄存在RAM中的前两行数据,从而获得一个“三行一列”的像素数据。三行数据分别位于row1_data、row2_data和row3_data三个变量中,其中row3_data表示当前行(第三行)图像数据。
接下来,我们将“三行一列”的像素数据,连续寄存三次,从而获取一个“三行三列”的像素阵列,如代码中的第96至98行所示。其中,matrix_p11、matrix_p12、 matrix_p13代表阵列中第一行中的三列像素数据,而matrix_p21、matrix_p22、matrix_p23代表阵列中第二行中的三列像素数据,以此类推。这个“三行三列”的矩阵就是我们所需要的3x3模板。
前面获取“三行一列”和获取“三行三列”的操作分别需要一个时钟周期,即该模块生成3x3模板共消耗两个时钟周期。因此,我们要对场有效信号、数据有效信号和时钟使能信号延迟两个周期以作同步,如代码第81至83行所示。
代码的第62行调用了“line_shift_RAM_8bit”模块,其代码如下:

  • 1 module line_shift_RAM_8bit(
  • 2 input clock,
  • 3
  • 4 input clken,
  • 5 input per_frame_href,
  • 6
  • 7 input [7:0] shiftin, //当前行的数据
  • 8 output [7:0] taps0x, //前一行的数据
  • 9 output [7:0] taps1x //前前一行的数据
  • 10 );
  • 11
  • 12 //reg define
  • 13 reg [2:0] clken_dly;
  • 14 reg [9:0] ram_rd_addr;
  • 15 reg [9:0] ram_rd_addr_d0;
  • 16 reg [9:0] ram_rd_addr_d1;
  • 17 reg [7:0] shiftin_d0;
  • 18 reg [7:0] shiftin_d1;
  • 19 reg [7:0] shiftin_d2;
  • 20 reg [7:0] taps0x_d0;
  • 21
  • 22 //*****************************************************
  • 23 //** main code
  • 24 //*****************************************************
  • 25
  • 26 //在数据到来时,RAM的读地址累加
  • 27 always@(posedge clock)begin
  • 28 if(per_frame_href)
  • 29 if(clken)
  • 30 ram_rd_addr <= ram_rd_addr + 1 ;
  • 31 else
  • 32 ram_rd_addr <= ram_rd_addr ;
  • 33 else
  • 34 ram_rd_addr <= 0 ;
  • 35 end
  • 36
  • 37 //对时钟使能信号延迟3拍
  • 38 always@(posedge clock) begin
  • 39 clken_dly <= { clken_dly[1:0] , clken };
  • 40 end
  • 41
  • 42 //将RAM地址延迟2拍
  • 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 //输入数据延迟3拍送入RAM
  • 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 .clka (clock),
  • 58 .wea (clken_dly[2]),
  • 59 .addra (ram_rd_addr_d1), //在延迟的第三个时钟周期,当前行的数据写入RAM0
  • 60 .dina (shiftin_d2),
  • 61
  • 62 .clkb (clock),
  • 63 .addrb (ram_rd_addr),
  • 64 .doutb (taps0x) //延迟一个时钟周期,输出RAM0中前一行图像的数据
  • 65 );
  • 66
  • 67 //寄存前一行图像的数据
  • 68 always@(posedge clock)begin
  • 69 taps0x_d0 <= taps0x;
  • 70 end
  • 71
  • 72 //用于存储前前一行图像的RAM
  • 73 blk_mem_gen_0 u_ram_1024x8_1(
  • 74 .clka (clock),
  • 75 .wea (clken_dly[1]),
  • 76 .addra (ram_rd_addr_d0),
  • 77 .dina (taps0x_d0), //在延迟的第二个时钟周期,将前一行图像的数据写入RAM1
  • 78
  • 79 .clkb (clock),
  • 80 .addrb (ram_rd_addr),
  • 81 .doutb (taps1x) //延迟一个时钟周期,输出RAM1中前前一行图像的数据
  • 82 );
  • 83
  • 84 endmodule

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



图 27.3.4 RAM中存储的两行图像

在读出两个RAM中前两行的图像数据之后,我们还要将RAM0中的数据写入RAM1,如代码中第76和77行所示;然后将新行图像数据写入RAM0,如代码第59和60行所示,从而不断更新两个RAM中的图像数据。
从RAM中读取数据,以及向RAM1和RAM0中更新数据各需要花费一个时钟周期,因此我们在代码的第37至40行将输入的clken信号延时了三个时钟周期。并使用延迟之后的clken信号作为两个RAM中的写使能信号,如代码的第75和58行所示。
在模块中我们例化了两个伪双端RAM(一个端口只能读,一个端口只能写),用于存储两行图像,以生成图像矩阵的列数据。下面我们简单介绍一下如何调用并配置这两个RAM IP核。
首先在左侧“Flow Navigater”下点击“IP Catalog”,再在右侧弹出的界面宽内输入“ram”,选择“Block Memory Generator”并双击打开,进入RAM配置界面。



图 27.3.5 RAM选择界面

接下来进入RAM配置界面,如下图所示:



图 27.3.6 RAM配置界面

在“Basic”页面,我们将name一栏设置为“blk_mem_gen_0”,RAM类型设为“Simple Dual Port RAM”,即伪双端口类型,如上图红色标注部分。
接着就是“Port A Options”页面的配置,如下图所示:



图 27.3.7 Port A Options配置

Port A端口用于向RAM中写入数据。如上图所示,我们将RAM的位宽设置为8,深度设置为1024,操作模式设置为先写模式,写使能设置为时钟使能。如上图红色标记部分。
接着配置“Port B Options”页面,Port B端口用于从RAM中读取数据。同样的,将位宽设置为8,深度设置为1024,操作模式设置为先写模式,写使能设置为始终使能,同时选择“Primitives Output Register”输出寄存选项(RAM中的数据会寄存一个周期后输出)。如下图所示:



图 27.3.8 Port B Options页面配置

到这里我们RAM的配置基本完成,后面两项保持默认即可,最后生成IP核就完成了RAM的设置。
前面我们已经生成了3x3的矩阵,接着就是求3x3矩阵的中值,中值算法模块的代码如下:

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

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



图 27.3.9 取中值快速算法

图 27.3.9中所示的算法在模块中的实现共分为三步:第一步(step1),我们例化了三次stor3模块,用以对矩阵的每一行数据进行排序,分别求出矩阵每一行的最小值、中值和最大值,如程序第58到96行;第二步(step2),再例化三次stor3模块,与之前不同的是,我们此处stor3模块的输入是step1得到的三行数据每一行的三个最小值、三个中值和三个最大值,并输出三个最小值的最大值,三个中值的中间值以及三个最大值的最小值,如代码第98到136行;第三步(step3),再次例化sort3,并以step2中得到的三个最小值、中值及最大值作为输入,取三个值的中值,如代码第138行到150行。
经过以上三步排序操作,我们就能得到一个像素在其8邻域模板上的中值。由于在求得中之过程中,step1、step2和step3一共需要消耗三个时钟周期,一次我们需要将median_frame_vsync、median_frame_href和median_frame_clken三个信号延迟三个时钟周期以作同步,如代码第152到164行。
在median_filter模块我们多次调用了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

上述代码实现了对三个数的排序,如代码第22到28行,取三个数的最大值;如第29到35行,取三个数的中值;如代码第36到42行,取三个数的最小值。
我们对模块median_3x3进行中值提取的结果进行了仿真,仿真结果如下图所示:



图 27.3.10 中值滤波仿真图

如上图所示,红色标记部分,矩阵的三行数据分别为{99,138,30}、{138,30,69}和{30,69,108}按本文中所介绍的中值滤波算法可以求得中值为69。由于median_filter模块的中值提取操作共消耗了三个时钟周期,所以中值(mid_value)会在三个时钟周期后输出,上图仿真结果图也表明中值是在三个周期后输出。如图中红色圆圈所指示,模块输出的中值也是69,与我们计算的相同,这说明中值提取成功。
到这里,我们的VIP模块就介绍完了,我们要将模块打包成IP并添加到工程的IP库中,方便在Block Design中进行调用。连线后的 Block Design 如下图所示:



图 27.3.11 Block Design整体框图

接下来验证当前设计。验证完成后弹出对话框提示没有错误或者关键警告,点击“OK”。如果验证结果报出错误或者警告,则需要重新检查设计。为工程添加的约束文件与“OV5640摄像头LCD显示”完全相同,有关这一部分内容请读者参考“OV5640摄像头LCD显示”实验。
最后在左侧 Flow Navigator 导航栏中找到 PROGRAM AND debug,点击该选项中的“Generate
Bitstream”,对设计进行综合、实现、并生成 Bitstream文件。在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件, 并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择 File > Launch SDK,启动SDK软件。
27.4软件设计
本实验软件设计与“OV5640摄像头LCD显示”实验相同,大家可参考“OV5640摄像头LCD显示”实验软件设计部分,本章就不再赘述。
27.5下载验证
“OLED/CAMERA”插座上,并将 HDMI 电缆一端连接到开发板上的 HDMI 插座、另一端连接到显器。将下载器一端连电脑,另一端与开发板上的 JTAG 端口连接,连接电源线并打开电源开关。在 SDK 软件下方的 SDK Terminal 窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的 BIT 文件,来对 PL 进行配置。最后下载软件程序,下载完成后, 在下方的 SDK Terminal中可以看到应用程序打印的信息,如下图所示:



图 27.5.1 中值滤波实验串口打印信息

下载完成后,我们可以看到LCD屏上的灰度图像。我们可以看到中值滤波处理后的图像与原图几乎没有差别,这是因为现如今的彩色摄像头的采集的图像质量都很高,含有的干扰很少,因此在中值滤波前后图像差别不明显,中值滤波结果可以用仿真来验证。



图 27.5.2 LCD显示中值滤波灰度图


0
分享淘帖 显示全部楼层

评论

高级模式
您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发资料
关闭

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

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