OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 5552|回复: 1

[XILINX] 【正点原子FPGA连载】第十章 基于OV5640的直方图均衡实验--摘自【正点原子】领航者ZYNQ之HLS开发指南V1.1

[复制链接]

1107

主题

1118

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4615
金钱
4615
注册时间
2019-5-8
在线时间
1218 小时
发表于 2020-8-30 12:13:24 | 显示全部楼层 |阅读模式
本帖最后由 正点原子01 于 2020-8-30 12:13 编辑

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)关注正点原子公众号,获取最新资料更新
1.jpg
1120.png
第十章 基于OV5640的直方图均衡实验

直方图是图像空间域处理技术的基础,对图像的直方图操作可用于图像增强。图像直方图中的固有信息在图像处理相关的应用中非常有用,如图像压缩与分割。由于直方图统计在软件中计算简单,有助于商用硬件实现,因此已经成为一种流行的实时图像处理工具。本章我们将在HLS中实现图像的直方图均衡算法。
本章包括以下几个部分:
1         
1.1        简介
1.2        实验任务
1.3        HLS设计
1.4        IP验证
1.5        下载验证
1.1 简介
直方图是一种统计报告图,大家可以简单地理解成“柱状图”。图像的直方图反应了一幅图像中各灰度级出现的个数,如下图所示:
image002.png

10.1.1 图像的直方图
图 10.1.1左边是一幅数字图像的示意图,分辨率为7x7(即7行7列)。每个像素点由一个方格表示,格中的数字代表该像素点的灰度值,共有16个灰度级(1~16)。我们通过统计各灰度级在图像中出现的次数,就可以得到该图像的灰度直方图,即上图右侧的二维离散图。其中横坐标代表一幅图像的灰度等级,纵坐标代表各灰度级在该幅图像中出现的个数。
通过观察直方图的分布我们可以得到图像的明暗、对比度等信息。如下图所示:
image004.jpg

10.1.2 图像的亮度与直方图
图 10.1.2左侧是花粉的电子显微图像,已经放大了近700倍,右侧是图像对应的直方图。从图中可以看出,对于较亮的图像,它的直方图分量主要分布在直方图的右侧,即灰度级较高的区域;而在暗图像中,直方图分量则集中在灰度级的低端(左侧)。
下面我们再来观察一下图像的对比度与直方图的关系,如下图所示:
image005.jpg

10.1.3 图像的对比度与直方图
从图 10.1.3中可以看到,低对比度图像具有较窄的直方图,且集中于灰度级的中部。直方图越窄,说明图像中像素点所占据的灰度级越少,而且比较集中。这样就导致像素点之间的灰度差别不大,看上去像是图像被冲淡了一样,像这种低对比度的图像不利于肉眼观察分析图像的细节。
而高对比度图像中,直方图的分量覆盖了很宽的灰度级范围,并且在不同的灰度级上像素的分布比较均匀。也就是说,如果一幅图像的像素倾向于占据整个可能的灰度级并且均匀分布,则该图像的对比度更高。最终效果将是一幅灰度细节丰富且动态范围较大的图像。
从上面的例子中我们可以得出结论,如果想要增强图像对比度,那么就需要使图像中的像素占据尽可能多的灰度级。也就是说使图像的直方图像图 10.1.3右下角那样“均匀分布”,直方图均衡化 (Histogram Equalization)算法就可以实现这样的效果,它是一种增强图像对比度(Image Contrast)的方法。顾名思义,它可以将一副图像的直方图分布变成近似均匀分布(均衡),从而增强图像的对比度。如下图所示:
image007.png

10.1.4 直方图均衡化
图 10.1.4左侧为原始图像及其直方图,右侧是经过直方图均衡化之后的图像和它的直方图。可以看出,直方图均衡化使得原始图像的直方图趋向于在整个灰度级中均匀分布,反映在图像上面就是图像的对比度得到了很大的提升。
1.2 实验任务
本节的实验任务是使用Vivado HLS实现一个图像处理的IP核,该IP核能够将OV5640摄像头产生的RGB彩色图像转换成灰度图像,并对灰度图像进行直方图均衡化。
1.3 HLS设计
我们在电脑中的“F:\ZYNQ\High_Level_Synthesis”目录下新建一个名为ov5640_equalize_histogram的文件夹,作为本次实验的工程目录。然后打开Vivado HLS 2018.3,创建一个新的工程“ov5640_equalize_histogram”,选择工程路径为刚刚创建的文件夹。需要注意的是,工程名以及路径只能由英文字母、数字和下划线组成,不能包含中文、空格以及其他特殊字符。如下图所示:
image009.jpg

