OpenCV3之——形态学滤波(1):腐蚀与膨胀
形态学操作
-
简单来讲,形态学操作就是基于形状的一系列图像处理操作。通过将 结构元素 作用于输入图像来产生输出图像。
-
最基本的形态学操作有二:腐蚀与膨胀(Erosion 与 Dilation)。 他们的运用广泛:
- 消除噪声
- 分割(isolate)独立的图像元素,以及连接(join)相邻的元素
- 寻找图像中的明显的极大值区域或极小值区域
- 求出图像的梯度
-
通过以下图像,我们简要来讨论一下膨胀与腐蚀操作(译者注:注意这张图像中的字母为黑色,背景为白色,而不是一般意义的背景为黑色,前景为白色):

膨胀
-
此操作将图像
与任意形状的内核 (
),通常为正方形或圆形,进行卷积。 -
内核
有一个可定义的 锚点, 通常定义为内核中心点。 -
进行膨胀操作时,将内核
划过图像,将内核
覆盖区域的最大相素值提取,并代替锚点位置的相素。显然,这一最大化操作将会导致图像中的亮区开始”扩展” (因此有了术语膨胀 dilation )。对上图采用膨胀操作我们得到:
背景(白色)膨胀,而黑色字母缩小了。
腐蚀
-
腐蚀在形态学操作家族里是膨胀操作的孪生姐妹。它提取的是内核覆盖下的相素最小值。
-
进行腐蚀操作时,将内核
划过图像,将内核
覆盖区域的最小相素值提取,并代替锚点位置的相素。 -
以与膨胀相同的图像作为样本,我们使用腐蚀操作。从下面的结果图我们看到亮区(背景)变细,而黑色区域(字母)则变大了。

