正点原子 发表于 2020-9-27 10:37:59

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

1)实验平台:正点原子领航者ZYNQ开发板
2)购买链接:https://item.taobao.com/item.htm?id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:                                                                                                                
6)关注正点原子公众号,获取最新资料





第十章基于OV5640的直方图均衡实验


直方图是图像空间域处理技术的基础,对图像的直方图操作可用于图像增强。图像直方图中的固有信息在图像处理相关的应用中非常有用,如图像压缩与分割。由于直方图统计在软件中计算简单,有助于商用硬件实现,因此已经成为一种流行的实时图像处理工具。本章我们将在HLS中实现图像的直方图均衡算法。
本章包括以下几个部分:
1010.1简介
10.2实验任务
10.3HLS设计
10.4IP验证
10.5下载验证


10.1简介
直方图是一种统计报告图,大家可以简单地理解成“柱状图”。图像的直方图反应了一幅图像中各灰度级出现的个数,如下图所示:

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

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

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

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

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

图 10.3.2 设置顶层函数
然后选择ZYNQ器件所对应的型号,如下图所示:

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

图 10.3.4 输入源文件名
我们输入的源文件的后缀名为“.cpp”,即使用C++语言进行设计。
“ov5640_equalize_histogram.cpp”文件源代码如下:
1#include "hls_video.h"
2
3#define MAX_HEIGHT 800       //图像最大高度
4#define MAX_WIDTH1024      //图像最大宽度
5
6typedef hls::stream<ap_axiu<24,1,1,1> >AXI_STREAM;
7typedef hls::Mat<MAX_HEIGHT,MAX_WIDTH,HLS_8UC3> RGB_IMAGE;
8typedef hls::Mat<MAX_HEIGHT,MAX_WIDTH,HLS_8UC1> GRAY_IMAGE;
9
10 void ov5640_equalize_histogram(AXI_STREAM & INPUT_STREAM,
11                   AXI_STREAM & OUTPUT_STREAM,
12                   int rows,
13                   int cols
14                   ){
15
16 #pragma HLS INTERFACE axis port=INPUT_STREAM
17 #pragma HLS INTERFACE axis port=OUTPUT_STREAM
18 #pragma HLS INTERFACE s_axilite port=rows
19 #pragma HLS INTERFACE s_axilite port=cols
20 #pragma HLS INTERFACE ap_ctrl_none port=return
21 #pragma HLS dataflow
22
23//hls::mat格式变量
24RGB_IMAGEimg_0(rows,cols);
25GRAY_IMAGE img_1(rows,cols);
26GRAY_IMAGE img_2(rows,cols);
27RGB_IMAGEimg_3(rows,cols);
28
29//将AXI4 Stream数据转换成hls::mat格式
30hls::AXIvideo2Mat(INPUT_STREAM,img_0);
31
32//将RGB888格式的彩色数据转换成灰度数据
33hls::CvtColor<HLS_RGB2GRAY,HLS_8UC3,HLS_8UC1>(img_0,img_1);
34
35//对灰度图像进行直方图均衡
36hls::EqualizeHist<HLS_8UC1,HLS_8UC1,MAX_HEIGHT,MAX_WIDTH>(img_1,img_2);
37
38//将灰度数据转换成三个通道的灰度图像
39hls::CvtColor<HLS_GRAY2RGB,HLS_8UC1,HLS_8UC3>(img_2,img_3);
40
41
42//将hls::mat格式数据转换成AXI4 Stream格式
43hls::Mat2AXIvideo(img_3,OUTPUT_STREAM);
44 }
代码的主体部分与《OV5640摄像头灰度显示实验》非常类似,只是在代码的36行多了一个直方图均衡化的函数hls::EqualizeHist()。它是HLS视频库中的函数,可以对输入的图像进行直方图均衡化,从而提高图像的对比度。在本次实验中,OV5640摄像头输入的RGB888格式的彩色数据经过hls::CvtColor()函数转成灰度图像,然后函数hls::EqualizeHist()对转换得到的灰度图像进行直方图均衡,最后重新由hls::CvtColor()函数将单通道的灰度图像转成三个通道的灰度图像。
hls::EqualizeHist()函数的声明如下所示:

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

图 10.3.7 接口信息1

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

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

