形态学变化本质是数学上一个分支,是基于形状的一系列图像处理操作。
一、基础理论
形态学的主要用途是获取物体拓扑和结果信息,通过物体结构一系列运算,得到物体更本质的形态。在图像处理的主要应用有:
1、利用形态学处理图像,用于改善图像质量目的,比如通过原图像减去顶冒图像用于提高对比度
2、描述和定义图像的各种几何参数和特征,如面积、连通、颗粒度、骨架和方向性。
1.1 腐蚀和膨胀
腐蚀和膨胀是对二维图片的进行操作的形态学运算,简单来讲形态学操作就是基于形状的一系列图像处理操作,通过将结构元素作用于输入图像来产生输出图像。腐蚀(Erosion)和膨胀(Dilation)是最基本的形态学操作,他们运用广泛主要有:
1、消除噪声
2、 分割(ioslate)独立的图像元素以及连接(join)相邻的元素
3、 寻找图像中的明显的极大值区域或极小值区域
腐蚀是将构建的内核B对原图像SRC进行卷积,将内核B覆盖区域内的最小值提取,代替锚点位置(默认为内核中心点,因为是选取的最小值,所以是白色腐蚀,即白色区域减小。
膨胀是腐蚀的孪生兄弟,操作过程一模一样,区别在于选取覆盖区域的最大值提到锚点位置的像素,所以是白色区域膨胀,扩展白色区域。
其实腐蚀和膨胀是相对于值的选取,腐蚀则为选取最小值,膨胀选取最大值,对于数字图像则是相对于白色区域,腐蚀就是腐蚀白色区域的,膨胀则是膨胀白色区域的。谨记白色代表背景还是前景。
(src) (膨胀-dilate) (腐蚀-erode)
void dilate( InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar borderValue = morphologyDefaultBorderValue() )
void erode ( InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar borderValue = morphologyDefaultBorderValue() )
参数解释:
. InputArray src: 输入图像可以是Mat类型,可以是任意通道图像,图像深度只能是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F其中的一个。
. OutputArray dst: 输出图像,与输入图像尺寸类型一致。
. Input Array kernel: 用于腐蚀操作的kernel,当参数=Mat()即NULL时,kernel是一个锚点位于中心的3x3模板。可以通过getStructuringElement函数来制定kernel的形状和尺寸,具体用法请参考对膨胀的表述
. Point anchor = Point(-1, -1): 锚点位置
. int iterations = 1: 迭代腐蚀操作次数,有默认值1
. int borderType = BORDER_CONSTANT: 用于推断图像外部像素的某种边界模式,其有默认值BORDER_CONSTANT,可以通过cv::BorderTypes查询其他的方法。
. const Scalar borderValue = morphologyDefaultBorderValue(): 边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般不用进行设置,如果有特殊需要,可以查看createMorphologyFilter()函数得到详细信息。
应用讲解:腐蚀和膨胀经常使用,基本是对二值化图像进行操作,但两者皆是需要组合使用,以去除噪点和连通区域,但后续出现了形态学运算,此函数应用较少了。
1.2 开运算和闭运算
开运算是通过先对图像腐蚀再膨胀实现。
1、开运算(Opening Operation) = 先腐蚀,后膨胀
dst=open(src,element)=dilate(erode(src,element))
腐蚀和膨胀的原理前期讲解过,假设背景是白色区域(较明亮),开运算则是先腐蚀白色,意味前景区域连通了些,后又膨胀,意味白色膨胀,可能将一些噪点去除。结果是删除了不属于结构元素的对象区域,平滑了轮廓,断开了狭窄的连接,去掉细小的突出部分。
左图是原图像,右图是采用开运算转换之后的结果图,可以发现字母拐弯处的白色空间消失。
从上面两幅图像可以看出,开运算以腐蚀为主,即第一步为主,连通了区域,将不想关区域最后去掉。
2、闭运算(Closing Operation) = 先膨胀,后腐蚀
dst=close(src,element)=erode(dilate(src,element))
闭运算当然是开运算相反操作,即先膨胀,后腐蚀。能够排除小型黑洞(黑色区域),能够平滑对象的轮廓,但是与开运算不同的是闭运算一般会将狭窄的缺口连接起来形成细长的弯口,并填充比结构元素小的洞。
能够排除小型黑洞(黑色区域)。
综上所述,对于开运算和闭运算,比较侧重第一步,开和闭相对于白色来讲,开就是是白色区域断开点(侧重腐蚀,即开运算第一步),闭则相反。
在应用中首先确定目标为白色还是黑色,然后根据是连通还是去燥,最后确定开运算还是闭运算。
1.3 形态学梯度
形态梯度是膨胀图与腐蚀图之差,其操作原理表达式如下:
形态学梯度(Morphological Gradient) = 膨胀图 - 腐蚀图
dst=morph_grad(src,element)=dilate(src,element)?erode(src,element)
形态学梯度很容易理解,公式表述为膨胀图-腐蚀图,就是利用白色膨胀,另一幅图像腐蚀,本质就是目标边缘一个左移,一个右移,相减生成里差值图像,即为边缘图像。
因为opencv中有很多获取边缘的函数,则这个功能很少应用。
1.4 顶帽和黑帽
顶帽操作是原图像与开运算结果图之差。
顶帽(Top Hat) = 原图 - 开运算图
dst=tophat(src,element)=src?open(src,element)
开运算的结果是白色区域断开,因此从原图中减去开运算后的图得到的效果图能够突出比原图轮廓周围的区域更明亮的区域,且这一操作与选择的核的大小有关。
顶帽操作往往用来分离比邻近点亮一些的板块,在一幅图像具有大幅背景而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
在图像轮廓有较为明亮区域,如果加上原图,可以增加边缘区域的对比度。
黑帽(Black Hat) = 闭运算图 - 原图
dst=blackhat(src,element)=close(src,element)?src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,这一操作也与选择的核尺寸有关。所以黑帽运算用来分离比邻近点暗一些的斑块,效果图有着非常完美的轮廓。
二、open函数介绍
opencv中提供了形态学操作函数morphologyEx()来实现开运算、闭运算、形态学梯度、顶帽、黑帽等五种相对高级的操作,也可以实现膨胀核腐蚀两种基本的形态学操作。
2.1 函数详解
void cv::morphologyEx (InputArray src,
OutputArray dst,
int op,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar borderValue = morphologyDefaultBorderValue()
)
. InputArray src: 输入图像,可以是Mat类型,对于图像通道数无要求,但图像深度必须是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F
. OutPutArray dst: 目标图像,与原图像尺寸核类型相同
. int op: 形态学运算的类型,可以通过MorphTypes查看,如下所示:
标识符 | 运算类型
MORPH_OPEN: 开运算
MORPH_CLOSE :闭运算
MORPH_GRADIENT: 形态学梯度
MORPH_TOPHAT:顶帽运算
MORPH_BLACKHAT: 黑帽运算
MORPH_ERODE :腐蚀运算
MORPH_DILATE :膨胀运算
MORPH_HITMISS: 击中击不中运算(只支持CV_8UC1类型的二值图像)
. InputArray kernel: 形态学运算的内核,如果是Mat()则表示的是参考点位于内核中心3x3的核,前面也提到一般使用前需要定义一个Mat变量结合getStructuringElement()函数使用,getStructuringElement会返回指定形状和尺寸的结构元素,这里再重申一下getStructuringElement的参数,其函数原型如下:
Mat cv::getStructuringElement ( int shape,
Size ksize,
Point anchor = Point(-1,-1)
)
Point anchor=Point(-1, -1): 锚点位置
. int iterations=1: 迭代使用函数的次数,默认值为1
. int borderType=BORDER_CONSTANT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_CONSTANT
. const Scalar borderValue=morphologyDefaultBorderValue(): 当边界为常数时的边界值,可以通过createMorphologyFilter() 查看更多细节。
2.2 源码分析
//
void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,
InputArray kernel, Pointanchor, int iterations,
int borderType, constScalar borderValue )
{
Mat src = _src.getMat(), temp;
_dst.create(src.size(), src.type());
Mat dst = _dst.getMat();
//一个大switch,根据不同的标识符取不同的操作
switch( op )
{
case MORPH_ERODE:
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_DILATE:
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_OPEN:
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case CV_MOP_CLOSE:
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case CV_MOP_GRADIENT:
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
dst -= temp;
break;
case CV_MOP_TOPHAT:
if( src.data != dst.data )
temp = dst;
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );
dst = src - temp;
break;
case CV_MOP_BLACKHAT: //代码效果参考:http://www.zidongmutanji.com/bxxx/191434.html
if( src.data != dst.data )
temp = dst;
dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);
erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);
dst = temp - src;
break;
default:
CV_Error( CV_StsBadArg, "unknown morphological operation" );
}
}
这个函数使用了一个大的switch实现了多种形态学滤波的调用
2.3 示例程序
#coding=utf-8
import cv2
import numpy as np
img = cv2.imread('D:/binary.bmp',0)
#定义结构元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))
#闭运算
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
#显示腐蚀后的图像
cv2.imshow("Close",closed);
#开运算
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
#显示腐蚀后的图像
cv2.imshow("Open", opened);
cv2.waitKey(0) //代码效果参考:http://www.zidongmutanji.com/bxxx/303794.html
cv2.destroyAllWindows()
其实就是改一下morphologyEx里面的第三个标识符参数而已。核都是选的MORPH_RECT,矩形元素结构。另外,通过看源码我们发现,最基本的腐蚀和膨胀操作也可以用morphologyEx函数来实现。
2.4 知识补充
形态学处理的核心就是定义结构元素,在OpenCV-Python中,可以使用其自带的getStructuringElement函数,也可以直接使用NumPy的ndarray来定义一个结构元素。首先来看用getStructuringElement函数定义一个结构元素:
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
这就定义了一个5×5的十字形结构元素,如下:
也可以用NumPy来定义结构元素,如下:
NpKernel = np.uint8(np.zeros((5,5)))
for i in range(5):
NpKernel【2, i】 = 1 #感谢chenpingjun1990的提醒,现在是正确的
NpKernel【i, 2】 = 1
【【0 0 1 0 0】
【0 0 1 0 0】
【1 1 1 1 1】
【0 0 1 0 0】
【0 0 1 0 0】】
cv2.getStructuringElement(cv2.MORPH_RECT, (24, 3)) ,输出为:
【【1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1】
【1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1】
【1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1】】
四、参考文献
1、opencv学习(二十四)之腐蚀与膨胀:对腐蚀、膨胀进行了详细的介绍
2、腐蚀与膨胀(Eroding and Dilating):opencv中文教程,详细说明函数细节
3、opencv学习(二十五)之开运算、闭运算、形态梯度、顶帽、黑帽:文中对形态学应用有较清晰的介绍,并详细介绍了各个算法的原理及应用
4、【OpenCV入门教程之十一】 形态学图像处理(二):开运算、闭运算、形态学梯度、顶帽、黑帽合辑:浅墨写的博客,很详细,很全面。