数学形态学是一门建立在集论基础上的学科,是几何形态学分析和描述的有利工具。数学形态学的历史可回溯到19世纪。1964年法国的Matheron和Serra在积分几何的研究成果上,将数学形态学引入图像处理邻域,并研制了基于数学形态学的图像处理系统。1982年出版的专著Image Analysis and Mathematical Morphology是数学形态学发展的重要里程碑,表明数学形态学在理论上趋于完备及应用上不断深入。数学形态学蓬勃发展,由于其并行快速,易于硬件实现,已引起了人们的广泛关注。目前,数学形态学已在计算机视觉,信号处理与图像分析,模式识别,计算方法与数据处理等方面得到了极为广泛的应用。数学形态学可以用来解决抑制噪声,特征提取,边缘检测,图像分割,形状识别,纹理分析,图像恢复与重建,图像压缩等图像处理问题。
1. 数学形态学的定义
数学形态学是以形态结构元素为基础对图像进行分析的数学工具,它的基本思想是,用具有一定形态的结构元素度量和提取图像中的对应形状,以达到对图像分析和识别的目的。数学形态学的应用可以简化图像数据,保持它们基本的形状特征,并除去不相干的结构。数学形态学的基本运算有4个:膨胀,腐蚀,开运算和闭运算。它们在二值图像中和灰度图像中各有特点。基于这些基本运算还可以推导和组合成各种数学形态学实用算法。
2. 分类
1)二值形态学
数学形态学中二值图像的形态变换是一种针对集合的处理过程。其形态算子的实质是表达物体或形状的集合与结构元素间的相互作用,结构元素的形状就决定了这种运算所提取的信号的形状信息。形态学图像处理是在图像中移动一个结构元素,然后将结构元素与下面的二值图像进行交,并等集合运算。二值形态膨胀与腐蚀可转化为集合的逻辑运算,算法简单,适合于并行处理,且易于硬件实现,适合于对二值图像进行图像分割,细化,抽取骨架,边缘提取,形状分析。但是,在不同的应用场合,结构元素的选择及其相应的处理算法是不一样的,对不同的目标图像需设计不同的结构元素和不同的处理算法。结构元素的大小,形状选择合适与否,将直接影响图像的形态运算结构。因此,很多学者结合自己的实际应用,提出了一系列的改进算法。
2)灰度数学形态学
二值数学形态学可方便地推广到灰度图像空间。只是灰度数学形态学的运算对象不是集合而是图像函数。灰度数学形态学中开启和闭合运算的定义与在二值数学形态学中的定义一致。
3)模糊数学形态学
将模糊集合理论用于数学形态学就形成了模糊形态学。模糊算子的定义不同,相应的模糊形态运算的定义也不相同,模糊性由结构元素对原图像的适应程度来决定。模糊形态学是传统数学形态学从二值逻辑向模糊逻辑的推广,与传统数学形态学有相似的计算结果和相似的代数特性。模糊形态学重点研究n维空间目标物体的形状特征和形态变换,主要应用于图像处理领域,例如模糊增强,模糊边缘检测和模糊分割等。
1. 膨胀与腐蚀
膨胀与腐蚀是形态学滤波的两个基本运算,能实现多种多样的功能,主要功能如下:消除噪声,分割出独立的图像元素,在图像中连接相邻的元素,寻找图像中明显的极大值和极小值区域,求出图像的梯度。
膨胀(dilate)就是求局部最大值的操作。通过膨胀操作,会使图像中的高亮区域逐渐增大,也就是对图像中高亮的部分进行膨胀,类似于“领域扩张”。可以预见的是,效果图将会拥有比原图更大的高亮区域,亮度会有所增加,同时可以联通相邻的高亮度区域。通过膨胀我们可以将图像中的裂缝得到填补。
腐蚀(erode)和膨胀是一对对立的操作,因此,腐蚀就是求局部的最小值。通过腐蚀操作,会使图像中的高亮区域被腐蚀掉了,类似于“领域被蚕食”,可以预见的是腐蚀过后的图像将会拥有比原图更小的高亮区域,亮度会有所下降。同时,腐蚀操作还会连通相邻的比较暗的区域。
开运算(Opening Operation)其实就是先腐蚀后膨胀的过程。其数学表达式如下:
开运算一般使对象的轮廓变得光滑,断开狭窄的间断和消除细小的凸起物。因此,常用来消除小物体,在纤细点处分离物体。
闭运算(Closing Operation)是开运算的逆运算,其实就是先膨胀后腐蚀的过程。其数学表达式如下:
闭运算同样可以使轮廓线变得更光滑,但是与开运算相反的是,它通常能够消除狭窄的间断和长细的沟壑,消除细小的孔洞,并填补轮廓线中的断裂。因此,常用来排除小型黑洞的场合。
图 1
图1所示为最大/最小值滤波的电路结构图。其实统计排序滤波已经实现了最大/最小值滤波的电路,只需要前2级3输入比较就能得到最大/最小值的输出了。但是,这里我使用了均值滤波的电路结构,将均值滤波电路结构中的ADD_Tree模块转换成了Compare_Tree模块,这样Data_Capture模块采样进的有效数据不是累加后的值,而是经过比较后的最大/最小值。
图 2
图2所示为最大/最小值的Compare_Tree模块的电路结构,3x3大小的最大/最小值滤波与3x3大小的均值滤波相似,都是经过3拍开窗操作,不同的是,最大/最小值滤波开窗操作后的9个数据是进入到比较树(compare_tree),而均值滤波是进入到累加树(add_tree)。图1中输入信号mode是用决定比较单元cmp是用来执行最大值输出还是最小值输出的。
最大/最小值滤波器的具体实现如下所示:
module Compare3x3 #(
parameter LINE_NUM = 3,
parameter PIXEL_WIDTH = 14,
parameter KX_WIDTH =3,
parameter IMAGE_WIDTH = 128
)(clk,arstn,data_in,din_valid,data_out,dout_valid,mode);
function integer clogb2(input integer bit_depth);
begin
for(clogb2 = 0; bit_depth >0;clogb2 = clogb2 +1)
bit_depth = bit_depth >> 1;
end
endfunction
localparam CNT_WIDTH = clogb2(IMAGE_WIDTH-1);
localparam DATA_WIDTH = PIXEL_WIDTH*LINE_NUM;
input clk;
input arstn;
input [DATA_WIDTH-1:0]data_in;
input din_valid;
input mode;
output [PIXEL_WIDTH-1:0]data_out;
output dout_valid;
reg [PIXEL_WIDTH-1:0]k00_reg;
reg [PIXEL_WIDTH-1:0]k01_reg;
reg [PIXEL_WIDTH-1:0]k02_reg;
reg [PIXEL_WIDTH-1:0]k10_reg;
reg [PIXEL_WIDTH-1:0]k11_reg;
reg [PIXEL_WIDTH-1:0]k12_reg;
reg [PIXEL_WIDTH-1:0]k20_reg;
reg [PIXEL_WIDTH-1:0]k21_reg;
reg [PIXEL_WIDTH-1:0]k22_reg;
reg [CNT_WIDTH:0]cnt_reg;
reg [CNT_WIDTH:0]cnt;
always@(posedge clk or negedge arstn)
begin
if(~arstn)
begin
k00_reg <= 0;
k01_reg <= 0;
k02_reg <= 0;
end
else
begin
if(din_valid)
begin
k00_reg <= data_in[PIXEL_WIDTH-1:0];
k01_reg <= data_in[2*PIXEL_WIDTH-1:PIXEL_WIDTH];
k02_reg <= data_in[3*PIXEL_WIDTH-1:2*PIXEL_WIDTH];
end
end
end
always@(posedge clk or negedge arstn)
begin
if(~arstn)
begin
k10_reg <= 0;
k11_reg <= 0;
k12_reg <= 0;
end
else
begin
k10_reg <= k00_reg;
k11_reg <= k01_reg;
k12_reg <= k02_reg;
end
end
always@(posedge clk or negedge arstn)
begin
if(~arstn)
begin
k20_reg <= 0;
k21_reg <= 0;
k22_reg <= 0;
end
else
begin
k20_reg <= k10_reg;
k21_reg <= k11_reg;
k22_reg <= k12_reg;
end
end
/* The first compare pipe */
reg [PIXEL_WIDTH-1:0]comp_delay_00;
reg [PIXEL_WIDTH-1:0]comp_delay_01;
reg [PIXEL_WIDTH-1:0]comp_delay_02;
reg [PIXEL_WIDTH-1:0]comp_delay_03;
reg [PIXEL_WIDTH-1:0]comp_delay_04;
always@(posedge clk or negedge arstn)
begin
if(~arstn)
begin
comp_delay_00 <= 0;
comp_delay_01 <= 0;
comp_delay_02 <= 0;
comp_delay_03 <= 0;
comp_delay_04 <= 0;
end
else
begin
if(mode == 1'b1)
begin
if(k00_reg > k01_reg)
comp_delay_00 <= k00_reg;
else
comp_delay_00 <= k01_reg;
if(k02_reg > k10_reg)
comp_delay_01 <= k02_reg;
else
comp_delay_01 <= k10_reg;
if(k11_reg > k12_reg)
comp_delay_02 <= k11_reg;
else
comp_delay_02 <= k12_reg;
if(k20_reg > k21_reg)
comp_delay_03 <= k20_reg;
else
comp_delay_03 <= k21_reg;
comp_delay_04 <= k22_reg;
end
else
begin
if(k00_reg > k01_reg)
comp_delay_00 <= k01_reg;
else
comp_delay_00 <= k00_reg;
if(k02_reg > k10_reg)
comp_delay_01 <= k10_reg;
else
comp_delay_01 <= k02_reg;
if(k11_reg > k12_reg)
comp_delay_02 <= k12_reg;
else
comp_delay_02 <= k11_reg;
if(k20_reg > k21_reg)
comp_delay_03 <= k21_reg;
else
comp_delay_03 <= k20_reg;
comp_delay_04 <= k22_reg;
end
end
end
/* The second compare pipe */
reg [PIXEL_WIDTH-1:0]comp_delay_10;
reg [PIXEL_WIDTH-1:0]comp_delay_11;
reg [PIXEL_WIDTH-1:0]comp_delay_12;
always@(posedge clk or negedge arstn)
begin
if(~arstn)
begin
comp_delay_10 <= 0;
comp_delay_11 <= 0;
comp_delay_12 <= 0;
end
else
begin
if(mode == 1'b1)
begin
if(comp_delay_00 > comp_delay_01)
comp_delay_10 <= comp_delay_00;
else
comp_delay_10 <= comp_delay_01;
if(comp_delay_02 > comp_delay_03)
comp_delay_11 <= comp_delay_02;
else
comp_delay_11 <= comp_delay_03;
comp_delay_12 <= comp_delay_04;
end
else
begin
if(comp_delay_00 > comp_delay_01)
comp_delay_10 <= comp_delay_01;
else
comp_delay_10 <= comp_delay_00;
if(comp_delay_02 > comp_delay_03)
comp_delay_11 <= comp_delay_03;
else
comp_delay_11 <= comp_delay_02;
comp_delay_12 <= comp_delay_04;
end
end
end
/* The Third compare pipe */
reg [PIXEL_WIDTH-1:0]comp_delay_20;
reg [PIXEL_WIDTH-1:0]comp_delay_21;
always@(posedge clk or negedge arstn)
begin
if(~arstn)
begin
comp_delay_20 <= 0;
comp_delay_21 <= 0;
end
else
begin
if(mode == 1'b1)
begin
if(comp_delay_10 > comp_delay_11)
comp_delay_20 <= comp_delay_10;
else
comp_delay_20 <= comp_delay_11;
comp_delay_21 <= comp_delay_12;
end
else
begin
if(comp_delay_10 > comp_delay_11)
comp_delay_20 <= comp_delay_11;
else
comp_delay_20 <= comp_delay_10;
comp_delay_21 <= comp_delay_12;
end
end
end
/* The Fourth compare pipe */
reg [PIXEL_WIDTH-1:0]comp_delay_30;
always@(posedge clk or negedge arstn)
begin
if(~arstn)
comp_delay_30 <= 0;
else
begin
if(mode == 1'b1)
begin
if(comp_delay_20 > comp_delay_21)
comp_delay_30 <= comp_delay_20;
else
comp_delay_30 <= comp_delay_21;
end
else
begin
if(comp_delay_20 > comp_delay_21)
comp_delay_30 <= comp_delay_21;
else
comp_delay_30 <= comp_delay_20;
end
end
end
assign data_out = comp_delay_30;
always@(posedge clk or negedge arstn)
begin
if(~arstn)
cnt_reg <= 0;
else
cnt_reg <= cnt;
end
always@(*)
begin
if(din_valid)
begin
if(cnt_reg == IMAGE_WIDTH - 1)
cnt = 0;
else
cnt = cnt_reg + 1'b1;
end
else
cnt = cnt_reg;
end
/* dout_valid pipe*/
reg tvalid;
reg tvalid_delay_0;
reg tvalid_delay_1;
reg tvalid_delay_2;
reg tvalid_delay_3;
always@(posedge clk or negedge arstn)
begin
if(~arstn)
tvalid <= 0;
else
begin
if(cnt == KX_WIDTH)
tvalid <= 1'b1;
else if(cnt_reg == IMAGE_WIDTH - 1)
tvalid <= 1'b0;
else
tvalid <= tvalid;
end
end
always@(posedge clk or negedge arstn)
begin
if(~arstn)
begin
tvalid_delay_0 <= 0;
tvalid_delay_1 <= 0;
tvalid_delay_2 <= 0;
tvalid_delay_3 <= 0;
end
else
begin
tvalid_delay_0 <= tvalid;
tvalid_delay_1 <= tvalid_delay_0;
tvalid_delay_2 <= tvalid_delay_1;
tvalid_delay_3 <= tvalid_delay_2;
end
end
assign dout_valid = tvalid_delay_3;
endmodule
图 3
图 4
图3和图4是最大/最小值滤波电路在最大值滤波和最小值滤波场景的仿真电路图。
我们知道开运算是先腐蚀后膨胀的过程,闭运算是先膨胀后腐蚀的过程。因此,实现开/闭运算其实可以将两个最大/最小值滤波电路串联,两个分别做相反的运算即可。
图 5
如图5所示为开/闭运算的电路结构图。这里要注意的是因为做膨胀或腐蚀后的结果图,会在上,下,左,右四个边界少滤波核半径大小的值,而且图像外边界为滤波核半径大小外的图像数据没有被滤波运算值替代,因此,边界值会保留。所以,进行一次最大/最小值滤波后,在数据进入下一个最大/最小值滤波电路中时,需要进行像素填充,如果采用的滤波核大小是3x3的,则卷积核半径就是1,因此,填充像素个数为2*(WIDTH+HEGIHT)个。