使用FPGA进行实时边缘检测

引言


我们的项目实现了一个实时边缘检测系统,该系统基于从OV7670摄像机捕获图像帧并在应用了灰度滤镜和Sobel运算符之后将其流传输到VGA监视器。 我们的设计建立在Cyclone IV FPGA板上,使我们能够利用底层硬件的强大功能和并行计算来优化性能,这对于满足实时系统的要求至关重要。


我们使用了基于Cyclone IV(EP4CE6E22C8N)的ZEOWAA FPGA开发板。 此外,我们使用Quartus Prime Lite Edition作为开发环境,并使用Verilog HDL作为编程语言。 此外,我们使用内置的VGA接口来驱动VGA监视器,并使用GPIO(输入和输出的通用引脚)将外部硬件与我们的板连接。


ZEOWAA FPGA开发板


建筑学


我们的设计分为3个主要部分:


  1. 从相机读取数据像素。
  2. 实施我们的边缘检测算法(灰度转换器和Sobel运算符)。
  3. 通过与VGA监视器接口显示最终图像。

同样,在读/写数据和对该数据进行操作之间有一个中间存储器。 为此,我们实现了两个缓冲区,它们在使用之前作为像素的临时空间。


实施架构


请注意,从相机中取出像素后,我们没有将其直接存储到中间存储缓冲区中。 相反,我们将其转换为灰度,然后将其存储在缓冲区中。 这是因为存储8位灰度像素比存储16位彩色像素占用更少的内存。 同样,我们还有另一个缓冲区,该缓冲区在应用Sobel运算符以使它们准备好在监视器上显示之后,可以存储数据。


以下是有关我们架构实施的详细信息:


摄影机

我们使用了OV7670相机,它是我们发现的最便宜的相机模块之一。 同样,该相机可以在3.3V电压下工作,不需要困难的通信协议(例如I2c或SPI)来提取图像数据。 它只需要SCCB接口(类似于I2c接口)就可以按照颜色格式(RGB565,RGB555,YUV,YCbCr 4:2:2),分辨率(VGA,QVGA,QQVGA,CIF,QCIF)设置摄像机的配置和许多其他设置。


OV7670摄像头模块


该视频包含以特定速率更改的帧。 一帧是由行和列的像素组成的图像,其中每个像素由颜色值表示。 在此项目中,我们使用了摄像机的默认配置,其中帧的大小是VGA分辨率640 x 480(0.3兆像素),像素的颜色格式是RGB565(红色5位,蓝色6位,绿色5位)。 ),更改帧的速率为30 fps。


在下面,使用开发板上存在的GPIO将摄像机连接到FPGA:


固定在相机中FPGA中的引脚内容描述固定在相机中FPGA中的引脚内容描述
3.3伏3.3伏电源(+)地线地线地面供电水平(-)
Sdioc地线SCCB时钟SDIOD地线SCCB数据
垂直同步P31垂直同步HrefP55水平同步
时钟P23像素时钟XclkP54输入系统时钟(25 MHz)
D7P46数据的第8位D6P44数据的第7位
D5P43第六位数据D4P42第五位数据
D3P39第四位数据D2P38数据的第三位
D1P34数据第二位D0P33第一位数据
复位(低电平有效)3.3伏复位针个人数据网地线断电引脚

请注意,我们没有使用SCCB接口进行配置。 因此,我们将其相应的导线放在地面上,以防止任何会影响数据的浮动信号。


为了给摄像机提供25MHz的时钟,我们使用了锁相环(PLL),它是一个闭环频率控制系统,可以从板上提供的50MHz频率提供所需的时钟。 为了实现PLL,我们使用了Quartus软件中的内部IP分类工具。


本相机使用垂直同步(VSYNC)信号来控制帧的发送过程,并使用水平同步(HREF)信号来控制帧的每一行的发送。 当照相机将16位RGB像素值分成2个(8位)部分并分别发送时,仅使用8行数据(D0-D7)来传输代表像素颜色值的位。