相关OpenCV源码分析朔源:
在···\opencv\sources\modules\imgproc\src\morph.cpp路径中,我们可以发现erode(腐蚀)函数和dilate(膨胀)函数的源码,如下:
void cv::erode( InputArray src, OutputArray dst, InputArray kernel,Point anchor, int iterations,int borderType, const Scalar& borderValue )
{CV_INSTRUMENT_REGION()morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType, borderValue );
}void cv::dilate( InputArray src, OutputArray dst, InputArray kernel,Point anchor, int iterations,int borderType, const Scalar& borderValue )
{CV_INSTRUMENT_REGION()morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType, borderValue );
}
可以发现,erode和dilate这两个函数内部就是调用了一下morphOp,只是他们调用morphOp时,第一个参数标识符不同:一个为MORPH_ERODE(腐蚀),一个为MORPH_DILATE(膨胀)。
morphOp 函数的源码在···\opencv\sources\modules\imgproc\src\morph.cpp的第1816行(opencv3.4.1),如下所示:
static void morphOp( int op, InputArray _src, OutputArray _dst,InputArray _kernel,Point anchor, int iterations,int borderType, const Scalar& borderValue )
{CV_INSTRUMENT_REGION()Mat kernel = _kernel.getMat();Size ksize = !kernel.empty() ? kernel.size() : Size(3,3);anchor = normalizeAnchor(anchor, ksize);CV_OCL_RUN(_dst.isUMat() && _src.dims() <= 2 && _src.channels() <= 4 &&borderType == cv::BORDER_CONSTANT && borderValue == morphologyDefaultBorderValue() &&(op == MORPH_ERODE || op == MORPH_DILATE) &&anchor.x == ksize.width >> 1 && anchor.y == ksize.height >> 1,ocl_morphOp(_src, _dst, kernel, anchor, iterations, op, borderType, borderValue) )if (iterations == 0 || kernel.rows*kernel.cols == 1){_src.copyTo(_dst);return;}if (kernel.empty()){kernel = getStructuringElement(MORPH_RECT, Size(1+iterations*2,1+iterations*2));anchor = Point(iterations, iterations);iterations = 1;}else if( iterations > 1 && countNonZero(kernel) == kernel.rows*kernel.cols ){anchor = Point(anchor.x*iterations, anchor.y*iterations);kernel = getStructuringElement(MORPH_RECT,Size(ksize.width + (iterations-1)*(ksize.width-1),ksize.height + (iterations-1)*(ksize.height-1)),anchor);iterations = 1;}Mat src = _src.getMat();_dst.create( src.size(), src.type() );Mat dst = _dst.getMat();Point s_ofs;Size s_wsz(src.cols, src.rows);Point d_ofs;Size d_wsz(dst.cols, dst.rows);bool isolated = (borderType&BORDER_ISOLATED)?true:false;borderType = (borderType&~BORDER_ISOLATED);if(!isolated){src.locateROI(s_wsz, s_ofs);dst.locateROI(d_wsz, d_ofs);}hal::morph(op, src.type(), dst.type(),src.data, src.step,dst.data, dst.step,src.cols, src.rows,s_wsz.width, s_wsz.height, s_ofs.x, s_ofs.y,d_wsz.width, d_wsz.height, d_ofs.x, d_ofs.y,kernel.type(), kernel.data, kernel.step, kernel.cols, kernel.rows, anchor.x, anchor.y,borderType, borderValue.val, iterations,(src.isSubmatrix() && !isolated));
}}
一、膨胀操作:dilate函数
dilate函数使用像素邻域内的局部极大运算符来膨胀一张图片,支持就地(in-place)操作。
函数原型:
void cv::dilate( InputArray src, OutputArray dst,InputArray kernel,Point anchor = Point(-1,-1), //默认值int iterations = 1,//默认值int borderType = BORDER_CONSTANT, //默认值const Scalar& borderValue = morphologyDefaultBorderValue() )//默认值
{CV_INSTRUMENT_REGION()morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType, borderValue );
}
参数详解如下:
- 第一参数,InputArray类型的src,函数的输入参数,填1,3或者4通道的Mat类型的图像。当ksize为3或5的时候,图像深度需为CV_8U、CV_16U、CV_16S、CV_32F或CV_64F其中之一;
- 第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数需要和源图片有一样的尺寸和类型;
- 第三个参数,InputArray类型的kernel,膨胀操作的核。当为NULL时,则使用参考点位于中心的3×3的核。我们一般使用getStructuringElement函数配合这个参数使用,getStructuringElement会返回指定形状和尺寸的结构元素(内核矩阵),其中getStructuringElement函数的第一个参数表示内核的形状(三种形状可选),如下:
- 矩形:MORPH_RECT
- 交叉形:MORPH_CROSS
- 椭圆形:MORPH_ELLIPSE
而getStructuringElement的第二个参数和第三个参数分别是内核的尺寸以及锚点的位置。一般在调用erode以及dilate之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心,此外需要注意,十字形的element形状唯一依赖于锚点位置,在其他情况下,锚点只是影响了形态学运算的偏移。
g_nStructElementSize 函数相关的调用相关代码如下。
int g_nStructElementSize = 3;//结构元素(内核矩阵)的尺寸//获取自定义核
Mat element = getStructuringElement( MORPH_RECT,Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1 ),Point(g_nStructElementSize ,g_nStructElementSize ) );
调用之后,我们可以在接下来调用erode或dilate函数时,在第三个参数填保存g_nStructElementSize 返回值的Mat类型的element变量。
- 第四个参数,Point类型的anchor,锚点位置,有默认值(-1,-1),表示锚点位于中心;
- 第五个参数,int类型的iterations,迭代 使用dilate函数的次数,默认值为1;
- 第六个参数,int类型的borderType,默认值BORDER_DEFAULT;
- 第七个参数,const Scalar&类型的borderValue,边界为常数值时,有默认值morphologyDefaultBorderValue(),一般不用去管它,需要使用时,可以查看官方文档中的createMorphologyFilter()函数。
使用erode函数,一般只需要填写前面的三个参数,后面的四个参数都有默认值,而且往往会结合getStructuringElement一起使用。
膨胀操作代码示例:
#include
using namespace cv;
using namespace std;int main() {//载入原图Mat srcImage = imread("1.jpg");//创建窗口namedWindow("膨胀操作【原图】");namedWindow("膨胀操作【效果图】");//显示原图imshow("膨胀操作【原图】", srcImage);//获取自定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));Mat dstImage;//膨胀操作dilate(srcImage, dstImage, element);//显示效果图imshow("膨胀操作【效果图】", dstImage);waitKey(0);return 0;
}
#include
using namespace cv;
using namespace std;int main() {//载入原图Mat srcImage = imread("1.jpg");//创建窗口namedWindow("膨胀操作【原图】");namedWindow("膨胀操作【效果图】");//显示原图imshow("膨胀操作【原图】", srcImage);//获取自定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));Mat dstImage;//膨胀操作dilate(srcImage, dstImage, element);//显示效果图imshow("膨胀操作【效果图】", dstImage);waitKey(0);return 0;
} 运行结果:
二、腐蚀:erode函数
erode函数使用像素邻域内的局部极小值运算符来腐蚀一张图片,支持就地(in-place)操作。
函数原型:
void cv::erode( InputArray src, OutputArray dst,InputArray kernel,Point anchor = Point(-1,-1), //默认值int iterations = 1,//默认值int borderType = BORDER_CONSTANT, //默认值const Scalar& borderValue = morphologyDefaultBorderValue() )//默认值
{CV_INSTRUMENT_REGION()morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType, borderValue );
}
参数详解与dilate函数类似。
腐蚀操作代码示例:
#include
using namespace cv;
using namespace std;int main() {//载入原图Mat srcImage = imread("1.jpg");//创建窗口namedWindow("腐蚀操作【原图】");namedWindow("腐蚀操作【效果图】");//显示原图imshow("腐蚀操作【原图】", srcImage);//获取自定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));Mat dstImage;//膨胀操作erode(srcImage, dstImage, element);//显示效果图imshow("腐蚀操作【效果图】", dstImage);waitKey(0);return 0;
}
运行结果:
三、综合实例:腐蚀与膨胀
#include
using namespace cv;
using namespace std;//全局变量声明
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nTrackbarNumber = 0;//0表示腐蚀(erode),1表示膨胀(dilate)
int g_nStructElementSize = 3;//结构元素(内核矩阵)的尺寸//全局函数声明部分
void on_Operation(int, void *);//两个轨迹条的值改变时的回调函数,重新(腐蚀/膨胀)渲染目标图片//============main()函数==============
int main() {//改变console字体颜色system("color 5E");//载入原图g_srcImage = imread("1.jpg");if (!g_srcImage.data) {printf("读取srcImage错误!\n");return false;}//显示原始图namedWindow("【原始图】");imshow("【原始图】", g_srcImage);//进行初次腐蚀操作,并显示效果图namedWindow("【效果图】");on_Operation(5, NULL);//回调函数中的变量全为全局变量,填实参只为临时调用这个函数,初次显示图片,//无实际对应关系,任意参数//on_Operation(g_nTrackbarNumber, NULL);//初始化回调函数并执行//on_Operation(g_nStructElementSize, 0);//初始化回调函数并执行,这样写貌似会专业点,正确性高//创建轨迹条,两个轨迹条位于一张效果图中,所以可以共用一个回调函数createTrackbar("腐蚀/膨胀", "【效果图】", &g_nTrackbarNumber,1, on_Operation);createTrackbar("内核尺寸", "【效果图】", &g_nStructElementSize, 21, on_Operation);//轮询获取按键信息,按下q键程序退出while(char(waitKey(1))!='q'){}return(0);
}//进行自定义的腐蚀/膨胀操作
void on_Operation(int,void *) {//获取自定义核Mat element = getStructuringElement(MORPH_RECT, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1), Point(g_nStructElementSize, g_nStructElementSize));//进行腐蚀或膨胀操作if (g_nTrackbarNumber == 0) {erode(g_srcImage, g_dstImage, element);}else {dilate(g_srcImage, g_dstImage, element);}//显示效果图imshow("【效果图】", g_dstImage);
}
运行结果截图:
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
