膨胀和腐蚀运算的问题:

边缘形状发生了变化,膨胀发生了扩张,腐蚀发生了收缩
目标物体变形,对识别时的特征提取会造成影响

解决方法:

开操作:

形态学操作——开闭运算、顶帽底(黑)帽变换_计算机视觉
B对A的开操作就是先B对A腐蚀,紧接着用B对结果进行膨胀
形态学操作——开闭运算、顶帽底(黑)帽变换_计算机视觉_02
先腐蚀再膨胀的结果并不是恢复原状,而是会消除黏连部分,同时不影响其他部分的形状.
平滑物体的轮廓,断开较窄的狭颈并消除较细的突出。
效果:
形态学操作——开闭运算、顶帽底(黑)帽变换_opencv_03

闭操作:

形态学操作——开闭运算、顶帽底(黑)帽变换_opencv_04
B对A的闭操作就是先B对A膨胀,紧接着用B对结果进行腐蚀
形态学操作——开闭运算、顶帽底(黑)帽变换_黑帽_05
先膨胀再腐蚀的结果并不是恢复原状,而是填充小的裂缝、孔隙,且不影响形状.
平滑物体的轮廓,弥合较窄的间断和细小的沟壑,消除小的孔洞,填充轮廓中的断痕。
效果:
形态学操作——开闭运算、顶帽底(黑)帽变换_黑帽_06
更加详细的过程如下:
形态学操作——开闭运算、顶帽底(黑)帽变换_计算机视觉_07
除了开闭运算,黑帽顶帽运算也是形态学操作比较重要的操作。

顶帽变换:原图-灰度开运算(灰度腐蚀+灰度膨胀)

效果:
1、保留比结构元素小的部分
2、保留比周围环境亮的像素

底帽变换:灰度闭运算(灰度膨胀+灰度腐蚀)-原图 效果:

1、保留比结构元素小的部分
2、保留比周围环境暗的像素

解释:
顶帽处理使背景变得趋于一致了,前景和背景的对比度加深了
这是因为,在顶帽处理中当结构元素比前景目标物的大小
大的时候,腐蚀的步骤会选择周围比较暗的值代替比较亮的值
所以背景变暗了,同时前景被去掉了。再用原图和结果相减,就可以把背景去掉,把开运算中去掉的前景给保留下来
形态学操作——开闭运算、顶帽底(黑)帽变换_c++_08
形态学操作——开闭运算、顶帽底(黑)帽变换_c++_09

代码实现

#include <opencv2/opencv.hpp>
#include <iostream>
#include "windows.h"
#include <stdio.h>

using namespace cv;
using namespace std;

//*--------------------------【练习】形态学操作morphology大练习------------------------------------*/
//请调整滚动条观察图像效果
//按键操作说明 :
//键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环
//键盘按键【1】- 使用椭圆结构元素
//键盘按键【2】- 使用矩形结构元素
//键盘按键【3】- 使用十字形结构元素
Mat g_secImage, g_dstImage; //原图和效果图
int g_nElementShape = MORPH_RECT; //初始化元素结构形状

//变量接受的Trackbar值
int g_nMaxIterationMun = 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_TopBlakHat(int, void*); //回调函数
void ShowHelpText1();
void ShowHelpText2();
int main()
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); //字体为绿色
//载入原图
g_secImage = imread("D:\\opencv_picture_test\\形态学操作\\黑白.jpg");
//判断图像是否加载成功
if (g_secImage.empty())
{
cout << "图像加载失败!" << endl;
return -1;
}
else
cout << "图像加载成功!" << endl << endl;
//显示原图像
namedWindow("原图像", WINDOW_NORMAL); //定义窗口显示属性
imshow("原图像", g_secImage);
//创建三个窗口
namedWindow("【开运算/闭运算】", WINDOW_NORMAL);
namedWindow("【腐蚀/膨胀】", WINDOW_NORMAL);
namedWindow("【顶帽/黑帽】", WINDOW_NORMAL);

//参数赋值
g_nOpenCloseNum = 9;
g_nErodeDilateNum =9;
g_nTopBlackHatNum =2;

//分别为三个窗口建立滑动条
createTrackbar("迭代值", "【开运算/闭运算】", &g_nOpenCloseNum, g_nMaxIterationMun*2+1,on_OpenClose);
createTrackbar("迭代值", "【腐蚀/膨胀】", &g_nErodeDilateNum, g_nMaxIterationMun * 2 + 1, on_ErodeDilate);
createTrackbar("迭代值", "【顶帽/黑帽】", &g_nTopBlackHatNum, g_nMaxIterationMun * 2 + 1, on_TopBlakHat);
//show帮助信息
ShowHelpText1();
//轮询获取按键信息
while (1)
{
int c;
//执行回调函数
on_OpenClose(g_nOpenCloseNum,0);
on_ErodeDilate(g_nErodeDilateNum,0);
on_TopBlakHat(g_nTopBlackHatNum,0);

//获取按键
c = waitKey(0);
//按下按键ESC程序退出
if ((char)c == 27)
{
break;
}
//按键1,使用椭圆结构元素
if ((char)c == 49)
{
g_nElementShape = MORPH_ELLIPSE;
}
//按键2,使用矩形结构元素
else if ((char)c == 50)
{
g_nElementShape = MORPH_RECT;
}
//按键3,使用十字形结构元素
else if ((char)c == 51)
{
g_nElementShape = MORPH_CROSS;
}
//按键空格,换一种结构元素
else if ((char)c == ' ')
{
g_nElementShape = (g_nElementShape+1)%3;
}
}
}
//*--------------------------【回调函数】-----------------------------------*/

