您的位置:首页 > 其它

基于Zedboard的视频处理系统(可缩放显示)

2016-12-21 19:45 381 查看
最近在Zedboard上实现了一个简单的视频视频处理系统,可以将摄像头采集进来的图像进行1~8倍的放大显示。在这里讲一下我做这个系统的过程和思路,还有一些我觉得容易忽略的细节问题,和大家一起交流探讨,也欢迎大家指出我的设计中不好的地方,能提出更好的方案就更好了。整个系统如下图所示:



系统设计我用的是XilinxVivado,系统中用到的Video Scaler、VDMA和Data Converter等IP都可以使用Xilinx提供的(注意自Vivado 2016.3起Xilinx不再提供Video Scaler IP,只能使用Video Processing Subsystem IP)。Video Scaler不是免费使用的,不过可以到Xilinx官网申请Hardware EvaluationLicense,一次可以免费使用120天。

从左到右简单介绍一下,摄像头我用的是OV7725,这种摄像头在智能车竞赛中常用,分辨率不高,最大只有640x480,但简单易用,采用CMOS接口,可以直接接到开发板上,有多种数据输出格式可选,网上资料也比较多了。摄像头的寄存器需要用SCCB协议配置,这里推荐用软件实现协议(我就是这么做的),因为寄存器的配置经常要修改以在不同环境下达到满意的显示效果。CPU要与开发板外部接口交互,需要经过一个GPIO的IP,这比较简单。我用板上按键的组合来实现选择输出模式,所以还需要让GPIO模块与板上按键的接口相连,并要enable
interrupt,然后接到ZYNQ7上的PL-PS Interrupt Ports,这种外部中断的教程网上也很多,就不多说了。

往右边的模块是摄像头驱动,其实这里说摄像头驱动并不完全合适,这个模块的功能是提供给摄像头时钟、接收摄像头数据以及往后传递接收到数据。接收数据要根据OV7725产生数据的时序来,如下两幅图所示:





从上面两张图可以看出,摄像头产生数据每一帧为784x510 pixels,VSYNC为场同步信号,HREF可看作行同步信号(我们也可以让摄像头产生真正的行同步信号HSYNC)。HREF高电平期间摄像头输出可见光数据,但每次只输出8位(或10位),完整输出一个点的数据需要两个PCLK,PCLK为摄像头内部时钟,其与摄像头的外部输入时钟XCLK的关系可通过寄存器的配置来确定。由于最后HDMI输出的数据格式YUV422,这里我们可以让摄像头产生数据的格式就是YUV422。接下来就看个人发挥了,可以让数据产生后立即往后送(当然这样就不能直接给VDMA,而需要加一个data
converter模块了),也可以在这个模块中设置一个BRAM作为行缓存,然后将行缓存的数据往后送。简便起见,后面的Video Scaler、VDMA和Data Converter都是用的Xilinx提供的,它们的数据通路都固定是AXI-Stream接口,所以我们也要自己摄像头驱动模块顶层设计一个AXI-Stream的Master接口,设计时参考Xilinx提供的AXI-Stream接口的标准样板,看懂之后改一改就可以使用了,相比AXI-Full和Lite接口,这个还是比较简单的。其实对于摄像头来说,主要的问题是对于不同的输入时钟、帧率和外部环境,摄像头寄存器要进行不同的配置(比如要调整曝光时间)才能得到比较清晰稳定的图像。OV7725有一百多个寄存器,而其datasheet对每个寄存器功能的描述都是非常简略的,只能自己结合《OV7725
Camera Module Software Application Note》这份文档进行摸索、尝试了。

