文章目录
- 前言
- 一、系统整体设计
- 二、各模块的功能
- 三、彩色图像灰度化处理模块的设计
- 1.基本原理
- 2.彩色图像灰度化处理方法介绍
- 方法 1:分量法
- 方法 2:最大值法
- 方法 3:平均值法
- 平均值法的实现
- 方法 4 加权平均法
- 加权平均法的实现
- rgb2gray 模块
- rgb2gray TB文件
- 四、uart_ddr3_tft_rgb2gray模块
- 五、传图显示
前言
本次实验主要是在上一节的基础上,加入图像处理模块,实现在 PC 端通过上位机下发尺寸为 400*480 大小的彩色图像数据到 FPGA 的串口,FPGA 通过串口接收的彩色图像数据并进行实时彩色图像灰度化处理,然后将原始彩色图像和处理后的图像拼接在一起并缓存在DDR3 中,最终在 TFT 屏上同时显示处理前的彩色图像和处理后的灰度图像。
提示:以下是本篇文章正文内容,下面案例可供参考
一、系统整体设计
系统整体设计框图如下。大体结构与“基于 DDR3 的串口传图帧缓存系统设计实现”基本一致,增加了彩色图像灰度化处理模块 rgb2gray 模块和图像合并模块 image_stitche_x 模块。
二、各模块的功能
(1)uart_byte_rx 模块:负责串口图像数据的接收,该模块的设计前面章节已经有讲。数据(图像数据是 16bit 的 RGB565 的数据,电脑是通过串口将一个像素点数据分两次发送到 FPGA,FPGA 需将串口接收数据重组成 16bit 的图像数据),实现过程相对比较简单(可参考串口接收)。
(2)bit8_trans_bit16 模块:将串口接收的每两个 8bit 数据转换成一个 16bit(可参考8_trans_16)。
(3)rgb2gray 模块:彩色图像灰度化处理,对串口接收的彩色图像数据实时进行灰度化处理(参考第三章rgb2gray 模块)。
(4)image_stitche_x 模块:将串口接收的尺寸为 400480 大小的彩色图像与灰度化处理后的 400480 大小的图像数据以左右形式合并成一张 800*480 的图像(image_stitche_x 模块)。
(5)disp_driver 模块:tft 屏显示驱动控制,对缓存在 DDR3 中的图像数据进行显示(可参考VGA成像原理)。
(6)wr_ddr3_fifo 模块:使用的 FIFO IP ,主要用于写入 DDR3 数据的缓存、解决数据跨时钟域以及数据位宽的转换(参考串口传图IP模块)。
(7)rd_ddr3_fifo 模块:使用的 FIFO IP ,主要用于读出 DDR3 数据的缓存、解决数据跨时钟域以及数据位宽的转换(参考串口传图IP模块)。
(8)fifo2mig_axi 模块:主要是用于接口的转换,将 MIG IP 的 AXI 接口换成与 FIFO 对接的接口(可参考fifo2mig_axi)。
(9)mig_7series_0 模块: DDR3 控制器,使用的 Memory Interface Generator(MIG 7 Series)IP(可参考DDR3 控制器 MIG IP 详解完整版)。
(10)pll 模块:上述各个模块所需时钟的产生,使用 PLL IP(参考串口传图IP模块)。
本系统就是在上一系统基础上添加图像处理模块搭建系统。除去使用 IP 和前面章节讲过的模块外,还需要设计的模块包括 rgb2gray 模块和 image_stitche_x 模块。
三、彩色图像灰度化处理模块的设计
1.基本原理
将彩色图像转化为灰度图像的过程称为图像灰度化处理。常见的 24 位深度彩色图像RGB888 中的每个像素的颜色由 R、G、B 三个分量决定,并且三个分量各占 1 个字节,每个分量可以取值 0~255,这样一个像素点可以有 1600 多万(255255255)的颜色的变化范围。而灰度图像是 R、G、B 三个分量相同的一种特殊的彩色图像,其一个像素点的变化范围为 0~255。对于一幅彩色图来说,其对应的灰度图则是只有 8 位的图像深度,这也说明了用灰度图做图像处理所需的计算量确实要少。不过需要注意的是,虽然丢失了一些颜色等级,但是从整幅图像的整体和局部的色彩以及亮度等级分布特征来看,灰度图描述与彩色图的描述是一致的。一般有分量法、最大值法、平均值法、加权平均法四种方法对彩色图像进行灰度化。
2.彩色图像灰度化处理方法介绍
方法 1:分量法
将彩色图像中的三分量的亮度作为三个灰度图像的灰度值,可根据应用需要选取一种灰度图像。具体表达式如下。
gray1(𝑖,𝑗) = 𝑅(𝑖,𝑗)
gray2(𝑖,𝑗) = 𝐺(𝑖,𝑗)
gray3(𝑖,𝑗) = 𝐵(𝑖,𝑗)
其中,gray1(𝑖,𝑗), gray2(𝑖,𝑗), gray3(𝑖,𝑗)为转换后的灰度图像在(i,j)处的灰度值,R(i,j),G(i,j),B(i,j)分别为转换前的彩色图像在(i,j)处 R、G、B 三个分量的值。
方法 2:最大值法
将彩色图像中的三分量亮度 R,G,B 的最大值作为灰度图的灰度值。具体表达式如下。
gray(i, j) = max[𝑅(𝑖,𝑗),𝐺(𝑖,𝑗),𝐵(𝑖,𝑗)]
方法 3:平均值法
将彩色图像中的三分量亮度求平均得到一个灰度值。如下:
gray(i, j) =(𝑅(𝑖,𝑗) + 𝐺(𝑖,𝑗) + 𝐵(𝑖,𝑗))/3
上式中有除法,考虑到在 FPGA 中实现除法比较的消耗资源,这里在实现前可以先做如下的近似处理。可以将上面公式乘以 3/256,这样就需要同时乘以 256/3 保证公式的正确性。公式处理过程如下:
对 256/3 做近似取整处理,将 256/3 替换成 85,则公式变为如下。
这样式子中除以 256 就可以采用移位方法来处理,式子变为如下:
上面处理过程中使用是对 256/3 的近似处理,当然这里可以采用其他数据,比如512/3、1024/3、2048/3 等等,基本的原则是将平均公式法中分母的 3 替换成 2 的幂次的数,这样除法就可以使用移位的方式实现,减小 FPGA 中由于存在除法带来的资源消耗。
平均值法的实现
该方法实现起来并不复杂,通过上面的计算公式可以知道,计算公式里只有加法、乘法和移位计算,这里的乘法通过移位相加的方式进行计算,计算具体实现见下面代码。
//求平均法 GRAY = (R+B+G)/3=((R+B+G)*85)>>8
wire [9:0]sum;
reg [15:0]gray_r;
assign sum = red_8b_i + green_8b_i + blue_8b_i;
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
gray_r <= 16'd0;
else if(rgb_valid)
gray_r <= (sum << 6)+(sum << 4)+(sum << 2)+ sum;
else
gray_r <= 16'd0;
end
assign gray_8b_o = gray_r[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid;
gray_hs <= rgb_hs;
gray_vs <= rgb_vs;
end
对该模块的仿真也相对比较简单,只需要在 testbech 中给时钟复位激励以及 RGB 三通道的图像数据即可,具体代码如下,仿真中分别给 R、G、B 通道不同的是起始数据值,然后通过递增加 1 的形式改变 R、G、B 通道数据,验证设计的正确性。
initial begin
reset_p = 1;
rgb_valid = 0;
red_8b_i = 0;
green_8b_i = 0;
blue_8b_i = 0;
#(`CLK_PERIOD*200+1);
reset_p = 0;
red_8b_i = 56;
green_8b_i = 124;
blue_8b_i = 203;
#2000;
rgb_valid = 1;
repeat(256)begin
#(`CLK_PERIOD)
red_8b_i = red_8b_i + 1;
green_8b_i = green_8b_i + 1;
blue_8b_i = blue_8b_i + 1;
end
rgb_valid = 0;
#2000;
$stop;
end
为了能验证采用平均值法实现彩色图像灰度化计算的正确性,在仿真代码中加入了如下代码。直接通过平均法的原始公式产生一组对比数据。
always@(posedge clk)
begin
if(rgb_valid == 1'b1)
comp1_gray <= (red_8b_i + green_8b_i + blue_8b_i)/3;
else
comp1_gray <= 0;
end
这样可以比较容易的通过对比 comp1_gray 和 gray_8b_o 的值来验证设计模块的正确性。其仿真波形图如下:
在后面有个地方可以看到从一个地方开始 comp1_gray 和 gray_8b_o 的值相差 1,可以分析下出现这个问题的原因,其实 gray_8b_o 和 comp1_gray 的计算公式并非完全一样,gray_8b_o 的计算公式是为了避免除法做了一定的近似,而在仿真文件中 comp1_gray 是直接求的平均。可以通过 red、green、blue 这 3 个数据分别对 gray_8b_o 和 comp1_gray 进行计算,计算结果与仿真波形结果是一致的。这也说明了,避免除法做近似处理计算的 gray 和直接通过除法求的平均值 comp1_gray 是稍存在偏差的。这个是可以接收的误差范围。
方法 4 加权平均法
根据重要性及其它指标,将三个分量以不同的权值进行加权平均。有一个很著名的心理学公式:
这里 0.299+0.587+0.114=1,刚好是满偏,这是通过不同的敏感度以及经验总结出来的公式,一般可以直接用这个。在实际应用时,为了能避免低速的浮点运算以及除法运算,可以先将式子缩放 1024 倍来实现运算算法,如下:
通过近似取整处理后得到近似公式如下。
式子中除以 1024(这里是 2 的 n 次方就可以,n 不同,结果会略微有差别)可以采用移位方法来处理,式子变为如下:
也可以压缩到 8 位以内,式子变为如下。具体压缩到多少位可以根据实际需求。
加权平均法的实现
对于加权平均法可通过两种方式实现,公式直接计算法和查找表法。
公式直接计算法
公式直接计算法与方法 3 实现类似,通过转换公式直接进行计算,只是具体计算数值发生了变化,同样乘法采用移位相加的方式实现。具体代码如下:
//典型灰度转换公式 Gray = R*0.299+G*0.587+B*0.114=(R*77 + G*150 + B*29) >>8
wire [15:0]red_x77;
wire [15:0]green_x150;
wire [15:0]blue_x29;
reg [15:0]sum;
//乘法转换成移位相加方式
assign red_x77 = (red_8b_i << 6) + (red_8b_i << 3) + (red_8b_i <<
2) + red_8b_i;
assign green_x150 = (green_8b_i<< 7) + (green_8b_i<< 4) + (green_8b_i<<
2) + (green_8b_i<<1);
assign blue_x29 = (blue_8b_i << 4) + (blue_8b_i << 3) + (blue_8b_i <<
2) + blue_8b_i;
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
sum <= 16'd0;
else if(rgb_valid)
sum <= red_x77 + green_x150 + blue_x29;
else
sum <= 16'd0;
end
assign gray_8b_o = sum[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid;
gray_hs <= rgb_hs;
gray_vs <= rgb_vs;
end
公查找表法
通过观察计算公式发现,R、G、B 数据值均乘以了一个定值,然后对乘法之后的结果相加,最后右移 8 位,上面采用直接计算法实现是对常数乘法采用的移位相加方法计算,对于这类固定范围内的数值,同时可取数据不多情况下(这里 R、G、B 数值范围在 0~255,可取的数据有限)乘以一个常数,可以采用查找表方法实现,该方法主要的优势是直接通过访问 ROM 内的数据,相对使用移位相加实现乘法使用的 LUT 资源会少点,但占用的存储器会更多。因为需要将 R、G、B 乘以系数之后的数值存储在 ROM 中,然后通过读取 ROM方式来得到计算之后的数值。这里使用 Vivado 添加 3 个 ROM IP 核,分别通过 R75、G147、B36(0≤R≤255,0≤G≤255,0≤B≤255)的计算值建立 3 个初始化 coe 文件,然后在ROM IP 核中分别添加 coe 文件进行初始化。具体代码如下:代码中 rom_red_x77、rom_green_x150、rom_blue_x29 分别存储着 R 75、G147、B36(0≤R≤255,0≤G≤255,0≤B≤255)256 个数值。
//查找表方式,可以省去公式法中乘法运算 Gray =(R*77 + G*150 + B*29) >>8,将 3 个分量
乘以系数后的数值存储在 ROM 中
wire [14:0]red_x77;
wire [15:0]green_x150;
wire [13:0]blue_x29;
reg [15:0]sum;
reg rgb_valid_dly1;
reg rgb_hs_dly1;
reg rgb_vs_dly1;
rom_red_x77 rom_red_x77(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (red_8b_i ), // input wire [7 : 0] addra
.douta (red_x77 ) // output wire [14 : 0] douta
);
rom_green_x150 rom_green_x150(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (green_8b_i ), // input wire [7 : 0] addra
.douta (green_x150 ) // output wire [15 : 0] douta
);
rom_blue_x29 rom_blue_x29(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (blue_8b_i ), // input wire [7 : 0] addra
.douta (blue_x29 ) // output wire [13 : 0] douta
);
always@(posedge clk)
begin
rgb_valid_dly1 <= rgb_valid;
rgb_hs_dly1 <= rgb_hs;
rgb_vs_dly1 <= rgb_vs;
end
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
sum <= 16'd0;
else if(rgb_valid_dly1)
sum <= red_x77 + green_x150 + blue_x29;
else
sum <= 16'd0;
end
assign gray_8b_o = sum[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid_dly1;
gray_hs <= rgb_hs_dly1;
gray_vs <= rgb_vs_dly1;
end
仿真过程与方法 3 类似,这里就不重复描述。对于方法 4 的两种不同实现方式,可以对比下综合后消耗的资源情况。下图 1 和下图 2 分别为使用公式直接计算法和查找表法实现使用资源情况。
比较两种方法使用资源情况,可以很明显的看出查找表方式消耗的 LUT 相对较少,但消耗了 1.5 个 Block RAM 资源。
对于同一功能多种不同实现方法的模块代码如何整合到一起呢?当然每种方法作为一个单独的模块使用一个.v 文件保存肯定是没有问题的,这个就不太便于后期的维护和使用。如果能将多种实现方法整合到一个模块保存在一个.v 文件,使用起来就更加的方便。方法肯定是有的,而且还不只一种。下面提供两种方式,宏定义法,和使用 generate -if 方法。宏定义法相对比较好理解,通过不同的宏定义条件编译方式进行选择某种实现方式,实现的部分代码如下。该种方法可通过修改模块代码的宏定义选择不同的方法,还算是比较方便的。(此部分可参考语法使用)
rgb2gray 模块
/
// Module Name : rgb2gray
// Description : 图像处理之彩色图像灰度化,提供三种方式
//
module rgb2gray
#(
parameter PROC_METHOD = "FORMULA" //"AVERAGE" :求平均法
//or "FORMULA" :直接公式法
//or "LUT" :查找表法
)
(
input clk, //时钟
input reset_p, //复位
input rgb_valid, //rgb输入有效标识
input rgb_hs, //rgb输入行信号
input rgb_vs, //rgb输入场信号
input [7:0] red_8b_i, //R输入
input [7:0] green_8b_i, //G输入
input [7:0] blue_8b_i, //B输入
output [7:0] gray_8b_o, //GRAY输出
output reg gray_valid, //gray输出有效标识
output reg gray_hs, //gray输出行信号
output reg gray_vs //gray输出场信号
);
generate
if (PROC_METHOD == "AVERAGE") begin: PROC_AVERAGE
//---------------------------------------------
//求平均法GRAY = (R+B+G)/3=((R+B+G)*85)>>8
wire [9:0]sum;
reg [15:0]gray_r;
assign sum = red_8b_i + green_8b_i + blue_8b_i;
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
gray_r <= 16'd0;
else if(rgb_valid)
gray_r <= (sum << 6)+(sum << 4)+(sum << 2)+ sum;
else
gray_r <= 16'd0;
end
assign gray_8b_o = gray_r[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid;
gray_hs <= rgb_hs;
gray_vs <= rgb_vs;
end
//---------------------------------------------
end
else if (PROC_METHOD == "FORMULA") begin: PROC_FORMULA
//---------------------------------------------
//典型灰度转换公式Gray = R*0.299+G*0.587+B*0.114=(R*77 + G*150 + B*29) >>8
wire [15:0]red_x77;
wire [15:0]green_x150;
wire [15:0]blue_x29;
reg [15:0]sum;
//乘法转换成移位相加方式
assign red_x77 = (red_8b_i << 6) + (red_8b_i << 3) + (red_8b_i << 2) + red_8b_i;
assign green_x150 = (green_8b_i<< 7) + (green_8b_i<< 4) + (green_8b_i<< 2) + (green_8b_i<<1);
assign blue_x29 = (blue_8b_i << 4) + (blue_8b_i << 3) + (blue_8b_i << 2) + blue_8b_i;
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
sum <= 16'd0;
else if(rgb_valid)
sum <= red_x77 + green_x150 + blue_x29;
else
sum <= 16'd0;
end
assign gray_8b_o = sum[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid;
gray_hs <= rgb_hs;
gray_vs <= rgb_vs;
end
//---------------------------------------------
end
else if(PROC_METHOD == "LUT") begin: PROC_LUT
//---------------------------------------------
//查找表方式,可以省去公式法中乘法运算Gray =(R*77 + G*150 + B*29) >>8,将3个分量乘以系数后的数值存储在ROM中
wire [14:0]red_x77;
wire [15:0]green_x150;
wire [13:0]blue_x29;
reg [15:0]sum;
reg rgb_valid_dly1;
reg rgb_hs_dly1;
reg rgb_vs_dly1;
rom_red_x77 rom_red_x77(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (red_8b_i ), // input wire [7 : 0] addra
.douta (red_x77 ) // output wire [14 : 0] douta
);
rom_green_x150 rom_green_x150(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (green_8b_i ), // input wire [7 : 0] addra
.douta (green_x150 ) // output wire [15 : 0] douta
);
rom_blue_x29 rom_blue_x29(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (blue_8b_i ), // input wire [7 : 0] addra
.douta (blue_x29 ) // output wire [13 : 0] douta
);
always@(posedge clk)
begin
rgb_valid_dly1 <= rgb_valid;
rgb_hs_dly1 <= rgb_hs;
rgb_vs_dly1 <= rgb_vs;
end
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
sum <= 16'd0;
else if(rgb_valid_dly1)
sum <= red_x77 + green_x150 + blue_x29;
else
sum <= 16'd0;
end
assign gray_8b_o = sum[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid_dly1;
gray_hs <= rgb_hs_dly1;
gray_vs <= rgb_vs_dly1;
end
//---------------------------------------------
end
else begin: PROC_NONE
//---------------------------------------------
assign gray_8b_o = 8'h00;
always@(posedge clk)
begin
gray_valid <= rgb_valid;
gray_hs <= rgb_hs;
gray_vs <= rgb_vs;
end
//---------------------------------------------
end
endgenerate
endmodule
rgb2gray TB文件
/
// Module Name : rgb2gray_tb
// Description : 图像处理之彩色图像灰度化仿真
/
`timescale 1ns/1ns
`define CLK_PERIOD 20
module rgb2gray_tb();
reg clk;
reg rst_n;
reg rgb_valid;
reg rgb_hs;
reg rgb_vs;
reg [7:0] red_8b_i;
reg [7:0] green_8b_i;
reg [7:0] blue_8b_i;
reg [7:0] comp1_gray;
reg [7:0] comp2_gray;
initial clk = 1'b1;
always #(`CLK_PERIOD/2)clk = ~clk;
initial begin
reset_p = 1;
rgb_valid = 0;
rgb_hs = 0;
rgb_vs = 0;
red_8b_i = 0;
green_8b_i = 0;
blue_8b_i = 0;
#(`CLK_PERIOD*200+1);
reset_p = 0;
red_8b_i = 56;
green_8b_i = 124;
blue_8b_i = 203;
#2000;
rgb_hs = 1;
rgb_vs = 1;
rgb_valid = 1;
repeat(256)begin
#(`CLK_PERIOD)
red_8b_i = red_8b_i + 1;
green_8b_i = green_8b_i + 1;
blue_8b_i = blue_8b_i + 1;
end
rgb_valid = 0;
rgb_hs = 0;
rgb_vs = 0;
#2000;
$stop;
end
always@(posedge clk)
begin
if(rgb_valid == 1'b1)
comp1_gray <= (red_8b_i + green_8b_i + blue_8b_i)/3;
else
comp1_gray <= 0;
end
always@(posedge clk)
begin
if(rgb_valid == 1'b1)
comp2_gray <= red_8b_i*0.299 + green_8b_i*0.587 + blue_8b_i*0.114;
else
comp2_gray <= 0;
end
rgb2gray
#(
.PROC_METHOD("AVERAGE") //"AVERAGE" :求平均法
//or "FORMULA" :直接公式法
//or "LUT" :查找表法
)rgb2gray_average
(
.clk (clk ),
.reset_p (reset_p ),
.rgb_valid (rgb_valid ),
.rgb_hs (rgb_hs ),
.rgb_vs (rgb_vs ),
.red_8b_i (red_8b_i ),
.green_8b_i(green_8b_i),
.blue_8b_i (blue_8b_i ),
.gray_8b_o ( ),
.gray_valid( ),
.gray_hs ( ),
.gray_vs ( )
);
rgb2gray
#(
.PROC_METHOD("FORMULA") //"AVERAGE" :求平均法
//or "FORMULA" :直接公式法
//or "LUT" :查找表法
)rgb2gray_formula
(
.clk (clk ),
.reset_p (reset_p ),
.rgb_valid (rgb_valid ),
.rgb_hs (rgb_hs ),
.rgb_vs (rgb_vs ),
.red_8b_i (red_8b_i ),
.green_8b_i(green_8b_i),
.blue_8b_i (blue_8b_i ),
.gray_8b_o ( ),
.gray_valid( ),
.gray_hs ( ),
.gray_vs ( )
);
rgb2gray
#(
.PROC_METHOD( "LUT" ) //"AVERAGE" :求平均法
//or "FORMULA" :直接公式法
//or "LUT" :查找表法
)rgb2gray_lut
(
.clk (clk ),
.reset_p (reset_p ),
.rgb_valid (rgb_valid ),
.rgb_hs (rgb_hs ),
.rgb_vs (rgb_vs ),
.red_8b_i (red_8b_i ),
.green_8b_i(green_8b_i),
.blue_8b_i (blue_8b_i ),
.gray_8b_o ( ),
.gray_valid( ),
.gray_hs ( ),
.gray_vs ( )
);
endmodule
四、uart_ddr3_tft_rgb2gray模块
该模块主要作用是串联其他模块,完成传图过程。其中各模块对应的介绍均在第二节后附有连接,以供参考。
/
// Module Name : uart_ddr3_tft_rgb2gray
// Description : 基于串口传图DDR3缓存TFT屏显示彩色图像灰度化处理
// Name :C.V-Pupil
/
module uart_ddr3_tft_rgb2gray(
//System clock reset
input clk50m , //系统时钟输入,50MHz
input reset_n , //复位信号输入
//LED
output [3:0] led ,
//Uart interface
input uart_rx , //串口输入信号
//TFT Interface
output [15:0] TFT_rgb , //TFT数据输出
output TFT_hs , //TFT行同步信号
output TFT_vs , //TFT场同步信号
output TFT_clk , //TFT像素时钟
output TFT_de , //TFT数据使能
output TFT_pwm , //TFT背光控制
//DDR3 Interface
// Inouts
inout [15:0] ddr3_dq ,
inout [1:0] ddr3_dqs_n ,
inout [1:0] ddr3_dqs_p ,
// Outputs
output [13:0] ddr3_addr ,
output [2:0] ddr3_ba ,
output ddr3_ras_n ,
output ddr3_cas_n ,
output ddr3_we_n ,
output ddr3_reset_n ,
output [0:0] ddr3_ck_p ,
output [0:0] ddr3_ck_n ,
output [0:0] ddr3_cke ,
output [0:0] ddr3_cs_n ,
output [1:0] ddr3_dm ,
output [0:0] ddr3_odt
);
//*********************************
//Internal connect
//*********************************
//clock
wire pll_locked;
wire loc_clk50m;
wire loc_clk200m;
wire loc_clk33m;
wire loc_clk9m;
//reset
wire g_rst_p;
//uart Interface
wire [7:0] uart_byte;
wire uart_byte_vaild;
//wr_fifo Interface
wire [15:0] wrfifo_din;
wire wrfifo_wren;
wire wrfifo_rden;
wire [127:0] wrfifo_dout;
wire [5 : 0] wrfifo_rd_cnt;
wire wrfifo_empty;
wire wrfifo_wr_rst_busy;
wire wrfifo_rd_rst_busy;
//rd_fifo Interface
wire rdfifo_wren;
wire [127:0] rdfifo_din;
wire rdfifo_rden;
wire [15 :0] rdfifo_dout;
wire [5 : 0] rdfifo_wr_cnt;
wire rdfifo_full;
wire rdfifo_wr_rst_busy;
wire rdfifo_rd_rst_busy;
//mig Interface
wire mig_reset_n;
wire aresetn;
wire mmcm_locked;
wire init_calib_complete;
wire ui_clk;
wire ui_clk_sync_rst;
wire[3:0] s_axi_awid;
wire[27:0] s_axi_awaddr;
wire[7:0] s_axi_awlen;
wire[2:0] s_axi_awsize;
wire[1:0] s_axi_awburst;
wire[0:0] s_axi_awlock;
wire[3:0] s_axi_awcache;
wire[2:0] s_axi_awprot;
wire[3:0] s_axi_awqos;
wire s_axi_awvalid;
wire s_axi_awready;
wire[127:0] s_axi_wdata;
wire[15:0] s_axi_wstrb;
wire s_axi_wlast;
wire s_axi_wvalid;
wire s_axi_wready;
wire [3:0] s_axi_bid;
wire [1:0] s_axi_bresp;
wire s_axi_bvalid;
wire s_axi_bready;
wire[3:0] s_axi_arid;
wire[27:0] s_axi_araddr;
wire[7:0] s_axi_arlen;
wire[2:0] s_axi_arsize;
wire[1:0] s_axi_arburst;
wire[0:0] s_axi_arlock;
wire[3:0] s_axi_arcache;
wire[2:0] s_axi_arprot;
wire[3:0] s_axi_arqos;
wire s_axi_arvalid;
wire s_axi_arready;
wire [3:0] s_axi_rid;
wire [127:0] s_axi_rdata;
wire [1:0] s_axi_rresp;
wire s_axi_rlast;
wire s_axi_rvalid;
wire s_axi_rready;
//tft
wire clk_disp;
wire frame_begin;
wire rdfifo_clr;
reg rdfifo_clr_sync_ui_clk;
reg rd_addr_clr;
wire wrfifo_clr;
reg wrfifo_clr_sync_ui_clk;
reg wr_addr_clr;
reg frame_rx_done_flip;
//兼容小梅哥TFT5.0寸和TFT4.3寸显示屏,可根据实际进行配置选择
/*
parameter DISP_WIDTH = 480;
parameter DISP_HEIGHT = 272;
assign clk_disp = loc_clk9m;
*/
parameter DISP_WIDTH = 800;
parameter DISP_HEIGHT = 480;
assign clk_disp = loc_clk33m;
assign mig_reset_n = pll_locked;
assign aresetn = pll_locked;
assign g_rst_p = ui_clk_sync_rst;
assign led = {frame_rx_done_flip,init_calib_complete,mmcm_locked,pll_locked};
pll pll
(
// Clock out ports
.clk_out1 (loc_clk50m ), // output clk_out1
.clk_out2 (loc_clk200m ), // output clk_out2
.clk_out3 (loc_clk33m ), // output clk_out3
.clk_out4 (loc_clk9m ), // output clk_out4
// Status and control signals
.resetn (reset_n ), // input reset
.locked (pll_locked ), // output locked
// Clock in ports
.clk_in1 (clk50m ) // input clk_in1
);
uart_byte_rx#(
.CLK_FRQ(50000000)
)
uart_byte_rx(
.clk (loc_clk50m ),
.reset_p (g_rst_p ),
.baud_set (3'd5 ), //1562500bps
.uart_rx (uart_rx ),
.data_byte(uart_byte ),
.rx_done (uart_byte_vaild )
);
//---------------------------------------------
//仅仿真用,正常功能时,将197~226行代码屏蔽
//仿真时,取消屏蔽197~226行代码,将179~191代码屏蔽
//---------------------------------------------
// reg [15:0]col_data_cnt;
// reg [15:0]row_data_cnt;
//
// always@(posedge loc_clk50m or posedge g_rst_p)
// begin
// if(g_rst_p)
// col_data_cnt <= 16'd0;
// else if(!init_calib_complete)
// col_data_cnt <= 16'd0;
// else if(col_data_cnt == DISP_WIDTH/2)
// col_data_cnt <= 16'd0;
// else
// col_data_cnt <= col_data_cnt + 1'b1;
// end
//
// always@(posedge loc_clk50m or posedge g_rst_p)
// begin
// if(g_rst_p)
// row_data_cnt <= 16'd0;
// else if(col_data_cnt == DISP_WIDTH/2)
// if(row_data_cnt >= DISP_HEIGHT-1)
// row_data_cnt <= 16'hffff;
// else
// row_data_cnt <= row_data_cnt + 1'b1;
// else
// row_data_cnt <= row_data_cnt;
// end
//
// assign uart_byte = row_data_cnt[7:0];
// assign uart_byte_vaild = (col_data_cnt > 1'b0) && (col_data_cnt <= DISP_WIDTH/2) && (row_data_cnt <= DISP_HEIGHT-1);
//-------------------------------------------
wire [15:0]image_data;
wire image_data_valid;
reg [15:0] image_data_hcnt;
reg [15:0] image_data_vcnt;
reg image_data_hs;
reg image_data_vs;
bit8_trans_bit16 bit8_trans_bit16
(
.clk (loc_clk50m ),
.reset_p (g_rst_p ),
.bit8_in (uart_byte ),
.bit8_in_valid (uart_byte_vaild ),
.bit16_out (image_data ),
.bit16_out_valid (image_data_valid)
);
//generate image data hs or vs
always@(posedge loc_clk50m or posedge g_rst_p)
if(g_rst_p)
image_data_hcnt <= 'd0;
else if(image_data_valid) begin
if(image_data_hcnt == (DISP_WIDTH/2 - 1'b1))
image_data_hcnt <= 'd0;
else
image_data_hcnt <= image_data_hcnt + 1'b1;
end
always@(posedge loc_clk50m or posedge g_rst_p)
if(g_rst_p)
image_data_vcnt <= 'd0;
else if(image_data_valid) begin
if(image_data_hcnt == (DISP_WIDTH/2 - 1'b1)) begin
if(image_data_vcnt == (DISP_HEIGHT - 1'b1))
image_data_vcnt <= 'd0;
else
image_data_vcnt <= image_data_vcnt + 1'b1;
end
end
//hs
always@(posedge loc_clk50m or posedge g_rst_p)
if(g_rst_p)
image_data_hs <= 1'b0;
else if(image_data_valid && image_data_hcnt == (DISP_WIDTH/2 - 1'b1))
image_data_hs <= 1'b0;
else
image_data_hs <= 1'b1;
//vs
always@(posedge loc_clk50m or posedge g_rst_p)
if(g_rst_p)
image_data_vs <= 1'b0;
else if(image_data_valid && image_data_hcnt == (DISP_WIDTH/2 - 1'b1) &&
image_data_vcnt == (DISP_HEIGHT - 1'b1))
image_data_vs <= 1'b0;
else
image_data_vs <= 1'b1;
always@(posedge loc_clk50m or posedge g_rst_p)
if(g_rst_p)
frame_rx_done_flip <= 1'b0;
else if(image_data_valid && image_data_hcnt == (DISP_WIDTH/2 - 1'b1) &&
image_data_vcnt == (DISP_HEIGHT - 1'b1))
frame_rx_done_flip <= ~frame_rx_done_flip;
//---------------------------------------------
//image processing
//---------------------------------------------
wire [7:0] image_gray_data;
wire image_gray_data_valid;
wire [15:0]image_stitche_data;
wire image_stitche_data_valid;
rgb2gray
#(
.PROC_METHOD ( "FORMULA" )//"AVERAGE" :求平均法
//or "FORMULA" :直接公式法
//or "LUT" :查找表法
)rgb2gray
(
.clk (loc_clk50m ),//input
.reset_p (g_rst_p ),//input
.rgb_valid (image_data_valid ),//input
.rgb_hs (image_data_hs ),//input
.rgb_vs (image_data_vs ),//input
.red_8b_i ({image_data[15:11],3'b000}),//input [7:0]
.green_8b_i ({image_data[10:5],2'b00} ),//input [7:0]
.blue_8b_i ({image_data[4:0],3'b000} ),//input [7:0]
.gray_8b_o (image_gray_data ),//output [7:0]
.gray_valid (image_gray_data_valid ),//output reg
.gray_hs ( ),//output reg
.gray_vs ( ) //output reg
);
image_stitche_x
#(
.DATA_WIDTH ( 16 ), //16 or 24
//image1_in: 400*480
.IMAGE1_WIDTH_IN ( DISP_WIDTH/2),
.IMAGE1_HEIGHT_IN ( DISP_HEIGHT ),
//image2_in: 400*480
.IMAGE2_WIDTH_IN ( DISP_WIDTH/2),
.IMAGE2_HEIGHT_IN ( DISP_HEIGHT ),
//image_out: 800*480
.IMAGE_WIDTH_OUT ( DISP_WIDTH ),
.IMAGE_HEIGHT_OUT ( DISP_HEIGHT )
)image_stitche_x(
.clk_image1_in (loc_clk50m ),
.clk_image2_in (loc_clk50m ),
.clk_image_out (loc_clk50m ),
.reset_p (g_rst_p ),
.rst_busy_o ( ),
.image_in_ready_o ( ),
.image1_data_pixel_i(image_data ),
.image1_data_valid_i(image_data_valid ),
.image2_data_pixel_i({image_gray_data[7:3],image_gray_data[7:2],image_gray_data[7:3]}),
.image2_data_valid_i(image_gray_data_valid),
.data_out_ready_i (1'b0 ),
.data_pixel_o (image_stitche_data ),
.data_valid_o (image_stitche_data_valid)
);
//---------------------------------------------
assign wrfifo_din = image_stitche_data;
assign wrfifo_wren = image_stitche_data_valid;
disp_driver disp_driver
(
.ClkDisp (clk_disp ),
.Rst_p (g_rst_p ),
.Data (rdfifo_dout ),
.DataReq (rdfifo_rden ),
.H_Addr ( ),
.V_Addr ( ),
.Disp_HS (TFT_hs ),
.Disp_VS (TFT_vs ),
.Disp_Red (TFT_rgb[15:11] ),
.Disp_Green (TFT_rgb[10:5] ),
.Disp_Blue (TFT_rgb[4:0] ),
.Frame_Begin (frame_begin ),
.Disp_DE (TFT_de ),
.Disp_PCLK (TFT_clk )
);
assign TFT_pwm = 1'b1;
assign wrfifo_clr = ui_clk_sync_rst;
assign rdfifo_clr = frame_begin || ui_clk_sync_rst;
wr_ddr3_fifo wr_ddr3_fifo
(
.rst (wrfifo_clr ), // input wire rst
.wr_clk (loc_clk50m ), // input wire wr_clk
.rd_clk (ui_clk ), // input wire rd_clk
.din (wrfifo_din ), // input wire [15 : 0] din
.wr_en (wrfifo_wren ), // input wire wr_en
.rd_en (wrfifo_rden ), // input wire rd_en
.dout (wrfifo_dout ), // output wire [127 : 0] dout
.full ( ), // output wire full
.empty (wrfifo_empty ), // output wire empty
.rd_data_count (wrfifo_rd_cnt ), // output wire [5 : 0] rd_data_count
.wr_data_count ( ), // output wire [8 : 0] wr_data_count
.wr_rst_busy (wrfifo_wr_rst_busy ), // output wire wr_rst_busy
.rd_rst_busy (wrfifo_rd_rst_busy ) // output wire rd_rst_busy
);
rd_ddr3_fifo rd_ddr3_fifo
(
.rst (rdfifo_clr ), // input wire rst
.wr_clk (ui_clk ), // input wire wr_clk
.rd_clk (clk_disp ), // input wire rd_clk
.din (rdfifo_din ), // input wire [127 : 0] din
.wr_en (rdfifo_wren ), // input wire wr_en
.rd_en (rdfifo_rden ), // input wire rd_en
.dout (rdfifo_dout ), // output wire [15 : 0] dout
.full (rdfifo_full ), // output wire full
.empty ( ), // output wire empty
.rd_data_count ( ), // output wire [8 : 0] rd_data_count
.wr_data_count (rdfifo_wr_cnt ), // output wire [5 : 0] wr_data_count
.wr_rst_busy (rdfifo_wr_rst_busy ), // output wire wr_rst_busy
.rd_rst_busy (rdfifo_rd_rst_busy ) // output wire rd_rst_busy
);
always@(posedge ui_clk)
begin
wrfifo_clr_sync_ui_clk <= wrfifo_clr;
wr_addr_clr <= wrfifo_clr_sync_ui_clk;
end
always@(posedge ui_clk)
begin
rdfifo_clr_sync_ui_clk <= rdfifo_clr;
rd_addr_clr <= rdfifo_clr_sync_ui_clk;
end
fifo2mig_axi
#(
.WR_DDR_ADDR_BEGIN (0 ),
.WR_DDR_ADDR_END (DISP_WIDTH*DISP_HEIGHT*2 ),
.RD_DDR_ADDR_BEGIN (0 ),
.RD_DDR_ADDR_END (DISP_WIDTH*DISP_HEIGHT*2 ),
.AXI_ID (4'b0000 ),
.AXI_LEN (8'd31 ) //axi burst length = 32
)fifo2mig_axi
(
//FIFO Interface ports
.wr_addr_clr (wr_addr_clr ), //1:clear sync ui_clk
.wr_fifo_rdreq (wrfifo_rden ),
.wr_fifo_rddata (wrfifo_dout ),
.wr_fifo_empty (wrfifo_empty ),
.wr_fifo_rd_cnt (wrfifo_rd_cnt ),
.wr_fifo_rst_busy (wrfifo_wr_rst_busy | wrfifo_rd_rst_busy),
.rd_addr_clr (rd_addr_clr ), //1:clear sync ui_clk
.rd_fifo_wrreq (rdfifo_wren ),
.rd_fifo_wrdata (rdfifo_din ),
.rd_fifo_alfull (rdfifo_full ),
.rd_fifo_wr_cnt (rdfifo_wr_cnt ),
.rd_fifo_rst_busy (rdfifo_wr_rst_busy | rdfifo_rd_rst_busy),
// Application interface ports
.ui_clk (ui_clk ),
.ui_clk_sync_rst (ui_clk_sync_rst ),
.mmcm_locked (mmcm_locked ),
.init_calib_complete (init_calib_complete ),
// Slave Interface Write Address Ports
.m_axi_awid (s_axi_awid ),
.m_axi_awaddr (s_axi_awaddr ),
.m_axi_awlen (s_axi_awlen ),
.m_axi_awsize (s_axi_awsize ),
.m_axi_awburst (s_axi_awburst ),
.m_axi_awlock (s_axi_awlock ),
.m_axi_awcache (s_axi_awcache ),
.m_axi_awprot (s_axi_awprot ),
.m_axi_awqos (s_axi_awqos ),
.m_axi_awvalid (s_axi_awvalid ),
.m_axi_awready (s_axi_awready ),
// Slave Interface Write Data Ports
.m_axi_wdata (s_axi_wdata ),
.m_axi_wstrb (s_axi_wstrb ),
.m_axi_wlast (s_axi_wlast ),
.m_axi_wvalid (s_axi_wvalid ),
.m_axi_wready (s_axi_wready ),
// Slave Interface Write Response Ports
.m_axi_bid (s_axi_bid ),
.m_axi_bresp (s_axi_bresp ),
.m_axi_bvalid (s_axi_bvalid ),
.m_axi_bready (s_axi_bready ),
// Slave Interface Read Address Ports
.m_axi_arid (s_axi_arid ),
.m_axi_araddr (s_axi_araddr ),
.m_axi_arlen (s_axi_arlen ),
.m_axi_arsize (s_axi_arsize ),
.m_axi_arburst (s_axi_arburst ),
.m_axi_arlock (s_axi_arlock ),
.m_axi_arcache (s_axi_arcache ),
.m_axi_arprot (s_axi_arprot ),
.m_axi_arqos (s_axi_arqos ),
.m_axi_arvalid (s_axi_arvalid ),
.m_axi_arready (s_axi_arready ),
// Slave Interface Read Data Ports
.m_axi_rid (s_axi_rid ),
.m_axi_rdata (s_axi_rdata ),
.m_axi_rresp (s_axi_rresp ),
.m_axi_rlast (s_axi_rlast ),
.m_axi_rvalid (s_axi_rvalid ),
.m_axi_rready (s_axi_rready )
);
mig_7series_0 u_mig_7series_0 (
// Memory interface ports
.ddr3_addr (ddr3_addr ), // output [13:0] ddr3_addr
.ddr3_ba (ddr3_ba ), // output [2:0] ddr3_ba
.ddr3_cas_n (ddr3_cas_n ), // output ddr3_cas_n
.ddr3_ck_n (ddr3_ck_n ), // output [0:0] ddr3_ck_n
.ddr3_ck_p (ddr3_ck_p ), // output [0:0] ddr3_ck_p
.ddr3_cke (ddr3_cke ), // output [0:0] ddr3_cke
.ddr3_ras_n (ddr3_ras_n ), // output ddr3_ras_n
.ddr3_reset_n (ddr3_reset_n ), // output ddr3_reset_n
.ddr3_we_n (ddr3_we_n ), // output ddr3_we_n
.ddr3_dq (ddr3_dq ), // inout [15:0] ddr3_dq
.ddr3_dqs_n (ddr3_dqs_n ), // inout [1:0] ddr3_dqs_n
.ddr3_dqs_p (ddr3_dqs_p ), // inout [1:0] ddr3_dqs_p
.init_calib_complete (init_calib_complete ), // output init_calib_complete
.ddr3_cs_n (ddr3_cs_n ), // output [0:0] ddr3_cs_n
.ddr3_dm (ddr3_dm ), // output [1:0] ddr3_dm
.ddr3_odt (ddr3_odt ), // output [0:0] ddr3_odt
// Application interface ports
.ui_clk (ui_clk ), // output ui_clk
.ui_clk_sync_rst (ui_clk_sync_rst ), // output ui_clk_sync_rst
.mmcm_locked (mmcm_locked ), // output mmcm_locked
.aresetn (aresetn ), // input aresetn
.app_sr_req (1'b0 ), // input app_sr_req
.app_ref_req (1'b0 ), // input app_ref_req
.app_zq_req (1'b0 ), // input app_zq_req
.app_sr_active ( ), // output app_sr_active
.app_ref_ack ( ), // output app_ref_ack
.app_zq_ack ( ), // output app_zq_ack
// Slave Interface Write Address Ports
.s_axi_awid (s_axi_awid ), // input [3:0] s_axi_awid
.s_axi_awaddr (s_axi_awaddr ), // input [27:0] s_axi_awaddr
.s_axi_awlen (s_axi_awlen ), // input [7:0] s_axi_awlen
.s_axi_awsize (s_axi_awsize ), // input [2:0] s_axi_awsize
.s_axi_awburst (s_axi_awburst ), // input [1:0] s_axi_awburst
.s_axi_awlock (s_axi_awlock ), // input [0:0] s_axi_awlock
.s_axi_awcache (s_axi_awcache ), // input [3:0] s_axi_awcache
.s_axi_awprot (s_axi_awprot ), // input [2:0] s_axi_awprot
.s_axi_awqos (s_axi_awqos ), // input [3:0] s_axi_awqos
.s_axi_awvalid (s_axi_awvalid ), // input s_axi_awvalid
.s_axi_awready (s_axi_awready ), // output s_axi_awready
// Slave Interface Write Data Ports
.s_axi_wdata (s_axi_wdata ), // input [127:0] s_axi_wdata
.s_axi_wstrb (s_axi_wstrb ), // input [15:0] s_axi_wstrb
.s_axi_wlast (s_axi_wlast ), // input s_axi_wlast
.s_axi_wvalid (s_axi_wvalid ), // input s_axi_wvalid
.s_axi_wready (s_axi_wready ), // output s_axi_wready
// Slave Interface Write Response Ports
.s_axi_bid (s_axi_bid ), // output [3:0] s_axi_bid
.s_axi_bresp (s_axi_bresp ), // output [1:0] s_axi_bresp
.s_axi_bvalid (s_axi_bvalid ), // output s_axi_bvalid
.s_axi_bready (s_axi_bready ), // input s_axi_bready
// Slave Interface Read Address Ports
.s_axi_arid (s_axi_arid ), // input [3:0] s_axi_arid
.s_axi_araddr (s_axi_araddr ), // input [27:0] s_axi_araddr
.s_axi_arlen (s_axi_arlen ), // input [7:0] s_axi_arlen
.s_axi_arsize (s_axi_arsize ), // input [2:0] s_axi_arsize
.s_axi_arburst (s_axi_arburst ), // input [1:0] s_axi_arburst
.s_axi_arlock (s_axi_arlock ), // input [0:0] s_axi_arlock
.s_axi_arcache (s_axi_arcache ), // input [3:0] s_axi_arcache
.s_axi_arprot (s_axi_arprot ), // input [2:0] s_axi_arprot
.s_axi_arqos (s_axi_arqos ), // input [3:0] s_axi_arqos
.s_axi_arvalid (s_axi_arvalid ), // input s_axi_arvalid
.s_axi_arready (s_axi_arready ), // output s_axi_arready
// Slave Interface Read Data Ports
.s_axi_rid (s_axi_rid ), // output [3:0] s_axi_rid
.s_axi_rdata (s_axi_rdata ), // output [127:0] s_axi_rdata
.s_axi_rresp (s_axi_rresp ), // output [1:0] s_axi_rresp
.s_axi_rlast (s_axi_rlast ), // output s_axi_rlast
.s_axi_rvalid (s_axi_rvalid ), // output s_axi_rvalid
.s_axi_rready (s_axi_rready ), // input s_axi_rready
// System Clock Ports
.sys_clk_i (loc_clk200m ),
.sys_rst (mig_reset_n ) // input sys_rst
);
endmodule
五、传图显示
顶层的仿真与“基于 DDR3 的串口传图帧缓存系统”类似,这里就不做详细讲解,读者自己建立仿真文件完成顶层的仿真。
在顶层设计分析综合没有错误并且顶层仿真确认设计功能没有问题后,进行上板验证。对工程的管脚和时钟进行约束后,生成 Bit 文件。上板调试硬件平台基于 ACX720 开发板,使用一根数据线,一端接入 ACX720 开发板的的 USB2TTL 接口,另一端接入 PC 机的 USB口,显示屏使用的是 TFT5.0 寸屏幕,开发板连接示意图与上一章节一样。硬件连接好并上电后然后下载生成的 Bit 文件。下载完成后,开发板上 LED0~LED2 会亮,TFT5.0 寸屏上显示花屏状态。如下图所示。
出现这种花屏是因为这个时候,DDR3 中并未写入数据,显示的数据是不可知的一些数据。LED0 和 LED1 分别表示的是DDR 控制器内时钟锁相环的 locked 信号和 DDR 初始化校准完成信号的状态,亮表示这个两个信号均变为高电平,说明 DDR 已经正常完成初始化和校准操作。接下来通过串口传图工具向 FPGA 传输图片数据。
双击打开串口传图工具,通过点击“打开图片”按钮设置图片存放路径;图片宽度设置成与显示屏分辨率宽度一半,高度设置成与显示屏分辨率高度一致(TFT5.0 寸屏是 800480,上位机上宽度设置为 400,高度设置为 480;TFT4.3 寸屏是 480272,上位机上宽度设置为240,高度设置为 272)。
注:这里提供的上位机要求图片为位深度为 24 的 bmp 格式图片,图片的宽度和高度需要为 400480(插 5 寸屏情况下)或 240272(插 4.3 寸屏情况下)。
传图过程中,可以看到 TFT 屏上开始显示发送的图片。图片传送完成后,TFT 屏显示效果如下。
通过对比可以看出,TFT 屏上左右两边分别显示的是原始的彩色图像和灰度化之后的灰度图像。可通过改变在顶层例化彩色图像灰度化模块使用的方式观察不同的实现方法下的实现效果。
【附件:】链接:https://pan.baidu.com/s/1kk3gUrrznMLlZoH3Tz0GGQ?pwd=r1ds
提取码:r1ds