OV7670摄像机模块的数据表中的下图说明了垂直和水平同步信号。


VGA帧时序


水平定时


RGB565输出时序图


灰度转换器

从原始彩色图像生成灰度图像时,应考虑许多因素,因为图像可能会失去对比度,清晰度,阴影和结构。 此外,图像应保留色彩空间的相对亮度。 几种线性和非线性技术用于将彩色图像转换为灰度。 因此,为了实现我们的目标,我们使用了比色(保持感觉亮度)转换为灰度,公式如下:



为了提高计算性能,使用移位运算符的速度更快。 因此,以上等式可以简化为:



结果,在从相机捕获了(565 RGB)像素值之后,可以立即使用转换公式将其转换为8位灰度像素值。 灰度图像更容易存储在存储器中,并且足够快地用于服务实时系统的功能,因为它的复杂度近似为对数,FPGA可以通过并行访问存储器来使其更快。 之后,存储的图像准备好用于实施边缘检测算法。


中间存储器(缓冲区)

我们有2个缓冲区,第一个用于将像素转换为灰度及其大小(8位x 150 x 150)后用于存储像素,第二个用于在应用Sobel运算符和阈值后存储像素。输出值及其大小(1位x 150 x 150)。 不幸的是,150 x 150的缓冲区不能存储相机的整个图像,而只能存储其中的一部分。


由于旋风IV内存的限制,我们选择缓冲区的大小为150 x 150,因为它只有276.480 Kbit,而我们的两个缓冲区占用202.500 Kbit(150 x 150 x 9),相当于原始内存的73.24%。气旋IV和其余的存储器用于存储算法和体系结构。 此外,我们尝试将缓冲区的大小设置为(170 x 170),这会占用94.07%的内存,而内存没有足够的空间来实现该算法。


我们的缓冲器是True Dual-port RAM,可以同时在不同的时钟周期内进行读写。 在这里,我们创建了实现,而不是使用Quartus软件中的IP目录工具来使实现更具灵活性。 另外,我们将两个缓冲区仅集成在一个模块中,而不使用不同的模块。


索贝尔算子

我们使用了一阶导数边缘检测算子,它是确定不同像素之间亮度变化的矩阵面积梯度算子。 更准确地说,由于这是一种在内存使用和时间复杂度方面直接有效的方法,我们使用了Sobel梯度算子,该算子使用以所选像素为中心的3x3内核来表示边缘的强度。 Sobel运算符是由以下公式计算得出的梯度的大小:


G方程


其中Gx和Gy可以使用卷积掩码表示:


Gx和Gy卷积矩阵


请注意,更接近蒙版中心的像素具有更大的权重。 同样,G x和G y可以计算如下:


Gx和gy方程


其中p i是以下数组中的对应像素,并且p i的值是8位灰度值:


像素矩阵


通过绝对值近似估算Sobel算子的梯度大小是一种常见做法:


等式


这种近似更易于实现并且计算起来更快,从而再次在时间和内存方面满足了我们的功能。


这是Sobel运算符的框图,该运算符采用9(8位)像素作为输入并产生(8位)像素值:


索贝尔核心


这是Sobel运算符实现的详细框图。


详细的Sobel核心


VGA监视器

我们的开发板具有内置的VGA接口,该接口只能在VGA显示器上显示8种颜色,因为它只有3位来控制颜色,红色为1位,绿色为1位,蓝色为1位。 这使我们的调试更加困难,因为它阻止了我们将图像直接从相机显示到监视器。 因此,我们使用阈值将像素转换为1位值,从而可以显示图像。


VGA接口的工作方式与摄像机相似,它从左上角到右下角逐个像素地操作。 使用垂直和水平同步,我们可以同步控制像素流的信号。


垂直同步信号用于表示行的索引,而水平同步信号用于表示列的索引。 同样,两个信号都使用前沿,同步脉冲和后沿作为同步信号,以在水平同步信号中将旧行与新行分开,并在垂直同步信号中将旧帧与新帧分开。


VGA信号时序图