由于我在摄像头驱动模块使用了32 bits的BRAM行缓存,所以我就用AXI-Stream接口直接把数据从BRAM中送到VDMA1。VDMA全称是VideoDirect Memory Access,顾名思义,它的作用是做一个PL与DDR或者arm芯片内部ram的接口。我们又知道,PS本身就可以通过AXI-Full或者Lite接口直接与PL交互,所以更详细地说,VDMA提供的是AXI-Full和AXI-Stream之间的接口,它可以接收AXI-Stream的数据流,并通过AXI-Full写到内存中,也可以通过AXI-Full从内存中读取数据,再通过AXI-Stream数据流输出,其利用的是FIFO的原理。VDMA还有一个好处是可以轻松实现帧缓存(最多实现32个帧存),所谓乒乓缓存就是2个帧存,这里我们一般是采用3个帧存。当然要正常使用VDMA,有很多寄存器需要通过PS配置,同时也要选择正确的同步机制,同步机制也要和信号流相配合。我在设计时卡的比较久是因为没有注意tuser信号,当VDMA写通道帧同步选项设置为s2mm
tuser时,其要求tuser信号要和每一帧的第一个数据同步(这个和Video Scaler IP的要求是相同的),其读通道的输出无论我们选择哪一种帧同步模式,tuser信号也是这个特点。但比较坑的是VDMA的production guide中并没有指出这个帧同步信号的要求,对于像我这种新手来说,就会误以为其要求和s2mm fsync模式(这种模式要求倒是在pg中写清楚了)差不多,这样就错得很远了。

接下来再说Video Scaler这个IP,看了它的pg之后我们知道一般情况下我们可以直接将摄像头的数据输给它的,也就是说摄像头驱动模块可以直接接Video Scaler进行缩放,为什么中间还要先经过一个VDMA,实现帧缓存呢?我的考虑是这样可以进行数据在缩放前的前处理(比如帧间滤波等),也可以提高整个系统的通用性,当然这个大家可以根据自己的实际需要进行设计。Video Scaler两端的数据流通路都是AXI-Stream接口,且其数据通路固定为16 bits,不可调,PS可以通过AXI-Lite接口对其内部寄存器进行读写。此外,Video
Scaler是一个付费的IP,所以其内部程序设计不可见,这在一定程度增加了调试的难度。我们可以从它的production guide中查到其实现scale功能的算法原理,以及一些寄存器的功能等。AXI-Stream接口自不必说,只需注意其帧同步特点对tuser信号的要求。还有一个问题是这个IP对YUV422数据输入格式的要求,这个在它的pg中没有明说,但我在尝试之后发现只有按照Y0U0,Y1V1,… 这样输入到Scaler才能得到正确的输出。我一开始是让摄像头以U0Y0,V1Y1,… 这样产生数据的,结果最后无论如何都得不到正确的输出。其它的一些问题比如Taps,Phases,Coefficients还有时钟问题等在pg中都写得比较清楚,这里就不多说了。

由于Video Scaler的输出不可控,所以不能将其输出直接送给HDMI驱动模块,必须先进行帧缓存(还是利用VDMA,这个IP简单易用而且功能强大),但直接用VDMA IP其输入的数据宽度固定为32 bits,所以只好在Video Scaler和VDMA0中加一个Data Converter IP,将16 bits数据转换成32 bits数据
4000
,Data Converter基本无脑连就行,不过别忘了tuser这个帧同步信号。

VDMA0将Video Scaler的输出一帧一帧存到DDR中,HDMI驱动再经VDMA0从DDR中将数据再一帧一帧读出来,当然还是通过AXI-Stream接口。HDMI驱动除了读取数据并最终输出数据外,还要通过I2C配置板上的ADV7511芯片,ADV7511芯片才是真正实现HDMI接口的,我们在PL中设计HDMI驱动主要是输出时钟、帧同步信号、行同步信号和数据到ADV7511。ADV7511的配置,这个Xilinx也有相应教程。此外,如果显示屏支持多种分辨率,也可以在HDMI驱动模块做一个AXI-Lite
Slave接口,内部配置一些寄存器,然后我们就可以通过PS配置寄存器来改变HDMI输出的分辨率了。

最后说说关于软件编程,其实上面的系统设计好之后,在SDK上通过配置Video Scaler的寄存器,想怎么放大都行,用C(或C++)写一写,改一改就行了,还是比较easy的。最近一段时间都在做FPGA,还是比较怀念用STM32还有DSP编程的,编译一下只要几百毫秒,FPGA系统只要做一点改动然后Synthesis、Implementation一下稍微复杂一点的系统动辄半个多小时一个小时,简直是磨人!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息