10.3.1 工程配置界面
设置好工程名及路径之后,点击“Next”,进入如下界面设置顶层函数:
image011.jpg

10.3.2 设置顶层函数
然后选择ZYNQ器件所对应的型号,如下图所示:
图10.3.3 设置时钟周期和器件型号
工程创建完成后,在工程面板中的“source”目录上点击右键,然后在打开的列表中选择“New File”新建源文件,在弹出的对话框中输入源文件的名称“ov5640_equalize_histogram.cpp”,如下图所示。源文件默认的保存路径为HLS工程目录,为方便源文件的管理,我们在工程目录下新建一个名为“src”的文件下,将源文件保存在src目录下。
image013.jpg

10.3.4 输入源文件名
我们输入的源文件的后缀名为“.cpp”,即使用C++语言进行设计。
“ov5640_equalize_histogram.cpp”文件源代码如下:
  1. #include "hls_video.h"

  2. #define MAX_HEIGHT 800       //图像最大高度
  3. #defineMAX_WIDTH  1024      //图像最大宽度

  4. typedef hls::stream<ap_axiu<24,1,1,1> > AXI_STREAM;
  5. typedef hls::Mat<MAX_HEIGHT,MAX_WIDTH,HLS_8UC3> RGB_IMAGE;
  6. typedef hls::Mat<MAX_HEIGHT,MAX_WIDTH,HLS_8UC1> GRAY_IMAGE;

  7. void ov5640_equalize_histogram(AXI_STREAM& INPUT_STREAM,
  8.                  AXI_STREAM & OUTPUT_STREAM,
  9.                   introws,
  10.                   intcols
  11.                   ){

  12. #pragma HLS INTERFACE axis port=INPUT_STREAM
  13. #pragma HLS INTERFACE axis port=OUTPUT_STREAM
  14. #pragma HLS INTERFACE s_axilite port=rows
  15. #pragma HLS INTERFACE s_axilite port=cols
  16. #pragma HLS INTERFACE ap_ctrl_none port=return
  17. #pragma HLS dataflow

  18. //hls::mat格式变量
  19. RGB_IMAGE  img_0(rows,cols);
  20. GRAY_IMAGE img_1(rows,cols);
  21. GRAY_IMAGE img_2(rows,cols);
  22. RGB_IMAGE  img_3(rows,cols);

  23. //将AXI4 Stream数据转换成hls::mat格式
  24. hls::AXIvideo2Mat(INPUT_STREAM,img_0);

  25. //将RGB888格式的彩色数据转换成灰度数据
  26. hls::CvtColor<HLS_RGB2GRAY,HLS_8UC3,HLS_8UC1>(img_0,img_1);

  27. //对灰度图像进行直方图均衡
  28. hls::EqualizeHist<HLS_8UC1,HLS_8UC1,MAX_HEIGHT,MAX_WIDTH>(img_1,img_2);

  29. //将灰度数据转换成三个通道的灰度图像
  30. hls::CvtColor<HLS_GRAY2RGB,HLS_8UC1,HLS_8UC3>(img_2,img_3);


  31. //将hls::mat格式数据转换成AXI4 Stream格式
  32. hls::Mat2AXIvideo(img_3,OUTPUT_STREAM);
  33. 44 }
复制代码
代码的主体部分与《OV5640摄像头灰度显示实验》非常类似,只是在代码的36行多了一个直方图均衡化的函数hls::EqualizeHist()。它是HLS视频库中的函数,可以对输入的图像进行直方图均衡化,从而提高图像的对比度。在本次实验中,OV5640摄像头输入的RGB888格式的彩色数据经过hls::CvtColor()函数转成灰度图像,然后函数hls::EqualizeHist()对转换得到的灰度图像进行直方图均衡,最后重新由hls::CvtColor()函数将单通道的灰度图像转成三个通道的灰度图像。
hls::EqualizeHist()函数的声明如下所示:
image017.jpg