我们使用了标准的VGA信号接口(640 x 480 @ 60 MHz)。 此处描述了信号的所有标准规格。


测试中


在将所有内容放在一起并测试实时系统之前。 我们首先必须分别测试每个部分。 首先,我们通过显示某些像素值来检查来自摄像机的值和信号。 然后,借助使用Python编程语言的OpenCV,我们能够在多个图像上应用Sobel过滤器,以将结果与我们的算法进行比较并检查逻辑的正确性。 此外,在应用Sobel运算符和阈值设置之后,我们通过在VGA监视器上显示几个静态图像来测试了缓冲区和VGA驱动程序。 此外,通过更改阈值,会影响图像的准确性。


我们使用的python代码:


# This code is made to test the accuracy of our algorithm on FPGA import cv2 #import opencv library f = open("sample.txt",'w') # Open file to write on it the static image initialization lines img = cv2.imread('us.jpg') # Read the image which has our faces and its size 150x150 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #convert to grayscale sobelx = cv2.Sobel(gray,cv2.CV_64F,1,0,ksize=3) #x-axis sobel operator sobely = cv2.Sobel(gray,cv2.CV_64F,0,1,ksize=3) #y-axis sobel operator abs_grad_x = cv2.convertScaleAbs(sobelx) abs_grad_y = cv2.convertScaleAbs(sobely) grad = abs_grad_x + abs_grad_y for i in range(0,150): for x in range(0,150): #read the pixels of the grayscaled image and Store them into file with specific format to initialize the buffer in FPGA code f.write("data_a[{:d}]<=8'd{:d};\n".format(i*150+x,gray[i][x])) #apply threshold to be exactly like the code on FPGA if(grad[i][x] < 100): grad[i][x] = 255 else: grad[i][x] = 0 cv2.imshow("rgb", img) #Show the real img cv2.imshow("gray",gray) #Show the grayscale img cv2.imshow("sobel",grad)#Show the result img cv2.waitKey(0) #Stop the img to see it 

结果


实施的结果是,我们获得了一个实时边缘检测系统,该系统在应用了灰度滤镜和Sobel运算符后会生成150x150的图像。 实施的系统提供30 fps。 该摄像机的时钟频率为25MHz,通常情况下,系统可以满足实时期限,而不会出现明显的延迟。 此外,阈值会影响最终图像中的细节量和噪点。


这是FPGA上的Sobel运算符与OpenCV sobel运算符之间的比较:


比较方式


以下是结果的说明性视频:


项目视频


是Github上存储库的链接,其中包含所有源代码。


未来的改进


当我们使用FPGA Cyclone IV时,我们受限于其存储容量和逻辑门的数量。 因此,作为未来的改进,我们可以使用外部存储源,也可以在另一块板上实现我们的工作,从而可以显示从摄像机接收到的图像中的所有像素。


此外,尽管Sobel运算符快速且易于实现,但对噪声非常敏感。 为了消除产生的噪声,我们可以使用噪声滤波器,例如非线性中值滤波器,如果我们有足够的内存来实现第三个缓冲区,则该滤波器可以很好地与我们的系统配合使用。 这将产生更平滑的图像,并且去除了锐利的特征。


因此,我们使用了只能产生3位图像的FPGA内置VGA接口。 因此,我们无法显示灰度图像,因为它需要显示8位。 结果,实现另一个接口或使用功能更强大的板将增强显示图像的灵活性。


结论


我们能够利用对状态机,计算并行性和硬件软件接口的嵌入式系统中关键概念的知识和理解,来创建满足我们目标的高效边缘检测应用程序。


致谢


该项目由一个由两个学生组成的团队构建: 侯赛因·尤尼斯Hussein Youness)汉尼 ·哈默德Hany Hamed) ,他们在俄罗斯Innopolis大学获得了计算机科学的第一年学士学位。


该项目是Innopolis大学 2018年秋季计算机体系结构课程的一部分。


Source: https://habr.com/ru/post/zh-CN431326/


All Articles