图 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 HDL Wrapper”。
最后在左侧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//宏定义
17#define BYTES_PIXEL      3                           //像素字节数,RGB888占3个字节
18#define FRAME_BUFFER_NUM   3                           //帧缓存个数3
19#define DYNCLK_BASEADDR    XPAR_AXI_DYNCLK_0_BASEADDR//动态时钟基地址
20#define VDMA_ID            XPAR_AXIVDMA_0_DEVICE_ID    //VDMA器件ID
21#define DISP_VTC_ID      XPAR_VTC_0_DEVICE_ID      //VTC器件ID
22//PL端AXI GPIO 0(lcd_id)器件 ID
23#define AXI_GPIO_0_ID      XPAR_AXI_GPIO_0_DEVICE_ID   
24//使用AXI GPIO(lcd_id)通道1
25#define AXI_GPIO_0_CHANEL1                           
26
27//全局变量
28//frame buffer的起始地址
29unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR
30                                          + 0x1000000);
31XAxiVdma            vdma;
32DisplayCtrl         dispCtrl;
33XGpio               axi_gpio_inst;   //PL端 AXI GPIO 驱动实例
34VideoMode         vd_mode;
35
36//PL端XOv5640_equalize_histogram驱动实例
37XOv5640_equalize_histogramequ_his_inst;   
38
39unsigned int lcd_id;
40
41int main(void)
42{
43      u32 status;
44      u16 cmos_h_pixel;   //ov5640 DVP 输出水平像素点数
45      u16 cmos_v_pixel;   //ov5640 DVP 输出垂直像素点数
46      u16 total_h_pixel;//ov5640 水平总像素大小
47      u16 total_v_pixel;//ov5640 垂直总像素大小
48
49      //获取LCD的ID
50      XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_0_ID);
51      lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
52      xil_printf("lcd_id = %x\n\r",lcd_id);
53
54      //根据获取的LCD的ID号来进行ov5640显示分辨率参数的选择
55      switch(lcd_id){
56          case 0x4342 ://4.3寸屏,480*272分辨率
57            cmos_h_pixel = 480;
58            cmos_v_pixel = 272;
59            total_h_pixel = 1800;
60            total_v_pixel = 1000;
61            break;
62          case 0x4384 ://4.3寸屏,800*480分辨率
63            cmos_h_pixel = 800;
64            cmos_v_pixel = 480;
65            total_h_pixel = 1800;
66            total_v_pixel = 1000;
67            break;
68          case 0x7084 ://7寸屏,800*480分辨率
69            cmos_h_pixel = 800;
70            cmos_v_pixel = 480;
71            total_h_pixel = 1800;
72            total_v_pixel = 1000;
73            break;
74          case 0x7016 ://7寸屏,1024*600分辨率
75            cmos_h_pixel = 1024;
76            cmos_v_pixel = 600;
77            total_h_pixel = 2200;
78            total_v_pixel = 1000;
79            break;
80          case 0x1018 ://10.1寸屏,1280*800分辨率
81            cmos_h_pixel = 1280;
82            cmos_v_pixel = 800;
83            total_h_pixel = 2570;
84            total_v_pixel = 980;
85            break;
86          default :
87            cmos_h_pixel = 480;
88            cmos_v_pixel = 272;
89            total_h_pixel = 1800;
90            total_v_pixel = 1000;
91            break;
92      }
93
94      emio_init();                         //初始化EMIO
95      status = ov5640_init( cmos_h_pixel,//初始化ov5640
96                            cmos_v_pixel,
97                           total_h_pixel,
98                           total_v_pixel);
99      if(status == 0)
100         xil_printf("OV5640 detected successful!\r\n");
101   else
102         xil_printf("OV5640 detected failed!\r\n");
103
104   //根据获取的LCD的ID号来进行video参数的选择
105   switch(lcd_id){
106         case 0x4342 : vd_mode = VMODE_480x272;break;//4.3寸屏,480*272分辨率
107         case 0x4384 : vd_mode = VMODE_800x480;break;//4.3寸屏,800*480分辨率
108         case 0x7084 : vd_mode = VMODE_800x480;break;//7寸屏,800*480分辨率
109         case 0x7016 : vd_mode = VMODE_1024x600; break;//7寸屏,1024*600分辨率
110         case 0x1018 : vd_mode = VMODE_1280x800; break;//10.1寸屏,1280*800分辨率
111         default : vd_mode = VMODE_800x480; break;
112   }
113
114   //初始化直方图均衡IP核
115   XOv5640_equalize_histogram_Initialize(&equ_his_inst,
116                                                 XPAR_OV5640_EQUALIZE_HIST_0_DEVICE_ID);
117   //配置灰度转换IP核OV5640_RGB2GRAY的行数
118   XOv5640_equalize_histogram_Set_rows(&equ_his_inst, vd_mode.height);
119   //配置灰度转换IP核OV5640_RGB2GRAY的行数
120   XOv5640_equalize_histogram_Set_cols(&equ_his_inst, vd_mode.width);
121
122   //配置VDMA
123   run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
124                           frame_buffer_addr,0,0,BOTH);
125   //初始化Display controller
126   DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
127   //设置VideoMode
128   DisplaySetMode(&dispCtrl, &vd_mode);
129   DisplayStart(&dispCtrl);
130
131   return 0;
132 }
在代码的第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"头文件中均有声明。
10.5下载验证
编译完工程之后我们就可以开始下载程序了。将 OV5640 摄像头模块插在领航者 Zynq 开发板的“OLED/CAMERA”插座上,并将 LCD 的排线接头插入开发板上的 LCD 接线座。将下载器一端连电脑,
另一端与开发板上的 JTAG 端口连接,连接电源线并打开电源开关。
在 SDK 软件下方的 SDK Terminal 窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的 BIT 文件,来对 PL 进行配置。最后下载软件程序,下载完成后在LCD上就可以看到摄像头采集的彩色图像被转换成了灰度图像,中间经过了直方图均衡。
需要注意的是,在照明条件下良好的环境下对OV5640摄像头图像进行直方图均衡,效果并不明显。可以在较暗的环境中(如夜间关灯状态下)进行测试,这样场景的亮度很低,但是大家会发现LCD上显示的图像依然像白天一样明亮。但是由于直方图均衡在增强对比度的同时会放大噪声,因此图像的质量会明显下降,如下图所示:

图 10.5.1 黑暗环境中直方图均衡结果
页: [1]
查看完整版本: 【正点原子FPGA连载】第十章基于OV5640的直方图均衡实验--摘自【正点原子】领航者ZYNQ之HLS 开发指南