当前位置:网站首页>【OpenCV】形态学滤波(2):开运算、形态学梯度、顶帽、黑帽
【OpenCV】形态学滤波(2):开运算、形态学梯度、顶帽、黑帽
2022-07-07 01:41:00 【我菜就爱学】
1、开运算
开运算(Opening Operation),其实就是先腐蚀后膨胀的过程,其数学表达式如下:
d s t = o p e n ( s r c , e l e m e n t ) = d i l a t e ( e r o d e ( s r c , e l e m e n t ) ) dst=open(src,element)=dilate(erode(src,element)) dst=open(src,element)=dilate(erode(src,element))
作用:开运算可以消除小物体,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积。
示例代码:
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
//载入原始图
Mat image = imread("E:\\Pec\\美国队长.jpg");
//创建窗口
namedWindow("【原始图】");
namedWindow("【效果图】");
//显示原始图
imshow("【原始图】", image);
//定义内核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
//进行形态学操作
morphologyEx(image, image, MORPH_OPEN, element);
//显示原始图
imshow("【效果图】", image);
waitKey(0);
}
2、闭运算
先膨胀后腐蚀的过程称为闭运算(Closing Operation),其数学表达式如下:
d s t = c l o s e ( s r c , e l e m e n t ) = e r o d e ( d i l a t e ( s r c , e l e m e n t ) ) dst=close(src,element)=erode(dilate(src,element)) dst=close(src,element)=erode(dilate(src,element))
作用:闭运算能够排除小型黑洞(黑色区域)
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
//载入原始图
Mat image = imread("E:\\Pec\\美国队长.jpg");
//创建窗口
namedWindow("【原始图】");
namedWindow("【效果图】");
//显示原始图
imshow("【原始图】", image);
//定义内核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
//进行形态学操作
morphologyEx(image, image, MORPH_CLOSE, element);
//显示原始图
imshow("【效果图】", image);
waitKey(0);
}
3、形态学梯度
形态学梯度(Morphological Gradient)是膨胀土与腐蚀图之差,数学表达式如下:
d s t = m o r p h − g r a d ( s r c , e l e m e n t ) = d i l a t e ( s r c , e l e m e n t ) − e r o d e ( s r c , e l e m e n t ) dst=morph-grad(src,element)=dilate(src,element)-erode(src,element) dst=morph−grad(src,element)=dilate(src,element)−erode(src,element)
作用:二值图像进行这一操作可以将团块(blob)的边缘突出出来,利用形态学梯度来保留物体的边缘轮廓。
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
//载入原始图
Mat image = imread("E:\\Pec\\美国队长.jpg");
//创建窗口
namedWindow("【原始图】");
namedWindow("【效果图】");
//显示原始图
imshow("【原始图】", image);
//定义内核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
//进行形态学操作
morphologyEx(image, image, MORPH_GRADIENT, element);
//显示原始图
imshow("【效果图】", image);
waitKey(0);
}
4、顶帽
顶帽运算(Top Hat)又常常被译为“礼帽”运算,是原图像与上文刚刚介绍的“开运算”的结果图之差。
d s t = t o p h a t ( s r c , e l e m e n t ) = s r c − o p e n ( s r c , e l e m e n t ) dst=tophat(src,element)=src-open(src,element) dst=tophat(src,element)=src−open(src,element)
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域。因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作与选择的核的大小相关。
作用:顶帽运算往往用来分离比邻近点亮一些的斑块。在一副图像具有大幅的背景,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提前。
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
//载入原始图
Mat image = imread("E:\\Pec\\美国队长.jpg");
//创建窗口
namedWindow("【原始图】");
namedWindow("【效果图】");
//显示原始图
imshow("【原始图】", image);
//定义内核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
//进行形态学操作
morphologyEx(image, image, MORPH_TOPHAT, element);
//显示原始图
imshow("【效果图】", image);
waitKey(0);
}
5、黑帽
黑帽(Black Hat)运算是闭运算的结果图与原图像之差,数学表达式为:
d s t = b l a c k h a t ( s r c , e l e m e n t ) = c l o s e ( s r c , e l e m e n t ) − s r c dst=blackhat(src,element)=close(src,element)-src dst=blackhat(src,element)=close(src,element)−src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小有关。
作用:黑帽运算用来分离比邻近点暗一些的斑块,效果图有着非常完美的轮廓。
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
//载入原始图
Mat image = imread("E:\\Pec\\美国队长.jpg");
//创建窗口
namedWindow("【原始图】");
namedWindow("【效果图】");
//显示原始图
imshow("【原始图】", image);
//定义内核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
//进行形态学操作
morphologyEx(image, image, MORPH_BLACKHAT, element);
//显示原始图
imshow("【效果图】", image);
waitKey(0);
}
6、核心API函数:morphologyEx()
void morphologyEx(
InputArray src,
OutputArray dst,
int op,
InputArray kernel,
Pointanchor=Point(-1,-1),
int iteration=1,
int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue()
);
第一个参数:输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下5种之一:CV_8U、CV_16U、CV_16S、CV_32F和CV_64F
第二个参数:即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型
第三个参数:int类型的op,表示形态学运算的类型,可以是如下表中的任意之一的标识符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G93BBVtQ-1656987857859)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220705095322440.png)]
第四个参数:InputArray 类型的kernel,形态学运算的内核。若为NULL,表示的是使用参考点位于中心3x3的核。一般使用函数getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定的形状和尺寸的结构元素(内核矩阵)
getStructuringElement函数的第一个参数表示内核的形状:
- (1)矩形——MORPH_RECT
- (2)交叉形——MORPH_CROSS
- (3)椭圆形——MORPH_ELLIPSE
而getStructuringElement函数的第二个参数和第三个参数分别是内核的尺寸以及锚点的位置
一般在调用erode和dilate函数之前,要先定义一个Mat类型的变量来获得getStructuringElement函数的返回值,对于锚点的位置,有默认值(-1,-1),表示锚点位于中心。另外注意:十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响形态学运算结果的偏移
//getStructuringElement函数相关调用如下: int g_nStructuringElement(MORPH_RECT,Size(2*g_nStructuringElement+1, 2*g_nStructuringElement+1),Point(g_nStructuringElement,g_nStructuringElement));
之后便可以调用erode、dilate或morphologyEx函数时,由kernel参数填保存getStructuringElement返回值的Mat类型变量。
第五个参数:锚点的位置,默认值(-1,-1),锚点位于中心
第六个参数:迭代使用函数的次数,默认值为1.
第七个参数:用于推断图像外部像素的某种边界模式。有默认值BORDER_CONSTANT
第八个参数:当边界为常数时的边界值,有默认值morphologyDEfaultBorderValue(),一般不需要考虑。这些操作都是可以进行就地(in-place)操作,且对于多通道图像,每一个通道都单独进行操作。
7、综合示例:形态学滤波
说明:下面的程序运行示例一共会出现4个显示图像的窗口,包含原始图一个、开\闭运算一个,膨胀\腐蚀一个,顶帽\黑帽运算一个。它们分别使用滚动条,来控制得到形态学效果,且迭代值为10的时候为中间点。此外,还可以通过键盘1、2、3以及空格键来调节称不同的元素结构
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//-----------------
//全局变量声明
//---------------------
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()
{
system("color 5E");
//载入原始图
g_srcImage = imread("E:\\Pec\\钢铁侠.jpg");
if (!g_srcImage.data) {
printf("图片读取错误\n");
return false;
}
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//创建三个窗口
namedWindow("【开运算/闭运算】", 1);
namedWindow("【腐蚀/膨胀】", 1);
namedWindow("【顶帽/黑帽】", 1);
//参数赋值
g_nOpenCloseNum = 9;
g_nErodeDilateNum = 9;
g_nTopBlackHatNum = 9;
//分别创建三个窗口的轨迹条
createTrackbar("迭代值", "【开运算/闭运算】", &g_nOpenCloseNum, g_nMaxIterationNum * 2 + 1, on_OpenClose);
createTrackbar("迭代值", "【腐蚀/膨胀】", &g_nErodeDilateNum, g_nErodeDilateNum * 2 + 1, on_ErodeDilate);
createTrackbar("迭代值", "【顶帽/黑帽】", &g_nTopBlackHatNum, g_nTopBlackHatNum * 2 + 1, on_TopBlackHat);
printf("----------------------------------------------------------------------\n");
printf("| 操作说明 |\n");
printf("| 按下键盘Q或者ESC,程序退出 |\n");
printf("| 按下键盘1,使用椭圆(Elliptic)结构原始结构元素MORPH_ELLIPSE| |\n");
printf("| 按下键盘2,使用矩形(Rectangle)结构原始结构元素MORPH_RECT |\n");
printf("| 按下键盘3,使用十字形(Cross-shaped)结构原始结构元素MORPH_CROSS |\n");
printf("| 按下键盘space,在矩形、椭圆和十字形之间循环 |\n");
printf("-------------------------------------------------------------------\n");
//轮询获取按键信息
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
else if ((char)c == 49)//1的ASII码为49
g_nElementShape = MORPH_ELLIPSE;
//按下键盘2,使用矩形(Rectangle)结构原始结构元素MORPH_RECT
else if ((char)c == 50)
g_nElementShape = MORPH_RECT;
//按下键盘3,使用十字形(Cross-shaped)结构原始结构元素MORPH_CROSS
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, CV_MOP_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);
}
原图像:
开运算、腐蚀、顶帽图像
闭运算、膨胀、黑帽图像
边栏推荐
- JVM命令之 jstat:查看JVM統計信息
- ETCD数据库源码分析——从raftNode的start函数说起
- Vscode for code completion
- A freshman's summary of an ordinary student [I don't know whether we are stupid or crazy, but I know to run forward all the way]
- Rk3399 platform development series explanation (interruption) 13.10, workqueue work queue
- 可极大提升编程思想与能力的书有哪些?
- JVM命令之 jstat:查看JVM统计信息
- Jcmd of JVM command: multifunctional command line
- 蚂蚁庄园安全头盔 7.8蚂蚁庄园答案
- JVM命令之 jstack:打印JVM中线程快照
猜你喜欢
Markdown displays pictures side by side
On the discrimination of "fake death" state of STC single chip microcomputer
[FPGA tutorial case 14] design and implementation of FIR filter based on vivado core
PowerPivot - DAX (function)
JVM命令之 jinfo:实时查看和修改JVM配置参数
为不同类型设备构建应用的三大更新 | 2022 I/O 重点回顾
If you don't know these four caching modes, dare you say you understand caching?
Detailed explanation of platform device driver architecture in driver development
关于STC单片机“假死”状态的判别
开发者别错过!飞桨黑客马拉松第三期链桨赛道报名开启
随机推荐
[FPGA tutorial case 13] design and implementation of CIC filter based on vivado core
980. Different path III DFS
牛客小白月赛52 E.分组求对数和(二分&容斥)
JMeter's own functions are not enough? Why don't you develop one yourself
Qt多线程的多种方法之一 QThread
安装VMmare时候提示hyper-v / device defender 侧通道安全性
可极大提升编程思想与能力的书有哪些?
New Year Fireworks code plus copy, are you sure you don't want to have a look
Software testing knowledge reserve: how much do you know about the basic knowledge of "login security"?
A freshman's summary of an ordinary student [I don't know whether we are stupid or crazy, but I know to run forward all the way]
Implementation of VGA protocol based on FPGA
VMware安装后打开就蓝屏
关于STC单片机“假死”状态的判别
Deep clustering: joint optimization of depth representation learning and clustering
3428. 放苹果
CTFshow--常用姿势
POI excel export, one of my template methods
On the discrimination of "fake death" state of STC single chip microcomputer
Check Point:企业部署零信任网络(ZTNA)的核心要素
外设驱动库开发笔记43:GPIO模拟SPI驱动