摘要:
形态学一般指生物学中研究动物和植物结构的一个分支。用数学形态学(也称图像代数)表示以形态为基础对图像进行分析的数学工具。
基本思想是用具有一定形态的结构元素去度量和提取图像中的对应形状以达到对图像分析和识别的目的。
形态学图像处理的基本运算有:
- 膨胀和腐蚀(膨胀区域填充,腐蚀分割区域)
- 开运算和闭运算(开运算去除噪点,闭运算填充内部孔洞)
- 击中与击不中
- 顶帽变换,黑帽变换
形态学的应用:消除噪声、边界提取、区域填充、连通分量提取、凸壳、细化、粗化等;分割出独立的图像元素,或者图像中相邻的元素;求取图像中明显的极大值区域和极小值区域;求取图像梯度
🧡在讲各种形态学操作之前,先来看看结构元素:
膨胀和腐蚀操作的核心内容是结构元素。(后面的开闭运算等重要的也是结构元素的设计,一个合适的结构元素的设计可以带来很好的处理效果)
OpenCV里面的API介绍:
Mat kernel = getStructuringElement(int shape,Size ksize,Point anchor); shape //结构元素的定义:形状 (MORPH_RECT \MORPH_CROSS(交叉形) \MORPH_ELLIPSE); ksize //结构元素大小; anchor //锚点 默认是Point(-1, -1)意思就是中心像素,也可以自己指定
一,腐蚀和膨胀
腐蚀和膨胀是最基本的形态学操作,腐蚀和膨胀都是针对白色部分(高亮部分)而言的。
- 膨胀就是使图像中高亮部分扩张,效果图拥有比原图更大的高亮区域(是求局部最大值的操作)
- 腐蚀是原图中的高亮区域被蚕食,效果图拥有比原图更小的高亮区域(是求局部最小值的操作)
膨胀与腐蚀能实现多种多样的功能,主要如下:
1、消除噪声
2、腐蚀分割(isolate)出独立的图像元素,膨胀在图像中连接(join)相邻的元素。
3、寻找图像中的明显的极大值区域或极小值区域
4、求出图像的梯度
opencv中膨胀/腐蚀API:(两者相同)
void dilate/erode( const Mat& src, //输入图像(任意通道的) Mat& dst, //输出图像 const Mat& element, //结构元素 Point anchor=Point(-1,-1), //中心位置锚点 int iterations=1, //操作次数。省略时为默认值1。 int borderType=BORDER_CONSTANT, //边缘填充类型 const Scalar& borderValue //填充值(默认即可) )
opencv实现:
Mat src1 = imread("D:/opencv练习图片/腐蚀膨胀.png"); Mat src_erode, src_dilate; imshow("原图", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(60, 60), Point(-1, -1)); erode(src1, src_erode, kernel, Point(-1, -1),2); dilate(src1, src_dilate, kernel, Point(-1, -1),2); imshow("腐蚀", src_erode); imshow("膨胀", src_dilate);
膨胀: 腐蚀:
1️⃣腐蚀操作的原理就是求局部最小值的操作,并把这个最小值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐减少。
2️⃣膨胀操作的原理就是求局部最大值的操作,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。
二,高阶形态学变换
对于更加高级形态学变换就要用到morphologyEx()函数了,其函数原型如下:
void morphologyEx( InputArray src, //输入图像 OutputArray dst, //输出图像 int op, //形态学运算类型 InputArray kernel, //结构元素 Point anchor = Point(-1,-1), //锚点 int iterations = 1, //运算次数 int borderType = BORDER_CONSTANT, )
✨对于输入参数op(形态学运算类型)有以下几种参数可以设置:
- MORPH_ERODE(腐蚀)
- MORPH_DILATE(膨胀)
- MORPH_OPEN(开运算)
- MORPH_CLOSE(闭运算)
- MORPH_GRADIENT(形态学梯度,即膨胀图减腐蚀图)
- MORPH_TOPHAT(顶帽运算)
- MORPH_BLACKHAT(底帽运算)
- MORPH_HITMISS(击中与击不中)
🧡开运算和顶帽运算
开运算就是先腐蚀膨胀。作用:用来消除图像中细小对象,在纤细点处分离物体和平滑较大物体的边界而有不明显改变其面积和形状。
顶帽运算就是求原图与原图的开运算的差值图像。作用:由于开运算可以消除较暗背景下的较亮区域,那么用原图减开运算后的结果就可以得到原图中灰度较亮的区域(即得到开运算消除的区域)
opencv实现:
Mat src1 = imread("D:/opencv练习图片/开运算.png"); Mat src_open, src_tophat; imshow("原图", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1)); morphologyEx(src1, src_open, MORPH_OPEN, kernel,Point(-1, -1));//开运算 morphologyEx(src1, src_tophat, MORPH_TOPHAT, kernel, Point(-1, -1));//顶帽运算 imshow("开运算", src_open); imshow("顶帽运算", src_tophat);
开运算: 顶帽运算:(可以用来观察开运算的效果)
💛闭运算和底帽运算
闭运算就是先膨胀后腐蚀。作用:用来填充目标内部的细小孔洞(fill hole),将断开的邻近目标连接,在不明显改变物体面积和形状的情况下平滑其边界。
底帽运算就是求原图与原图的闭运算的差值图像。作用:闭运算是去噪点的过程,所以黑帽操作实质上保留的是噪点的部分。
opencv实现:
Mat src1 = imread("D:/opencv练习图片/闭运算.png"); Mat src_close, src_blackhat; imshow("原图", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1)); morphologyEx(src1, src_close, MORPH_CLOSE, kernel,Point(-1, -1));//闭运算 morphologyEx(src1, src_blackhat, MORPH_BLACKHAT, kernel, Point(-1, -1));//底帽运算 imshow("闭运算", src_close); imshow("底帽运算", src_blackhat);
闭运算: 底帽运算:
💚形态学梯度(求二值图边缘)
图像形态学的梯度跟我们前面介绍的图像卷积计算出来的梯度有本质不同,形态学梯度可以帮助我们获得连通组件的边缘与轮廓,实现图像轮廓或者边缘提取。
根据使用的形态学操作不同,形态学梯度又分为:
- 基本梯度(图像膨胀与腐蚀操作之间的差值)
- 内梯度(输入图像与腐蚀之间的差值)
- 外梯度(膨胀与输入图像之间的差值)
opencv实现:
Mat src1 = imread("D:/opencv练习图片/腐蚀膨胀.png"); Mat src_giad1, src_exter,src_inter,src_dilate,src_erode; imshow("原图", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1)); //基本梯度 morphologyEx(src1, src_giad1, MORPH_GRADIENT, kernel,Point(-1, -1)); imshow("基本梯度", src_giad1); //外梯度 morphologyEx(src1, src_dilate, MORPH_DILATE, kernel, Point(-1, -1)); subtract(src_dilate, src1, src_exter);//求差值 imshow("外梯度", src_exter); //内梯度 morphologyEx(src1, src_erode, MORPH_ERODE, kernel, Point(-1, -1)); subtract(src1, src_erode, src_inter); imshow("内梯度", src_inter);
外梯度: 内梯度:
内外梯度区别不是很大。。。
💙击中与击不中
形态学的击中击不中操作, 击中击不中也是基础形态学操作组合,它可以实现对象的细化跟剪枝操作,根据结构元素不同,可以提取二值图像中的一些特殊区域,得到我们想要的结果。并且击中击不中操作在二值图像的模式匹配跟发现上也非常有用
Hit-miss算法步骤(两次腐蚀,求交集):
击中击不中变换是形态学中用来检测特定形状所处位置的一个基本工具。它的原理就是使用腐蚀;如果要在一幅图像A上找到B形状的目标,我们要做的是:
- 首先,建立一个比B大的结构元素B1;使用B1对图像A进行腐蚀,得到图像假设为A_B1;
- 其次,用B减去B1,从而得到结构元素B2(B2-B);使用B2对图像A的补集进行腐蚀,得到图像假设为A_B2;
- 然后,A_B1与A_B2取交集;得到的结果就是B的位置。
opencv实战(利用击中与击不中,提取网绳的结点位置):
Mat src = imread("D:/opencv练习图片/击中与击不中.png"); imshow("原图", src); // 二值图像 Mat gray, binary, hitImg; cvtColor(src, gray, COLOR_BGR2GRAY); //高斯滤波 Mat gauss; GaussianBlur(gray, gauss, Size(5, 5), 0, 0); //二值化 threshold(gauss, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); imshow("binary", binary); // 定义结构元素 Mat se = getStructuringElement(MORPH_CROSS, Size(11, 11), Point(-1, -1)); // 击中击不中 morphologyEx(binary, hitImg, MORPH_HITMISS, se); imshow("击中击不中", hitImg); //膨胀一下 Mat openImg; Mat kern2 = getStructuringElement(MORPH_RECT, Size(3, 3)); morphologyEx(hitImg, openImg, MORPH_OPEN, kern2, Point(-1, -1), 2); imshow("dilate", openImg); //寻找轮廓 vector<vector<Point>>contours; vector<Vec4i>hie; findContours(openImg, contours, hie, RETR_TREE, CHAIN_APPROX_SIMPLE, Point()); for (size_t i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area < 20.0)continue; Rect rect = boundingRect(contours[i]); rectangle(src, rect, Scalar(0, 0, 255), 1, 8); } // 显示 imshow("结果", src);