OpenCV形态学操作——膨胀与腐蚀
一、学习目标
- 了解膨胀和腐蚀的原理
- 使用OpenCV的相关API实现膨胀和腐蚀
- 膨胀与腐蚀实例
二、原理简介
1、形态学操作
以下部分原理介绍参考博客 友情链接
形态学一般指生物学中研究动物和植物结构的一个分支。用数学形态学(也称图像代数)表示以形态为基础对图像进行分析的数学工具。基本思想是用具有一定形态的结构元素去度量和提取图像中的对应形状以达到对图像分析和识别的目的。形态学图像处理的数学基础和所用语言是集合论。形态学图像处理的应用可以简化图像数据,保持它们基本的形状特性,并除去不相干的结构。
形态学图像处理的基本运算有:膨胀、腐蚀、开操作和闭操作,击中与击不中变换,TOP-HAT变换,黑帽变换等。
形态学的应用包括消除噪声、边界提取、区域填充、连通分量提取、凸壳、细化、粗化等;分割出独立的图像元素,或者图像中相邻的元素;求取图像中明显的极大值区域和极小值区域;求取图像梯度等。
图像的形态学操作中,有一个很重要的因素就是结构元素。结构元素一般来说是由元素为1或者0的矩阵组成。结构元素为1的区域定义了图像的领域,领域内的像素在进行膨胀和腐蚀等形态学操作时要进行考虑。如提取水平线或者竖直线需要的水平和竖直结构元素,后面进行直线提取的时候会用到。
一般来说,二维或者平面结构的结构元素要比处理的图像小得多。结构元素的中心像素,即结构元素的原点,与输入图像中感兴趣的像素值(即要处理的像素值)相对应。三维的结构元素使用0和1来定义x-y平面中结构元素的范围,使用高度值定义第三维。
OpenCV使用cv::getStructuringElement函数获取结构元素,其函数原型为:
Mat cv::getStructuringElement( int shape,
Size ksize,
Point anchor = Point(-1,-1) )
- 参数 shape: 可以是MorphShapes之一的元素形状
- 参数 ksize: 结构元素的大小
- 参数 anchor: 结构元素内的锚定位置。默认值(−1,−1)表示锚点位于中心位置。注意,只有十字形结构元素的形状取决于锚的位置。在其他情况下,锚点只是调节形态操作的结果被移动的程度
该函数构造并返回结构化元素,这些元素可以被进一步传递给腐蚀、膨胀或morphologyEx。但您也可以自己构造任意的二进制掩码,并将其用作结构元素。
MorphShapes的取值可以是下表中的任意一种:
参数取值 | 取值说明 |
---|---|
MORPH_RECT | 矩形结构元素,即 Eij = 1 |
MORPH_CROSS | 十字形的结构元素,即与锚点同行列的元素Eij =1,其余元素为0,见下面绘图示例 |
MORPH_ELLIPSE | 椭圆结构元素,即内接在矩形矩形中的一个已填充的椭圆 |
2、膨胀(Dilate)
膨胀是腐蚀运算的对偶运算,其作用是在结构元素的约束下将与目标区域相接触的背景合并到该目标物中,使目标边界向外部扩张,物体的面积增大了相应数量的点。
膨胀操作包括一个图像A与某个核B(结构元素)的卷积,核B可以有任何形状或大小,通常是一个正方形或圆形。核B有一个确定的定位点(锚点anchor),通常是核的中心。当核B在图像上扫描时,我们计算与B重叠下的最大像素值,用该最大像素值替换锚点位置的图像像素。在图像的像素表示中,像素值越接近0,则越黑,体现为背景,进行膨胀操作即是一个部分前景像素替换背景像素的操作,这个取最大值的操作会导致图像中的明亮区域“增长”(因此得名膨胀)。
用通俗点的语言讲就是给你一个模板形状,你拿着它去图像上滑动,找出该形状覆盖下的最大值替换掉原像素值。绘图举例如下:
可见,膨胀使数字8几乎胖了一圈,这是膨胀一次的效果,可见女孩子可千万别膨胀。
OpenCV使用函数cv::dilate实现膨胀,其原型如下:
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() )
- 参数 src: 输入图像;通道的数量可以是任意的,但是深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一
- 参数 dst: 与src相同大小和类型的输出图像
- 参数 kernel: 用于膨胀的结构单元;如果elemenat=Mat(),则使用一个3×3的矩形结构元素(即根据需要自行构建结构元素),也可以使用getStructuringElement创建结构元素
- 参数 anchor: 锚点在结构元素中的位置;默认值(-1,-1)表示锚位于结构元素的中心
- 参数 iterations: 施加膨胀的次数,默认施加一次
- 参数 borderType: 图像边界像素向外扩展的方法,见BorderTypes。不支持BORDER_WRAP。默认为BORDER_CONSTANT,表示使用边界像素进行扩展
- 参数 borderValue: 边界为常量时的边界值,使用函数 morphologyDefaultBorderValue 返回膨胀和腐蚀的“Magic”边界值。它会自动转换为标量::all(-DBL_MAX)进行膨胀
本函数支持就地模式,膨胀可以应用多次(迭代)。对于多通道图像,每个通道都是独立处理的。
应用实例:
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
string fileName = samples::findFile("pic1.png");
Mat src = imread(fileName, IMREAD_GRAYSCALE);
if (!src.data)
{
cout << "failed to load image: " << fileName << endl;
system("pause");
return EXIT_FAILURE;
}
// 定义结构元素,大小为3*3,形状为矩形
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat dst;
// 使用函数dilate进行膨胀运算,迭代2次
dilate(src, dst, kernel, Point(-1, -1), 2);
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 100, 200);
imshow("Original", src);
namedWindow("Dilate", WINDOW_AUTOSIZE);
moveWindow("Dilate", src.cols, 0);
imshow("Dilate", dst);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
3、腐蚀(Erode)
腐蚀是一种在结构元素约束下消除目标图形的部分边界点,使其边界向内部收缩的的算法,具有收缩目标区域的作用。
腐蚀是膨胀的对偶操作。它计算给定核覆盖像素下的局部最小值。当核B在图像上扫描时,我们计算B重叠的最小像素值,用该最小值替换锚点下的图像像素,即相当于用背景像素替换部分前景像素,所以称为腐蚀。
腐蚀的形象化例子如下图所示:
可见,即使使用相同的结构元素,一张图像经过膨胀后再使用腐蚀,还是回不到原图。这告诉我们生活中有些东西是需要小心珍惜的,错过了就不存在了。
OpenCV使用函数cv::erode实现腐蚀操作,其函数原型为:
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() )
- 参数 src: 输入图像;通道的数量可以是任意的,但是深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一
- 参数 dst: 与src相同大小和类型的输出图像
- 参数 kernel: 用于腐蚀的结构单元;如果elemenat=Mat(),则使用一个3×3的矩形结构元素(即根据需要自行构建结构元素),也可以使用getStructuringElement创建结构元素
- 参数 anchor: 锚点在结构元素中的位置;默认值(-1,-1)表示锚位于结构元素的中心
- 参数 iterations: 施加腐蚀的次数,默认施加一次
- 参数 borderType: 图像边界像素向外扩展的方法,见BorderTypes。不支持BORDER_WRAP。默认为BORDER_CONSTANT,表示使用边界像素进行扩展
- 参数 borderValue: 边界为常量时的边界值,使用函数 morphologyDefaultBorderValue 返回膨胀和腐蚀的“Magic”边界值。它会自动转换为标量::all(-DBL_MAX)进行腐蚀
本函数支持就地模式。腐蚀可以应用多次(迭代)。对于多通道图像,每个通道都是独立处理的。
应用实例:
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
string fileName = samples::findFile("pic1.png");
Mat src = imread(fileName, IMREAD_GRAYSCALE);
if (!src.data)
{
cout << "failed to load image: " << fileName << endl;
system("pause");
return EXIT_FAILURE;
}
// 自定义结构元素
Mat kernel = Mat;
Mat dst;
erode(src, dst, kernel, Point(-1, -1), 2);
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 100, 200);
imshow("Original", src);
namedWindow("Erode", WINDOW_AUTOSIZE);
moveWindow("Erode", src.cols + 100, 200);
imshow("Erode", dst);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
三、膨胀与腐蚀的综合案例
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
// 提前声明处理腐蚀和膨胀操作的trackBar回调函数
void dealErode(int, void* img);
void dealDilate(int, void* img);
int main(int argc, char** argv)
{
// 指定图片路径,加载图片,判断加载是否成功
string fileName = samples::findFile("LinuxLogo.jpg");
Mat src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
fprintf(stderr, "failed to load image: %s.\n", fileName);
system("pause");
return EXIT_FAILURE;
}
int Elem = 0;
int ErodeSize = 0;
int DilateSize = 0;
// 建立两个窗口分别显示原图和膨胀腐蚀的效果
namedWindow("Original", WINDOW_AUTOSIZE);
namedWindow("Dilate or Erode", WINDOW_AUTOSIZE);
// 建立3个trackBar,一个用于确定结构元素的类型,三选1,一个用于确定腐蚀操作的Size,一个用于
// 确定膨胀操作的Size
createTrackbar("Type", "Dilate or Erode", &Elem, 2, 0, 0);
createTrackbar("Erode Size", "Dilate or Erode", &ErodeSize, 21, dealErode, (void*)&src);
createTrackbar("Dilate Size", "Dilate or Erode", &DilateSize, 21, dealDilate, (void*)&src);
// 移动窗体位置,显示窗体
moveWindow("Original", 200, 200);
moveWindow("Dilate or Erode", src.cols + 200, 200);
imshow("Original", src);
imshow("Dilate or Erode", src);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
// 处理膨胀trackBar点击的回调函数
void dealDilate(int, void* img)
{
Mat src = ((Mat*)img)->clone();
int DilateType = 0;
// 获得当前指定的结构元素形状
int barValue = getTrackbarPos("Type", "Dilate or Erode");
switch (barValue)
{
case 0:
DilateType = MORPH_RECT;
break;
case 1:
DilateType = MORPH_CROSS;
break;
case 2:
DilateType = MORPH_ELLIPSE;
break;
}
// 获取内核的大小
int DilateSize = getTrackbarPos("Dilate Size", "Dilate or Erode");
// 生成结构元素
Mat kernel = getStructuringElement(DilateType, Size(2 * DilateSize + 1, 2 * DilateSize + 1), Point(DilateSize, DilateSize));
Mat dst;
// 进行膨胀操作
dilate(src, dst, kernel);
imshow("Dilate or Erode", dst);
waitKey(0);
}
// 处理腐蚀trackBar点击的回调函数
void dealErode(int, void* img)
{
Mat src = ((Mat*)img)->clone();
int ErodeType = 0;
// 获得当前指定的结构元素形状
int barValue = getTrackbarPos("Type", "Dilate or Erode");
switch (barValue)
{
case 0:
ErodeType = MORPH_RECT;
break;
case 1:
ErodeType = MORPH_CROSS;
break;
case 2:
ErodeType = MORPH_ELLIPSE;
break;
}
// 获取内核的大小
int ErodeSize = getTrackbarPos("Erode Size", "Dilate or Erode");
// 生成结构元素
Mat kernel = getStructuringElement(ErodeType, Size(2 * ErodeSize + 1, 2 * ErodeSize + 1), Point(ErodeSize, ErodeSize));
Mat dst;
// 进行腐蚀操作
erode(src, dst, kernel);
imshow("Dilate or Erode", dst);
waitKey(0);
}