本帖最后由 正点原子01 于 2020-9-11 11:00 编辑  
 
1)实验平台:正点原子领航者ZYNQ开发板 
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html 
4)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入群聊:  
5)关注正点原子公众号,获取最新资料更新 
 
 
 
 
第二十三章 OV5640摄像头灰度图显示实验  
前面的实验我们介绍了OV5640摄像头LCD显示实验,在该实验中摄像头输出的是RGB格式的图像数据。而在数字图像处理领域中YUV是一种更常用的图像格式,其特点是将亮度和色度进行分离,更适合运用于图像处理领域。在本章实验中,我们将进行RGB到YUV的颜色空间转换,并通过LCD显示灰度图的实验。 本章包括以下几个部分: 1          1          1.1        OV5640简介 1.2        实验任务 1.3        软件设计 1.4        程序设计 1.5        下载验证 1.1 简介人眼中的锥状细胞是负责彩色视觉的传感器,可分为三个主要的感知类别,分别对应红色、绿色、蓝色,我们人眼看到的彩色实际上是红、绿、蓝三原色的各种组合。前面我们用到的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 信号的提出,是因为国际上出现彩色电视,为了兼容黑白电视的信号而设计的,在视频码率,压缩,兼容性等方面有很大优势,我们最常用的主要是YUV4:4:4和V4:2:2两中采样格式的YUV信号。下面我们将介绍这两种格式的信号。 (1)YUV4:4:4 在YUV4:4:4格式中,YUV三个信道的采样率相同。因此在生成的图像里,每个像素都有各自独立的三个分量,每个分量通常为8bit,故每个像素占用3个字节。下图为YUV444单个像素的模型图,可以看出,每个Y都对应一组U、V数据,共同组成一个像素。 图 1.1.1 单个V444的素 (2)YUV4:2:2 在YUV4:2:2格式中,U和V的采样率是Y的一半(两个相邻的像素共用一对U、V数据)。如下图所示,图中包含两个相邻的像素。第一个像素的三个YUV分量分别是Y1、U1、V1,第二个像素的三个YUV分量分别是Y2、U1、V1,两个像素共用一组U1、V1。 图 1.1.2 两个相邻的YUV422像素 YUV4:4:4格式和YUV4:2:2格式的数据流也是不同的。如一组连续的四个像素P1、P2、P3、P4,采用YUV444的采样格式时,数据流为Y0 U0 V0、Y1 U1 V1、Y2 U2 V2、Y3U3 V3,每组数据代表一个像素点。而用YUV422的采样格式时,数据流为Y0 U0 Y1 V1、Y2 U2 Y3 V3。其中,Y0 U0 Y1 V1表示P1、P2两个像素,Y2 U2 Y3 V3表示P3、P4两个像素。 一般意义上 YCbCr即为 YUV信号,没有严格的划分。我们通常说的YUV就是指YCbCr。 CbCr 分别为蓝色色度分量、红色色度分量。下面为 RGB 与 YCbCr 色彩空间转换的算法公式, RGB 转 YCbCr 的公式如下所示: 图 1.1.3 RGB 转 YcbCr算法 由于 Verilog HDL 无法进行浮点运算,因此使用扩大256 倍,再向右移8Bit的方式,来转换公式,(0.083=00010101) 如下所示: 图 1.1.4 RGB 转 YcbCr算法 为了防止运算过程中出现负数,我们对上述公式进行进一步变换,得到如下公式: 图 1.1.5 RGB 转 YcbCr算法 实际上OV5640本身支持输出RGB、YUV格式的数据,本章节实验是着重于实RGB转YUV的HDL算法实现,因此我们把摄像头设置为RGB565格式。当我们需要显示器显示灰度图时,我们只需要将转换后的Y值作为R、G、B三原色通道的输入就可以实现了。 1.2 实验任务本节实验任务是使用启明星开发板及OV5640摄像头采集RGB565格式的数据,并通过算法转换,将RGB格式的图像数据转换为YCbCr格式,然后通过LCD实时显示灰度图。 1.3 硬件设计图25.3.1是根据本章实验任务画出的系统框图。对比OV5640摄像头LCD显示实验,本实验中仅添加了RGB转Ycbcr模块,该模块用于将摄像头采集到的RGB数据转换成YCbCr格式数。 OV5640摄像头LCD显示灰度图系统框图如下图所示: 图 1.3.1 OV5640摄像头LCD显示灰度图系统框图 如下图 25.3.2我们给出了rgb2ycbcr的ip核以及其接口的连线图: 图 1.3.2 rgb2ycbcr模块连接图 有关其他各个模块的功能可以参考“OV5640摄像头LCD显示实验”的相关描述,在本实验中我们将对rgb2ycbcr模块的功能进行描述。摄像头采集模块输出24为RGB数据经“rgb2ycbcr”模块转换,输出24位的Ycbcr数据。 RGB转Ycbcr模块的代码如下: -  module rgb2ycbcr
 
  
-  (
 
  
-      //module clock
 
  
-      input               clk          ,  // 模块驱动时钟
 
  
-      input               rst_n        ,  // 复位信号
 
  
 
 
-      //图像处理前的数据接口
 
  
-      input               rgb_vsync    ,  // vsync信号
 
  
-      input               rgb_clken    ,  // 时钟使能信号
 
  
-      input               rgb_valid    ,  // 数据有效信号
 
  
-      input       [23:0]  rgb_data     ,  // 输入图像数据RGB
 
  
-    
 
  
 
 
-      //图像处理后的数据接口
 
  
-      output              ycbcb_vsync  ,  // vsync信号
 
  
-      output              ycbcbr_clken ,  // 时钟使能信号
 
  
-      output              ycbcr_valid  ,  // 数据有效信号
 
  
-      output      [23:0]  gray_data       // 输出图像Y数据
 
  
-   
 
  
-  );
 
  
 
 
-  //reg define
 
  
-  reg  [15:0]   rgb_r_m0, rgb_r_m1, rgb_r_m2;
 
  
-  reg  [15:0]   rgb_g_m0, rgb_g_m1, rgb_g_m2;
 
  
-  reg  [15:0]   rgb_b_m0, rgb_b_m1, rgb_b_m2;
 
  
-  reg  [15:0]   img_y0 ;
 
  
-  reg  [15:0]   img_cb0;
 
  
-  reg  [15:0]   img_cr0;
 
  
-  reg  [ 7:0]   img_y1 ;
 
  
-  reg  [ 7:0]   img_cb1;
 
  
-  reg  [ 7:0]   img_cr1;
 
  
-  reg  [ 2:0]   rgb_vsync_d;
 
  
-  reg  [ 2:0]   rgb_clken_d;
 
  
-  reg  [ 2:0]   rgb_valid_d   ;
 
  
 
 
-  //wire define
 
  
-  wire [ 7:0]   rgb888_r;
 
  
-  wire [ 7:0]   rgb888_g;
 
  
-  wire [ 7:0]   rgb888_b;
 
  
-  wire [ 7:0]   img_y;
 
  
-  wire [ 7:0]   img_cb;
 
  
-  wire [ 7:0]   img_cr;
 
  
 
 
-  //*****************************************************
 
  
-  //**                    main code
 
  
-  //*****************************************************
 
  
 
 
-  //RGB565 to RGB 888
 
  
-  assign rgb888_r         = rgb_data[23:16];
 
  
-  assign rgb888_g     = rgb_data[15:8];
 
  
-  assign rgb888_b     = rgb_data[7:0] ;
 
  
-  //同步输出数据接口信号
 
  
-  assign ycbcb_vsync  = rgb_vsync_d[2] ;
 
  
-  assign ycbcbr_clken = rgb_clken_d[2] ;
 
  
-  assign ycbcr_valid  = rgb_valid_d[2] ;
 
  
-  assign img_y        = ycbcbr_clken ? img_y1 : 8'd0;
 
  
-  assign img_cb       = ycbcbr_clken ? img_cb1: 8'd0;
 
  
-  assign img_cr       = ycbcbr_clken ? img_cr1: 8'd0;
 
  
-  assign gray_data    = {img_y,img_y,img_y};
 
  
-  //--------------------------------------------
 
  
-  //RGB 888 to YCbCr
 
  
 
 
-  /********************************************************
 
  
-              RGB888 to YCbCr
 
  
-   Y  = 0.299R +0.587G + 0.114B
 
  
-   Cb = 0.568(B-Y) + 128 = -0.172R-0.339G + 0.511B + 128
 
  
-   CR = 0.713(R-Y) + 128 = 0.511R-0.428G -0.083B + 128
 
  
 
 
-   Y  = (77 *R    +    150*G    +    29 *B)>>8
 
  
-   Cb = (-43*R    -    85 *G    +    128*B)>>8 + 128
 
  
-   Cr = (128*R    -    107*G    -    21 *B)>>8 + 128
 
  
 
 
-   Y  = (77 *R    +    150*G    +    29 *B        )>>8
 
  
-   Cb = (-43*R    -    85 *G    +    128*B + 32768)>>8
 
  
-   Cr = (128*R    -    107*G    -    21 *B + 32768)>>8
 
  
-  *********************************************************/
 
  
 
 
-  //step1 计算括号内的各乘法项
 
  
-  always @(posedge clk or negedge rst_n) begin
 
  
-      if(!rst_n) begin
 
  
-          rgb_r_m0 <= 16'd0;
 
  
-          rgb_r_m1 <= 16'd0;
 
  
-          rgb_r_m2 <= 16'd0;
 
  
-          rgb_g_m0 <= 16'd0;
 
  
-          rgb_g_m1 <= 16'd0;
 
  
-          rgb_g_m2 <= 16'd0;
 
  
-          rgb_b_m0 <= 16'd0;
 
  
-          rgb_b_m1 <= 16'd0;
 
  
-          rgb_b_m2 <= 16'd0;
 
  
-      end
 
  
-      else begin
 
  
-          rgb_r_m0 <= rgb888_r * 8'd77 ;
 
  
-          rgb_r_m1 <= rgb888_r * 8'd43 ;
 
  
-          rgb_r_m2 <= rgb888_r * 8'd128;
 
  
-          rgb_g_m0 <= rgb888_g * 8'd150;
 
  
-          rgb_g_m1 <= rgb888_g * 8'd85 ;
 
  
-          rgb_g_m2 <= rgb888_g * 8'd107;
 
  
-          rgb_b_m0 <= rgb888_b * 8'd29 ;
 
  
-          rgb_b_m1 <= rgb888_b * 8'd128;
 
  
-          rgb_b_m2 <= rgb888_b * 8'd21 ;
 
  
-      end
 
  
-  end
 
  
 
 
-  //step2 括号内各项相加
 
  
-  always @(posedge clk or negedge rst_n) begin
 
  
-      if(!rst_n) begin
 
  
-          img_y0  <= 16'd0;
 
  
-          img_cb0 <= 16'd0;
 
  
-          img_cr0 <= 16'd0;
 
  
-      end
 
  
-      else begin
 
  
-          img_y0  <= rgb_r_m0 + rgb_g_m0 + rgb_b_m0;
 
  
-          img_cb0 <= rgb_b_m1 - rgb_r_m1 - rgb_g_m1 + 16'd32768;
 
  
-          img_cr0 <= rgb_r_m2 - rgb_g_m2 - rgb_b_m2 + 16'd32768;
 
  
-      end
 
  
 
 
-  end
 
  
 
 
-  //step3 括号内计算的数据右移8位
 
  
-  always @(posedge clk or negedge rst_n) begin
 
  
-      if(!rst_n) begin
 
  
-          img_y1  <= 8'd0;
 
  
-          img_cb1 <= 8'd0;
 
  
-          img_cr1 <= 8'd0;
 
  
-      end
 
  
-      else begin
 
  
-          img_y1  <= img_y0 [15:8];
 
  
-          img_cb1 <= img_cb0[15:8];
 
  
-          img_cr1 <= img_cr0[15:8];
 
  
-      end
 
  
-  end
 
  
 
 
-  //延时3拍以同步数据信号
 
  
-  always@(posedge clk or negedge rst_n) begin
 
  
-      if(!rst_n) begin
 
  
-          rgb_vsync_d <= 3'd0;
 
  
-          rgb_clken_d <= 3'd0;
 
  
-          rgb_valid_d <= 3'd0;
 
  
-      end
 
  
-      else begin
 
  
-          rgb_vsync_d <= {rgb_vsync_d[1:0], rgb_vsync};
 
  
-          rgb_clken_d <= {rgb_clken_d[1:0], rgb_clken};
 
  
-          rgb_valid_d <= {rgb_valid_d[1:0], rgb_valid};
 
  
-      end
 
  
-  end
 
  
 
 
-  endmodule
 
  复制代码在RGB转成Ycbcr格式的算法换算过程中,我们需要提取RGB信号的红绿蓝分量,我们只需取24位RGB数据的高8位、中8位和低8位我作为红绿蓝三原色分量就可以了,如代码第48之51行所示;下面就是进行RGB转YCbCr算法的HDL实现:第一步,先计算出算法公式中括号里每一个乘法的乘积,如代码第78至102行所示;第二步,计算出Y、Cb、Cr括号内的值,代码104至117行;第三步,右移 8Bit,由于Step2 计算结果为 16Bit, 因此直接提取高8位即可,代码如119至131行所示。下图为RGB转YUV的算法公式: 图 1.3.3 RGB转YUV算法公式 实际上从第一步到第三步的运算, 均直接通过寄存器描述,没有顾虑时序问题。但在实际电路中会有一个数据流上的先后顺序,这三步操作同时对连续数据进行处理,通过这种方式实现硬件加速的方法称为流水线设计。前面计算出 Y、cb、 cr 我们消耗了step1、step2、step3这三个时钟,因此需要将输入的场信号、时钟使能信号和数据有效信号同步移动 3 个时钟,如代码第134值145行。 根据实验任务,我们需要输出的是灰度数据,因此我们需要用计算得到的灰度Y来作为图像cb、cr分量,如代码第59行所示: -  assign gray_data     = {img_y,img_y,img_y};
 
  复制代码我们在设计完代码后,需要将设计好的代码封装成IP核,以便于后续设计,有关IP核的封装请参考第十七章“IP核封装与接口定义”实验。 连线后的Block Design 如下图所示: 图1.3.4 Block Design整体示意图 接下来验证当前设计。验证完成后弹出对话框提示没有错误或者关键警告,点击“OK”。如果验证结果 