static void on_OpenClose(int, void*)
{
//偏移量定义
int offset = g_nOpenCloseNum - g_nMaxIterationMun; //偏移量
int Abs_offset = offset > 0 ? offset : -offset; //偏移量的绝对值
//自定义核
Mat element = getStructuringElement(g_nElementShape,Size(Abs_offset*2+1, Abs_offset*2+1),Point(Abs_offset, Abs_offset)); //返回的是内核矩阵
//进行操作
if (offset < 0)
{
morphologyEx(g_secImage, g_dstImage, MORPH_OPEN, element);
}
else
{
morphologyEx(g_secImage, g_dstImage, MORPH_CLOSE, element);
}
//显示效果图
imshow("【开运算/闭运算】", g_dstImage);
}
static void on_ErodeDilate(int, void*)
{
//偏移量定义
int offset = g_nErodeDilateNum - g_nMaxIterationMun; //偏移量
int Abs_offset = offset > 0 ? offset : -offset; //偏移量的绝对值
//自定义核
Mat element = getStructuringElement(g_nElementShape, Size(Abs_offset * 2 + 1, Abs_offset * 2 + 1), Point(Abs_offset, Abs_offset)); //返回的是内核矩阵
//进行操作
if (offset < 0)
{
morphologyEx(g_secImage, g_dstImage, MORPH_ERODE, element);
}
else
{
morphologyEx(g_secImage, g_dstImage, MORPH_DILATE, element);
}
//显示效果图
imshow("【腐蚀/膨胀】", g_dstImage);
}
static void on_TopBlakHat(int, void*)
{
//偏移量定义
int offset = g_nTopBlackHatNum - g_nMaxIterationMun; //偏移量
int Abs_offset = offset > 0 ? offset : -offset; //偏移量的绝对值
//自定义核
Mat element = getStructuringElement(g_nElementShape, Size(Abs_offset * 2 + 1, Abs_offset * 2 + 1), Point(Abs_offset, Abs_offset)); //返回的是内核矩阵
//进行操作
if (offset < 0)
{
morphologyEx(g_secImage, g_dstImage, MORPH_TOPHAT, element);
}
else
{
morphologyEx(g_secImage, g_dstImage, MORPH_BLACKHAT, element);
}
//显示效果图
imshow("【顶帽/黑帽】", g_dstImage);
}

//-----------------------------------【ShowHelpText( )函数】-----------------------------
// 描述:输出一些帮助信息
//关于morphologyEx参数的问题:
//MORPH_BLACKHAT:黑帽运算
//MORPH_TOPHAT:顶帽运算
//MORPH_CLOSE:闭运算
//MORPH_OPEN:开运算
//MORPH_GRADIENT:形态学梯度
//MORPH_ERODE:腐蚀运算
//MORPH_DILATE:膨胀运算
//关于getStructuringElement参数的问题:
//MORPH_RECT:矩形内核
//MORPH_CROSS:交叉型内核
//MORPH_ELLIPSE:椭圆形矩阵
//请调整滚动条观察图像效果
//按键操作说明 :
//键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环
//键盘按键【1】- 使用椭圆结构元素
//键盘按键【2】- 使用矩形结构元素
//键盘按键【3】- 使用十字形结构元素
//----------------------------------------------------------------------------------------------
void ShowHelpText1()
{
//输出一些帮助信息
printf("\n\n\n请调整滚动条观察图像效果\n");
printf("\n\n\t按键操作说明\n");
printf("\n\n\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n");
printf("\n\n\t键盘按键【1】- 使用椭圆结构元素\n");
printf("\n\n\t键盘按键【2】- 使用矩形结构元素\n");
printf("\n\n\t键盘按键【3】- 使用十字形结构元素\n");
}
void ShowHelpText2()
{
//输出一些帮助信息
printf("\n\n\n\morphologyEx 参数有以下几种类型\n");
printf("\n\n\tMORPH_BLACKHAT:黑帽运算\n");
printf("\n\n\tMORPH_TOPHAT:顶帽运算\n");
printf("\n\n\tMORPH_CLOSE:闭运算\n");
printf("\n\n\tMORPH_OPEN:开运算\n");
printf("\n\n\t//MORPH_GRADIENT:形态学梯度\n");
printf("\n\n\tMORPH_ERODE:腐蚀运算\n");
printf("\n\n\tMORPH_DILATE:膨胀运算\n");
printf("\n\n\n\getStructuringElement参数的问题:\n");
printf("\n\n\tMORPH_RECT:矩形内核\n");
printf("\n\n\tMORPH_CROSS:交叉型内核\n");
printf("\n\n\tMORPH_ELLIPSE:椭圆形矩阵\n");
}

代码实现效果:
形态学操作——开闭运算、顶帽底(黑)帽变换_黑帽_10形态学操作——开闭运算、顶帽底(黑)帽变换_opencv_11形态学操作——开闭运算、顶帽底(黑)帽变换_黑帽_12

代码解释

调用opencv库函数morphologyEx,通过调整参数就可以实现不同的形态学操作。
其中回调函数中要求得变量:偏移量(当前迭代值和预置最大迭代值之差),用它的正负来判断是进行哪种对偶操作。用它的绝对值来决定矩阵核的大小(边长为奇数),以及结构元素锚点的位置。
另外需要注意:十字形的element形状唯一依赖于锚点的位置。
而在其他情况下,锚点只是影响形态学运算结果的偏移。