7.3.5 仿真与调试结果
1.Testbench设计
Testbench只需例化一个Sobel运算模块,将输入视频流直接带测试模块即可。代码如下:
/*sobel operation module*/
sobel_2d_ex #(local_dw,ksz_sobel,ih,iw)
sobel_2d_ex_ins(
.rst_n (reset_l),
.clk (cap_clk),
.din (sobel_ex_data_in),
.din_valid (sobel_ex_dvalid_in),
.dout_valid (sobel_ex_dvalid),
.vsync(sobel_ex_vsync_in),
.vsync_out(sobel_ex_vsync),
.dout (sobel_ex_data), //输出模值
.dout_a(sobel_data_a) //输出角度
);
assign sobel_ex_data_in = cap_data;
assign sobel_ex_vsync_in = cap_vsync;
assign sobel_ex_dvalid_in = cap_dvalid;
2.模块仿真结果 1)Cordic坐标转换模块 下面验证我们所设计的坐标转换系统的正确性。首先验证角度的 计算是否正确,分别取如图7-35所示的8个子象限16个测试点的坐标进 行测试验证。
restart
force -freeze sim:/img_process_tb/reset_l 0 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_valid 1 0
run 500 ns
force -freeze sim:/img_process_tb/reset_l 1 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x 70 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y 10 0
run 20 ns
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x 10 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y 70 0
run 20 ns
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x -10 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y 70 0
run 20 ns
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x -70 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y 10 0
run 20 nsforce -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x -70 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y -10 0
run 20 ns
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x -10 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y -70 0
run 20 ns
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x 10 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y -70 0
run 20 ns
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_x 70 0
force -freeze
sim:/img_process_tb/cordic_operation/r2p_ins/din_y -10 0
run 4000 ns
摘取仿真结果如图7-36所示。
由此可见,计算出来的角度值基本正确,只是有一定的误差。如 果读者多做些测试就可以发现,输入坐标值越小,误差越大,最大的 误差在0.14°左右。因此,建议在使用时读者可根据实际误差需求, 在位宽有余量的情况下尽量多保留小数位。关于误差的分析及补偿, 有兴趣的读者可以参考相关的文献进行分析和补偿。 由仿真图还可以得到的一个信息是模块的计算延迟,由前面的分 析,预处理2个时钟延时,迭代器15-1个时钟延时,后期处理3个延 时,整个计算开销是19个延时,仿真结果与预期相同。 接下来我们将验证模值计算的正确性。由模值计算公式
可见,计算基本正确,只是稍有误差。并且随着输入数据绝对值 的增大,误差逐渐减小。模值误差一方面是由于采用了近似计算所带 来的误差,一方面是原理上的误差,采用更多的位数来近似和更多的 迭代次数,较大数值计算等手段无疑能够减小计算误差。如同角度分 析一样,详细的误差我们不再细究,读者可自行研究。 2)Sobel模板计算电路验证 对此模块的验证需要和VC等外部工具联合进行调试并分析,以图 7-4为例,用VC和FPGA分别对其进行Sobel运算,对于X方向的运算结果Sobel_Result_X,VC和Moedelsim的仿真调试结果分别如图7-38和图7- 39所示(第一个有效数据行)。 仔细对比就能验证,X方向的计算结果Sobel_Result_X完全正确。 在这里不再验证Y方向的正确性。其实,我们注意到在仿真图中,开始 行的前几十个Sobel_Result_Y运算结果为0。我们对这个结果并不感到 诧异,这是由于图像左上角的前几列像素值是相同的。
同时,我们读出FPGA的输出数据,给出由FPGA计算出的Sobel结 果,如图7-40所示。这与我们预期的结果是一致的。需要注意的是, 图中并不包含相位信息。
第8章 非线性滤波器
非线性滤波器在通常情况下没有特定的转移函数。一类比较重要 的非线性滤波就是统计排序滤波器。本章将重点介绍统计排序滤波的 原理及其FPGA实现。
8.1 统计排序滤波
统计排序滤波器对窗口内的像素值进行排序并通过多路选择器选 择使用排序后的值,例如中值滤波、最大/最小值滤波等。 排序滤波器或者其组合,可以在很多图像处理的场合得到应用。 用接近中间位置的排序值作为输出,进行图像的平滑滤波,能得到很 好的噪声平滑性质。中值滤波对去除椒盐噪声十分有用,而形态学滤 波中主要用到的算子就是最大/最小值滤波。 下面,我们给出统计排序滤波的数学定义。不妨设r为处理窗口的 半径,设I(x,y)为输入像素值,g(x,y)为输出像素值,则有如下定 义:
上式中,Sort算子代表对i和j的有效区域进行排序运算,同时输 出排序结果的第n个值。 由数学定义不难看出,排序滤波器主要完成对图像当前窗口内的 所有像素进行排序,同时按指定输出排序结果。 若令n=(2r+1)2 /2,则上式则变为中值滤波器,若排序结果按升 序排列,n=0,则为最小值滤波器。同样,若n=(2r+1)2 -1,则为最 大值滤波器。 常用的排序算子有冒泡排序、希尔排序及简单排序等。以冒泡排 序为例,C语言处理算法如下:
#define SORT_RADIUS 2 /*处理半径*/
#define SORT_NUM_ALL ((SORT_RADIUS*2+1)*(SORT_RADIUS*2 +
1))
/*窗口数据数目*/
#define SORT_OUT_NUM (SORT_NUM_ALL/2)/*取中值作为排序输出
*/
BYTE * m_pTemp = new BYTE[m_dwHeight*m_dwWidth];
BYTE SortTemp[SORT_NUM_ALL];
int i,j,m,k = 0;
memset(m_pTemp,0,m_dwHeight*m_dwWidth);
memset(SortTemp,0,SORT_NUM_ALL);
int cnt = 0;
for (i = SORT_RADIUS; i< m_dwHeight - SORT_RADIUS; i++)
for (j = SORT_RADIUS; j < m_dwWidth - SORT_RADIUS; j++)
{
cnt = 0;
/*首先得到窗口缓存*/for (m = -SORT_RADIUS; m <= SORT_RADIUS; m++)
for (k = -SORT_RADIUS; k <= SORT_RADIUS; k++)
{
SortTemp[cnt++]= m_pBitmap[(i + m)*m_dwWidth + j +
k];
}
/*对窗口缓存进行冒泡排序*/
for (int i0 = 0; i0 < SORT_NUM_ALL; i0++)
for (int j0 = 1; j0 < SORT_NUM_ALL - i0; j0++)
if (SortTemp[j0 - 1]> SortTemp[j0])
{
int temp = SortTemp[j0];
SortTemp[j0]= SortTemp[j0 - 1];
SortTemp[j0 - 1]= temp;
}
/*取指定顺序的排序值作为输出*/
m_pTemp[i*m_dwWidth + j]= SortTemp[SORT_OUT_NUM];
}
对 图8-1(a)的有椒盐噪声的图像进行如上5×5的排序滤波,取 中值输出,处理结果如图8-1(b)所示。可见,中值排序滤波很好地 消除了椒盐噪声。
可以预见的是,我们只需改变上述代码中的宏SORT_OUT_NUM,即 可得到不同的输出结果。取最小值作为输出,即可得到最小值滤波 器,取最大值作为输出,即可得到最大值滤波器。最大值和最小值滤 波器的效果分别如图8-2和图8-3所示。
最大值和最小值滤波器主要用在形态学操作中,我们将在第10章 中详细介绍形态学操作。
8.2 基于FPGA的统计排序滤波器
8.2.1 并行全比较排序法原理
在进行FPGA映射之前,必须首先确定排序算法。由于在FPGA的图 像处理领域,中值滤波的处理窗口不会太大,消耗的资源也不会太 大,因此,在选择排序方法时优先考虑时间开销比较小的算法,在本 书中采用并行全比较排序算法。 并行全比较排序法采用并行结构,其基本原理是在同一时刻完成 所有数据与其他数据的比较结果。因此其时间复杂度最小,仅需一个 时钟即可完成排序工作,很适合FPGA流水线处理,但是也带来了相对 较大的资源消耗。正如前面所提到的,在处理窗口比较小的情况下, 这个不是我们主要考虑的问题。首 先 将 详 细 介 绍 并 行 比 较 的 原 理 。 假 定 现 在 要 对 n 个 数 据 d0,d1,d2,…,dn进行排序。那么进行并行排序的步骤如下。 (1)同时得到这n个数,即对这n个数目对齐。 (2)同时将这n个数分别与其他数做比较,并记录比较结果。不 妨规定:若当前数大于其他数,则将结果记为1;若当前数小于等于其 他数,则将结果记为0。 (3)计算每个数第(2)步的所有结果之和:由于一共是n个数 目,因此,必然有n-1个比较结果。 (4)第(3)步结果的值即为排序结果。 举个实际的例子可能比较直观,假定要对以下5个数进行排序: 14,45,32,9,43 假定现在已经完成了时序对齐,也就是同时得到了这5个数,接下 来的某个时钟内,我们要同时完成这5个数分别与其他四个数的比较结 果并记录比较结果,即当前结果大于其他值为1,否则为0。比较结果 如表8-1所示。
表8-1中,×表示无关项,因为跟自身比较是无意义的,也可以记 这个值为0。由上表可见,经过一个时钟的比较之后,就得到了各个数 据与其他数据的次序信息,可以在下个时钟通过加法运算计算出每个 数据在所有数据中的排序信息,如表8-1最后一行所示。由于上表中的 行列一致性,这个时候只需要2×(n-1)个比较器即可实现全比较。一个值得注意的问题是,如果中间有两个数或多个数相等该怎么 处理?不妨看下面的待排序序列: 91,23,91,5,8 同样按照上面的排序规则进行排序,如表8-2所示。
在最后一行的排序结果中,相同数目的2个数得到的总和一致,这 是由于这两个数目是“各向同性”的。这样的计数方式显然是有问题 的,试想,如果5个数完全一致,那么最后一行的数据必然全为0。这 个情况下,只能得到一个最大值,而没法得到中值或者其他序列的输 出。 因此,对于相同的数值,必须找出其“异性点”进行区别对待。 一个明显的“异性”便是各个数值的输入次序。做如下规定: (1)当前数目大于本数据之前输入数据时,结果记为1,小于或 等于时记为0。 (2)当前数目大于等于本数据之后输入数据时,结果记为1,小 于时记为0。 再用此规则对上述几个数目进行重新排序,如表8-3所示。 表8-3 利用新规则进行重新排序结果
可见,后面输入的数据由于“输入优先级”较小,因此排序结果 被“降了”一档,也刚好达到了我们的目的。 需要注意的是,重新排序后的资源消耗问题,这个时候行列出现 了不一致性。因为大于和大于等于是不同的逻辑。以3个数据d1 ,d2 ,d3 的排序为例,要完成的比较如下: d1 ≥d2 d1 ≥d3 d2 >d1 d2 ≥d3 d3 >d1 d3 >d2 因此,除非设计单独的等号判别电路,每次比较都是不重叠的, 这样下来,需要的比较器数目为n(n-1)个,以1个比较器占用5个逻辑 资源来算,整个比较器的资源消耗为5n(n-1)个,此外还需要n-1个比 较结果寄存器以及n-1个加法器来实现比较结果相加。 假定处理窗口为5×5,则比较器一共消耗的逻辑单元为 5×5×4=100 个 整个资源消耗在我们的接收范围之内,但是,如果加大处理窗 口,例如31×31的处理窗口,则比较器资源消耗为 5×31×30=4650个资源消耗已经非常可观了,因此在设计时需综合考虑。