OpenCV图像处理系列八 --- 腐蚀与膨胀
今天,我们一起来学习图像形态学操作中两种最基本的形态学操作,即腐蚀与膨胀。
一、理论
数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论。其基本的运算包括:腐蚀和膨胀、开运算和闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换等。
腐蚀与膨胀主要实现以下功能:
- 消除噪声
- 分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素。
- 寻找图像中的明显的极大值区域或极小值区域
- 求出图像的梯度
1、腐蚀
腐蚀对图像求局部最小值;腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与卷积核(我们称之为B)进行卷积。
核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。
1)腐蚀的数学表达式
2)腐蚀效果演示
3)腐蚀的作用:
- 对象大小减小一个像素(3x3)
- 平滑对象边缘
- 弱化或者分割图像之间的半岛型连接
2、膨胀
膨胀对图像求局部的最大值;核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素;这样就会使图像中的高亮区域逐渐增长。
核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。
1)膨胀的数学表达式
膨胀的作用:
- 对象大小增加一个像素(3x3)
- 平滑对象边缘
- 减少或者填充对象之间的距离
小结:
可以看做膨胀是将白色区域扩大,腐蚀是将黑色区域扩大。
二、OpenCV腐蚀与膨胀API函数详解
1、腐蚀
1)函数原型:
void erode(InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1, -1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue =
morphologyDefaultBorderValue());
2)函数功能:
用指定的卷积核腐蚀图像,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出,支持就地(in-place)操作。
3)参数详解:
-
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一;
-
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型;
-
第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵);
-
第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它;
-
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1;
-
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT;
-
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。
第三个参数一般用getStructuringElement()函数获取卷积核的大小;getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。
其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:
- 矩形: MORPH_RECT
- 交叉形: MORPH_CROSS
- 椭圆形: MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1, -1),表示锚点位于中心;并且需要注意,十字形的element形状唯一依赖于锚点的位置;而在其他情况下,锚点只是影响了形态学运算结果的偏移。
4)实例:
#include <iostream>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
using namespace std;
using namespace cv;
int main()
{
//载入原图
Mat image = imread("lena.png");
if (image.empty())
{
printf("image error!");
return -1;
}
//创建窗口
namedWindow("【原图】腐蚀操作");
namedWindow("【效果图】腐蚀操作");
//显示原图
imshow("【原图】腐蚀操作", image);
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(15, 15));
Mat out;
//进行腐蚀操作
erode(image, out, element);
//显示效果图
imshow("【效果图】腐蚀操作", out);
waitKey(0);
return 0;
}
实验结果:
卷积核为15腐蚀的实验结果2、膨胀
1)函数原型:
void dilate(InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1, -1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue =
morphologyDefaultBorderValue());
2)函数功能:
使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出,支持就地(in-place)操作。
3)参数详解:
-
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一;
-
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型;
-
第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核;
-
第四个参数,Point类型的anchor,锚的位置,其有默认值( - 1, - 1),表示锚位于中心;
-
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1;
-
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT;
-
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。
4)实例:
int main()
{
//载入原图
Mat image = imread("3.png");
if (image.empty())
{
printf("image error!");
return -1;
}
//创建窗口
namedWindow("【原图】膨胀操作");
namedWindow("【效果图】膨胀操作");
//显示原图
imshow("【原图】膨胀操作", image);
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(5, 5));
Mat out;
//进行膨胀操作
dilate(image, out, element);
//显示效果图
imshow("【效果图】膨胀操作", out);
waitKey(0);
return 0;
}
实验结果:
卷积核为5膨胀的实验结果三、综合实例
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//原始图和效果图
Mat g_srcImage, g_dstImage;
//0表示腐蚀erode, 1表示膨胀dilate
int g_nTrackbarNumer = 0;
//结构元素(内核矩阵)的尺寸
int g_nStructElementSize = 3;
//膨胀和腐蚀的处理函数
void Process();
//回调函数
void on_TrackbarNumChange(int, void *);
//回调函数
void on_ElementSizeChange(int, void *);
int main()
{
//载入原图
g_srcImage = imread("lena.png");
if (!g_srcImage.data)
{
printf("image error!");
return -1;
}
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//进行初次腐蚀操作并显示效果图
namedWindow("【效果图】");
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(2 * g_nStructElementSize + 1,
2 * g_nStructElementSize + 1),
Point(g_nStructElementSize,
g_nStructElementSize));
erode(g_srcImage, g_dstImage, element);
imshow("【效果图】", g_dstImage);
//创建轨迹条
createTrackbar("腐蚀/膨胀",
"【效果图】",
&g_nTrackbarNumer,
1,
on_TrackbarNumChange);
createTrackbar("内核尺寸",
"【效果图】",
&g_nStructElementSize,
21,
on_ElementSizeChange);
waitKey(0);
return 0;
}
//进行自定义的腐蚀和膨胀操作
void Process()
{
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(2 * g_nStructElementSize + 1,
2 * g_nStructElementSize + 1),
Point(g_nStructElementSize,
g_nStructElementSize));
//进行腐蚀或膨胀操作
if (g_nTrackbarNumer == 0) {
erode(g_srcImage, g_dstImage, element);
}
else {
dilate(g_srcImage, g_dstImage, element);
}
//显示效果图
imshow("【效果图】", g_dstImage);
}
//腐蚀和膨胀之间切换开关的回调函数
void on_TrackbarNumChange(int, void *)
{
//腐蚀和膨胀之间效果已经切换,
//回调函数体内需调用一次Process函数,
//使改变后的效果立即生效并显示出来
Process();
}
//腐蚀和膨胀操作内核改变时的回调函数
void on_ElementSizeChange(int, void *)
{
//内核尺寸已改变,回调函数体内需
//调用一次Process函数,使改变后的
//效果立即生效并显示出来
Process();
}
实验结果:
用滑动条控制图像腐蚀膨胀以及内核大小好了,今天OpenCV学到这里就结束了,喜欢的朋友可以给我点个赞哦!!!