10.3.5直方图均衡函数声明
在上图中,函数EqualizeHist()有两个参数,_src表示输入的图像,_dst为直方图均衡化之后的图像,它们的格式为hls::Mat。
尖括号<>内部为函数EqualizeHist()的模板,其中前两个参数SRC_T和DST_T分别表示输入和输出图像像素数据的类型。该数据类型仅支持HLS_8UC1类型,即只支持单通道8位的视频数据。后面两个参数ROW和COL分别表示输入图像的行数和列数。
由于代码的其他部分与《OV5640摄像头灰度显示实验》基本相同,在这里就不再赘述。如果大家不清楚的话,可以参考《正点原子HLS开发指南》的相应章节。
接下来点击工具栏中向右的绿色三角形对设计进行综合,综合完成后,会自动打开综合结果(solution)的报告。在综合报告中还给出了设计的性能评估、资源评估以及接口等信息。本次实验中,我们重点关注综合工具为我们生成的接口信息,如下图所示:
image019.png

10.3.7 接口信息1

10.3.6 接口信息2
从图中可以看出,设计综合出了一个“AXI4-Lite”从接口和两个“AXI4-Stream”接口(一个输入,一个输出)。其中“AX4-Lite”总线接口用于控制视频处理的分辨率,而两个“AXI4-Stream”总线接口分别用于输入待处理的视频以及输出处理之后的视频流。
在综合结果正确的情况下,在工具栏中点击黄色的“田”字按钮,导出RTL。
在导出RTL结束之后,我们到工程目录所指向的文件夹中可以看到以ZIP压缩文件形式存在的IP核,如下图所示:
image021.png

10.3.18 文件夹中的IP核
HLS设计结束之后,我们将在Vivado中对导出的IP核进行验证。
1.4 IP验证
在IP验证环节,我们会使用Vivado工具的IP集成器将生成的IP核添加到Block Design中,然后完成设计后将程序下载到领航者开发板上进行验证。用于IP验证的底层硬件可以在《领航者ZYNQ之嵌入式开发指南》第二十三章“OV5640 摄像头 LCD 显示”实验的基础上进行。
参考《正点原子HLS开发指南》“OV5640摄像头灰度显示实验”中的下载验证部分,将生成的IP插入到“OV5640 摄像头 LCD 显示”实验底层硬件中的Block Design中。最终的设计如下图所示:
image023.png