报出错误或者警告,则需要重新检查设计。 为工程添加的约束文件与“OV5640摄像头 LCD 显示”完全相同,有关这一部分内容请读者参考“OV5640摄像头 LCD 显示”实验。 最后在左侧 Flow Navigator 导航栏中找到 PROGRAM AND DEBUG,点击该选项中的“ Generate 
Bitstream”,对设计进行综合、实现、并生成 Bitstream文件。在生成 Bitstream之后,在菜单栏中选择 File> Export > Export hardware 导出硬件, 并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > LaunchSDK, 启动 SDK软件。 1.4 软件设计本实验软件设计与OV5640摄像头LCD显示实验像是完全相同,在本章节就不再赘述,有需要的小伙伴可以参考OV5640摄像头LCD显示实验软件设计部分。 1.5 下载验证 编译完工程之后我们就可以开始下载程序了。将 OV5640 摄像头模块插在启明星Zynq开发板的“OLED/CAMERA”插座上,并将LCD 的排线接头插入开发板上的LCD 接线座。将下载器一端连电脑, 
另一端与开发板上的 JTAG端口连接,连接电源线并打开电源开关。 
在 SDK 软件下方的SDK Terminal 窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件 
设计过程中所生成的 BIT文件,来对 PL 进行配置。最后下载软件程序,下载完成后,在下方的 SDKTerminal中可以看到应用程序打印的信息,如下图所示: 图1.5.1 串口打印信息窗口 同时, RGB LCD 液晶屏上显示出 OV5640 摄像头采集的图像的灰度图,说明本次OV5640 摄像头LCD 屏显示的实验在启明星ZYQN 开发板上验证成功,如下图所示: 图1.5.2 LCD屏显示结果  
 
 |