数字图像处理第九章形态学图像处理(膨胀、腐蚀、开操作、闭操作、击中或击不中、边界提取、连通分量提取)
9.1预备知识
数学形态学的语言是集合;
数学形态学中的集合表示图像中的不同对象;
例如在二值图像中,所有黑色像素的集合是图像完整的形态学描述;
在二值图像中,正在被讨论的集合是二维整数空间z^2的元素,在这个整数二维空间中,集合的每个元素都是一个多元组,是一个黑色(或白色。取决于事先约定)像素在图像中的坐标(x,y)。
一般默认,二值图像中,白色为背景图,黑色为描述图像的元素。
假设存在集合A 和集合B:
集合A的补集合=是不包含集合A元素的所有元素组成A^c.
集合A和B的差表示为 A - B = A ∩ B^c ;
集合B的反射是指关于坐标原点进行翻转, B^ = {W | W = - b , b ∈ B};
集合A平移到点z=(z1,z2)表示为(A)z,定义为:(A)z = {c | c = a + z , a ∈ A}。
9.2膨胀与腐蚀
膨胀与腐蚀是许多形态学算法的基础。
腐蚀和膨胀是针对高亮区域的操作:
图像的像素值越大的地方,图像越亮,而腐蚀和膨胀就是求图像像素局部最小值和局部最大值的过程
膨胀(dilate):就是对图像的高亮部分进行膨胀,相当于高亮部分的领域扩张
腐蚀(erode):就是对图像的高亮部分的侵蚀,也就是经过腐蚀操作之后图像的高亮部分变得更少了
9.2.1 膨胀
假设A与B是Z^2中的集合,则以B为一个结构元,A为被膨胀的集合(图像物体),定义为:
可以将B视为一个卷积模板,首先B关于远点翻转旋转,然后逐步移动以滑过整个集合(图像)A,这一过程类似于空间卷积,但是这一过程是非线性操作,而空间卷积是属于线性操作。
膨胀会增长或者粗化二值图像中的物体,这种特殊方式的粗化的宽度由所用的结构元来控制。.
如下图所示:虚线显示了原始集合,实线显示了一个界限,B^ 的原点进一步移动z,若原点不在虚线上,超出了这一界限,会导致B^和A的交集为空,因此处在该边界上或该边界内的所有点就构成了B 对A的膨胀。后面一张图是用来实现垂直方向膨胀比水平方向膨胀更多的结构元。
9.2.2腐蚀
假设A与B是Z^2中的集合,则以B为一个结构元,A为被腐蚀的集合(图像物体),定义为:
上述式子表示为B对A的腐蚀是一个用z平移的B包含在A中所有的点z的集合。
A和B的元素显示为阴影,背景显示为白色。
实线边界是B的原点进一步移动的界限,若超出该界限,则会导致结构元不再完全包含于A中,这样该边界内的点的轨迹(B的原点位置)就构成了对A的腐蚀。
腐蚀是简单满足上述定义式的z值的一个集合。
9.2.3对偶性
膨胀和腐蚀彼此关于集合求补运算和反射运算是对偶的,即为:
9.2.4 代码实现
腐蚀是求局部最小值;
膨胀是求局部最大值;
首先,在腐蚀与膨胀的操作过程中,给出一个核窗口,它有一个单独定义出来的参考点,我们称之为锚点,(这个窗口的给出我们一般会结合函数getStructuringElement给出),然后我们把这个窗口在原图像上滑动,计算原图像在这个窗口覆盖部分之下的最值,然后把这个最值赋值给参考点指定的像素
如果这个最值是最大值,那么上述操作就是膨胀dilate,它会使原图像高亮部分的领域逐渐扩张
如果这个最值是最小值,那么上述操作就是腐蚀erode,它会使原图像高亮部分的领域被侵蚀
1、opencv自带函数
(1)膨胀:dilate
此操作将图像 A 与任意形状的内核 (B),通常为正方形或圆形,进行卷积。
内核 B 有一个可定义的锚点, 通常定义为内核中心点。
进行膨胀操作时,将内核 B 划过图像,将内核B 覆盖区域的最大相素值提取,并代替锚点位置的相素。
void dilate( const Mat& src, Mat& dst, const Mat& element,Point anchor=Point(-1,-1), int iterations=1,int borderType=BORDER_CONSTANT,const Scalar& borderValue=morphologyDefaultBorderValue() );
1
第一个参数:输入图像
第二个参数:输出图像,要和原图像有一样的尺寸和类型
第三个参数:原图像类型的element,膨胀操作的核,当为NULL时表示使用的是参考点位于中心的3x3的核(通常使用函数getStructuringElement来计算)
第四个参数:锚点位置,有默认值Point(-1, -1)
第五个参数:迭代使用dilate的次数,默认值为1
第六个参数:有默认值BORDER_DEFAULT
第七个参数:const Scalar类型的 borderValue,一般不用管他
(2)腐蚀:erode
腐蚀在形态学操作家族里是膨胀操作的孪生姐妹。它提取的是内核覆盖下的相素最小值。
进行腐蚀操作时,将内核 B 划过图像,将内核B 覆盖区域的最小相素值提取,并代替锚点位置的相素。
(3)获取锚点模板的函数: getStructuringElement函数
Mat element = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
1
第一个参数:内核的形状,有下面三种形状可以选择:
MORPH_RECT : 矩形
MORPH_CROSS : 交叉形
MORPH_ELLIPSE : 椭圆形
Size类型的内核的尺寸
Point类型的锚点的位置,有默认值Point(-1, -1)
注意:交叉型的element形状唯一依赖于锚点的位置,其他情况下,锚点的位置只是影响到了形态学运算结果的偏移
2、c++自实现腐蚀膨胀函数
#include<iostream>
#include<opencv/cv.hpp>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void mydilate( Mat &src, Mat &dst)
{
dst = src.clone();
//Mat element1 = getStructuringElement(MORPH_RECT, ksize, Point(-1, -1));
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
uchar maxV = 0;
for (int k = i - 1; k <= i + 1; k++) //3 x 3模板
{
for (int r = j - 1; r <= j + 1; r++)
{
if (k < 0 || k >= src.rows || r < 0 || r >= src.cols)
{
continue;
}
maxV = (std::max<uchar>)(maxV, src.at<uchar>(k, r));//比较两个函数大小
}
}
dst.at<uchar>(i, j) = maxV;
}
}
}
int main()
{
Mat src = imread("C:/Users/征途/Desktop/vs-cpp/第九章/01.jpg", IMREAD_GRAYSCALE);
if (!src.data)
{
cout << "输入错误" << endl;
}
imshow("原图", src);
Mat dst;
//mydilate(src, dst);
//imshow("膨胀", dst);
waitKey(0);
return 0;
}
9.3开操作和闭操作
开操作:一般平滑物体的轮廓、断开较窄的狭颈并消除细的突出物;
闭操作:也会平滑轮廓的一部分,通常会弥合较窄的简断和细长的沟壑,消除小的孔洞,填补轮廓线中的断裂。
9.3.1开操作
结构元B对集合A进行开操作就是先B对A进行腐蚀,紧接着用B对A进行膨胀;定义为:
假设将结构元B设为一个扁平的转球,然后开操作后的边界由B中的点建立:当B在A边界进行滚动时,B能到达的A边界的最远点就是开操作的边界。
开操作也可以表示为:
该式表明B对A的开操作是通过拟合到A 的B的所有平移的并集得到的。
开操作性质:
9.3.2闭操作
结构元B对集合A进行闭操作就是先B对A进行膨胀,紧接着用B对A进行腐蚀;定义为:
假设B为一个转球,在边界的外侧进行滚动,然后闭操作的边界由B确定;
闭操作性质:
9.3.3 对称性
开操作和闭操作彼此关于集合求补和反射也是对偶的,即为:
用于对二值化后的图像进行处理,属于形态学操作(morphology)
开操作:消除白色的小点,去除小的干扰块
闭操作:消除黑色的小块,填充闭合区域
opencv自带函数实现:
cv2.morphologyEX( img 输入图像,
cv2.MORPH_CLOSE(闭操作),cv2.MORPH_OPEN(开操作) 形态学操作
kernel (卷积核)
)
其中kernel,用 cv2.getStructuringElement(cv2.MORPH_RECT,(5,5)) 获得c++自定义实现开闭操作:
#include <iostream>
#include <algorithm>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
//开操作
void open_my(Mat &src, Mat &dst)
{
Mat dst1;
dst1.create(src.rows, src.cols, CV_8UC1);
for (int i = 0; i < src.rows; ++i)
{
for (int j = 0; j < src.cols; ++j)
{
uchar minV = 255;
for(int xi = i - 1; xi <= i + 1; xi++)
{
for (int yi = j - 1; yi <= j + 1; yi++)
{
if (xi < 0 || xi > src.rows || yi < 0 || yi > src.cols)
{
continue;
}
else
{
minV = (std::min<uchar>)(minV, src.at<uchar>(xi, yi));
}
}
}
dst1.at<uchar>(i, j) = minV;
}
}
Mat dst2;
dst2.create(src.rows, src.cols, CV_8UC1);
for (int k = 0; k < dst1.rows; ++k)
{
for (int r = 0; r < dst1.cols; ++r)
{
uchar maxV = 0;
for (int x2 = k - 1; x2 <= k + 1; x2++)
{
for (int y2 = r - 1; y2 <= r + 1; y2++)
{
if (x2 < 0 || x2 > dst1.rows || y2 < 0 || y2 > dst1.cols)
{
continue;
}
else
{
maxV = (std::max<uchar>)(maxV, dst1.at<uchar>(x2, y2));
}
}
}
dst2.at<uchar>(k,r) = maxV;
}
}
}
//闭操作
void close_my(Mat &src, Mat &dst)
{
Mat src1;
Mat dst1;
dst1.create(src.rows,src.cols,CV_8UC1);
for (int k = 0; k < src.rows; ++k)
{
for (int r = 0; r < src.cols; ++r)
{
uchar maxV = 0;
for (int x2 = k - 1; x2 <= k + 1; x2++)
{
for (int y2 = r - 1; y2 <= r + 1; y2++)
{
if (x2 < 0 || x2 > src.rows || y2 < 0 || y2 > src.cols)
{
continue;
}
else
{
maxV = (std::max<uchar>)(maxV, dst1.at<uchar>(x2, y2));
}
}
}
dst1.at<uchar>(k, r) = maxV;
}
}
Mat dst2;
dst2.create(dst1.rows, dst1.cols, CV_8UC1);
for (int i = 0; i < dst1.rows; ++i)
{
for (int j = 0; j < dst1.cols; ++j)
{
uchar minV = 255;
for (int xi = i - 1; xi <= i + 1; xi++)
{
for (int yi = j - 1; yi <= j + 1; yi++)
{
if (xi < 0 || xi > dst1.rows || yi < 0 || yi > dst1.cols)
{
continue;
}
else
{
minV = (std::min<uchar>)(minV, dst1.at<uchar>(xi, yi));
}
}
}
dst2.at<uchar>(i, j) = minV;
}
}
}
int main()
{
Mat img,dst1,dst2;
img = imread("C:\\Users\\wj257\\Desktop\\vs2019\\Project2\\1.jpg",IMREAD_GRAYSCALE);
imshow("1", img);
open_my(img, dst2);
imshow("2", dst2);
close_my(img, dst1);
imshow("3", dst1);
waitKey(0);
return 0;
}
9.4击中或击不中变换
形态学中的击中击不中是一种形状检测工具。
击中击不中变换主要用来检测输入图像的某个特定图像的位置。例如,我们想在A图像内部找到和D图像的位置,可以表示如下:
A⊛D=(A⊖D)∩(Ac⊖(W-D))
其中,Ac表示A的补集,
击中是指 如果B表示由D及其背景组成的集合,则B在A中的匹配,表示为
如果令B = (B1, B2),其中B1是代表与一个目标相联系的B的元素构成的集合,B2是由与相应背景相联系的B的元素构成的集合。则有:.
最终可以化为:
我们将上面三个公式称为 形态学的击中或击不中变换。
利用D对A进行腐蚀
利用(W-D)对A的补集进行腐蚀
将步骤一和步骤二得到的图像进行与操作,最终得到变换后的操作
C++代码自实现:
上图是图像A,我们利用击中击不中原理找到其中较大的正方形。
上述图形为D;
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat a, b, aInv, bInv, hitOrNot, b_erode_a, bInv_erode_aInv;
a = imread("C:\\Users\\wj257\\Desktop\\vs2019\\Project2\\a.jpg", IMREAD_GRAYSCALE);
threshold(a, a, 180, 255, THRESH_BINARY); //二值化,第五个参数
//imshow("二值化图像", input_image);
b = imread("C:\\Users\\wj257\\Desktop\\vs2019\\Project2\\b.jpg", IMREAD_GRAYSCALE);
threshold(b, b, 180, 1, THRESH_BINARY);
copyMakeBorder(b, b, 1, 1, 1, 1, BORDER_CONSTANT, Scalar(1)); //利用copyMakeBorder()扩大一圈,新区域的值与原核的值相反;避免使用值全0时的核对图像进行腐蚀(卷积)的操作
erode(a, b_erode_a, b, Point(-1, -1), 1, BORDER_DEFAULT, 0);
imshow("b_erode_a", b_erode_a);
aInv = imread("C:\\Users\\wj257\\Desktop\\vs2019\\Project2\\a.jpg", IMREAD_GRAYSCALE);
threshold(aInv, aInv, 180, 255, THRESH_BINARY_INV); //二值化第五个参数与上面的相反取补集
bInv = imread("C:\\Users\\wj257\\Desktop\\vs2019\\Project2\\b.jpg", IMREAD_GRAYSCALE);
threshold(bInv, bInv, 180, 1, THRESH_BINARY_INV); //二值化第五个参数与上面的相反取补集
copyMakeBorder(bInv, bInv, 1, 1, 1, 1, BORDER_CONSTANT, Scalar(0)); //利用copyMakeBorder()扩大一圈,新区域的值与原核的值相反;避免使用值全0时的核对图像进行腐蚀(卷积)的操作
erode(aInv, bInv_erode_aInv, bInv, Point(-1, -1), 1, BORDER_DEFAULT, 0);
imshow("bInv_erode_aInv", bInv_erode_aInv);
bitwise_and(b_erode_a, bInv_erode_aInv, hitOrNot);//逻辑与,求交集
imshow("hitOrNot", hitOrNot);
waitKey(0);
return 0;
}
9.5一些基本的形态学算法
9.5.1 边界提取
表示β(A)的集合A的边界可以通过先用B对A腐蚀,而后执行A和腐蚀结果之间的集合之差得到,即为:
其中B是一个适当的结构元。
下图说明了二值图像应用边界提取:
#include <iostream>
#include <algorithm>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
//从文件中读取成灰度图像
Mat img = imread("C:\\Users\\wj257\\Desktop\\vs2019\\Project2\\1.jpg", IMREAD_GRAYSCALE);
if (img.empty())
{
fprintf(stderr, "Can not load image %s\n");
return -1;
}
//OpenCV方法
Mat dilated_cv;
dilate(img, dilated_cv, Mat());
//自定义方法
Mat dst;
dst.create(img.rows, img.cols, CV_8UC1);
for (int i = 0; i < img.rows; ++i)
{
for (int j = 0; j < img.cols; ++j)
{
//uchar minV = 255;
uchar maxV = 0;
//遍历周围最大像素值
for (int k = i - 1; k <= i + 1; k++)
{
for (int r = j - 1; r <= j + 1; r++)
{
if (r < 0 || r >= img.cols || k < 0 || k >= img.rows)
{
continue;
}
//minV = (std::min<uchar>)(minV, img.at<uchar>(yi, xi));
maxV = (std::max<uchar>)(maxV, img.at<uchar>(k, r));
}
}
dst.at<uchar>(i, j) = maxV;
}
}
//比较两者的结果
Mat c;
compare(dilated_cv, dst, c, CMP_EQ);
//显示
imshow("原始", img);
imshow("膨胀_cv", dilated_cv);
imshow("膨胀_my", dst);
imshow("比较结果", c);
waitKey();
return 0;
}
9.5.2 连通分量的提取
令A是包含一个或多个连通分量的集合,并形成一个阵列X0(该阵列与包含A的阵列大小相同),除了在对应于A中每个连通分量中的一个点的已知的每一个位置处我们已置为,其他元素为背景值都为0;如下:
其中B是一个结构元,当Xk = Xk-1 时,迭代过程结束,Xk包含输入图像中的所有的连通分量。
提取连通域实际上是标记连通域的过程,其算法如下:
初始点:Bo=某个连通分量中的某点(这里通过遍历图像,知道像素值为255,将此点作为种子点)
循环:
(用3X3d的结构元素对种子点进行膨胀,然后用原图像对膨胀结果取交集)
结束条件:直到膨胀的图像不发生变化
在这里可以用一个模板来存储连通分量,其位置对应原图像的位置。提取完后可以修改原图像的像素值,即一个标签。
#include<iostream>
#include<opencv2\opencv.hpp>
using namespace std;
using namespace cv;
/*******************************************
功能:标注连通分量
参数:src-输入图像
nConn-取值4、8,表示4连通或8连通
********************************************/
void LabelConnRgn(Mat& src, int nConn)
{
int se[3][3] = { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } };//8连通
if (nConn == 4)
{
se[0][0] = -1;
se[0][2] = -1;
se[2][0] = -1;
se[2][2] = -1;
}
int nConnRgn = 254;//连通分量的标记号
Mat dst = Mat::zeros(src.size(), src.type());
for (int i = 0; i < src.rows; i++)
{
uchar* srcData = src.ptr<uchar>(i);
uchar* dstData = dst.ptr<uchar>(i);
for (int j = 0; j < src.cols; j++)
{
if (srcData[j] == 255)
{
dstData[j] = 255;
while (true)
{
Mat temp;
dst.copyTo(temp);
dilate(dst, dst, se[3][3]);
dst = dst & src;
//如果和上一次处理后的图像相同,说明连通区域已经提取完毕
if (dst.data == temp.data)
{
break;
}
}
//标注刚刚找到的连通区域
for (int k = 0; k < src.rows; k++)
{
uchar* srcData = src.ptr<uchar>(k);
uchar* dstData = dst.ptr<uchar>(k);
for (int l = 0; l < src.cols; l++)
{
if (dstData[l] == 255)
{
//标记原图像上的连通区域
srcData[l] = nConnRgn;
}
}
}
nConnRgn -= 50;//连通区域编号加1(此处为了显示的方便实际为:nConnRgn--;)
if (nConnRgn <= 0)
{
cout << "连通区域大于254个连通区域" << endl;
i = src.rows;//强制跳出外循环
break;
}
}
}
}
}
int main()
{
Mat src = imread("C:\\Users\\wj257\\Desktop\\vs2019\\Project2\\1.jpg", 0);
imshow("原始图像", src);
LabelConnRgn(src, 4);
imshow("连通区域", src);
waitKey(0);
return 0;
}
文章知识点与官方知识档案匹配,可进一步学习相关知识
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/wangjia2575525474/article/details/117919453
灰度图像--形态学处理(腐蚀,膨胀,开、闭运算,顶帽(礼帽),低帽(黒帽),测定腐蚀、测地膨胀,形态学重建)
写这个帖子的原因是在学习灰度图像处理中,发现没有一个博客很系统全面的讲解这些形态学变换,所以为了帮助后来人,特此做此工作,但是能力有限,如果有不对的地方请大家多多批评指正!另外有一些前辈的工作在里面,在后面我会给出引用。
灰度图像与二值图像的形态学变换不尽相同,形态学处理的定义与二值图像有些不同,因为二值图像可以用一系列的二维坐标来表示图像信息,而灰度图需要一个三维坐标表示,而且二值图像中结构元SE是平坦的,没有灰度信息的,但灰度图中,结构元是可以带有第三维信息的,即结构元也是灰度的,这就带来了一些问题,因为二值图像中,形态学的输出结果完全由输入图像产生,但是结构元一旦引入灰度信息,那么输出结果将不再由输入图像唯一确定。所以,一般情况下,结构元都使用平坦的结构元。
腐蚀与膨胀
是形态学的基本操作,在灰度图像中也是如此,在二值图像中腐蚀和膨胀定义为对图像进行translation以后的“与”和“或”的逻辑操作结果,在灰度图像中,为了保存灰度信息,“与”和“或”操作被对应的替换成了“最大值”和“最小值”操作这样就给出了灰度图像中腐蚀和膨胀的操作定义。
直接看矩阵中的操作吧,简单明了。
腐蚀
膨胀
开操作与闭操作
与二值图像中的定义相同,开、闭操作也又腐蚀膨胀操作衍生出来的,依旧先腐蚀后膨胀是开操作,先膨胀后腐蚀是闭操作。
开操作
闭操作
顶帽和低帽变换
这两个变换在翻译的时候有不同的叫法,但是不用管这些啦,毕竟都是一个意思嘛。
顶帽操作和底帽操作是灰度图像所特有的,其原理是开操作将使峰顶消去,具体消去了多少呢,可以用原图减去开操作结果,这样就能得到其消去的部分,而这个过程成为顶帽操作,顶帽就是开操作消去的峰顶,这一部分对应于图像中较亮的部分,也叫白色顶帽。
开运算放大了裂缝或者局部低亮度的区域,所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。TopHat运算一般用来分离比邻近点亮一些的斑块,可以使用这个运算提取背景。
同理,底帽操作是用闭操作的结果减去原图就得到被闭操作填充的谷底部分,这一部分对应于图像中较暗的部分,也叫黑色底帽。黑帽运算的结果突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
顶帽
低帽
测地腐蚀与测地膨胀
测地腐蚀与测地膨胀在二值图像中的操作是将腐蚀与膨胀结果与ground做“与”和“或”操作,与灰度膨胀中膨胀的推广一样,“与”和“或”被取代为最大值和最小值。
//当循环次数为负数时候为形态学重建,为0时候返回原图像,为整数时普通的测地腐蚀
void lhMorpRErode(const IplImage* src, const IplImage* msk, IplImage* dst, IplConvKernel* se = NULL, int iterations=-1)
{
assert(src != NULL && msk != NULL && dst != NULL && src != dst );
if(iterations < 0)
{
//腐蚀重建
cvMax(src, msk, dst);
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
IplImage* temp1 = cvCreateImage(cvGetSize(src), 8, 1);
IplImage* temp2 = cvCreateImage(cvGetSize(src), 8, 1);
do
{
cvCopy(dst, temp1);
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
cvCmp(temp1, dst, temp2, CV_CMP_NE);
}
while(cvSum(temp2).val[0] != 0);
cvReleaseImage(&temp1);
cvReleaseImage(&temp2);
return;
}
else if (iterations == 0)
{
cvCopy(src, dst);
}
else
{
//普通测地腐蚀 p137(6.2)
cvMax(src, msk, dst);
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
for(int i=1; i<iterations; i++)
{
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
}
}
}
版权声明:本文为博主原创文章,转载请告诉我下,我高兴高兴~ https://blog.csdn.net/u012851419/article/details/78026596 </div>
<div id="content_views" class="markdown_views">
<!-- flowchart 箭头图标 勿删 -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path></svg>
<p><img src="https://img-blog.csdn.net/20170919152558134?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg1MTQxOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""> </p>
写这个帖子的原因是在学习灰度图像处理中,发现没有一个博客很系统全面的讲解这些形态学变换,所以为了帮助后来人,特此做此工作,但是能力有限,如果有不对的地方请大家多多批评指正!另外有一些前辈的工作在里面,在后面我会给出引用。
灰度图像与二值图像的形态学变换不尽相同,形态学处理的定义与二值图像有些不同,因为二值图像可以用一系列的二维坐标来表示图像信息,而灰度图需要一个三维坐标表示,而且二值图像中结构元SE是平坦的,没有灰度信息的,但灰度图中,结构元是可以带有第三维信息的,即结构元也是灰度的,这就带来了一些问题,因为二值图像中,形态学的输出结果完全由输入图像产生,但是结构元一旦引入灰度信息,那么输出结果将不再由输入图像唯一确定。所以,一般情况下,结构元都使用平坦的结构元。
腐蚀与膨胀
是形态学的基本操作,在灰度图像中也是如此,在二值图像中腐蚀和膨胀定义为对图像进行translation以后的“与”和“或”的逻辑操作结果,在灰度图像中,为了保存灰度信息,“与”和“或”操作被对应的替换成了“最大值”和“最小值”操作这样就给出了灰度图像中腐蚀和膨胀的操作定义。
直接看矩阵中的操作吧,简单明了。
腐蚀
膨胀
开操作与闭操作
与二值图像中的定义相同,开、闭操作也又腐蚀膨胀操作衍生出来的,依旧先腐蚀后膨胀是开操作,先膨胀后腐蚀是闭操作。
开操作
闭操作
顶帽和低帽变换
这两个变换在翻译的时候有不同的叫法,但是不用管这些啦,毕竟都是一个意思嘛。
顶帽操作和底帽操作是灰度图像所特有的,其原理是开操作将使峰顶消去,具体消去了多少呢,可以用原图减去开操作结果,这样就能得到其消去的部分,而这个过程成为顶帽操作,顶帽就是开操作消去的峰顶,这一部分对应于图像中较亮的部分,也叫白色顶帽。
开运算放大了裂缝或者局部低亮度的区域,所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。TopHat运算一般用来分离比邻近点亮一些的斑块,可以使用这个运算提取背景。
同理,底帽操作是用闭操作的结果减去原图就得到被闭操作填充的谷底部分,这一部分对应于图像中较暗的部分,也叫黑色底帽。黑帽运算的结果突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
顶帽
低帽
测地腐蚀与测地膨胀
测地腐蚀与测地膨胀在二值图像中的操作是将腐蚀与膨胀结果与ground做“与”和“或”操作,与灰度膨胀中膨胀的推广一样,“与”和“或”被取代为最大值和最小值。
//当循环次数为负数时候为形态学重建,为0时候返回原图像,为整数时普通的测地腐蚀
void lhMorpRErode(const IplImage* src, const IplImage* msk, IplImage* dst, IplConvKernel* se = NULL, int iterations=-1)
{
assert(src != NULL && msk != NULL && dst != NULL && src != dst );
if(iterations < 0)
{
//腐蚀重建
cvMax(src, msk, dst);
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
IplImage* temp1 = cvCreateImage(cvGetSize(src), 8, 1);
IplImage* temp2 = cvCreateImage(cvGetSize(src), 8, 1);
do
{
cvCopy(dst, temp1);
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
cvCmp(temp1, dst, temp2, CV_CMP_NE);
}
while(cvSum(temp2).val[0] != 0);
cvReleaseImage(&temp1);
cvReleaseImage(&temp2);
return;
}
else if (iterations == 0)
{
cvCopy(src, dst);
}
else
{
//普通测地腐蚀 p137(6.2)
cvMax(src, msk, dst);
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
for(int i=1; i<iterations; i++)
{
cvErode(dst, dst, se);
cvMax(dst, msk, dst);
}
}
}