10.4.1 用于验证直方图均衡IP核的最终设计
在图 6.4.7中,ov5640_capture_data模块获取OV5640摄像头采集的图像,然后通过Video In to AXI4-Stream模块将摄像头图像转换成AXI4-Stream格式的视频流。该视频流输入HLS生成的中值滤波IP核ov5640_equalize_hist_0,将RGB888格式的彩色图像转换成灰度图像,并对灰度图像进行直方图均衡化,然后同样以AXI4-Stream格式将处理后的视频流输出给VDMA。
如果大家对设计中其他各模块的功能不了解的话,请大家参考《领航者ZYNQ之嵌入式开发指南》第二十三章“OV5640 摄像头 LCD 显示”实验。
到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDLWrapper”。
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成 Bitstream 之后,在菜单栏中选择 File > Export > Export hardware 导出硬件,并在弹出的对话框 中,勾选“Include bitstream”。然后在菜单栏选择 File >Launch SDK,启动 SDK 软件。
在Vivado SDK中新建空的应用工程,工程名为“ov5640_rgb2gray_lcd”。
然后找到《领航者ZYNQ之嵌入式开发指南》第二十三章“OV5640 摄像头 LCD 显示”实验的Vivado工程目录,将“21_ov7725_lcd\ov7725_lcd.sdk\ov7725_lcd”目录下的src文件夹拷贝到新建的应用工程目录下。
在SDK中刷新src目录,然后将“main.c”的代码修改为如下所示:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include "xil_types.h"
  5. #include "xil_cache.h"
  6. #include "xparameters.h"
  7. #include "xgpio.h"
  8. #include "xaxivdma.h"
  9. #include "xaxivdma_i.h"
  10. #include "display_ctrl/display_ctrl.h"
  11. #include "vdma_api/vdma_api.h"
  12. #include "emio_sccb_cfg/emio_sccb_cfg.h"
  13. #include "ov5640/ov5640_init.h"
  14. #include "xov5640_equalize_histogram.h"

  15. //宏定义
  16. #define BYTES_PIXEL        3                           //像素字节数,RGB888占3个字节
  17. #define FRAME_BUFFER_NUM   3                           //帧缓存个数3
  18. #define DYNCLK_BASEADDR   XPAR_AXI_DYNCLK_0_BASEADDR  //动态时钟基地址
  19. #define VDMA_ID           XPAR_AXIVDMA_0_DEVICE_ID    //VDMA器件ID
  20. #define DISP_VTC_ID       XPAR_VTC_0_DEVICE_ID        //VTC器件ID
  21. //PL端 AXI GPIO 0(lcd_id)器件 ID
  22. #define AXI_GPIO_0_ID     XPAR_AXI_GPIO_0_DEVICE_ID   
  23. //使用AXI GPIO(lcd_id)通道1
  24. #define AXI_GPIO_0_CHANEL  1                           

  25. //全局变量
  26. //frame buffer的起始地址
  27. unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR
  28.                                          + 0x1000000);
  29. XAxiVdma            vdma;
  30. DisplayCtrl         dispCtrl;
  31. XGpio               axi_gpio_inst;   //PL端 AXI GPIO 驱动实例
  32. VideoMode           vd_mode;

  33. //PL端XOv5640_equalize_histogram驱动实例
  34. XOv5640_equalize_histogram equ_his_inst;   

  35. unsigned int lcd_id;

  36. int main(void)
  37. {
  38.      u32 status;
  39.      u16 cmos_h_pixel;   //ov5640 DVP 输出水平像素点数
  40.      u16 cmos_v_pixel;   //ov5640 DVP 输出垂直像素点数
  41.      u16 total_h_pixel;  //ov5640 水平总像素大小
  42.      u16 total_v_pixel;  //ov5640 垂直总像素大小

  43.      //获取LCD的ID
  44.      XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_0_ID);
  45.      lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
  46.      xil_printf("lcd_id= %x\n\r",lcd_id);

  47.      //根据获取的LCD的ID号来进行ov5640显示分辨率参数的选择
  48.      switch(lcd_id){
  49.          case 0x4342 :  //4.3寸屏,480*272分辨率
  50.              cmos_h_pixel = 480;
  51.              cmos_v_pixel = 272;
  52.              total_h_pixel = 1800;
  53.              total_v_pixel = 1000;
  54.              break;
  55.          case 0x4384 :  //4.3寸屏,800*480分辨率
  56.              cmos_h_pixel = 800;
  57.              cmos_v_pixel = 480;
  58.              total_h_pixel = 1800;
  59.              total_v_pixel = 1000;
  60.              break;
  61.          case 0x7084 :  //7寸屏,800*480分辨率
  62.              cmos_h_pixel = 800;
  63.              cmos_v_pixel = 480;
  64.              total_h_pixel = 1800;
  65.             total_v_pixel = 1000;
  66.              break;
  67.          case 0x7016 :  //7寸屏,1024*600分辨率
  68.              cmos_h_pixel = 1024;
  69.              cmos_v_pixel = 600;
  70.              total_h_pixel = 2200;
  71.              total_v_pixel = 1000;
  72.              break;
  73.          case 0x1018 :  //10.1寸屏,1280*800分辨率
  74.              cmos_h_pixel = 1280;
  75.              cmos_v_pixel = 800;
  76.              total_h_pixel = 2570;
  77.              total_v_pixel = 980;
  78.              break;
  79.          default :
  80.              cmos_h_pixel = 480;
  81.              cmos_v_pixel = 272;
  82.              total_h_pixel = 1800;
  83.              total_v_pixel = 1000;
  84.              break;
  85.      }

  86.      emio_init();                         //初始化EMIO
  87.      status = ov5640_init( cmos_h_pixel,  //初始化ov5640
  88.                            cmos_v_pixel,
  89.                           total_h_pixel,
  90.                           total_v_pixel);
  91.      if(status == 0)
  92.          xil_printf("OV5640detected successful!\r\n");
  93.      else
  94.          xil_printf("OV5640detected failed!\r\n");

  95.      //根据获取的LCD的ID号来进行video参数的选择
  96.      switch(lcd_id){
  97.          case 0x4342 : vd_mode =VMODE_480x272;  break;  //4.3寸屏,480*272分辨率
  98.          case 0x4384 : vd_mode =VMODE_800x480;  break;  //4.3寸屏,800*480分辨率
  99.          case 0x7084 : vd_mode =VMODE_800x480;  break;  //7寸屏,800*480分辨率
  100.          case 0x7016 : vd_mode =VMODE_1024x600; break;  //7寸屏,1024*600分辨率
  101.          case 0x1018 : vd_mode =VMODE_1280x800; break;  //10.1寸屏,1280*800分辨率
  102.          default : vd_mode = VMODE_800x480; break;
  103.      }

  104.      //初始化直方图均衡IP核
  105.      XOv5640_equalize_histogram_Initialize(&equ_his_inst,
  106.                                                 XPAR_OV5640_EQUALIZE_HIST_0_DEVICE_ID);
  107.      //配置灰度转换IP核OV5640_RGB2GRAY的行数
  108.      XOv5640_equalize_histogram_Set_rows(&equ_his_inst, vd_mode.height);
  109.      //配置灰度转换IP核OV5640_RGB2GRAY的行数
  110.      XOv5640_equalize_histogram_Set_cols(&equ_his_inst, vd_mode.width);

  111.      //配置VDMA
  112.      run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
  113.                              frame_buffer_addr,0,0,BOTH);
  114.      //初始化Display controller
  115.      DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
  116.      //设置VideoMode
  117.      DisplaySetMode(&dispCtrl, &vd_mode);
  118.      DisplayStart(&dispCtrl);

  119.      return 0;
  120. }
