1)实验平台:正点原子开拓者
FPGA 开发板
2)摘自《开拓者FPGA开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-13912-1-1.html
第五十三章 OV5640 摄像头灰度图 VGA 显示实验
前面的实验我们介绍了OV5640摄像头的VGA显示实验,而在数字图像处理领域YUV是一种很
常用的图像格式,其特点是将亮度和色度进行分离。在本章实验中,我们将进行RGB565到YUV
的颜色空间转换,并通过VGA显示摄像头采集到的图像的灰度图的实验。
本章包括以下几个部分:
53.1 YUV 简介
53.2 实验任务
53.3 硬件设计
53.4 程序设计
53.5 下载验证
YUV 简介
人眼中的锥状细胞是负责彩色视觉的传感器,可分为三个主要的感知类别,分别对应红色、
绿色、蓝色,我们人眼看到的彩色实际上是红、绿、蓝三原色的各种组合。前面我们用到的RGB
就是以红、绿、蓝为三原色的颜色空间模型,通过对红(R)、绿(G)、蓝(B)三个颜色通道的变
化以及它们相互之间的叠加来得到各式各样的颜色的,这个标准几乎包括了人类视力所能感知
的所有颜色,是目前运用最广的颜色系统之一。
YUV(YCbCr)是欧洲电视系统所采用的一种颜色编码方法。‘Y’表示明亮度(Luminance
或Luma),也就是灰阶值;‘U’和‘V’表示色度,用于描述影像的饱和度和色调。RGB与YUV
的转换实际上是色彩空间的转换,即将RGB的三原色色彩空间转换为YUV所表示的亮度与色度的
色彩空间模型。YUV 主要应用在模拟系统中,而 YCbCr 是通过 YUV 信号的发展,并通过校正
的主要应用在数字视频中的一种编码方法。YUV适用于PAL和SECAM彩色电视制式,而YCrCb适用
于计算机用的显示器。
RGB着重于人眼对色彩的感应,YUV则着重于视觉对于亮度的敏感程度。使用YUV描述图像
的好处在于,(1)亮度(Y)与色度(U、V)是独立的;(2)人眼能够识别数千种不同的色
彩,但只能识别20多种灰阶值,采用YUV标准可以降低数字彩色图像所需的储存容量。因而YUV
在数字图像处理中是一种很常用的颜色标准。
YUV 信号的提出,是因为国际上出现彩色电视,为了兼容黑白电视的信号而设计的,在视
频码率,压缩,兼容性等方面有很大优势。一般意义上 YCbCr即为 YUV信号,没有严格的划
分。CbCr 分别为蓝色色度分量、红色色度分量。其主要采样格式有YCbCr4:2:2、YCbCr4:2:0、
YCbCr4:4:4、YCbCr4:1:1。下面将介绍这四种采样格式。
(1)YCbCr4:4:4:
YUV三个信道的抽样率相同,因此在生成的图像里,每个象素的三个分量信息完整(每个
分量通常8比特),经过8比特量化之后,未经压缩的每个像素占用3个字节。
下面的四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
存放的码流为: Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3
图 53.1.1 YUV444像素格式
根据存储顺序不同,YUV444又分为以下两种形式:
图 53.1.2 两种不同格式的YUV444像素
(2)YCbCr4:2:2:
每个色差信道的抽样率是亮度信道的一半,所以水平方向的色度抽样率只是4:4:4的一半。
对非压缩的8比特量化的图像来说,每个由两个水平方向相邻的像素组成的宏像素需要占用4字
节内存。
下面的四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
存放的码流为: Y0 U0 Y1 V1 Y2 U2 Y3 V3
映射出像素点为:[Y0 U0 V1] [Y1 U0 V1] [Y2 U2 V3] [Y3 U2 V3]
图 53.1.3 YUV422像素格式
同样,根据存储顺序,YUV422可以分为六种不同格式:
图 53.1.4 六种不同的YUV422格式
(3)YCbCr4:2:0
4:2:0并不意味着只有Y,Cb而没有Cr分量。它指得是对每行扫描线来说,只有一种色度分
量以2:1的抽样率存储。相邻的扫描行存储不同的色度分量,也就是说,如果一行是4:2:0的话,
下一行就是4:0:2,再下一行是4:2:0...以此类推。对每个色度分量来说,水平方向和竖直方
向的抽样率都是2:1,所以可以说色度的抽样率是4:1。对非压缩的8比特量化的视频来说,每
个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存。
下面八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6
V6] [Y7U7 V7] [Y8 U8 V8]
存放的码流为:Y0 U0 Y1 Y2 U2 Y3Y5 V5 Y6 Y7 V7 Y8
映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7][Y5 U0 V5] [Y6 U0
V5] [Y7U2 V7] [Y8 U2 V7]
(4)YCbCr4:1:1:
4:1:1的色度抽样,是在水平方向上对色度进行4:1抽样。对于低端用户和消费类产品这仍
然是可以接受的。对非压缩的8比特量化的视频来说,每个由4个水平方向相邻的像素组成的宏
像素需要占用6字节内存。
下面的四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
存放的码流为: Y0 U0 Y1 Y2 V2 Y3
映射出像素点为:[Y0 U0 V2] [Y1 U0 V2] [Y2 U0 V2] [Y3 U0 V2]
下面为 RGB 与 YCbCr 色彩空间转换的算法公式, RGB 转 YCbCr 的公式如下所示:
图 53.1.5 RGB 转 YcbCr算法
由于 Verilog HDL 无法进行浮点运算,因此使用扩大 256 倍,再向右移 8Bit的方式,
来转换公式,如下所示:
图 53.1.6 RGB 转 YcbCr算法
为了防止运算过程中出现负数,我们对上述公式进行进一步变换,得到如下公式:
图 53.1.7 RGB 转 YcbCr算法
实际上OV5640本身支持输出RGB、YUV格式的数据,本章节实验是着重于实RGB转YUV的HDL
算法实现,因此我们把摄像头设置为RGB565格式。当我们需要显示器显示灰度图时,我们只需
要将转换后的Y值作为R、G、B三原色通道的输入就可以实现了。
实验任务
本节实验任务是使用开拓者开发板及OV5640摄像头采集RGB565格式的数据,并通过算法转
换,将RGB565格式转换为YCbCr格式,然后通过VGA显示器实时显示灰度图。
硬件设计
本章节中硬件设计与OV5640的VGA显示实验完全相同,在此就不在赘述。
程序设计
下图是根据本章实验任务画出的系统框图。对比OV5640摄像头VGA显示实验,本实验中添
加了rgb2ycbcr模块,该模块用于将摄像头采集到的RGB565数据转换成YCbCr格式数据,并将转
换后的数据写入SDRAM。
OV5640摄像头VGA显示灰度图系统框图如下图所示:
图 53.4.1 OV5640摄像头VGA显示灰度图系统框图
顶层模块原理图如下图所示:
图 53.4.2 rgb2yuv模块原理图
有关各个模块的功能可以参考“OV5640摄像头VGA显示实验”的相关描述,在本实验中我
们添加了rgb2ycbcr模块,在这里我们将对rgb2ycbcr模块的功能进行描述。
rgb2ycbcr模块的原理图如下:
图 53.4.3 rgb2ycbcr模块原理图
在该模块以摄像头采集的16位RGB565红、绿、蓝三原色数据作为输入数据,通过算法实现
RGB到YCbCr的转换,并输出三路8位数据和数据输出使能信号。
有关于摄像头、SDRAM的配置,VGA的驱动在前面的章节以有过介绍,本章节我们着重介绍
RGB转YUV的算法模块。
RGB转YUV模块的代码如下:
1
module rgb2ycbcr
2
(
3 //module clock
4
input clk
, // 模块驱动时钟
5
input rst_n
, // 复位信号
6
7 //图像处理前的数据接口
8
input pre_frame_vsync
, // vsync信号
9
input pre_frame_hsync
, // hsync信号
10
input pre_frame_de
, // data enable信号
11
input [4
:0
] img_red
, // 输入图像数据R
12
input [5
:0
] img_green
, // 输入图像数据G
13
input [4
:0
] img_blue
, // 输入图像数据B
14
15 //图像处理后的数据接口
16
output post_frame_vsync
, // vsync信号
17
output post_frame_hsync
, // hsync信号
18
output post_frame_de
, // data enable信号
19
output [7
:0
] img_y
, // 输出图像Y数据
20
output [7
:0
] img_cb
,
21
output [7
:0
] img_cr
22
);
23
24 //reg define
25
reg [15
:0
] rgb_r_m0
, rgb_r_m1
, rgb_r_m2
;
26
reg [15
:0
] rgb_g_m0
, rgb_g_m1
, rgb_g_m2
;
27
reg [15
:0
] rgb_b_m0
, rgb_b_m1
, rgb_b_m2
;
28
reg [15
:0
] img_y0
;
29
reg [15
:0
] img_cb0
;
30
reg [15
:0
] img_cr0
;
31
reg [ 7
:0
] img_y1
;
32
reg [ 7
:0
] img_cb1
;
33
reg [ 7
:0
] img_cr1
;
34
reg [ 2
:0
] pre_frame_vsync_d
;
35
reg [ 2
:0
] pre_frame_hsync_d
;
36
reg [ 2
:0
] pre_frame_de_d
;
37
38 //wire define
39
wire [ 7
:0
] rgb888_r
;
40
wire [ 7
:0
] rgb888_g
;
41
wire [ 7
:0
] rgb888_b
;
42
43 //*****************************************************
44 //** main code
45 //*****************************************************
46
47 //RGB565 to RGB 888
48
assign rgb888_r
= {img_red
, img_red
[4
:2
] };
49
assign rgb888_g
= {img_green
, img_green
[5
:4
]};
50
assign rgb888_b
= {img_blue
, img_blue
[4
:2
] };
51 //同步输出数据接口信号
52
assign post_frame_vsync
= pre_frame_vsync_d
[2
] ;
53
assign post_frame_hsync
= pre_frame_hsync_d
[2
] ;
54
assign post_frame_de
= pre_frame_de_d
[2
] ;
55
assign img_y
= post_frame_hsync
? img_y1
: 8'd0
;
56
assign img_cb
= post_frame_hsync
? img_cb1
: 8'd0
;
57
assign img_cr
= post_frame_hsync
? img_cr1
: 8'd0
;
58
59 //--------------------------------------------
60 //RGB 888 to YCbCr
61
62 /********************************************************
63 RGB888 to YCbCr
64 Y = 0.299R +0.587G + 0.114B
65 Cb = 0.568(B-Y) + 128 = -0.172R-0.339G + 0.511B + 128
66 CR = 0.713(R-Y) + 128 = 0.511R-0.428G -0.083B + 128
67
68 Y = (77 *R + 150*G + 29 *B)>>8
69 Cb = (-43*R - 85 *G + 128*B)>>8 + 128
70 Cr = (128*R - 107*G - 21 *B)>>8 + 128
71
72 Y = (77 *R + 150*G + 29 *B )>>8
73 Cb = (-43*R - 85 *G + 128*B + 32768)>>8
74 Cr = (128*R - 107*G - 21 *B + 32768)>>8
75 *********************************************************/
76
77 //step1 计算括号内的各乘法项
78
always @(posedge clk
or negedge rst_n
) begin
79
if(!rst_n
) begin
80 rgb_r_m0
<= 16'd0
;
81 rgb_r_m1
<= 16'd0
;
82 rgb_r_m2
<= 16'd0
;
83 rgb_g_m0
<= 16'd0
;
84 rgb_g_m1
<= 16'd0
;
85 rgb_g_m2
<= 16'd0
;
86 rgb_b_m0
<= 16'd0
;
87 rgb_b_m1
<= 16'd0
;
88 rgb_b_m2
<= 16'd0
;
89
end
90
else begin
91 rgb_r_m0
<= rgb888_r
* 8'd77
;
92 rgb_r_m1
<= rgb888_r
* 8'd43
;
93 rgb_r_m2
<= rgb888_r
* 8'd128
;
94 rgb_g_m0
<= rgb888_g
* 8'd150
;
95 rgb_g_m1
<= rgb888_g
* 8'd85
;
96 rgb_g_m2
<= rgb888_g
* 8'd107
;
97 rgb_b_m0
<= rgb888_b
* 8'd29
;
98 rgb_b_m1
<= rgb888_b
* 8'd128
;
99 rgb_b_m2
<= rgb888_b
* 8'd21
;
100
end
101
end
102
103 //step2 括号内各项相加
104
always @(posedge clk
or negedge rst_n
) begin
105
if(!rst_n
) begin
106 img_y0
<= 16'd0
;
107 img_cb0
<= 16'd0
;
108 img_cr0
<= 16'd0
;
109
end
110
else begin
111 img_y0
<= rgb_r_m0
+ rgb_g_m0
+ rgb_b_m0
;
112 img_cb0
<= rgb_b_m1
- rgb_r_m1
- rgb_g_m1
+ 16'd32768
;
113 img_cr0
<= rgb_r_m2
- rgb_g_m2
- rgb_b_m2
+ 16'd32768
;
114
end
115
116
end
117
118 //step3 括号内计算的数据右移8位
119
always @(posedge clk
or negedge rst_n
) begin
120
if(!rst_n
) begin
121 img_y1
<= 8'd0
;
122 img_cb1
<= 8'd0
;
123 img_cr1
<= 8'd0
;
124
end
125
else begin
126 img_y1
<= img_y0
[15
:8
];
127 img_cb1
<= img_cb0
[15
:8
];
128 img_cr1
<= img_cr0
[15
:8
];
129
end
130
end
131
132 //延时3拍以同步数据信号
133
always@(posedge clk
or negedge rst_n
) begin
134
if(!rst_n
) begin
135 pre_frame_vsync_d
<= 3'd0
;
136 pre_frame_hsync_d
<= 3'd0
;
137 pre_frame_de_d
<= 3'd0
;
138
end
139
else begin
140 pre_frame_vsync_d
<= {pre_frame_vsync_d
[1
:0
], pre_frame_vsync
};
141 pre_frame_hsync_d
<= {pre_frame_hsync_d
[1
:0
], pre_frame_hsync
};
142 pre_frame_de_d
<= {pre_frame_de_d
[1
:0
] , pre_frame_de
};
143
end
144
end
145
146
endmodule
在RGB转成YUV格式的算法换算过程中数据都是以8位的数据进行的,因而我们需要将
RGB565格式的数据转换成RGB888的格式,如代码第47之50行所示,此处采用的是高位补充地位
的方法。转换后就是进行RGB565转YCbCr算法的HDL实现:第一步,先计算出前面公式中括号里
每一个乘法的乘积,如代码第77至101行所示;第二步,计算出Y、Cb、Cr括号内的值,代码103
至116行;第三步,右移 8Bit,由于 Step2 计算结果为 16Bit, 因此直接提取高8位 即可,
代码如118至130行所示。
实际上从第一步到第三步的运算, 均直接通过寄存器描述,没有顾虑行场有效时序等。
但在实际
电路中会有一个数据流上的先后顺序,这三步操作同时对连续数据进行处理,通过这
种方式实现硬件加速的方法称为流水线设计。前面计算出 Y、 Cb、 Cr 我们消耗了step1、
step2、step3这三个时钟, 因此需要将输入的行场信号、使能信号同步移动 3 个时钟,如代
码第132值144行。
在代码第21至22行可以看出,转换后输出的数据分别为8位的Y(灰度)、Cb(蓝色色度分
量)、Cr(红色色度分量),而我们实验中需要的是表示灰度的数据,因此我们只需取Y值的
高5位、高6位、高5位数据作为VGA显示的红、绿、蓝三通道输入数据,如顶层代码第171行所
示:
170
.wr_en
(wr_en
), //写端口FIFO: 写使能
171
.wr_data
({img_y
[7
:3
],img_y
[7
:2
],img_y
[7
:3
]}), //写端口FIFO: 写数据
172
.wr_min_addr
(24'd0
), //写SDRAM的起始地址
下载验证
首 先 我 们 打 开 OV5640 摄 像 头 VGA 显 示 实 验 工 程 , 在 工 程 所 在 的 路 径 下 打 开
ov5640_rgb565_yuv_vga/par文件夹,在里面找“ov5640_rgb565_yuv_vga.qpf”并双击打开。
注意工程所在的路径名只能由字母、数字以及下划线组成,不能出现中文、空格以及特殊字符
等。工程打开后如下图所示:
图 53.5.1 OV5640摄像头VGA显示实验工程
然后将OV5640摄像头插入开发板上的摄像头扩展接口(注意摄像头镜头朝外),将VGA连
接线一端连接显示器,另一端与开发板上的VGA接口连接。再将下载器一端连电脑,另一端与
开发板上对应端口连接,最后连接
电源线并打开电源开关。
开拓者开发板实物图如下所示:
图 53.5.2 ov5640摄像头连接图
接下来我们下载程序,验证OV5640摄像头VGA实时显示的功能。工程打开后通过点击工具
栏中的“Programmer”图标打开下载界面,通过“Add File”按钮选择ov5640_rgb565 _vga_yuv
/par/output_files目录下的“ov5640_rgb565_yuv_vga.sof”文件。开发板电源打开后, 在
程序下载界面点击“Hardware Setup”,在弹出的对话框中选择当前的硬件连接为“USB
Blaster[USB-0]”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中,如
图所示:
图 53.5.3 程序下载完成界面
下载完成后观察显示器的显示图像如下所示,说明OV5640摄像头VGA显示程序下载验证成
功。
图 53.5.4 VGA实时显示图像