正点原子FPGA连载第十章基于OV5640的直方图均衡实验(正点原子FPGA连载第十章基于OV5640的直方图均衡实验)
正点原子FPGA连载第十章基于OV5640的直方图均衡实验(正点原子FPGA连载第十章基于OV5640的直方图均衡实验)图 10.1.3 图像的对比度与直方图图 10.1.2左侧是花粉的电子显微图像,已经放大了近700倍,右侧是图像对应的直方图。从图中可以看出,对于较亮的图像,它的直方图分量主要分布在直方图的右侧,即灰度级较高的区域;而在暗图像中,直方图分量则集中在灰度级的低端(左侧)。下面我们再来观察一下图像的对比度与直方图的关系,如下图所示:图 10.1.1 图像的直方图图 10.1.1左边是一幅数字图像的示意图,分辨率为7x7(即7行7列)。每个像素点由一个方格表示,格中的数字代表该像素点的灰度值,共有16个灰度级(1~16)。我们通过统计各灰度级在图像中出现的次数,就可以得到该图像的灰度直方图,即上图右侧的二维离散图。其中横坐标代表一幅图像的灰度等级,纵坐标代表各灰度级在该幅图像中出现的个数。通过观察直方图的分布我们可以得到图像的明暗、对比度等信息。如下图所示:图 10.1.2 图像的亮度与直方图
1)摘自【正点原子】领航者ZYNQ之HLS 开发指南
2)实验平台:正点原子领航者ZYNQ开发板
3)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
4)全套实验源码 手册 视频下载:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
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_WIDTH 1024 //图像最大宽度
- 5
- 6 typedef hls::stream<ap_axiu<24 1 1 1> > AXI_STREAM;
- 7 typedef hls::Mat<MAX_HEIGHT MAX_WIDTH HLS_8UC3> RGB_IMAGE;
- 8 typedef 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格式变量
- 24 RGB_IMAGE img_0(rows cols);
- 25 GRAY_IMAGE img_1(rows cols);
- 26 GRAY_IMAGE img_2(rows cols);
- 27 RGB_IMAGE img_3(rows cols);
- 28
- 29 //将AXI4 Stream数据转换成hls::mat格式
- 30 hls::AXIvideo2Mat(INPUT_STREAM img_0);
- 31
- 32 //将RGB888格式的彩色数据转换成灰度数据
- 33 hls::CvtColor<HLS_RGB2GRAY HLS_8UC3 HLS_8UC1>(img_0 img_1);
- 34
- 35 //对灰度图像进行直方图均衡
- 36 hls::EqualizeHist<HLS_8UC1 HLS_8UC1 MAX_HEIGHT MAX_WIDTH>(img_1 img_2);
- 37
- 38 //将灰度数据转换成三个通道的灰度图像
- 39 hls::CvtColor<HLS_GRAY2RGB HLS_8UC1 HLS_8UC3>(img_2 img_3);
- 40
- 41
- 42 //将hls::mat格式数据转换成AXI4 Stream格式
- 43 hls::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_CHANEL 1
- 26
- 27 //全局变量
- 28 //frame buffer的起始地址
- 29 unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR
- 30 0x1000000);
- 31 XAxiVdma vdma;
- 32 DisplayCtrl dispCtrl;
- 33 XGpio axi_gpio_inst; //PL端 AXI GPIO 驱动实例
- 34 VideoMode vd_mode;
- 35
- 36 //PL端XOv5640_equalize_histogram驱动实例
- 37 XOv5640_equalize_histogram equ_his_inst;
- 38
- 39 unsigned int lcd_id;
- 40
- 41 int 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 黑暗环境中直方图均衡结果