2)闭运算
作为开运算的逆运算——闭运算(Closing Operation)其实就是先膨胀后腐蚀的过程。其数学表达式如下:
根据闭运算的数学定义,我们给出闭运算的C代码如下:
/*边界不作处理*/
void Morph_Close(BYTE *p_InputImg ,BYTE *p_CloseResult)
/* 输 入 图 像 存 放 在 p_InputImg 中 , 处 理 结 果 图 像 存 放 在
p_CloseResult 中*/
{
BYTE *p_Dialate = NULL;
p_Dialate = (BYTE *)malloc(m_dwWidth*m_dwHeight);/*申请
中间结果内存*/
Morph_Dialate(p_InputImg ,p_Dialate); /*先进行膨胀操作
*/
Morph_Erode(p_Dialate,p_CloseResult); /*再进行腐蚀操作
*/
free(p_Dialate); /*释放中间结果内存*/
}
闭 运算同样可以使轮廓线变得更光滑,但是与开运算相反的是, 它通常能够消弭狭窄的间断和长细的鸿沟,消除细小的孔洞,并填补 轮廓线中的断裂。因此,在排除小型黑洞的场合经常用到闭运算。 对图9-8进行尺寸为5×5的矩形闭运算,结果如图9-10所示。 3.Tophat滤波 Tophat变换实际上原图像与“开运算”的结果图之差。算法的数 学表达式如下:
根据Tophat滤波的数学定义,我们给出Tophat滤波的C代码如下:
/*边界不作处理*/
void Morph_Tophat(BYTE *p_InputImg ,BYTE *p_TophatResult)
/* 输 入 图 像 存 放 在 p_InputImg 中 , 处 理 结 果 图 像 存 放 在
p_TophatResult 中*/
{
BYTE *p_Open = NULL;
int i,j = 0;
p_Open = (BYTE *)malloc(m_dwWidth*m_dwHeight);/*申请中
间结果内存*/
Morph_Open(p_InputImg ,p_Open); /*先进行开操作*/
for(i= MorphRadius;i< m_dwHeight- MorphRadius;i++)
for(j= MorphRadius;i< m_dwWidth- MorphRadius;j++)
p_TophatResult[i*m_dwWidth+j]=
(p_InputImg[i*m_dwWidth+j]>p_Open[i*m_dwWidth+j
])?
(p_InputImg[i*m_dwWidth+j]-
p_Open[i*m_dwWidth+j]):0;
free(p_Open); /*释放中间结果内存*/
}
因 为开运算带来的结果是放大了裂缝或者局部低亮度的区域。从 原图中减去开运算后的图像,得出的效果图突出了比原图轮廓周围的 区域更明亮的区域。因此,Tophat运算往往用来分离比临近点亮一点 的斑块。对图9-8进行处理窗口尺寸为5×5的Tophat滤波,结果如图9-11所 示。
从处理结果图可以明显看出,图9-8中左上角和右下角的两个小尺 寸目标及飞机尾部经Tophat滤波后的灰度值明显比周围像素高。经过 下一步的分割处理,即可把目标分离出来。因此,Tophat滤波算法通 常应用在红外小目标识别场合。
9.3 基于FPGA的Tophat滤波设计
形态学操作具有天然的并行结构,因此非常适合于用FPGA来实 现。在本节中,将详细介绍9.2节中所介绍的形态学中的矩形窗Tophat 滤波的FPGA式(9-9)实现。
9.3.1 顶层框架设计
1.系统框架设计 Tophat变换没有反馈环节,流水线设计无疑是最好的设计方式。 采用自顶向下的设计方式,系统顶层为原图输入数据流与原图的开运 算之差。由于开运算需要一定时间的而延迟,原图需要经过行列延迟 电路与开运算进行对齐。系统顶层设计框图如图9-12所示。
开运算为先腐蚀后膨胀的过程,将原图输入流依次流过腐蚀运算 流水线和膨胀运算流水线即可。开运算的设计框图如图9-13所示。
腐蚀与膨胀运算可以理解为在形态学处理核(本例中为n×n的矩 形窗)范围内寻找像素灰度值的最小值或最大值。将处理核从图像左 上角遍历到图像右下角即完成运算。基于FPGA的并行处理特点,我们 应首先计算行方向的腐蚀或者膨胀操作。将处理结果经过行列延时电 路与处理结果再进行列方向上的腐蚀与膨胀操作即可。由于腐蚀和膨胀操作只有少许差别,因此在设计时,将一维的腐 蚀和膨胀操作划分为一个模块,通过入口参数进行腐蚀或膨胀选择。 二维腐蚀与膨胀操作的设计框图如图9-14所示。
一维膨胀与腐蚀模块完成输入数据流的连续几个时钟数据的比较,完成流水输出。设计框图如图9-15所示。
2.算法实时性估计 由于系统采用流水线架构进行设计,在顶层设计中,从输入流水 线有效到减法器输出的时刻即为算法的实时性开销。 Tophat运算的主要延时开销是开运算,开运算包括两个级联的运 算:腐蚀操作和膨胀操作,我们不妨设定Tophat处理的处理半径为r。 腐蚀操作需要等到输入数据流的前(2×r-1)行输入之后才能有有 效输出,需要注意的是,腐蚀的输出有r行的边界未处理像素。实际有效输出则延时输入数据流r行。 同样,接下来的膨胀操作实际有效输出也延时了r行。因此,输出 的数据有效比输入的数据有效延时了3×r行。 需要注意的是,这里的“有效数据”不包括边界。我们将在仿真 和调试章节来验证这个推论。
9.3.2 子模块设计
本节将介绍Tophat滤波电路的各个子模块设计。在子模块的设计 中我们往往采用自底向上的设计原则。根据上一节所设计的整体框架 设计及模块划分,我们需设计以下子模块:
(1)比较子模块。
(2)一维形态学腐蚀/膨胀子模块。
(3)二维形态学腐蚀/膨胀子模块。
(4)二维形态学开运算子模块。
(5)二维形态学Tophat运算模块。
以下将详细介绍这些模块的设计及实现。 1.比较子模块(MinMax)设计 考虑到代码维护性和移植性,将基本比较单元设计为单独的子模 块。这个子模块需实现以下功能: (1)输出两个数据的较大值。 (2)输出两个数据的较小值。 考虑到系统鲁棒性,将比较结果打一拍之后输出。根据设计需 求,本模块需要一个比较器、两个mux和两个reg。模块的设计结构如 图9-16所示。
很明显,本模块开销为1个时钟。
设在某时刻(即为时刻t)连续10个输入数据流为
din_a = 3,8,5,9,7,1,2,6,0,4,x;
din_b = x,3,8,5,9,7,1,2,6,0,4;
则din_a,din_b,min_tmp,max_tmp,dout_max,dout_min连续10个时钟的数据如表9-1所示(按膨胀操作,×表示无关项)。
2.一维形态学腐蚀/膨胀子模块(Morph_1D)设计 前文也已经提到,对于图像处理而言,是纵向和横向2个维度的处 理。我们知道,对于任何二维的操作,都可以分解为一维方向的操作 来简化设计。在图像处理中,我们习惯上是首先进行横向处理,然后 再纵向处理。所谓横向处理就是对每一行进行处理。对于尺寸为n×n 的处理窗口,可以采用一个1×n的窗体从图像第一行第一列开始,自 左向右滑动,依次取出窗口内的n个像素灰度值,比较得到灰度最小值 或者最大值并按顺序存储。第一行做完后,再在第二行滑动,直至图 像的最后一行的最后三个像素。存储后的图像即为腐蚀或膨胀的结果 图像。 我们已经有了基本的比较单元(MinMax),在基本的比较单元的 基础上,针对某一行,需要计算出指定宽度内像素的最大值或最小 值。由于是计算连续几个像素的最值,采用流水线方式来实现是再也 简单不过的了。 那么,行方向指定宽度的最值计算电路如何设计?这个指定宽度 即形态学处理的窗口尺寸。一般情况下,这个尺寸为奇数。按照软件 设计的思想,依次两两比较,最后比较输出最后的结果。 而对于FPGA来说,则要充分利用FPGA的并行特性,在同一时刻尽 量完成多个比较工作,以提高效率。 以处理窗口尺寸为5来说明,要完成5个数据的比较,可以在一个 时钟完成两对数据的比较,第二个时钟完成上述比较结果的比较,第 三个时钟完成与最后一个数据的比较。整个电路的时间开销为比较的 次数,即窗口尺寸/2+1,资源上需要4个比较器来实现5个数据的比 较,如图9-17所示。 上述是在我们能同时得到5个待比较数据的情况下的范例。实际情 况下,在流水线处理过程中,要得到前5个数据,至少需要4个时钟,也就是a,b,c,d,e这5个数据不能同时得到。这时若要得到下一个数 据,就必须等到下一个时钟。 由于比较单元MinMax正好为一个时钟的开销,因此,将输入数据 打一拍后与输入数据作为第一级比较,再将当前级的比较器输出与输 入数据送入下一个比较器,直到5个数据比较完毕数据输出,如图9-18 所示。
图9-18同样可以扩展到处理长度为1×n的情况下。由图中可以看 出,要完成1×n的腐蚀与膨胀操作,需要的基本资源为1个Reg,n-1个 MinMax子模块。基本的运算时钟为(n-1)个时钟。
看到这里,读者可能有点迷糊:图中9-18的是腐蚀操作还是膨胀 操作?由MinMax模块的设计框图可知道,此模块同时输出两个数的较 大值和较小值。因此,将MinMax模块输出的较小值接入下一级比较模 块的输入,所得到的就是腐蚀操作。否则,是膨胀操作。具体判断如 下: 取MinMax(i)=dout_max(i),为膨胀操作。 取MinMax(i)=dout_min(i),为腐蚀操作。在代码设计中,采用模块入口参数宏定义来确定当前模块是腐蚀 还是膨胀操作。这样可以提高模块的复用性,减小设计复杂度。 下面以一组数据实例来以上电路的工作流程。 设 在 某 时 刻 ( 即 为 时 刻 t ) 连 续 10 个 输 入 数 据 流 为 : din=3,8,5,9,7,1,2,6,0,4; 则 din,reg_din,MinMax(0),MinMax(1),MinMax(2),MinMax(3),dout 连 续 10个时钟的数据如表9-2所示(按膨胀操作,×表示无关项)。
由表中可以看出,输入寄存器和4个MinMax模块带来了5个时钟的 计算开销,即5个时钟的流水线潜伏期。输出数据与MinMax(3)对齐。 3.二维形态学腐蚀/膨胀子模块(Morph_2D)设计 先来整理一下我们现在拥有了哪些模块:输入流水线,一维运算 算子(Morph_1D)。既然已经拥有了一维算子,将每一行的输入数据 流流过一维算子,得到的结果即为每一行的一维运算结果。按照二维 扩展的思路,将每一行的一维算子的计算结果对齐在列方向上再进行 一维运算,得到的结果既是二维运算结果。 如何进行行列对齐?这个问题在4.3节里已经详细讨论过,在这里 不再详述,直接调用现成的行缓存电路(line_buffer)和通用的行列 计数及对齐电路即可。按照上述分析我们是不是可以例化两个一维算子进行二维运算? 答案是否定的。这是由于一维算子是基于数据流的,而列方向上的运 算则是“非实时”的。但是我们对一维运算已经了如指掌,再进行列 方向扩展也是轻而易举的事情。 下面我们给出本章中的二维运算(窗口尺寸为5×5)的结构,如 图9-19所示。
上面的结构图已经表达地很明白了,但是可能有一部分读者对数 据流走向和结构有点难以理解。下面将会详细解释2个要点: 设当前流水线所处行为第i行(不考虑边界)。 (1)MinMax(0)为当前行的一维运算数据流。(2)line_buffer(0)中数据为第(i-1)行的一维运算数据流 (3)line_buffer(i)中数据为第(i-1)行的一维运算数据流与第i 行的一维运算数据流的比较结果(i>0)。 4.二维形态学开运算子模块(Morph_Open_2D)设计 二维的开运算模块设计相对来说是比较简单的,只需将数据流先 经过二维腐蚀运算处理,再将输出结果输入二维膨胀运算模块输出即 可,设计结构如图9-20所示。
5.二维形态学Tophat变换模块(Morph_Tophat_2D)设计 二维的Tophat滤波模块设计需要等待原图等待开运算结束,结束 后将原图与开运算结果相减并输出。设计结构如图9-21所示。
9.3.3 Verilog代码设计
1.比较子模块(MinMax)
本模块的设计比较简单,需要1个比较器、2个选择器和2个寄存器即可。入口参数为数据宽度DW。
模块定义如下:
module MinMax(
clk, //同步时钟输入
valid, //输入数据有效
din_a, //输入数据a
din_b, //输入数据bdout_min, //输出较小值
dout_max //输出较大值
);
parameter DW = 14; //输入数据位宽
parameter USE_reg = 1; //是否将数据缓存一拍
input clk;
input valid;
input [DW-1:0] din_a;
input [DW-1:0] din_b;
output [DW-1:0]dout_min;
output [DW-1:0]dout_max;
关键代码如下:
//获得比较符号
assign a_g_b = ((din_a > din_b)) ? 1'b1 :
1'b0;
//得到较小值
assign min_tmp = ((a_g_b == 1'b1)) ? din_b :
din_a;
//得到较大值
assign max_tmp = ((a_g_b == 1'b1)) ? din_a :
din_b;
//先将数据打一拍
generate
if (USE_reg == 1)
begin : MAP0
always @(posedge clk)
beginif (valid == 1'b1)
begin
min_reg <= #1 min_tmp;
max_reg <= #1 max_tmp;
end
end
assign dout_min = min_reg;
assign dout_max = max_reg;
end
endgenerate
//直接输出比较值
generate
if ((~(USE_reg == 1)))
begin : MAP1
assign dout_min = min_tmp;
assign dout_max = max_tmp;
end
endgenerate