Opencv 形态学

数学形态学实际上可以理解为一种滤波行为,所以很多地方称它为形态学滤波。有了个这概念,我们就能更好的理解它。我们滤波中用的滤波器(kernel)在这里被称为结构元素,结构元素往往是由一个特殊的形状构成,如:线条、矩形、圆、菱形等。我们把结构元素的中心(Anchor Point)与图像上像素点对齐,然后结构元素覆盖的领域像素就是我们要分析的像素,我们定义一种操作就形成了一种形态学运算。

我们在这里不解释形态学操作的算法原理及它们的意义,有兴趣的可以参见相关数字图像处理方面的教材,或关注本博客,博主打算在OpenCV系列写完后,开始写图像处理方面算法系列的文章。

推荐阅读

Ubuntu 12.04 安装 OpenCV2.4.2 http://www.linuxidc.com/Linux/2012-09/70158.htm

CentOS下OpenCV无法读取视频文件 http://www.linuxidc.com/Linux/2011-07/39295.htm

Ubuntu 12.04下安装OpenCV 2.4.5总结 http://www.linuxidc.com/Linux/2013-06/86704.htm

Ubuntu 10.04中安装OpenCv2.1九步曲 http://www.linuxidc.com/Linux/2010-09/28678.htm

基于QT和OpenCV的人脸识别系统 http://www.linuxidc.com/Linux/2011-11/47806.htm

一、形态学的基本操作

腐蚀运算:erode

1
2
3
void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1),
int iterations=1, int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue());

src:输入图像,很多场合下我们使用的是二值图像,当然灰度图像也可以。

dst:输出图像,格式和输入图像一致。

kernel:定义的结构元素。

anchor:结构元素的中心,如果是默认参数(-1,-1),程序会自动将其设置为结构元素的中心。

iterations:迭代次数,我们可以选择对图像进行多次形态学运算。

后面两个参数是边界类型,由于要处理领域问题,所以图像需要扩充边界。一般情况下使用默认即可。

膨胀运算:dilate

膨胀跟腐蚀的参数完全一致,就不过多的说明了。这两个形态学操作是最基本的两个操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
Mat image=imread( "../cat.png" );
// 彩色转灰度
cvtColor(image,image,CV_BGR2GRAY);
// 阈值化
threshold(image,image,255*(0.5),255,THRESH_BINARY);
 
// 形态学操作
// 如果把结构元素设置为Mat(),则将用默认的3*3的矩形结构元素
Mat eroded;
erode(image,eroded,Mat());
Mat dilated;
dilate(image,dilated,Mat());
return 0;
}

image

下面要介绍的两个形态学操作,在实际应用中要比上面两个更加广泛,但实际上它们是上面两种操作的一个组合式的操作。

开运算与闭运算

这两个运算都是使用函数morphologyEx来实现的,这个函数的接口如下:

1
2
void 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());

函数的大部分参数都与上面介绍的erode函数参数是一样的,这里面的op是我们要进行的形态学的类型:

MORPH_OPEN:对图像进行开运算。

MORPH_CLOSE:对图像进行闭运算。

下面我们还是以小猫图像为例显示一下对二值图像进行开运算和闭��算后得到的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
Mat image=imread( "../cat.png" );
// 彩色转灰度
cvtColor(image,image,CV_BGR2GRAY);
// 阈值化
threshold(image,image,255*(0.5),255,THRESH_BINARY);
// 定义结构元素
Mat se(5,5,CV_8U,Scalar(1));
Mat closed;
morphologyEx(image,closed,MORPH_CLOSE,se);
Mat opened;
morphologyEx(image,opened,MORPH_OPEN,se);
return 0;
}

image

从图片中我们可以得出结论:

闭运算可以填充图像中的孔洞,连接一些缺口;开运算可以去除图像中一些较小的结构。前提是这些孔洞或碎片要与进行运算的结构元素尺度相当。

二、用形态学操作来检测边缘和角点

其实用形态学来检测边缘的原理非常简单,我们打开源码看它是怎么操作的:

1
2
3
4
5
case CV_MOP_GRADIENT:
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
dst -= temp;
break ;

可以看出来,它是对图像先做了一个腐蚀,再做了一次膨胀,然后将两次的结果相减即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
Mat image=imread( "../cat.png" );
// 彩色转灰度
cvtColor(image,image,CV_BGR2GRAY);
Mat catEdge;
morphologyEx(image,catEdge,MORPH_GRADIENT,Mat());
// 阈值化
threshold(catEdge,catEdge,40,255,THRESH_BINARY);
namedWindow( "catEdge" );imshow( "catEdge" ,catEdge);
waitKey();
return 0;
}

image

下面我们来实现用形态学操作来检测角点。

首先我们需要定义几个特殊的结构元素,我们这里都用Mat来定义,并像素式的赋值,你可以选择OpenCV里的getStructElement来更快的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 定义结构元素
Mat cross(5,5,CV_8U,Scalar(0));
Mat diamond(5,5,CV_8U,Scalar(1));
Mat square(5,5,CV_8U,Scalar(1));
Mat x(5,5,CV_8U,Scalar(0));
 
for ( int i=0;i<5;i++)
{
cross.at<uchar>(2,i)=1;
cross.at<uchar>(i,2)=1;
}
diamond.at<uchar>(0,0)=0;
diamond.at<uchar>(0,1)=0;
diamond.at<uchar>(1,0)=0;
diamond.at<uchar>(4,4)=0;
diamond.at<uchar>(3,4)=0;
diamond.at<uchar>(4,3)=0;
diamond.at<uchar>(4,0)=0;
diamond.at<uchar>(4,1)=0;
diamond.at<uchar>(3,0)=0;
diamond.at<uchar>(0,4)=0;
diamond.at<uchar>(0,3)=0;
diamond.at<uchar>(1,4)=0;
for ( int i=0;i<5;i++){
x.at<uchar>(i,i)=1;
x.at<uchar>(4-i,i)=1;
}

第一个为一个十字型的结构元素,第二个为菱形,第三个是矩形,第四个是一个“X”

型。

然后我们按下面的顺序对一幅图像进行操作,并对最后的结果进行阈值化。

1
2
3
4
5
6
7
Mat result;
dilate(image,result,cross);
erode(result,result,diamond);
Mat result2;
dilate(image,result2,x);
erode(result2,result2,square);
absdiff(result2,result,result);

经过上面步骤,我们得到了一张二值图像,显示了图像的一些角点的位置。

image

为了更形象的说明,我们将上面的这些点在原彩色图像上标出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 标记角点
void drawOnImage( const Mat& binary,Mat& image)
{
for ( int i=0;i<binary.rows;i++)
{
// 获取行指针
const uchar* data=binary.ptr<uchar>(i);
for ( int j=0;j<binary.cols;j++)
{
if (data[j]) //角点图像上的白点
circle(image,Point(j,i),8,Scalar(0,255,0)); // 画圈
}
}
}

image

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值