图像形态学中两种最基本的操作就是对图形的腐蚀和膨胀,可以说,形态学中的中高级操作都是建立在这两种操作之上。通过这两种基本的运算可以去除图像中的噪声,分割出独立的区域或者将两个区域连接在一起。
关于图像腐蚀和膨胀的概念,不严谨的简单理解是:经过腐蚀和膨胀操作后最终的效果是对图像中物体的边界进行一层一层的腐蚀和膨胀操作,这个边界包括内边界和外边界。
严谨的理解是这样的:图像的腐蚀和膨胀实际上是用某种形状的窗去遍历图像中的每一个元素,并用这个形状中的最大值或最小值做为锚点的值(锚点的概念可参考我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/124173092)。若取的是最大值,则是膨胀操作,若取的是最小值,则是腐蚀操作。这里某种形状可以是十字形、菱形、矩形、X形等。这里我补充说明一下,其实所有的窗都是矩形的,那么怎样定义不同的形状呢?是这样操作的:通过把矩形窗中的元素置为1形成不同的形状。更详细的说明可见本文下面对函数getStructuringElement()参数shape的介绍。
OpenCV提供了函数erode()和函数dilate()分别用于图像形态学的腐蚀与膨胀操作,下面分别介绍。
函数dilate()的数学运算表达式如下:
dst ( x , y ) = min ( x ′ , y ′ ) : element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=min(x′,y′):element(x′,y′)=0src(x+x′,y+y′)
其中(x’,y’)是窗中的元素坐标。
函数erode()的数学运算表达式如下:
dst ( x , y ) = max ( x ′ , y ′ ) : element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=max(x′,y′):element(x′,y′)=0src(x+x′,y+y′)
其中(x’,y’)是窗中的元素坐标。
图像形态学的腐蚀操作函数erode()的原型如下:
void erode( InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
图像形态学的腐蚀操作函数dilate()的原型如下:
void 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中的一种。每个通道将被单独处理。
ds—为输出图像,尺寸和数据类型与输入图像一致。
kernel—erode()使用某种形状的窗侵蚀一个图像,dilate()也使用某种形状的窗对图像进行膨胀。参数kernel就表示这个窗。显然这个窗越大,显然每次对图像的腐蚀或膨胀越多,反之亦然。有下面两种构造这个窗的方法。有下面两种构造这个窗的方法。
①使用函数getStructuringElement()来获取,函数getStructuringElement()的原型如下:
Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
函数getStructuringElement()的参数意义如下:
shape—窗的形状,MORPH_RECT为矩形,矩形中的每一个元素的值为1;MORPH_CROSS代表十字形结构,十字形的长和宽由ksize定,十字形上的元素值也为1;MORPH_ELLIPSE为椭圆,椭圆中的每一个元素的值也为1,椭圆内切于参数ksize定义的矩形大小。可以结合下面这个表更好理解。
ksize—其意义见对参数shape的讲解。
anchor—当shape为十字形时,这个参数表示十字线的横线和竖线的交点位置。
②自定义kernel窗,此种方法的示例可以参见下面这篇博文:
https://blog.csdn.net/wenhao_ir/article/details/51915869
继续讲函数erode()和函数dilate()的参数意义。
anchor—锚点位置。
iterations—表示迭代次数,即对图像做几次腐蚀和膨胀。
borderType—边界扩展处理方式,详情见 https://blog.csdn.net/wenhao_ir/article/details/124177989
borderValue—关于这个值,也请参见 https://blog.csdn.net/wenhao_ir/article/details/124177989
下面上使用函数erode()和函数dilate()进行腐蚀和膨胀处理的示例代码:
代码中用到的图像下载链接:https://pan.baidu.com/s/1Acoq3mR2RK0KHtYBCtq9qg?pwd=708g
//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理需求也可联系博主
//图像处理技术交流QQ群 271891601
//OpenCV版本:3.0
//VS版本:2013
#include
#include
#include
#include
using namespace cv;
int main()
{
//载入原图
Mat image = imread("F:/material/images/P0046-erode_dilate-02.bmp",0);
//显示原图
imshow("原图的灰度图", image);
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat out1, out2;
//进行膨胀操作
dilate(image, out1, element);
//进行腐蚀操作
erode(image, out2, element,cv::Point(-1,-1),2);
//显示效果图
imshow("膨胀操作效果图", out1);
imshow("腐蚀操作效果图", out2);
waitKey(0);
return 0;
}
代码运行结果如下图所示:
上面的示例代码是对图像的腐蚀操作和膨胀操作的直观认识,接下来,我们再通过Python示例代码来从数据运算上看一看图像的腐蚀操作和闭操作是怎么回事。
先来探究膨胀操作:
探究膨胀操作的示例代码如下:
# 博主微信/QQ 2487872782
# 有问题可以联系博主交流
# 有图像处理需求也可联系博主
# 图像处理技术交流QQ群 271891601
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# OpenCV的版本为4.1
import cv2 as cv
import numpy as np
A = np.zeros((9, 9), dtype='uint8')
A[3:6, 3:6] = 5
A[4, 4] = 9
structure1 = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
structure2 = cv.getStructuringElement(cv.MORPH_CROSS, (3, 3))
dilate_out1 = cv.dilate(A, structure1)
dilate_out2 = cv.dilate(A, structure2)
从以上运行结果可以看出,膨胀操作的运算规则的确是我上面介绍的那样。为了避免大家来回滚动页面,我再将运算规则摘录如下:
图像的腐蚀和膨胀实际上是用某种形状的窗去遍历图像中的每一个元素,并用这个形状中的最大值或最小值做为锚点的值(锚点的概念可参考我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/124173092)。若取的是最大值,则是膨胀操作,若取的是最小值,则是腐蚀操作。这里某种形状可以是十字形、菱形、矩形、X形等。这里我补充说明一下,其实所有的窗都是矩形的,那么怎样定义不同的形状呢?是这样操作的:通过把矩形窗中的元素置为1形成不同的形状。更详细的说明可见本文对函数getStructuringElement()参数shape的介绍。
函数dilate()的数学运算表达式如下:
dst ( x , y ) = min ( x ′ , y ′ ) : element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=min(x′,y′):element(x′,y′)=0src(x+x′,y+y′)
其中(x’,y’)是窗中的元素坐标。
再来探究腐蚀操作。
探究腐蚀操作的示例代码如下:
运行结果如下:
从以上运行结果可以看出,腐蚀操作的运算规则的确是我上面介绍的那样。为了避免大家来回滚动页面,我再将运算规则摘录如下:
图像的腐蚀和膨胀实际上是用某种形状的窗去遍历图像中的每一个元素,并用这个形状中的最大值或最小值做为锚点的值(锚点的概念可参考我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/124173092)。若取的是最大值,则是膨胀操作,若取的是最小值,则是腐蚀操作。这里某种形状可以是十字形、菱形、矩形、X形等。这里我补充说明一下,其实所有的窗都是矩形的,那么怎样定义不同的形状呢?是这样操作的:通过把矩形窗中的元素置为1形成不同的形状。更详细的说明可见本文对函数getStructuringElement()参数shape的介绍。
函数erode()的数学运算表达式如下:
dst ( x , y ) = max ( x ′ , y ′ ) : element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=max(x′,y′):element(x′,y′)=0src(x+x′,y+y′)
其中(x’,y’)是窗中的元素坐标。
知道了腐蚀和膨胀的具体运算过程后,我们便可以知道当参数shape取不同形状时,效果有什么区别。具体效果有什么不同,大家在理解以上运算过程后,便可以在脑海中想像了,这里博主就不再码字了。
扩展阅读:
图像的形态学开操作(开运算)和闭操作(闭运算)的概念和作用,并用OpenCV的函数morphologyEx()实现对图像的开闭操作
图像的形态学梯度运算(基本梯度、外部梯度、内部梯度、X方向梯度、Y方向梯度)的概念、作用以及相关的OpenCV示例代码
图像形态学操作之顶帽操作(TopHat)与黑帽操作(BlackHat)
详解图像形态学中的击中击不中变换操作(HMT),并提醒大家OpenCV4中的击中击不中变换操作是有问题的