在计算机视觉的早期阶段,大量的形态学操作被开发出来。大多数是为特定目的或其他目的而开发的,其中一些在过去几年中发现更广泛的用途。实质上,所有的形态学操作都基于两个基本操作。下面将从这些开始,然后继续进行更复杂的操作,每个操作通常都是用简单的先例来定义的。
1、 膨胀和腐蚀
基本的形态学操作称为膨胀和侵蚀,它们出现在各种各样的图像处理中,例如去燥,分割单个元素以及连通图像中的不同元素。基于这两个基本操作的更复杂的形态学操作也可以用于查找图像中峰值或空洞,并且定义特定形式的图像梯度。
膨胀是一些图像与内核的卷积,其中任何给定的像素被内核覆盖的所有像素值的局部最大值替换。这是一个非线性操作的例子,大多数情况下,内核是一个方形内核,或者有时是一个圆形,其中心位置为锚点。膨胀的效果是在图像中填充。
腐蚀是相反的操作。腐蚀算子的作用等同于计算核区域内的局部最小值。图像形态学通常在阈值操作产生的二值图像上完成。然而,因为膨胀只是一个最大的算子,腐蚀只是一个最小的算子,所以形态学也可以用在灰度图像上。
一般而言,膨胀扩大了一个明亮的区域,腐蚀减少了这样一个明亮的区域。而且,膨胀会倾向于填充凹陷而腐蚀将趋于移除突起。当然,确切的结果将取决于内核,但只要内核既是凸的又是满的,其操作结果就与上面所述类似。
在OpenCV中,使用dilate ()和 erode ()函数来实现这些操作:
void cv::erode(
cv::InputArray src,
cv::OutputArray dst,
cv::InputArray element,
cv::Point anchor = cv::Point(-1,-1),
int iterations = 1,
int borderType = cv::BORDER_CONSTANT
const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue()
);
void cv::dilate(
cv::InputArray src,
cv::OutputArray dst,
cv::InputArray element,
cv::Point anchor = cv::Point(-1,-1),
int iterations = 1,
int borderType = cv::BORDER_CONSTANT
const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue()
);
erode()和dilate()都支持源图像和目标图像是相同的图像。第三个参数是内核,可以向其传递未初始化的数组Mat(),它会默认使用一个3×3内核,并在其中心定位锚。第四个参数是迭代次数。如果未设置,默认值1。borderType参数是边界类型,borderValue是borderType设置为BORDER_CONSTANT时将用于边缘外像素的值。
腐蚀操作通常用于消除图像中的“散斑”噪声。“散斑”被腐蚀,而包含视觉重要内容的较大区域则不受影响。通常使用膨胀操作来试图找出连通的分量。膨胀效用的产生是因为在很多情况下,由于噪音,阴影或其他类似的影响,大的区域可能会被分解成多个分量。小的膨胀会将这些分量融合成一个整体。
当OpenCV处理erode()函数时,在锚点处对齐时,某个点p的值被设置为内核覆盖的所有点的最小值; 对于dilate()运算,除了考虑max而不是min之外,其他都是相同的。可以用公式表示如下。
2、 形态学函数
当使用二值图像时,基本的腐蚀和膨胀操作通常就足够了。但是,当使用灰度图或彩色图像时,许多其他操作通常都会有所帮助。几个更有用的操作可以通过morphologyEx()函数来处理。
void cv::morphologyEx(
cv::InputArray src,
cv::OutputArray dst,
int op,
cv::InputArray element,
cv::Point anchor = cv::Point(-1,-1),
int iterations = 1,
int borderType = cv::BORDER_DEFAULT
const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue()
);
除了用dilate()和erode()函数看到的参数之外, morphologyEx()还有一个新的非常重要的参数。这个名为op的新参数是要完成的具体操作。表1列出了该参数的可能值。
MOP_OPEN
开运算
MOP_CLOSE
闭运算
MOP_GRADIENT
形态学梯度
MOP_TOPHAT
顶帽运算
MOP_BLACKHAT
黑帽运算
3 、开运算和闭运算
开运算和闭运算,实际上是腐蚀和膨胀操作的简单组合。在开运算的情况下,先腐蚀然后膨胀。开运算通常用于对布尔图像中的区域进行计数。例如,如果在显微镜载玻片上设置阈值的细胞图像,可能会在计数区域之前使用开运算来分离彼此相邻的细胞。
闭运算是先膨胀然后腐蚀。闭运算用于大多数更复杂的连接区域算法,以减少不需要部分的或噪声。通常首先执行腐蚀或闭运算操作以消除纯粹由噪声引起的元素,然后使用开运算操作来连接附近的大区域。尽管使用开运算或闭运算的最终结果与使用腐蚀或膨胀类似,但这些新操作倾向于更精确地保留连接区域的面积。
当用于非二值图像时,闭运算的最显着效果是消除值低于其邻域的孤立值,而开运算的效果是消除高于其邻域的孤立值。
4、 形态学梯度
对于形态梯度。 可以从一个公式开始,然后找出它的含义可能更容易:
正如上面公式中看到的那样,从膨胀的图像中减去腐蚀图像的效果是留下原始图像中对象边缘的表示。
使用灰度图像,该操作告诉我们图像亮度变化的速度。这就是为什么叫“形态学梯度”。当想要分割明亮区域的周长时,通常会使用形态学梯度。由于从区域的膨胀中减去了腐蚀,留下了完整的周边边缘,因此可以找到区域的完整周长。
5、 顶帽和黑帽
顶帽(Top Hat)和黑帽(Black Hat)运算符用于分割比其邻域值更亮或更暗的值。当分割显示亮度变化的对象的某些部分仅与其所连接的对象相关时,可以使用这些操作。这两个操作都是按照更原始的操作符来定义的,公式如下所示:
顶帽操作从原图中减去原图的膨胀图。因此,从原图中减去开运算的结果应该显示比原图的周围区域更小的区域;相反,黑帽运算符揭示了区域比周围的更暗。
6、 自定义内核
到目前为止,所考虑的内核始终是方形的,并且是3×3。如果用户需要比这更通用的内核,OpenCV允许用户创建自己的内核。在形态学的情况下,内核通常被称为结构化元素。
用户可以创建自己喜欢的形状,并将它用作dilate(), erode()或morphologyEx()等函数中的结构元素,这通常很必要。这就是getStructuringElement()的用途。
cv::Mat cv::getStructuringElement(
int shape,
cv::Size ksize,
cv::Point anchor = cv::Point(-1,-1)
);
第一个参数shape用于控制哪个基本形状将用于创建元素,而ksize和anchor分别指定元素的大小和锚点的位置。如果anchor保留其默认值,则getStructuringElement()将表示该锚点应该自动放置在内核中心。getStructuringElement()的形状主要有MORPH_RECT、MORPH_ELLIPSE、MORPH_CROSS三种。如果需要更复杂的结构元素,可以将任何Mat作为结构元素传递给形态学运算。例1演示了部分形态学操作,其它的操作都是类似。
例1 形态学操作示意
#include #include using namespace std;using namespace cv;int main(int argc, char** argv){ Mat srcImg = imread("E:/形态学.png", 0); namedWindow("原始图像", 0); imshow("原始图像", srcImg); //二值化 Mat thresholdImg; threshold(srcImg, thresholdImg, 100, 255, 0); //膨胀 Mat dilateImg; Mat kernel = Mat::ones(3, 3, CV_8UC1); dilate(thresholdImg, dilateImg, kernel); namedWindow("膨胀", 0); imshow("膨胀", dilateImg); //腐蚀 Mat erodeImg; erode(thresholdImg, erodeImg, kernel); namedWindow("腐蚀", 0); imshow("腐蚀", erodeImg); //开运算 morphologyEx(thresholdImg, openImg, MorphTypes::MORPH_OPEN, horizontalStructure); namedWindow("开运算", 0); imshow("开运算", openImg); //闭运算 Mat closeImg; morphologyEx(thresholdImg, closeImg, MorphTypes::MORPH_CLOSE, horizontalStructure); namedWindow("闭运算", 0); imshow("闭运算", closeImg); Mat verticalStructure = getStructuringElement(MORPH_RECT, Size(1, 5)); morphologyEx(thresholdImg, openImg, MorphTypes::MORPH_OPEN, verticalStructure); namedWindow("开运算2", 0); imshow("开运算2", openImg); waitKey(0); return 0;}