1、数学形态学的定义
数学形态学是一门建立在集论基础上的学科,以形态结构元素为基础对图像进行分析的数学工具,它的基本思想是,用具有一定形态的结构元素度量和提取图像中的对应形状,以达到对图像分析和识别的目的。数学形态学的应用可以简化图像数据,保持它们基本的形状特征,并除去不相干的结构。数学形态学的基本运算有4个:膨胀、腐蚀、开运算和闭运算。它们在二值图像中和灰度图像中各有特点。基于这些基本运算还可以推导和组合成各种数学形态学实用算法。
2、分类
1)二值形态学
数学形态学中二值图像的形态变换是一种针对集合的处理过程。其形态算子的实质是表达物体或形状的集合与结构元素间的相互作用,结构元素的形状就决定了这种运算所提取的信号的形状信息。形态学图像处理是在图像中移动一个结构元素,然后将结构元素与下面的二值图像进行交、并等集合运算。
二值形态膨胀与腐蚀可转化为集合的逻辑运算,算法简单,适于并行处理,且易于硬件实现,适于对二值图像进行图像分割、细化、抽取骨架、边缘提取、形状分析。但是,在不同的应用场合,结构元素的选择及其相应的处理算法是不一样的,对不同的目标图像需设计不同的结构元素和不同的处理算法。结构元素的大小、形状选择合适与否,将直接影响图像的形态运算结果。因此,很多学者结合自己的应用实际,提出了一系列的改进算法。如梁勇提出的用多方位形态学结构元素进行边缘检测算法,既有较好的边缘定位能力又有很好的噪声平滑能力;徐超提出的以最短线段结构元素构造准圆结构元素或序列结构元素生成准圆结构元素相结合的设计方法,用于骨架的提取,可大大减少形态学运算的计算量,并同时满足尺度、平移及旋转相容性,适于对形状进行分析和描述。
2)灰度数学形态学
二值数学形态学可方便地推广到灰度图像空间。只是灰度数学形态学的运算对象不是集合而是图像函数。灰度数学形态学中开启和闭合运算的定义与在二值数学形态学中的定义一致。
本文介绍灰度图像形态学滤波的两个基本操作:膨胀和腐蚀,其主要功能包括:消除噪声、分割出独立的图像元素、在图像中连接相邻的元素、寻找图像中明显的极大值和极小值区域以及求出图像的梯度等,其余形态学滤波算法都是膨胀与腐蚀运算的变形。
膨胀(dilate)就是求局部最大值的操作。从数学角度上来说,膨胀和腐蚀就是将图像(或图像的一部分区域,称之为A)与核(称之为B)进行卷积的一个过程。需要注意的是,核可以是任意的形状和大小,它拥有一个单独定义出来的参考点,我们称之为锚点(anchorpoint)。在多数情况下,核是一个小的中间带有参考点的十字形、实心矩形(一般是正方形)或者圆盘。其实,可以将核视为模板或者掩码。核B与图像卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指点的像素。
腐蚀(erode)和膨胀是一对对立的操作,因此,腐蚀就是求局部的最小指。关于形态学滤波的原理大家可参考冈萨雷斯《数字图像处理》一书,本文重点介绍代码如何实现卷积核大小为3*3的腐蚀与膨胀运算:
膨胀运算:
`timescale 1ns / 1ps
module VIP_9Bit_Dilation_Detector(
input clk ,
input rst_n ,
input per_frame_vsync, //Prepared Image data vsync valid signal
input per_frame_href, //Prepared Image data href vaild signal
input per_frame_clken, //Prepared Image data output/capture enable clock
input [8:0] per_img_9Bit, //Prepared Image Bit flag outout(1: Value, 0:inValid)
//Image data has been processd
output post_frame_vsync, //Processed Image data vsync valid signal
output post_frame_href, //Processed Image data href vaild signal
output post_frame_clken, //Processed Image data output/capture enable clock
output [8:0] post_img_9Bit //Processed Image Bit flag outout(1: Value, 0:inValid)
);
//----------------------------------------------------
//Generate 1Bit 3X3 Matrix for Video Image Processor.消耗2个时钟周期
//Image data has been processd
wire matrix_frame_vsync; //Prepared Image data vsync valid signal
wire matrix_frame_href; //Prepared Image data href vaild signal
wire matrix_frame_clken; //Prepared Image data output/capture enable clock
wire [8:0] matrix_p11, matrix_p12, matrix_p13; //3X3 Matrix output
wire [8:0] matrix_p21, matrix_p22, matrix_p23;
wire [8:0] matrix_p31, matrix_p32, matrix_p33;
VIP_Matrix_Generate_3X3_9Bit VIP_Matrix_Generate_3X3_9Bit_inst
(
//global clock
.clk (clk) , //cmos video pixel clock
.rst_n (rst_n) , //global reset
//Image data prepred to be processd
.per_frame_vsync (per_frame_vsync) , //Prepared Image data vsync valid signal
.per_frame_href (per_frame_href) , //Prepared Image data href vaild signal
.per_frame_clken (per_frame_clken) , //Prepared Image data output/capture enable clock
.per_img_Y (per_img_9Bit) , //Prepared Image brightness input
//Image data has been processd
.matrix_frame_vsync (matrix_frame_vsync) , //Prepared Image data vsync valid signal
.matrix_frame_href (matrix_frame_href) , //Prepared Image data href vaild signal
.matrix_frame_clken (matrix_frame_clken) , //Prepared Image data output/capture enable clock
.matrix_p11(matrix_p11),.matrix_p12(matrix_p12),.matrix_p13(matrix_p13), //3X3 Matrix output
.matrix_p21(matrix_p21),.matrix_p22(matrix_p22),.matrix_p23(matrix_p23),
.matrix_p31(matrix_p31),.matrix_p32(matrix_p32),.matrix_p33(matrix_p33)
);
//对得到的3*3矩阵进行"卷积"运算 ,膨胀运算扩展到灰度图像中,就是寻找结构体下的最大值进行覆盖
//step1 找出每一行数据的最大值,消耗1个CLK
reg [8:0] max_row1;
reg [8:0] max_row2;
reg [8:0] max_row3;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
max_row1 <= 9'd0;
else if(matrix_frame_href) begin
if((matrix_p11 >= matrix_p12) && (matrix_p11 >= matrix_p13))
max_row1 <= matrix_p11;
if((matrix_p12 >= matrix_p11) && (matrix_p12 >= matrix_p13))
max_row1 <= matrix_p12;
if((matrix_p13 >= matrix_p11) && (matrix_p13 >= matrix_p12))
max_row1 <= matrix_p13;
end
else
max_row1 <= max_row1;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
max_row2 <= 9'd0;
else if(matrix_frame_href) begin
if((matrix_p21 >= matrix_p22) && (matrix_p21 >= matrix_p23))
max_row2 <= matrix_p21;
if((matrix_p22 >= matrix_p21) && (matrix_p22 >= matrix_p23))
max_row2 <= matrix_p22;
if((matrix_p23 >= matrix_p21) && (matrix_p23 >= matrix_p22))
max_row2 <= matrix_p23;
end
else
max_row2 <= max_row2;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
max_row3 <= 9'd0;
else if(matrix_frame_href) begin
if((matrix_p31 >= matrix_p32) && (matrix_p31 >= matrix_p33))
max_row3 <= matrix_p31;
if((matrix_p32 >= matrix_p31) && (matrix_p32 >= matrix_p33))
max_row3 <= matrix_p32;
if((matrix_p33 >= matrix_p31) && (matrix_p33 >= matrix_p32))
max_row3 <= matrix_p33;
end
else
max_row3 <= max_row3;
end
//step2 找出结构体下的最大值
reg [8:0] max;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
max <= 9'd0;
else if((max_row1 >= max_row2) && (max_row1 >= max_row3))
max <= max_row1;
else if((max_row2 >= max_row1) && (max_row2 >= max_row3))
max <= max_row2;
else if((max_row3 >= max_row1) && (max_row3 >= max_row2))
max <= max_row3;
else
max <= max;
end
assign post_img_9Bit = post_frame_href? max : 9'd0;
//控制信号也落后两个时钟周期,保持同步
reg [1:0] post_frame_vsync_r;
reg [1:0] post_frame_href_r;
reg [1:0] post_frame_clken_r;
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)begin
post_frame_vsync_r <= 2'b0;
post_frame_href_r <= 2'b0;
post_frame_clken_r <= 2'b0;
end
else begin
post_frame_vsync_r <= {post_frame_vsync_r[0],matrix_frame_vsync} ;
post_frame_href_r <= {post_frame_href_r[0],matrix_frame_href} ;
post_frame_clken_r <= {post_frame_clken_r[0],matrix_frame_clken} ;
end
end
assign post_frame_vsync = post_frame_vsync_r[1];
assign post_frame_href = post_frame_href_r[1];
assign post_frame_clken = post_frame_clken_r[1];
endmodule
腐蚀运算:
`timescale 1ns / 1ps
module VIP_9Bit_Erosion_Detector(
input clk ,
input rst_n ,
input per_frame_vsync, //Prepared Image data vsync valid signal
input per_frame_href, //Prepared Image data href vaild signal
input per_frame_clken, //Prepared Image data output/capture enable clock
input [8:0] per_img_9Bit, //Prepared Image Bit flag outout(1: Value, 0:inValid)
//Image data has been processd
output post_frame_vsync, //Processed Image data vsync valid signal
output post_frame_href, //Processed Image data href vaild signal
output post_frame_clken, //Processed Image data output/capture enable clock
output [8:0] post_img_9Bit //Processed Image Bit flag outout(1: Value, 0:inValid)
);
//----------------------------------------------------
//Generate 1Bit 3X3 Matrix for Video Image Processor.
//Image data has been processd
wire matrix_frame_vsync; //Prepared Image data vsync valid signal
wire matrix_frame_href; //Prepared Image data href vaild signal
wire matrix_frame_clken; //Prepared Image data output/capture enable clock
wire [8:0] matrix_p11, matrix_p12, matrix_p13; //3X3 Matrix output
wire [8:0] matrix_p21, matrix_p22, matrix_p23;
wire [8:0] matrix_p31, matrix_p32, matrix_p33;
VIP_Matrix_Generate_3X3_9Bit VIP_Matrix_Generate_3X3_9Bit_inst
(
//global clock
.clk (clk) , //cmos video pixel clock
.rst_n (rst_n) , //global reset
//Image data prepred to be processd
.per_frame_vsync (per_frame_vsync) , //Prepared Image data vsync valid signal
.per_frame_href (per_frame_href) , //Prepared Image data href vaild signal
.per_frame_clken (per_frame_clken) , //Prepared Image data output/capture enable clock
.per_img_Y (per_img_9Bit) , //Prepared Image brightness input
//Image data has been processd
.matrix_frame_vsync (matrix_frame_vsync) , //Prepared Image data vsync valid signal
.matrix_frame_href (matrix_frame_href) , //Prepared Image data href vaild signal
.matrix_frame_clken (matrix_frame_clken) , //Prepared Image data output/capture enable clock
.matrix_p11(matrix_p11),.matrix_p12(matrix_p12),.matrix_p13(matrix_p13), //3X3 Matrix output
.matrix_p21(matrix_p21),.matrix_p22(matrix_p22),.matrix_p23(matrix_p23),
.matrix_p31(matrix_p31),.matrix_p32(matrix_p32),.matrix_p33(matrix_p33)
);
//对得到的3*3矩阵进行"卷积"运算 ,腐蚀运算扩展到灰度图像中,就是寻找结构体下的最小值进行覆盖
reg [8:0] min_row1;
reg [8:0] min_row2;
reg [8:0] min_row3;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
min_row1 <= 9'd0;
else if(matrix_frame_href) begin
if((matrix_p11 <= matrix_p12) && (matrix_p11 <= matrix_p13))
min_row1 <= matrix_p11;
if((matrix_p12 <= matrix_p11) && (matrix_p12 <= matrix_p13))
min_row1 <= matrix_p12;
if((matrix_p13 <= matrix_p11) && (matrix_p13 <= matrix_p12))
min_row1 <= matrix_p13;
end
else
min_row1 <= min_row1;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
min_row2 <= 9'd0;
else if(matrix_frame_href) begin
if((matrix_p21 <= matrix_p22) && (matrix_p21 <= matrix_p23))
min_row2 <= matrix_p21;
if((matrix_p22 <= matrix_p21) && (matrix_p22 <= matrix_p23))
min_row2 <= matrix_p22;
if((matrix_p23 <= matrix_p21) && (matrix_p23 <= matrix_p22))
min_row2 <= matrix_p23;
end
else
min_row2 <= min_row2;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
min_row3 <= 9'd0;
else if(matrix_frame_href) begin
if((matrix_p31 <= matrix_p32) && (matrix_p31 <= matrix_p33))
min_row3 <= matrix_p31;
if((matrix_p32 <= matrix_p31) && (matrix_p32 <= matrix_p33))
min_row3 <= matrix_p32;
if((matrix_p33 <= matrix_p31) && (matrix_p33 <= matrix_p32))
min_row3 <= matrix_p33;
end
else
min_row3 <= min_row3;
end
//step2 找出结构体下的最小值
reg [8:0] min;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
min <= 9'd0;
else if((min_row1 <= min_row2) && (min_row1 <= min_row3))
min <= min_row1;
else if((min_row2 <= min_row1) && (min_row2 <= min_row3))
min <= min_row2;
else if((min_row3 <= min_row2) && (min_row3 <= min_row1))
min <= min_row3;
else
min <= min;
end
assign post_img_9Bit = post_frame_href? min : 9'd0;
//控制信号也落后两个时钟周期,保持同步
reg [1:0] post_frame_vsync_r;
reg [1:0] post_frame_href_r;
reg [1:0] post_frame_clken_r;
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)begin
post_frame_vsync_r <= 2'b0;
post_frame_href_r <= 2'b0;
post_frame_clken_r <= 2'b0;
end
else begin
post_frame_vsync_r <= {post_frame_vsync_r[0],matrix_frame_vsync} ;
post_frame_href_r <= {post_frame_href_r[0],matrix_frame_href} ;
post_frame_clken_r <= {post_frame_clken_r[0],matrix_frame_clken} ;
end
end
assign post_frame_vsync = post_frame_vsync_r[1];
assign post_frame_href = post_frame_href_r[1];
assign post_frame_clken = post_frame_clken_r[1];
endmodule
从代码可以看出,腐蚀与膨胀功能的实现并不难,重点在于卷积核如何生成,而这一部分内容,在我的另一篇文章FPGA图像处理_中值滤波实现(含源码)中有详细阐述,本文就介绍到这,谢谢阅读!
参考文献:牟新刚,周晓,郑晓亮.基于FPGA的数字图像处理原理及应用
阮秋琦.数字图像处理(MATLAB版)(第二版)》(本科教学版);