上一篇介绍了形态学的基本操作膨胀和腐蚀,我们这一篇将利用膨胀和腐蚀操作实现对图像更高级的形态学操作,而这些都是建立在膨胀和腐蚀操作基础之上。
首先形态学的主要用途是获取物体拓扑和结果信息,它通过物体和结构元素的某些运算,得到物体更本质的形态,在图像处理中的主要应用有:
(1). 利用形态学的基本运算对图像进行观察和处理,从而达到改善图像质量的目的
(2). 描述和定义图像的各种几何参数和特征如面积、周长、连通、颗粒度、骨架和方向性
我们通过腐蚀和膨胀两种基本的形态学操作实现开运算、闭运算、形态梯度、顶帽、黑帽五种形态学操作。
1.开运算(Opening)
开运算是通过先对图像腐蚀再膨胀实现,其原理表达式如下:
dst=open(src,element)=dilate(erode(src,element))
能够排除小团块物体(假设物体较背景明亮),开运算的结果删除了不能包含结构元素的对象区域,平滑了对象的轮廓,断开了狭窄的连接,去掉了细小的突出部分,如下图所示:左图是原图像,右图是采用开运算转换之后的结果图,可以发现字母拐弯处的白色空间消失。
2.闭运算(Closing)
闭运算在数学上是先膨胀再腐蚀的结果,其原理表达式如下:
dst=close(src,element)=erode(dilate(src,element))
能够排除小型黑洞(黑色区域),能够平滑对象的轮廓,但是与开运算不同的是闭运算一般会将狭窄的缺口连接起来形成细长的弯口,并填充比结构元素小的洞。
如下图所示:
3.形态梯度(Morphological Gradient)
形态梯度是膨胀图与腐蚀图之差,其操作原理表达式如下
dst=morph(src,element)=dilate(src,element)-erode(src,element)
4.顶帽(Top Hat)
顶帽操作是原图像与开运算结果图之差,其原理表达式如下:
dst=tophat(src,element)=src-open(src,element)
开运算的结果是放大了裂缝或局部降低亮度的区域,因此从原图中减去开运算后的图得到的效果图能够突出比原图轮廓周围的区域更明亮的区域,且这一操作与选择的核的大小有关。
顶帽操作往往用来分离比邻近点亮一些的板块,在一幅图像具有大幅背景而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。如下图所示:
5.黑帽(Black Hat)
黑帽运算是闭运算结果图与原图像之差,其原理表达式如下:
dst=blackhat(src,element)=close(src,element)-src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,这一操作也与选择的核尺寸有关。所以黑帽运算用来分离比邻近点暗一些的斑块,效果图有着非常完美的轮廓。如下图所示:
opencv中提供了形态学操作函数morphologyEx()来实现开运算、闭运算、形态学梯度、顶帽、黑帽等五种相对高级的操作,也可以实现膨胀核腐蚀两种基本的形态学操作。其原型如下:
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)
)
参数解释:
. int shape: kernel的形状,由cv::MorphShapes指定,如下:
分别是矩形(MORPH_RECT)、交叉形(MORPH_CROSS)、椭圆形(MORPH_ELLIPSE)
. Size ksize: kernel的尺寸
. 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() 查看更多细节。
这些形态学操作都是可执行就地操作(in-place),对于多通道图像,每个图像通道进行单独操作。
示例代码如下:
/*
*本程序将会产生5副目标图片与原图像进行对比
*每幅图像采用的模板Type和Size均可通过轨迹条进行调节
*如有兴趣也可尝试使用轨迹条改变形态学的运算类型可大幅减少代码量
*/
#include
#include
#include
#include
using namespace std;
using namespace cv;
//定义全局变量
Mat g_srcImage;
Mat g_dstImageOpen, g_dstImageClose, g_dstImageGradient;
Mat g_dstImageTopHat, g_dstImageBlackHat;
//定义两个轨迹条参数
const int kernelTypeMaxValue = 2;
const int kernelSizeMaxValue = 20;
int kernelTypeValue = 1;
int kernelSizeValue = 10;
//定义模板类型声明函数
int kernelTypeFun(int kernelTypeValue);
//定义两个回调函数
void openOperation(int, void*);
void closeOperation(int, void*);
void gradientOperation(int, void*);
void topHatOperation(int, void*);
void blackHatOperation(int, void*);
int main()
{
g_srcImage = imread("baboon.jpg");
//判断图像是否加载成功
if(!g_srcImage.data)
{
cout << "图像加载失败!" << endl;
return -1;
}
else
cout << "图像加载成功!" << endl << endl;
namedWindow("原图像", WINDOW_AUTOSIZE);
imshow("原图像", g_srcImage);
//定义轨迹条参数
char kernelTypeName[20];
char kernelSizeName[20];
sprintf(kernelTypeName, "模板类型 %d\n 0-Rect 1-Cross 2-Ellipse", kernelTypeMaxValue);
sprintf(kernelSizeName, "模板尺寸 %d", kernelSizeMaxValue);
namedWindow("开运算", WINDOW_AUTOSIZE);
namedWindow("闭运算", WINDOW_AUTOSIZE);
namedWindow("形态学梯度", WINDOW_AUTOSIZE);
namedWindow("顶帽", WINDOW_AUTOSIZE);
namedWindow("黑帽", WINDOW_AUTOSIZE);
//创建两个轨迹条
createTrackbar(kernelTypeName, "开运算", &kernelTypeValue, kernelTypeMaxValue, openOperation);
createTrackbar(kernelSizeName, "开运算", &kernelSizeValue, kernelSizeMaxValue, openOperation);
openOperation(kernelTypeValue, 0);
openOperation(kernelSizeValue, 0);
createTrackbar(kernelTypeName, "闭运算", &kernelTypeValue, kernelTypeMaxValue, closeOperation);
createTrackbar(kernelSizeName, "闭运算", &kernelSizeValue, kernelSizeMaxValue, closeOperation);
openOperation(kernelTypeValue, 0);
openOperation(kernelSizeValue, 0);
createTrackbar(kernelTypeName, "形态学梯度", &kernelTypeValue, kernelTypeMaxValue, gradientOperation);
createTrackbar(kernelSizeName, "形态学梯度", &kernelSizeValue, kernelSizeMaxValue, gradientOperation);
openOperation(kernelTypeValue, 0);
openOperation(kernelSizeValue, 0);
createTrackbar(kernelTypeName, "顶帽", &kernelTypeValue, kernelTypeMaxValue, topHatOperation);
createTrackbar(kernelSizeName, "顶帽", &kernelSizeValue, kernelSizeMaxValue, topHatOperation);
openOperation(kernelTypeValue, 0);
openOperation(kernelSizeValue, 0);
createTrackbar(kernelTypeName, "黑帽", &kernelTypeValue, kernelTypeMaxValue, blackHatOperation);
createTrackbar(kernelSizeName, "黑帽", &kernelSizeValue, kernelSizeMaxValue, blackHatOperation);
openOperation(kernelTypeValue, 0);
openOperation(kernelSizeValue, 0);
waitKey(0);
return 0;
}
//形态学操作模板类型判断
int kernelTypeFun(int kernelTypeValue)
{
int kernel_type;
if(kernelTypeValue == 0)
kernel_type = MORPH_RECT;
else if(kernelTypeValue == 1)
kernel_type = MORPH_CROSS;
else
kernel_type = MORPH_ELLIPSE;
return kernel_type;
}
//开运算
void openOperation(int, void*)
{
int kernel_type;
kernel_type = kernelTypeFun(kernelTypeValue);
Mat element = getStructuringElement(kernel_type, Size(kernelSizeValue * 2 + 1,
kernelSizeValue * 2 + 1));
morphologyEx(g_srcImage, g_dstImageOpen, MORPH_OPEN, element);
imshow("开运算", g_dstImageOpen);
}
//闭运算
void closeOperation(int, void*)
{
int kernel_type;
kernel_type = kernelTypeFun(kernelTypeValue);
Mat element = getStructuringElement(kernel_type, Size(kernelSizeValue * 2 + 1,
kernelSizeValue * 2 + 1));
morphologyEx(g_srcImage, g_dstImageClose, MORPH_CLOSE, element);
imshow("闭运算", g_dstImageClose);
}
//形态学梯度
void gradientOperation(int, void*)
{
int kernel_type;
kernel_type = kernelTypeFun(kernelTypeValue);
Mat element = getStructuringElement(kernel_type, Size(kernelSizeValue * 2 + 1,
kernelSizeValue * 2 + 1));
morphologyEx(g_srcImage, g_dstImageGradient, MORPH_GRADIENT, element);
imshow("形态学梯度", g_dstImageGradient);
}
//顶帽
void topHatOperation(int, void*)
{
int kernel_type;
kernel_type = kernelTypeFun(kernelTypeValue);
Mat element = getStructuringElement(kernel_type, Size(kernelSizeValue * 2 + 1,
kernelSizeValue * 2 + 1));
morphologyEx(g_srcImage, g_dstImageTopHat, MORPH_TOPHAT, element);
imshow("顶帽", g_dstImageTopHat);
}
//黑帽
void blackHatOperation(int, void*)
{
int kernel_type;
kernel_type = kernelTypeFun(kernelTypeValue);
Mat element = getStructuringElement(kernel_type, Size(kernelSizeValue * 2 + 1,
kernelSizeValue * 2 + 1));
morphologyEx(g_srcImage, g_dstImageBlackHat, MORPH_BLACKHAT, element);
imshow("黑帽", g_dstImageBlackHat);
}