1. 目标
在本文中,将学习如何:
-
使用 OpenCV 函数cv::morphologyEx应用形态变换,例如:
- 开运算 Opening
- 闭运算 Closing
- 形态学梯度 Morphological Gradient
- 顶帽变换 Top Hat
- 底帽变换 Black Hat
2. 理论
-
笔记
下面的解释来自Bradski 和 Kaehler的《Learning OpenCV 》一书。
在之前的文章中,介绍了两个基本的形态学操作:
- 腐蚀
- 膨胀
基于这两个,可以对图像进行更复杂的转换。在这里,将简要讨论 OpenCV 提供的 5 种操作:
2.1 开运算 Opening
-
先对图像进行腐蚀然后进行膨胀。
-
用于移除小物体(假设物体在黑暗的前景中是明亮的)
-
例如,看看下面的例子。左边的图像是原始图像,右边的图像是应用开放变换后的结果。我们可以观察到小点已经消失了。
2.2 闭运算 Closing
- 先对图像的膨胀然后腐蚀。
-
用于去除孔洞(暗区)。
2.3 形态学梯度 Morphological Gradient
- 图像膨胀和腐蚀的差。
-
用于查找对象的轮廓,如下所示:
2.4 顶帽变换 Top Hat
- 它是输入图像与图像开运算的差。
2.5 底帽变换 Black Hat
- 它是图像闭运算和输入图像之间的差
3. 代码
本教程的代码如下所示。也可以在这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
Mat src, dst;
int morph_elem = 0;
int morph_size = 0;
int morph_operator = 0;
int const max_operator = 4;
int const max_elem = 2;
int const max_kernel_size = 21;
const char* window_name = "Morphology Transformations Demo";
void Morphology_Operations( int, void* );
int main( int argc, char** argv )
{
CommandLineParser parser( argc, argv, "{@input | baboon.jpg | input image}" );
src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
if (src.empty())
{
std::cout << "Could not open or find the image!\n" << std::endl;
std::cout << "Usage: " << argv[0] << " <Input image>" << std::endl;
return EXIT_FAILURE;
}
namedWindow( window_name, WINDOW_AUTOSIZE ); // Create window
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
&morph_elem, max_elem,
Morphology_Operations );
createTrackbar( "Kernel size:\n 2n +1", window_name,
&morph_size, max_kernel_size,
Morphology_Operations );
Morphology_Operations( 0, 0 );
waitKey(0);
return 0;
}
void Morphology_Operations( int, void* )
{
// Since MORPH_X : 2,3,4,5 and 6
int operation = morph_operator + 2;
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
morphologyEx( src, dst, operation, element );
imshow( window_name, dst );
}
4. 代码解释
-
看一下 C++ 程序的一般结构:
-
加载图像
-
创建一个窗口来显示形态学操作的结果
-
创建三个Trackbars供用户输入参数:
-
第一个 trackbar Operator返回要使用的形态操作类型(morph_operator)。
-
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
- 第二个 trackbar元素返回morph_elem,它表明我们的内核是哪种结构:
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
&morph_elem, max_elem,
Morphology_Operations );
- 最后的 trackbar Kernel Size返回要使用的内核的大小(morph_size)
createTrackbar( "Kernel size:\n 2n +1", window_name,
&morph_size, max_kernel_size,
Morphology_Operations );
- 每次移动任何滑块时,都会调用用户函数Morphology_Operations来执行新的形态学操作,并且它将根据当前trackbar值更新输出图像。
void Morphology_Operations( int, void* )
{
// Since MORPH_X : 2,3,4,5 and 6
int operation = morph_operator + 2;
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
morphologyEx( src, dst, operation, element );
imshow( window_name, dst );
}
可以观察到执行形态变换的关键函数是cv::morphologyEx。在此示例中,使用四个参数(将其余参数保留为默认值):
-
src:源(输入)图像
-
dst : 输出图像
-
operation:要执行的形态变换类型。请注意,我们有 5 种选择:
- 开运算:MORPH_OPEN:2
- 闭运算:MORPH_CLOSE:3
- 形态学梯度:MORPH_GRADIENT:4
- 顶帽:MORPH_TOPHAT:5
- 底帽:MORPH_BLACKHAT:6
如您所见,值的范围是 <2-6>,这就是我们将 (+2) 加到 Trackbar 输入的值的原因:
int operation = morph_operator + 2;- element:要使用的内核。使用函数cv::getStructuringElement来自定义结构。
5. 结果
-
编译上面的代码后,运行,将图像路径作为参数。使用图像的结果:狒狒.png:
-
这是显示窗口的两个快照。第一张图显示了使用带有跨内核的算子Opening后的输出。 第二张图片(右侧,显示了使用带有椭圆内核的Blackhat算子的结果。