例说FPGA连载70:AV视频采集之去隔行处理实现
特权同学,版权所有
配套例程和更多资料下载链接:
http://pan.baidu.com/s/1c0nf6Qc
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg
该模块的功能框图如图12.37所示。由dbcheck_ctrl.v模块产生的视频流在该模块内先是通过FIFO缓存,同时进行时钟域变化,输出持续的1280字节的一行数据;接着进入移位寄存器缓存一行的视频流;最后通过插值算法产生插值行的数据;最终,原始行和插值行一共输出的完整视频流将送到后续模块继续处理。
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg
图12.37 去隔行插值模块功能框图
关于这里的“去隔行处理”,可能有些人还是“云里雾里”,不知道为什么需要“去隔行”。没关系,我们先来普及一下基本概念。
首先来阐明两个基本概念:隔行扫描和逐行扫描。
隔行扫描指显示屏在显示一幅图像时,先扫描奇数行,全部完成奇数行扫描后再扫描偶数行,因此每幅图像需扫描两次才能完成,造成图像显示画面闪烁较大。隔行扫描就是每一帧被分割为两场,每一场包含了一帧中所有的奇数扫描行或者偶数扫描行,通常是先扫描奇数行得到第一场,然后扫描偶数行得到第二场。我们前面给出的ITU656视频格式里面包含的奇场和偶场视频流,其示意如图12.38所示。
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg
图12.38 ITU656奇偶行格式
每一帧图像由电子束顺序地一行接着一行连续扫描而成,这种扫描方式称为逐行扫描。
隔行扫描最大的问题是容易出现行间闪烁、并行现象或垂直边缘锯齿效应,影响人眼观看的视觉效果。在开发过程中,笔者也遇到了一些采集的PAL隔行扫描还原时的问题。在摄像头采集的图像静止不动的时候,可以说画面蛮清晰漂亮的,但是画面中的物品如果频繁的运动,如手在镜头前晃动,其轨迹就会有明显的边缘锯齿感,静止时如图12.39右侧很正常的清晰,而一旦运动时就如图12.39左侧的锯齿效果。
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image008.jpg
图12.39 去隔行前后比对
要解决这个问题,其实也不难,问题就是出在隔行扫描的帧频率太慢,奇场和偶场加起来每秒才25Hz,而且奇场偶场的数据是在分别不同的时间进行采集的。因此,当物体运动时在视觉效果上看起来就会有锯齿感。
那么,解决的办法有一个:去隔行处理。所谓去隔行,就是用单独的一个奇场还原奇场偶场都有的完整图像,单独的一个偶场也还原奇场偶场都有的完整图像,这样在每秒看到的就是50Hz的图像,从人眼视觉敏感度上来说,就根本很难感觉到锯齿感了。说得难听点,去隔行就是想办法欺骗人的眼睛。
在xilinx的 《xapp294_04.pdf》中提到的4:2:2到4:4:4转换的算法有三种:其一如Equation1所示,简单用最近的两个数据的算术平均来重构图像;其二和其三分别如Equation2和Equation3所示,称之为并行FIR滤波方法。
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image010.gif
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image012.gif
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image014.gif
公式2、3涉及FIR滤波,显然实现起来需要付出比较大的资源和面积(换句话说,也就是money),但是效果肯定更佳。而公式1相对简单实现,我们对最后的图像还原效果要求也不算太高,选择了公式1来实现。而最终实现的效果也还是可以,足够欺骗群众的眼睛了。
该模块中,插值行运算有以下代码实现。
//插值行数据输出
always @(posedge clk or negedge rst_n)
if(!rst_n) sfdoutbr <= 8'd0;
else if(kf_db[0] || sfout_db[0])
sfdoutbr <={1'b0,kf_db[7:1]}+{1'b0,sfout_db[7:1]}+1'b1; //末尾进位
else sfdoutbr <={1'b0,kf_db[7:1]}+{1'b0,sfout_db[7:1]}; //末尾无进位
assign sfdoutb = sfdoutbr;
这里的运算实际上直接将插值行上、下行对应位置的数据求和取平均值(通过右移一位实现)了。