企业网站建设需求分析,百度网站验证是,个人网站做淘宝客如何备案,网站制作怎么添加图片OpenCV实战#xff08;11#xff09;——形态学变换详解0. 前言1. 腐蚀和膨胀运算1.1 腐蚀和膨胀基础1.2 使用形态学滤波器执行图像腐蚀和膨胀运算2. 开运算和闭运算2.1 使用形态学滤波器执行图像开运算和闭运算3. 形态学变换应用3.1 使用形态学滤波器检测边缘3.2 使用形态学…
OpenCV实战11——形态学变换详解0. 前言1. 腐蚀和膨胀运算1.1 腐蚀和膨胀基础1.2 使用形态学滤波器执行图像腐蚀和膨胀运算2. 开运算和闭运算2.1 使用形态学滤波器执行图像开运算和闭运算3. 形态学变换应用3.1 使用形态学滤波器检测边缘3.2 使用形态学滤波器检测边缘和角点4. 完整代码小结系列链接0. 前言
形态学变换( Morphological transformations )通常是在二值图像上执行、基于图像形状的操作。其具体的操作由核结构元素决定它决定了操作的性质。膨胀和腐蚀是形态学变换领域的两个基本算子此外开运算和闭运算是两个重要的运算它们可以通过上述两个运算(膨胀和腐蚀)获得。
1. 腐蚀和膨胀运算
1.1 腐蚀和膨胀基础
腐蚀 (Erosion) 和膨胀 (Dilation) 是最基本的形态学算子因此我们将首先介绍这两个基本算子。数学形态学的基本组成部分是结构元素结构元素可以简单地理解为定义原点(也称为锚点)的像素配置如下图中的正方形。应用形态学滤波器需要使用此结构元素检测图像的每个像素当结构元素的原点与给定像素对齐时它与图像的交集定义了一组像素在这些像素(下图中的九个阴影像素)上应用了特定的形态学操作。原则上结构元素可以是任何形状但出于效率考虑通常使用简单的形状例如正方形、圆形或以原点为中心的菱形。 1.2 使用形态学滤波器执行图像腐蚀和膨胀运算
由于形态学滤波器通常作用于二值图像因此我们使用通过阈值化创建的二值图像。然而由于在形态学中通常用白色像素值表示前景对象用黑色像素值表示背景对象因此我们需要对原二值图像求补(即用黑色像素值表示前景对象用白色像素值表示背景对象)。在形态学中下图可以认为是由阈值化创建的二值图像的补 与其他形态学滤波器一样腐蚀和膨胀两个滤波器对由结构元素定义的每个像素周围的像素集(或邻域)进行操作当应用于给定像素时结构元素的锚点与该像素位置对齐并且与结构元素相交的所有像素都包含在当前集合中。腐蚀使用在定义的像素集中找到的最小像素值替换当前像素。膨胀是腐蚀的互补算子它使用定义的像素集中找到的最大像素值替换当前像素。由于输入二值图像仅包含黑色 (0) 和白色 (255) 像素因此每个像素都被替换为白色或黑色像素。腐蚀和膨胀在 OpenCV 中分别使用 cv::erode 和 cv::dilate 函数实现。
(1) 首先读取二值图片
// 读取二值图像
cv::Mat binary;
binary cv::imread(binary.png, 0);(2) 应用 cv::erode 函数
// 腐蚀图像
cv::Mat eroded;
cv::erode(image, eroded, cv::Mat());应用腐蚀形态学滤波器后可以得到以下结果 (3) 使用 cv::dilate 膨胀图像
// 膨胀图像
cv::Mat dilated;
cv::dilate(image, dilated, cv::Mat());应用膨胀形态学滤波器后可以得到以下结果 接下来我们从这两个运算符的执行效果进行理解对于腐蚀算子如果结构元素置于给定像素位置与背景相接触(即相交集中的像素之一是黑色)则该像素将被赋值为黑色并归属至背景在膨胀算子中如果背景像素上的结构元素接触前景对象则该像素将被赋值为白色值。 这就解释了为什么在腐蚀图像中物体的尺寸会减小(形状被腐蚀)而一些小物体由于可能被认为是嘈杂的背景像素会被完全消除而膨胀运算后的物体将会变大并且物体内部的一些孔洞会被填满。默认情况下OpenCV 使用 3 x 3 方形结构元素当调用函数时第三个参数指定为空矩阵(即 cv::Mat() )时将使用此默认结构元素我们可以通过使用非零元素定义结构元素矩阵指定所需大小(和形状)的结构元素。例如我们可以使用以下代码定义 7 x 7 结构元素
cv::Mat element(7, 7, CV_8U, cv::Scalar(1));
cv::erode(image, eroded, element);在这种情况下效果更加明显如下图所示 我们可以简单的在图像上重复应用相同的结构元素cv::erode 和 cv::dilate 这两个函数都有一个可选参数来指定重复次数
// 腐蚀同一图像三次
cv::erode(image, eroded, cv::Mat(), cv::Point(-1, -1), 3);原点参数 cv::Point(-1,-1) 表示原点位于矩阵的中心(默认)原点也可以定义在结构元素的其他位置。使用以上代码得到的图像与我们使用 7 x 7 结构元素获得的图像相同。实际上腐蚀图像两次类似于腐蚀结构元素尺寸扩大的图像这一规律也适用于膨胀算子。 由于背景/前景的概念是相对的用结构元素腐蚀前景对象可也以看作是图像背景部分的膨胀这是腐蚀/膨胀算子的基本属性。换句话说
图像的腐蚀等价于互补图像膨胀的补图像的膨胀等价于互补图像腐蚀的补 虽然我们在本节主要介绍了如何将形态学滤波器应用于二值图像但这些滤波器同样也可以应用于灰度或彩色图像。 OpenCV 形态学函数支持就地处理即可以使用输入图像作为目标图像
cv::erode(image,image,cv::Mat());OpenCV 在函数内部创建所需的临时图像以使其正常工作。
2. 开运算和闭运算
2.1 使用形态学滤波器执行图像开运算和闭运算
上一节中我们介绍了两个基本的形态学算子——膨胀和腐蚀。利用这些基本算子我们可以定义其他运算符本节将介绍开运算和闭运算。为了应用高级形态学滤波器需要使用 cv::morphologyEx 函数。
(1) 要创建闭运算或开运算符必须创建一个 cv::Mat 元素用作开/闭运算核
cv::Mat element5(5, 5, CV_8U, cv::Scalar(1));(2) 创建另一个 cv::Mat 存储应用形态学运算符后的结果
cv::Mat closed;
cv::Mat opened;(3) 最后应用闭或开运算符。为了创建闭运算符我们使用 cv::MORPH_CLOSE 参数调用 cv::morphologyEx 函数
cv::morphologyEx(image, closed, // 输入和输出图像cv::MORPH_CLOSE, // 操作算子element5); // 结构元素如果我们使用二值图像作为输入结果如下图所示 (4) 使用 cv::MORPH_OPEN 作为参数调用 cv::morphologyEx 函数可以创建开运算
cv::morphologyEx(image, opened, cv::MORPH_OPEN, element5);应用形态学开运算可以得到以下图像 (5) 开和闭滤波器是根据基本腐蚀和膨胀操作定义的闭运算被定义为膨胀图像后腐蚀开运算被定义为腐蚀图像后膨胀。因此可以使用以下代码计算图像的闭
// 1. 膨胀原始图像
cv::Mat result;
cv::dilate(image, result, element5);
// 2. 腐蚀膨胀后的图像
cv::erode(result, result, element5);可以通过交换这两个函数的调用顺序得到开滤波器。在使用闭滤波器的结果图像中可以看到白色前景对象中的小孔被填充滤波器还会将几个相邻的对象连接在一起。实际上任何无法完全包含结构元素的过小的孔或间隙都将被滤波器消除。相反的开滤波器会消除图像中的小物体所有无法包含结构元素的过小对象都会被消除。 开/闭滤波器通常用于对象检测闭滤波器可以连接被错误分割成小块的对象而开滤波器可以去除由图像噪声引入的小斑点。因此根据具体应用可以按不同顺序使用它们。如果二值图像连续应用闭和开运算将得到一个仅显示场景中主要对象的图像如下图所示。如果我们希望优先过滤噪声也可以在闭滤波器之前应用开滤波器但这会消除一些碎片化对象 在图像上多次应用相同的开(或闭)运算符不会产生任何效果。实际上由于孔已被第一个开滤波器填充因此附加应用相同的滤波器不会对图像产生其他变化。在数学上这些运算符被称为幂等运算符。
3. 形态学变换应用
形态学滤波器也可用于检测图像中的特定特征。在本节中我们将学习如何检测灰度图像中的轮廓和角点。
3.1 使用形态学滤波器检测边缘
(1) 通过使用适当滤波器参数调用 cv::morphologyEx 函数提取要检测的图像的边缘
// 使用 3x3 结构元素获取梯度图像
cv::Mat result;
cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());
// 使用阈值获取二值图像
int threshold(80);
cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY);可以得到以下结果图像 理解形态学算子对灰度图像的影响时可以将图像视为拓扑浮雕其中灰度与高程(或海拔)相对应。从这个角度而言明亮的区域对应山脉而黑暗的区域对应山谷。此外由于边缘对应于暗像素和亮像素之间的快速过渡因此可以将其理解为陡峭的悬崖。如果在这样的地形上应用腐蚀算子最终将用邻域中的最低值替换每个像素从而降低其高度。因此随着山谷的扩大悬崖将被腐蚀。膨胀具有完全相反的效果也就是说悬崖将膨胀而山谷相应的会缩小。但在这两种情况下高原(即强度恒定的区域)均将保持相对不变。 基于此我们可以得到一种检测图像边缘(悬崖)的简单方法即计算膨胀和腐蚀图像之间的差。由于这两种变换后的得到的图像主要在与边缘位置不同因此相减将得到图像边缘。我们可以使用 cv::MORPH_GRADIENT 参数调用 cv::morphologyEx 函数完成以上操作。显然结构元素越大检测到的边缘就越宽这种边缘检测算子也称为 Beucher 梯度(之后的学习中将更详细地讨论图像梯度的概念)。通过简单地从膨胀图像中减去原始图像或从原始图像中减去图像图像也可以获得类似的结果但产生的边缘会更细。
3.2 使用形态学滤波器检测边缘和角点
(1) 为了使用形态学检测角点我们可以定义 MorphoFeatures 类
class MorphoFeatures {private:// 用于产生二值图像的阈值int threshold;// 用于角点检测的结构元素cv::Mat_uchar cross;cv::Mat_uchar diamond;cv::Mat_uchar square;cv::Mat_uchar x;(2) 使用形态学角点检测角点需要连续应用几个不同的形态学滤波器为此我们需要使用非方形结构元素实际上在构造函数中定义了四个不同的结构元素形状分别为正方形、菱形、十字形和 X 形为简单起见这些结构元素尺寸均为 5 x 5
public:MorphoFeatures() : threshold(-1), cross(5, 5), diamond(5, 5),square(5, 5), x(5, 5) {// 创建十字形结构元素cross 0, 0, 1, 0, 0,0, 0, 1, 0, 0,1, 1, 1, 1, 1,0, 0, 1, 0, 0,0, 0, 1, 0, 0;// 菱形结构元素diamond 0, 0, 1, 0, 0,0, 1, 1, 1, 0,1, 1, 1, 1, 1,0, 1, 1, 1, 0,0, 0, 1, 0, 0;// 方形结构元素square 1, 1, 1, 1, 1,1, 1, 1, 1, 1,1, 1, 1, 1, 1,1, 1, 1, 1, 1,1, 1, 1, 1, 1;// x形结构元素x 1, 0, 0, 0, 1,0, 1, 0, 1, 0,0, 0, 1, 0, 0,0, 1, 0, 1, 0,1, 0, 0, 0, 1;}(4) 在角点特征检测中顺序应用这些结构元素以获得结果角点图
// 角点检测
cv::Mat getCorners(const cv::Mat image) {cv::Mat result;// 膨胀cv::dilate(image, result, cross);// 腐蚀cv::erode(result, result, diamond);cv::Mat result2;// 膨胀cv::dilate(image, result2, x);// 腐蚀cv::erode(result2, result2, square);// 角点cv::absdiff(result2, result, result);applyThreshold(result);return result;
}(5) 在图像上检测角点
MorphoFeatures morpho;
morpho.setThreshold(35);
// 角点检测
cv::Mat corners;
corners morpho.getCorners(gray);
// 在图像中显示角点
morpho.drawOnImage(corners, gray);
cv::namedWindow(Corners on Image);
cv::imshow(Corners on Image, gray);在图像中检测到的角点显示为圆圈如下图所示 角点检测相较更为复杂因为它需要使用四种不同的结构元素。在 OpenCV 中并没有直接实现角点检测算子但我们可以通过定义和组合不同形状的结构元素实现。通过用两种不同的结构元素来膨胀和腐蚀图像实现图像闭运算。使用这些元素令图像中直线保持不变但不同结构元素也会影响角点处的边。以单个白色方块组成的简单图像为例介绍这种非对称闭操作的效果 上图中第一个方块是原始图像。当用十字形结构元素执行膨胀操作时方块边缘会被扩展除了十字形不碰到方形的角点如上图中间的方块所示。然后膨胀后的图像被菱形结构元素腐蚀腐蚀操作会令大多数边缘恢复到原来的位置但由于方形的角没有被膨胀所以其会被进一步腐蚀如上图中最右边的正方形所示可以看到其角已经消失。使用 X 形和方形结构元素重复以上过程由于 X 形是十字形结构元素的旋转版本因此可以捕获 45 度方向的角。最后对两个结果进行差分便可以提取角点特征。
4. 完整代码
头文件 (morphoFeatures.h) 完整代码如下
#if !defined MFEATURES
#define MFEATURES#include opencv2/core/core.hpp
#include opencv2/imgproc/imgproc.hppclass MorphoFeatures {private:// 用于产生二值图像的阈值int threshold;// 用于角点检测的结构元素cv::Mat_uchar cross;cv::Mat_uchar diamond;cv::Mat_uchar square;cv::Mat_uchar x;public:MorphoFeatures() : threshold(-1), cross(5, 5), diamond(5, 5),square(5, 5), x(5, 5) {// 创建十字形结构元素cross 0, 0, 1, 0, 0,0, 0, 1, 0, 0,1, 1, 1, 1, 1,0, 0, 1, 0, 0,0, 0, 1, 0, 0;// 菱形结构元素diamond 0, 0, 1, 0, 0,0, 1, 1, 1, 0,1, 1, 1, 1, 1,0, 1, 1, 1, 0,0, 0, 1, 0, 0;// 方形结构元素square 1, 1, 1, 1, 1,1, 1, 1, 1, 1,1, 1, 1, 1, 1,1, 1, 1, 1, 1,1, 1, 1, 1, 1;// x形结构元素x 1, 0, 0, 0, 1,0, 1, 0, 1, 0,0, 0, 1, 0, 0,0, 1, 0, 1, 0,1, 0, 0, 0, 1;}void setThreshold(int t) {threshold t;}int getThreshold() {return threshold;}void applyThreshold(cv::Mat result) {if (threshold 0) {cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY_INV);}}// 直线检测cv::Mat getEdges(const cv::Mat image) {cv::Mat result;cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());applyThreshold(result);return result;}// 角点检测cv::Mat getCorners(const cv::Mat image) {cv::Mat result;// 膨胀cv::dilate(image, result, cross);// 腐蚀cv::erode(result, result, diamond);cv::Mat result2;// 膨胀cv::dilate(image, result2, x);// 腐蚀cv::erode(result2, result2, square);// 角点cv::absdiff(result2, result, result);applyThreshold(result);return result;}void drawOnImage(const cv::Mat binary, cv::Mat image) {cv::Mat_uchar::const_iterator it binary.beginuchar();cv::Mat_uchar::const_iterator itend binary.enduchar();for (int i0; it!itend; it, i) {if (!*it) {cv::circle(image, cv::Point(i%image.step, i/image.step),5,cv::Scalar(255, 0, 0));}}}
};#endif主文件 (morphology.cpp) 完整代码如下所示
#include opencv2/core/core.hpp
#include opencv2/imgproc/imgproc.hpp
#include opencv2/highgui/highgui.hppint main() {// 读取图像cv::Mat image cv::imread(binary.png);if (!image.data) return 0;cv::namedWindow(Image);cv::imshow(Image, image);// 腐蚀图像cv::Mat eroded;cv::erode(image, eroded, cv::Mat());cv::namedWindow(Eroded Image);cv::imshow(Eroded Image, eroded);// 膨胀图像cv::Mat dilated;cv::dilate(image, dilated, cv::Mat());cv::namedWindow(Dilated Image);cv::imshow(Dilated Image,dilated);// 使用一个较大的结构元素腐蚀图像cv::Mat element(7, 7, CV_8U, cv::Scalar(1));cv::erode(image, eroded, element);cv::namedWindow(Eroded Image (7x7));cv::imshow(Eroded Image (7x7),eroded);// 腐蚀同一图像三次cv::erode(image, eroded, cv::Mat(), cv::Point(-1, -1), 3);cv::namedWindow(Eroded Image (3 times));cv::imshow(Eroded Image (3 times),eroded);// 图像闭运算cv::Mat element5(5, 5, CV_8U, cv::Scalar(1));cv::Mat closed;cv::morphologyEx(image, closed, // 输入和输出图像cv::MORPH_CLOSE, // 操作算子element5); // 结构元素cv::namedWindow(Closed Image);cv::imshow(Closed Image,closed);// 图像开运算cv::Mat opened;cv::morphologyEx(image, opened, cv::MORPH_OPEN, element5);cv::namedWindow(Opened Image);cv::imshow(Opened Image,opened);// 闭运算分解// 1. 膨胀原始图像cv::Mat result;cv::dilate(image, result, element5);// 2. 腐蚀膨胀后的图像cv::erode(result, result, element5);cv::namedWindow(Closed Image (2));cv::imshow(Closed Image (2), result);// 闭-开cv::morphologyEx(image, image, cv::MORPH_CLOSE, element5);cv::morphologyEx(image, image, cv::MORPH_OPEN, element5);cv::namedWindow(Closed|Opened Image);cv::imshow(Closed|Opened Image, image);cv::imwrite(binaryGroup.png, image);// 开-闭image cv::imread(binary.png);cv::morphologyEx(image, image, cv::MORPH_OPEN, element5);cv::morphologyEx(image, image, cv::MORPH_CLOSE, element5);cv::namedWindow(Opened|Closed Image);cv::imshow(Opened|Closed Image,image);// 读取输入图像image cv::imread(1.png, 0);if (!image.data) return 0;// 使用 3x3 结构元素获取梯度图像cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());cv::namedWindow(Edge Image);cv::imshow(Edge Image, 255 - result);// 使用阈值获取二值图像int threshold(80);cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY);cv::namedWindow(Thresholded Edge Image);cv::imshow(Thresholded Edge Image, result);// 使用 3x3 结构元素获取梯度图像cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());// 读取输入图像image cv::imread(10.png, 0);if (!image.data) return 0;cv::transpose(image, image);cv::flip(image, image, 0);// 使用 7x7 结构元素应用黑色顶帽变换 cv::Mat element7(7, 7, CV_8U, cv::Scalar(1));cv::morphologyEx(image, result, cv::MORPH_BLACKHAT, element7);cv::namedWindow(7x7 Black Top-hat Image);cv::imshow(7x7 Black Top-hat Image, 255-result);cv::waitKey();return 0;
}主文件 (cornersDetection.cpp) 完整代码如下所示
#include opencv2/core/core.hpp
#include opencv2/highgui/highgui.hpp#include morphoFeatures.hint main() {cv::Mat image cv::imread(3.png);cv::Mat gray;cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);cv::namedWindow(Image);cv::imshow(Image, gray);MorphoFeatures morpho;morpho.setThreshold(35);// 角点检测cv::Mat corners;corners morpho.getCorners(gray);// 在图像中显示角点morpho.drawOnImage(corners, gray);cv::namedWindow(Corners on Image);cv::imshow(Corners on Image, gray);cv::waitKey();return 0;
}小结
形态学变换( Morphological transformations )通常是在二值图像上执行、基于图像形状的操作。本节首先介绍了基本形态学算子腐蚀与膨胀并介绍了根据基本算子组合得到的开运算与闭运算最后利用形态学算子实现了经典的边缘/角点检测图像处理应用。
系列链接
OpenCV实战1——OpenCV与图像处理基础 OpenCV实战2——OpenCV核心数据结构 OpenCV实战3——图像感兴趣区域 OpenCV实战4——像素操作 OpenCV实战5——图像运算详解 OpenCV实战6——OpenCV策略设计模式 OpenCV实战7——OpenCV色彩空间转换 OpenCV实战8——直方图详解 OpenCV实战9——基于反向投影直方图检测图像内容 OpenCV实战10——积分图像详解