复制代码
在代码的第14行引入了"xov5640_equalize_histogram.h"头文件,这个头文件是Vivado HLS工具生成的,里面声明了直方图均衡IP核的驱动函数。
首先在代码的37行定义了直方图均衡IP核的驱动实例equ_his_inst,该变量会在后面对IP核进行配置时用到。然后在代码的第115行通过“XOv5640_equalize_histogram_Initialize()”函数来初始化Vivado HLS生成的直方图均衡IP核;在代码的第118行通过传入“vd_mode.height”形参来设置直方图均衡IP核的行数,在代码的第120行通过传入“vd_mode.width”形参来设置列数。这些数据类型和函数在"xov5640_equalize_histogram.h"头文件中均有声明。
1.5 下载验证
编译完工程之后我们就可以开始下载程序了。将 OV5640 摄像头模块插在领航者 Zynq 开发板的“OLED/CAMERA”插座上,并将 LCD 的排线接头插入开发板上的 LCD 接线座。将下载器一端连电脑,
另一端与开发板上的 JTAG 端口连接,连接电源线并打开电源开关。
在 SDK 软件下方的 SDK Terminal 窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的 BIT 文件,来对 PL 进行配置。最后下载软件程序,下载完成后在LCD上就可以看到摄像头采集的彩色图像被转换成了灰度图像,中间经过了直方图均衡。
需要注意的是,在照明条件下良好的环境下对OV5640摄像头图像进行直方图均衡,效果并不明显。可以在较暗的环境中(如夜间关灯状态下)进行测试,这样场景的亮度很低,但是大家会发现LCD上显示的图像依然像白天一样明亮。但是由于直方图均衡在增强对比度的同时会放大噪声,因此图像的质量会明显下降,如下图所示:
image027.jpg

10.5.1 黑暗环境中直方图均衡结果



正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
3
金钱
3
注册时间
2020-12-15
在线时间
0 小时
发表于 2020-12-15 16:29:21 | 显示全部楼层
有人运行成功吗?我用的是领航者的板子,自己按照说明创建的程序,运行时闪了一下图像,之后屏幕就黑了。运行网盘上自带的这个程序,烧录完FPGA程序并运行SDK程序,摄像头板上的LED灯亮了一下,屏幕没有输出完整的图像,之后屏幕就黑了。
有人遇到类似的问题吗?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-10-3 16:32

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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