上一节我们学习了形态学的基本操作,即图像的腐蚀与膨胀,那么,本节我们一起来学习更高级的形态学操作。
一、形态学操作 --- 开运算
开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。
其数学表达式如下:
开运算实例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//原始图和效果图
Mat g_srcImage, g_dstImage;
//0表示腐蚀erode, 1表示膨胀dilate
int g_nTrackbarNumer = 0;
//结构元素(内核矩阵)的尺寸
int g_nStructElementSize = 3;
int main()
{
//载入原图
g_srcImage = imread("lena.png");
if (!g_srcImage.data)
{
printf("image error!");
return -1;
}
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//进行初次腐蚀操作并显示效果图
namedWindow("【开操作效果图】");
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(2 * g_nStructElementSize + 1,
2 * g_nStructElementSize + 1),
Point(g_nStructElementSize,
g_nStructElementSize));
// 开操作 = 腐蚀+膨胀
erode(g_srcImage, g_dstImage,
element);
dilate(g_dstImage, g_dstImage,
element);
imshow("【开操作效果图】", g_dstImage);
waitKey(0);
return 0;
}
实验结果:
开操作原图(左)与效果图(右)2 、形态学操作 --- 闭运算
先膨胀后腐蚀的过程称为闭运算(Closing Operation)。
其数学表达式如下:
闭运算实例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//原始图和效果图
Mat g_srcImage, g_dstImage;
//0表示腐蚀erode, 1表示膨胀dilate
int g_nTrackbarNumer = 0;
//结构元素(内核矩阵)的尺寸
int g_nStructElementSize = 3;
int main()
{
//载入原图
g_srcImage = imread("lena.png");
if (!g_srcImage.data)
{
printf("image error!");
return -1;
}
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//进行初次腐蚀操作并显示效果图
namedWindow("【闭操作效果图】");
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(2 * g_nStructElementSize + 1,
2 * g_nStructElementSize + 1),
Point(g_nStructElementSize,
g_nStructElementSize));
// 闭运算= 膨胀+腐蚀
dilate(g_srcImage, g_dstImage,
element);
erode(g_dstImage, g_dstImage,
element);
imshow("【闭操作效果图】", g_dstImage);
waitKey(0);
return 0;
}
实验结果:
闭运算原图(左)与效果图(右)三、形态学梯度
形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差。
数学表达式如下:
形态学梯度对二值图像进行这一操作可以将团块(blob)的边缘突出出来,我们可以用形态学梯度来保留物体的边缘轮廓。
函数原型:
void morphologyEx(InputArray src,
OutputArray dst,
int op,
InputArray kernel,
Point anchor = Point(-1, -1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue =
morphologyDefaultBorderValue());
函数功能:
morphologyEx函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。
参数详解:
-
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F;
-
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型;
-
第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
-
MORPH_OPEN – 开运算(Opening operation)
-
MORPH_CLOSE – 闭运算(Closing operation)
-
MORPH_GRADIENT -形态学梯度(Morphological gradient)
-
MORPH_TOPHAT - “顶帽”(“Top hat”)
-
MORPH_BLACKHAT - “黑帽”(“Black hat“)
-
-
第四个参数,InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵);
-
第五个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心;
-
第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1;
-
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT;
-
第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。
实例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//原始图和效果图
Mat g_srcImage, g_dstImage;
//0表示腐蚀erode, 1表示膨胀dilate
int g_nTrackbarNumer = 0;
//结构元素(内核矩阵)的尺寸
int g_nStructElementSize = 3;
int main()
{
//载入原图
g_srcImage = imread("lena.png");
if (!g_srcImage.data)
{
printf("image error!");
return -1;
}
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//进行初次腐蚀操作并显示效果图
namedWindow("【形态学梯度效果图】");
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(2 * g_nStructElementSize + 1,
2 * g_nStructElementSize + 1),
Point(g_nStructElementSize,
g_nStructElementSize));
// 形态学梯度
morphologyEx(g_srcImage, g_dstImage,
MORPH_GRADIENT, element);
imshow("【形态学梯度效果图】",
g_dstImage);
waitKey(0);
return 0;
}
实验结果:
形态学梯度原图(左)与效果图(右)四、形态学操作 --- 顶帽
顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与上文刚刚介绍的“开运算“的结果图之差,数学表达式如下:
顶帽因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
实例--核心代码:
//获取自定义核
Mat element = getStructuringElement(
MORPH_RECT,
Size(2 * g_nStructElementSize + 1,
2 * g_nStructElementSize + 1),
Point(g_nStructElementSize,
g_nStructElementSize));
// 形态学梯度
morphologyEx(g_srcImage, g_dstImage,
MORPH_TOPHAT, element);
实验结果:
顶帽原图(左)与效果图(右)五、形态学操作 --- 黑帽
黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:
黑帽黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。
所以,黑帽运算用来分离比邻近点暗一些的斑块。
实例 - 核心代码:
Mat element = getStructuringElement(
MORPH_RECT,
Size(2 * g_nStructElementSize + 1,
2 * g_nStructElementSize + 1),
Point(g_nStructElementSize,
g_nStructElementSize));
// 形态学梯度
morphologyEx(g_srcImage, g_dstImage,
MORPH_BLACKHAT, element);
实验结果:
黑帽原图(左)与效果图(右)六、综合实例
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//原始图和效果图
Mat g_srcImage, g_dstImage;
//元素结构的形状
int g_nElementShape = MORPH_RECT;
//变量接收的TrackBar位置参数
int g_nMaxIterationNum = 10;
int g_nOpenCloseNum = 0;
int g_nErodeDilateNum = 0;
int g_nTopBlackHatNum = 0;
//开/闭运算回调函数
static void on_OpenClose(int, void*);
//腐蚀/膨胀回调函数
static void on_ErodeDilate(int, void*);
//顶帽/黑帽回调函数
static void on_TopBlackHat(int, void*);
//帮助文字显示
static void ShowHelpText();
int main()
{
ShowHelpText();
//载入原图
g_srcImage = imread("lena.png");
if (!g_srcImage.data)
{
printf("image error!\n");
return -1;
}
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//创建三个窗口
namedWindow("【开运算/闭运算】", 1);
namedWindow("【腐蚀/膨胀】", 1);
namedWindow("【顶帽/黑帽】", 1);
//参数赋值
g_nOpenCloseNum = 9;
g_nErodeDilateNum = 9;
g_nTopBlackHatNum = 2;
//分别为三个窗口创建滚动条
createTrackbar("迭代值",
"【开运算/闭运算】",
&g_nOpenCloseNum,
g_nMaxIterationNum * 2 + 1,
on_OpenClose);
createTrackbar("迭代值",
"【腐蚀/膨胀】",
&g_nErodeDilateNum,
g_nMaxIterationNum * 2 + 1,
on_ErodeDilate);
createTrackbar("迭代值",
"【顶帽/黑帽】",
&g_nTopBlackHatNum,
g_nMaxIterationNum * 2 + 1,
on_TopBlackHat);
//轮询获取按键信息
while (1)
{
int c;
//执行回调函数
on_OpenClose(g_nOpenCloseNum, 0);
on_ErodeDilate(g_nErodeDilateNum, 0);
on_TopBlackHat(g_nTopBlackHatNum, 0);
//获取按键
c = waitKey(0);
//按下键盘按键Q或者ESC,程序退出
if ((char)c == 'q' || (char)c == 27)
break;
//按下键盘按键1,使用椭圆(Elliptic)
//结构元素结构元素MORPH_ELLIPSE
//键盘按键1的ASII码为49
if ((char)c == 49)
g_nElementShape = MORPH_ELLIPSE;
//按下键盘按键2,使用矩形(Rectangle)
//结构元素MORPH_RECT
//键盘按键2的ASII码为50
else if ((char)c == 50)
g_nElementShape = MORPH_RECT;
//按下键盘按键3,使用十字形
//结构元素MORPH_CROSS
//键盘按键3的ASII码为51
else if ((char)c == 51)
g_nElementShape = MORPH_CROSS;
//按下键盘按键space,在矩形、
//椭圆、十字形结构元素中循环
else if ((char)c == ' ')
g_nElementShape = (g_nElementShape + 1) % 3;
}
return 0;
}
//开运算/闭运算
static void on_OpenClose(int, void*)
{
//偏移量的定义
int offset = g_nOpenCloseNum -
g_nMaxIterationNum;
//偏移量绝对值
int Absolute_offset = offset > 0 ?
offset : -offset;
//自定义核
Mat element = getStructuringElement(
g_nElementShape,
Size(Absolute_offset * 2 + 1,
Absolute_offset * 2 + 1),
Point(Absolute_offset,
Absolute_offset));
//进行操作
if (offset < 0)
morphologyEx(g_srcImage, g_dstImage,
MORPH_OPEN, element);
else
morphologyEx(g_srcImage, g_dstImage,
MORPH_CLOSE, element);
//显示图像
imshow("【开运算/闭运算】", g_dstImage);
}
//腐蚀/膨胀
static void on_ErodeDilate(int, void*)
{
//偏移量的定义
//偏移量
int offset = g_nErodeDilateNum -
g_nMaxIterationNum;
//偏移量绝对值
int Absolute_offset = offset > 0 ?
offset : -offset;
//自定义核
Mat element = getStructuringElement(
g_nElementShape,
Size(Absolute_offset * 2 + 1,
Absolute_offset * 2 + 1),
Point(Absolute_offset,
Absolute_offset));
//进行操作
if (offset < 0)
erode(g_srcImage, g_dstImage,
element);
else
dilate(g_srcImage, g_dstImage,
element);
//显示图像
imshow("【腐蚀/膨胀】", g_dstImage);
}
//顶帽运算/黑帽运算
static void on_TopBlackHat(int, void*)
{
//偏移量的定义
//偏移量
int offset = g_nTopBlackHatNum -
g_nMaxIterationNum;
//偏移量绝对值
int Absolute_offset = offset > 0 ?
offset : -offset;
//自定义核
Mat element = getStructuringElement(
g_nElementShape,
Size(Absolute_offset * 2 + 1,
Absolute_offset * 2 + 1),
Point(Absolute_offset,
Absolute_offset));
//进行操作
if (offset < 0)
morphologyEx(g_srcImage,
g_dstImage,
MORPH_TOPHAT,
element);
else
morphologyEx(g_srcImage,
g_dstImage,
MORPH_BLACKHAT,
element);
//显示图像
imshow("【顶帽/黑帽】", g_dstImage);
}
//输出一些帮助信息
static void ShowHelpText()
{
//输出一些帮助信息
printf("\n\n\n\t请调整滚动条观察图像效果~\n\n");
printf("\n\n\t按键操作说明: \n\n"
"\t\t键盘按键【ESC】或者【Q】- 退出程序\n"
"\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n"
"\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n"
"\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n"
"\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n"
);
}
实验结果:
综合实例好了,今天OpenCV形态学学习到这里就结束了,喜欢的朋友可以给我点个赞!!!
网友评论