1)实验平台:正点原子新起点V2FPGA开发板
2) 章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_xinqidian(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
第五十三章 基于OV5640摄像头的Sobel边缘检测实验
边缘检测是一种常用的图像分割技术,常用的边缘检测算子有Roberts Cross算子、Prewitt算子、Sobel算子和 Kirsch算子等。Sobel算子是根据像素点上下左右邻点灰度加权差在边缘处达到极值这一现象来检测边缘。其对噪声具有平滑作用,提供较为精确的边缘方向信息,当对精度要求不是很高时,是一种较为常用的边缘检测方法。在本章实验中将进行基于SOBEL算子的边缘检测实验。
本章包括以下几个部分:
52.1简介
52.2实验任务
52.3硬件设计
52.4程序设计
52.5下载验证
53.1简介
图像分割就是把图像分成若干个特定的、具有独特性质的区域并提出有意义目标的技术和过程。它是由图像处理到图像分析的关键步骤。现有的图像分割方法主要分以下几类:基于阈值的分割方法、基于区域的分割方法、基于边缘的分割方法以及基于特定理论的分割方法等。从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。图像分割的过程也是一个标记过程,即把属于同一区域的像素赋予相同的编号。
图像分割的一种重要方法就是边缘检测,即检测图像灰度级或者结构发生突变的像素点,表明一个区域的终结,也是另一个区域开始的地方。这种一副图像中灰度级或结构突变的像素点的集合称为边缘。不同的图像灰度变化不同,在边界处一般有明显的灰度变化,检测图像的灰度变化是一种常用的边缘检测方法。
图像的灰度值具有不连续性,图像局部的灰度突变可以用微分来检测,因为这种突变时间十分短促,因此一阶微分和二阶微分十分适合这种突变的检测。但使用二阶微分对噪声的响应更加强烈,其对图像的质量要求较高。
为了对有意义的边缘点进行分类,与这个点相联系的灰度级的变化必须比在这一点的背景上的变化更有效(灰度变化更剧烈)。由于本次实验用局部计算处理,因此决定一个值是否有效的方法就是设定一个阈值。当一个点的二维一阶导数(图像是一个二维函数)比指定阈值大,就认为这个点是一个边缘点。
一幅图像可以看做是一个二维函数,图像的的一阶导数可以用梯度表示(梯度的本意是一个向量(矢量),表示某一函数在某一点处的方向导数沿着该方向取得最大值。我们用∇f表示梯度,如下图所示为像素点(x,y)的梯度表达式:
图 53.1.1 点(x,y)处的梯度向量
∇f它指出了f(图像)在点(x,y)处最大变化率的方向。
向量∇f的大小(长度)表示为M(x,y),M(x,y)的表达式如下:
图 53.1.2 M(x,y)的表达式
它是梯度向量方向变化率的值。值得注意的是这些求和、平方及开平方都是阵列操作。
梯度的方向可以用图 53.1.3的公式来表示,它是以x轴为度量基准表示的,它与图像的边缘垂直。
图 53.1.3 梯度向量的方向
本次实验要计算梯度的大小,首先要计算梯度关于x和y方向的分量gy和gx,比较常用的计算梯度的分量的方法是使用一些空间域的模板。大家通常把用于计算梯度偏导数的滤波器模板称为梯度算子(也称为差分算子、边缘算子和边缘检测子),例如用于边缘检测的Sobel算子、Roberts算子和Prewitt算子等。如下图就是几种常用的边缘检测模板:
图 53.1.4 几种边缘检测算子
罗伯特交叉梯度算子是最早尝试具有对角优势的模板之一,它是一个2x2模板。2x2的模板在概念上很简单,但它是一个偶数模板,偶数模板对于中心点的确认很不便,因此一般情况下大家使用奇数的模板,最小的奇数模板是3x3的模板。这些奇数模板考虑了中心点对端数据的性质,并携带有关于边缘方向的更多信息,能够更好的表达图像的边缘。Sobel算子和Prewitt算子都是实际应用中常见的算子,但相比用Prewitt算子,Sobel算子具有更好的抑制噪声性能。下面给出使用Sobel算子计算梯度分量的算术表达式:
图 53.1.5 Sobel算子求取梯度分量
在这些公式中,3x3区域的第三行与第一行的差近似为x方向的偏导数,第三列与第一列的差近似为y方向的偏导数。上式用Verilog实现可以用下图的计算方法表示:
图 53.1.6 用Verilog对梯度分量的表示
在上图的算式中,gy1和gy3分别表示第一行和第三列行的值。gx1和gx3分别表示第一行列第三列的值。
在进行边缘检测时首先需要求出每一个像素在x和y方向的偏导数gy 和gx,再求出像素点的梯度大小M(x,y),然后把M(x,y)与本次实验设置的阈值进行比较,大于阈值则表示该像素点为边缘,反之,则表示该像素点不是边缘。
53.2实验任务
本次实验任务是通过摄像头采集的RGB565数据转化为YCbCr数据(也可直接配置摄像头采集yuv数据),然后再进行灰度的Sobel边缘检测, 最后在HDMI显示器上显示。
53.3硬件设计
本章节中硬件设计与“OV5640摄像头HDMI显示实验”完全相同,在此就不在赘述。
53.4程序设计
根据实验任务,首先设计如图 53.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头HDMI灰度显示实验”的整体架构。本次实验包括以下模块:时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、摄像头采集模块、图像处理模块和HDMI顶层模块。其中时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、摄像头采集模块和HDMI顶层模块本次实验没有做任何修改,这些模块在“OV5640摄像头HDMI显示实验”中已经说明过,这里不再详述,本次实验只是修改了图像处理模块。
ov5640的sobel边沿检测实验系统框图如下图所示:
图 53.4.1 顶层系统框图
由上图可知,时钟模块(pll和pll_hdmi)为HDMI顶层模块、SDRAM控制模块以及IIC驱动模块提供驱动时钟。IIC驱动模块和IIC配置模块控制着传感器初始化的开始与结束,传感器初始化完成后将采集到的数据写入摄像头采集模块。数据在摄像头采集模块处理完成后写入图像处理模块,图像处理模块将摄像头数据进行处理后存入SDRAM控制模块。顶层模块从SDRAM控制模块中读出数据并驱动显示器显示,这时整个系统才完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在SDRAM和传感器都初始化完成之后才开始输出数据的,避免了在SDRAM初始化过程中向里面写入数据。
顶层模块代码如下所示:
FPGA顶层模块(ov5640_hdmi_sobel)例化了以下八个模块:时钟模块1(pll)、时钟模块2(pll_hdmi)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov5640_rgb565_cfg)、图像采集模块(cmos_capture_data)、图像处理模块(vip)、SDRAM控制模块(sdram_top)和HDMI顶层模块(hdmi_top)。
时钟模块:时钟模块通过调用PLL IP核实现,共输出5个时钟,频率分别为100M时钟、100M偏移-75度时钟、50M时钟、71Mhz时钟和355M时钟(HDMI像素时钟的5倍频)。其中pll 产生了50M时钟、100M时钟和100M偏移-75度时钟,pll_hdmi 产生了71Mhz时钟和355M时钟,这里之所以用两个锁相环是因为HDMI所用的时钟71Mhz与SDRAM控制模块使用的100M时钟不是整数倍,使用一个锁相环不符合设计要求。100Mhz时钟作为SDRAM控制模块的驱动时钟,100M偏移-75度时钟用来输出给外部SDRAM芯片使用,50Mhz时钟作为I2C驱动模块的驱动时钟,71Mhz时钟和355M时钟(HDMI像素时钟的5倍频)负责驱动HDMI顶层模块。
I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV5640 SCCB接口总线,用户可根据该模块提供的用户接口可以很方便的对OV5640的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
I2C配置模块(i2c_ov5640_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV5640的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV5640传感器的初始化。
图像采集模块(cmos_capture_data):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。OV5640和OV7725图像输出时序非常相似,有关该模块的详细介绍请大家参考“OV7725摄像头LCD显示实验”章节。
图像处理模块(vip):对采集后的图像数据进行处理,并将处理后的数据存入SDRAM控制模块。
SDRAM控制模块(sdram_top):SDRAM读写控制器模块负责驱动SDRAM片外存储器,缓存图像传感器输出的图像数据。有关该模块的详细介绍请大家参考“SDRAM读写测试实验”章节。
HDMI顶层模块(hdmi_top):HDMI顶层模块负责驱动HDMI显示器的驱动信号的输出,同时为其他模块提供显示器参数、场同步信号和数据请求信号。关HDMI顶层模块的详细介绍请大家参考“OV5640摄像头HDMI显示实验”章节。
vip模块框图如下图所示:
图 53.4.2 vip模块框图
vip模块例化了RGB转YCbCr模块(rgb2ycbcr)和sobel边缘检测模块(vip_sobel_edge_detector)。RGB转YCbCr模块负责将摄像头采集的RGB565格式数据到转换为YUV格式的数据。sobel边缘检测模块负责将YUV格式的视频图像进行边缘检测后输出。有关RGB转YCbCr模块的详细介绍请大家参考“OV5640摄像头HDMI灰度显示实验”章节。
vip模块原理图如下图所示:
图 53.4.3 vip模块原理图
如上图所示,摄像头采集到16位rgb565输入vip模块,经过“rgb2ycbcr”模块转化为8位的yuv444数据,然后在将转化后的灰度数据(img_y)作为“vip_sobel_edge_detector”模块的输入,对灰度进行边缘检测处理,最后输出处理后的灰度数据“post_img_bit”。
图像处理模块负责图像数据的格式转换,代码如下:
- 1 module vip(
- 2 //module clock
- 3 input clk , // 时钟信号
- 4 input rst_n , // 复位信号(低有效)
- 5
- 6 //图像处理前的数据接口
- 7 input pre_frame_vsync,
- 8 input pre_frame_hsync,
- 9 input pre_frame_de ,
- 10 input [15:0] pre_rgb ,
- 11 input [10:0] xpos ,
- 12 input [10:0] ypos ,
- 13
- 14 //图像处理后的数据接口
- 15 output post_frame_vsync, // 场同步信号
- 16 output post_frame_hsync, // 行同步信号
- 17 output post_frame_de , // 数据输入使能
- 18 output [15:0] post_rgb // RGB565颜色数据
- 19 );
- 20
- 21 parameter SOBEL_THRESHOLD = 128; //sobel阈值
- 22
- 23 //wire define
- 24 wire [ 7:0] img_y;
- 25 wire [ 7:0] post_img_y;
- 26 wire pe_frame_vsync;
- 27 wire pe_frame_href;
- 28 wire pe_frame_clken;
- 29 wire ycbcr_vsync;
- 30 wire ycbcr_hsync;
- 31 wire ycbcr_de;
- 32
- 33 //*****************************************************
- 34 //** main code
- 35 //*****************************************************
- 36
- 37 //RGB转YCbCr模块
- 38 rgb2ycbcr u_rgb2ycbcr(
- 39 //module clock
- 40 .clk (clk ), // 时钟信号
- 41 .rst_n (rst_n ), // 复位信号(低有效)
- 42 //图像处理前的数据接口
- 43 .pre_frame_vsync (pre_frame_vsync), // vsync信号
- 44 .pre_frame_hsync (pre_frame_hsync), // href信号
- 45 .pre_frame_de (pre_frame_de ), // data enable信号
- 46 .img_red (pre_rgb[15:11] ),
- 47 .img_green (pre_rgb[10:5 ] ),
- 48 .img_blue (pre_rgb[ 4:0 ] ),
- 49 //图像处理后的数据接口
- 50 .post_frame_vsync(pe_frame_vsync), // vsync信号
- 51 .post_frame_hsync(pe_frame_href), // href信号
- 52 .post_frame_de (pe_frame_clken), // data enable信号
- 53 .img_y (img_y), //灰度数据
- 54 .img_cb (),
- 55 .img_cr ()
- 56 );
- 57
- 58 vip_sobel_edge_detector
- 59 #(
- 60 .SOBEL_THRESHOLD (SOBEL_THRESHOLD) //sobel阈值
- 61 )
- 62 u_vip_sobel_edge_detector(
- 63 .clk (clk),
- 64 .rst_n (rst_n),
- 65
- 66 //处理前数据
- 67 .per_frame_vsync (pe_frame_vsync), //处理前帧有效信号
- 68 .per_frame_href (pe_frame_href), //处理前行有效信号
- 69 .per_frame_clken (pe_frame_clken), //处理前图像使能信号
- 70 .per_img_y (img_y), //处理前输入灰度数据
- 71
- 72 //处理后的数据
- 73 .post_frame_vsync (post_frame_vsync), //处理后帧有效信号
- 74 .post_frame_href (post_frame_hsync), //处理后行有效信号
- 75 .post_frame_clken (post_frame_de), //输出使能信号
- 76 .post_img_bit (post_rgb) //输出像素
- 77
- 78 );
- 79
- 80 endmodule
复制代码
代码的第21行表示对边缘检测的阈值的设定,本次实验设定的值为256级灰度的中间值128。
代码的第38行至56行是对灰度转换模块的例化,在该模块以摄像头采集的16位RGB565红、绿、蓝三原色数据作为输入数据,通过算法实现RGB到YCbCr的转换,并输出8位灰度数据,并输出数据输出使能信号。有关RGB转YCbCr模块的详细介绍请大家参考“OV5640摄像头HDMI灰度显示实验”章节。
代码的第58行至78行是对sobel边缘检测模块的例化,前面已经介绍过Sobel算子是3x3的模板,因此在实现边缘检测时也需要生成3x3的阵列,有关3x3阵列的生成在“基于OV5640的中值滤波实验”有过介绍,在此就不再赘述。
sobel边缘检测模块如下:
在上述代码中,首先求x、y方向的偏导数,如代码第88至115行;在代码第118至123行,计算了偏导数的平方和;代码第126行至131行,对上一步计算的平方和进行开平方,所得的值就是梯度向量的大小,在此处调用了开方函数,稍后会展示关于开方函数IP的配置界面;最后将开方所得的值与预设阈值进行比较,若大于阈值则post_img_bit_r赋为1,表示该像素点是边缘像素,否则赋为0,表示该像素点不是边缘像素,如代码第135行至142行;前面step1到5一共消耗了5个时钟周期,因此需要对post_frame_vsync、post_frame_href 和post_frame_clken延迟5个时钟周期进行同步,如代码第145行至156行。
有关阈值的设置,本实验中设为128,如顶层代码第3行所示。理论上讲阈值可以为0到255中的任意值(灰度值为0到255),但实际上,阈值过小或过大都会影响实验结果。阈值过大会导致边缘间断;阈值过小,会导致伪边缘。
关于开方函数IP的调用,我们先在搜索框输入“SQRT”,选择“ALTSQRT”,如下图所示:
图53.4.4 选择“SQRT”
点击“Next”进入配置界面,如下图:
图 53.4.5 “SQRT”参数配置界面
位宽设为21,点击“Finish”,这样就完成了开方函数的配置。
53.5下载验证
编译完工程之后就可以开始下载程序了。将OV5640摄像头模块插在新起点开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,下载完成后观察HDMI显示器显示图案。如下图所示:
图 53.5.1 HDMI实时显示边缘检测图像
|