形态学运算是针对二值图像依据数学形态学(Mathematical
Morphology)的集合论方法发展起来的图像处理方法。数学形态学起源于岩相学对岩石结构的定量描述工作,近年来在数字图像处理和机器视觉领域中得到了广泛的应用,形成了一种独特的数字图像分析方法和理论。
结构元素可以简单的定义为像素的组合,在对应的像素上定义了原点(也称锚点)。形态学滤波器的应用过程就是利用这个结构元素探测图像中每个像素的操作过程。把某个像素设为结构元素的锚点后,结构元素和图像重叠部分的像素集合就是特定形态学运算的应用对象。结构元素原则上可以是任何形状,但通常是一个简单形状,如正方形、圆形、菱形等,且把中心点作为原点。
腐蚀的定义
用3×3的核去扫描二值图像,仅当核的与前景像素有完全重合区域时,将二值图像中对应的卷积核中心位置的像素保留,其余情况下,将中心位置的像素置0。
拓展:卷积核可以为任意形状,且重置点可以选用卷积核中的任意位置
void cv::erode ( InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
也就是说,由B对X腐蚀所产生的二值图像E是满足以下条件的点(x,y)的集合:如果B的原点平移到点(x,y),那么B将完全包含于X中。
另一种理解,腐蚀就是把当前像素替换成所定义的像素集中的最小像素值。由于输入的二值图像只包含黑色(0)和白色(255)像素,因此如果结构元素覆盖的图像区域中有黑色像素,则锚点所在像素(x,y)将会被替换成黑色0,否则替换成白色255。而物体的边界通常会有黑色像素,所以腐蚀相当于收缩边界。
腐蚀作用:腐蚀是一种消除边界点,使边界向内部收缩的过程。可以用来消除小且无意义的物体
// Read input image
cv::Mat image= cv::imread("binary.bmp");
if (!image.data)
return 0;
// Display the image
cv::namedWindow("Image");
cv::imshow("Image",image);
// Erode the image
cv::Mat eroded;
cv::erode(image,eroded,cv::Mat());//cv::Mat()为空矩阵,此时采用默认3*3正方形结构元素
// Display the eroded image
cv::namedWindow("Eroded Image");
cv::imshow("Eroded Image",eroded);
// Erode the image with a larger s.e.定义更大的结构元素
cv::Mat element(7,7,CV_8U,cv::Scalar(1));
// Display the eroded image by large s.e.
cv::erode(image,eroded,element);
cv::namedWindow("Eroded Image (7x7)");
cv::imshow("Eroded Image (7x7)",eroded);
// Erode the image 3 times.腐蚀3次
cv::erode(image,eroded,cv::Mat(),cv::Point(-1,-1),3);//cv::Point(-1,-1)表示原点是矩阵的中心点
// Display the eroded image
cv::namedWindow("Eroded Image (3 times)");
cv::imshow("Eroded Image (3 times)",eroded);
cv::waitKey(0);
膨胀的定义
用3×3的核去扫描二值图像,当核与图像中的前景像素(值为1的像素)有交集时,则将二值图像中对应的卷积核中心位置的像素值置为1。
拓展:卷积核可以为任意形状(除1×1),且重置点可以选用卷积核中的任意位置,有‘交集‘就对重置点位置像素置1。
cv::dilate ( InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
由B对X膨胀所产生的二值图像D是满足以下条件的点(x,y)的集合:如果B的原点平移到点(x,y),那么它与X的交集非空。
另一种理解为:膨胀是腐蚀的反运算,它把当前像素(原点所在位置(x,y))替换成所定义的像素集中的最大像素值。由于输入的二值图像只包含黑色(0)和白色(255)像素,因此当结构元素覆盖的图像中有白色(物体),则该结构元素原点所在位置(x,y)的值将会被替换成白色255。
膨胀的作用:也就是说,膨胀是将与物体接触的所有背景点合并到该物体中,使边界向外部扩张的过程。可以用来填补物体中的空洞
// Dilate the image
cv::Mat dilated;
cv::dilate(image,dilated,cv::Mat());
// Display the dialted image
cv::namedWindow("Dilated Image");
cv::imshow("Dilated Image",dilated);
除了使用常规的规则结构元素,我们也可以自定义结构元素。下面使用Mat类型的构造函数创建一个3×3十字型的结构元素
// 创建自定义结构元素
unsigned char m[9] = {
0,1,0,
1,1,1,
0,1,0
};
cv::Mat element1(3,3,CV_8U,m); //创建自定义矩阵element1
//显示该结构元素
int nr = element1.rows;
int nl = element1.cols;
for(int j = 0;j<nr;j++)
{
char *data = element1.ptr<char>(j);
for(int i = 0; i<nl; i++)
{
int value = data[i];
cout<<value<<" ";
}
cout<<endl;
}
// Display the eroded image by large s.e.
cv::erode(image,eroded,element1);
cv::namedWindow("Eroded Image (user define)");
cv::imshow("Eroded Image (user define)",eroded);
使用Mat_模板类自定义5×5大小十字形、菱形、方形、x形结构元素:
cv::Mat_<uchar> cross(5,5);
cv::Mat_<uchar> diamond(5,5);
cv::Mat_<uchar> x(5,5);
cv::Mat_<uchar> square(5,5);
// Creating the cross-shaped structuring element
cross <<
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
1, 1, 1, 1, 1,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0;
// Creating the diamond-shaped structuring element
diamond <<
0, 0, 1, 0, 0,
0, 1, 1, 1, 0,
1, 1, 1, 1, 1,
0, 1, 1, 1, 0,
0, 0, 1, 0, 0;
// Creating the x-shaped structuring element
x <<
1, 0, 0, 0, 1,
0, 1, 0, 1, 0,
0, 0, 1, 0, 0,
0, 1, 0, 1, 0,
1, 0, 0, 0, 1;
// Creating the square-shaped structuring element
square <<
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1;
<span style="white-space:pre"> </span> //display x-shaped structuring element
cout<<endl<<"x-shaped structuring element"<<endl<<endl;
int xnr = x.rows;
int xnl = x.cols;
for(int j = 0;j<nr;j++)
{
char *data = x.ptr<char>(j);
for(int i = 0; i<nl; i++)
{
int value = data[i];
cout<<value<<" ";
}
cout<<endl;
}
void cv::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()
)
对于输入参数op(形态学运算类型)有以下几种参数可以设置:
MORPH_ERODE(腐蚀)
MORPH_DILATE(膨胀)
MORPH_OPEN(开运算)
MORPH_CLOSE(闭运算)
MORPH_GRADIENT(形态学梯度,即膨胀图减腐蚀图)
MORPH_TOPHAT(顶帽运算)
MORPH_BLACKHAT(底帽运算)
MORPH_HITMISS(击中与击不中)
void QuickDemo::open_close(Mat& image)
{
Mat gray;
cvtColor(image, gray, COLOR_BGR2GRAY);
Mat binary;
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
namedWindow(“THRESH_OTSU”, WINDOW_FREERATIO);
imshow(“THRESH_OTSU”, binary);
Mat dst1, dst2;
//定义核
int kernel_size = 5;
Mat kernel = getStructuringElement(MORPH_RECT, Size(kernel_size, kernel_size), Point(-1, -1));
//开
morphologyEx(binary, dst1, MORPH_OPEN, kernel, Point(-1, -1), 1, 0);
namedWindow("MORPH_OPEN", WINDOW_FREERATIO);
imshow("MORPH_OPEN", dst1);
//闭、
morphologyEx(binary, dst2, MORPH_CLOSE, kernel, Point(-1, -1), 1, 0);
namedWindow("MORPH_CLOSE", WINDOW_FREERATIO);
imshow("MORPH_CLOSE", dst2);
}
开运算作用:
用来背景中的消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。所有小到不能容纳结构元素的物体都会被移除。
void erode_dilate(Mat& image)
{
Mat gray;
cvtColor(image, gray, COLOR_BGR2GRAY);
Mat binary;
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
namedWindow("THRESH_OTSU", WINDOW_FREERATIO);
imshow("THRESH_OTSU", binary);
Mat dst1, dst2;
//定义核
int kernel_size = 5;
Mat kernel = getStructuringElement(MORPH_RECT, Size(kernel_size, kernel_size), Point(-1, -1));
//腐蚀
erode(binary, dst1, kernel);
namedWindow("erode", WINDOW_FREERATIO);
imshow("erode", dst1);
//膨胀
dilate(binary, dst2, kernel);
namedWindow("dilate", WINDOW_FREERATIO);
imshow("dilate", dst2);
}
cv::Mat element5(5,5,CV_8U,cv::Scalar(1));//5*5正方形,8位uchar型,全1结构元素
cv::Mat opened;
cv::morphologyEx(image, opened,cv::MORPH_OPEN,element5);
// Display the opened image
cv::namedWindow("Opened Image");
cv::imshow("Opened Image",opened);
cv::waitKey(0);
cv::Mat element5(5,5,CV_8U,cv::Scalar(1));//5*5正方形,8位uchar型,全1结构元素
cv::Mat closed;
cv::morphologyEx(image, closed,cv::MORPH_CLOSE,element5);//高级形态学运算函数
// Display the opened image
cv::namedWindow("Closed Image");
cv::imshow("Closed Image",closed);
cv::waitKey(0);
注:在边缘提取应用中,梯度边缘后,会再进行二值化,从而获取更好的边缘图像。
基本梯度:膨胀图 - 腐蚀图内梯度:原图 - 腐蚀图
外梯度:膨胀图 - 原图
作用:
基本梯度(图像膨胀与腐蚀操作之间的差值)
内梯度(输入图像与腐蚀之间的差值)
外梯度(膨胀与输入图像之间的差值)
这里opencv只能直接实现基本梯度,在使用API:morphologyEx 时,调用MORPH_GRADIENT方法即可。
内梯度、外梯度没有直接的API,一般通过已有API间接实现。
void QuickDemo::shape_gradient(Mat& image)
{
Mat gray;
cvtColor(image, gray, COLOR_BGR2GRAY);
Mat g1, g2, g3;
Mat dst1, dst2;
int kernel_size = 7;
Mat kernel = getStructuringElement(MORPH_RECT, Size(kernel_size, kernel_size), Point(-1, -1));
//基本梯度
morphologyEx(gray, g1, MORPH_GRADIENT, kernel, Point(-1, -1), 1, 0);
//膨胀
morphologyEx(gray, dst1, MORPH_DILATE, kernel, Point(-1, -1), 1, 0);
//腐蚀
morphologyEx(gray, dst2, MORPH_ERODE, kernel, Point(-1, -1), 1, 0);
//外梯度
subtract(dst1, gray, g2);
//内梯度
subtract(gray, dst2, g3);
namedWindow("基本梯度", WINDOW_FREERATIO);
imshow("基本梯度", g1);
namedWindow("外梯度", WINDOW_FREERATIO);
imshow("外梯度", g2);
namedWindow("内梯度", WINDOW_FREERATIO);
imshow("内梯度", g3);
//最后再进行二值化,获取更好的边缘图像,选用基本梯度示例
Mat binary;
threshold(g1, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
namedWindow("二值化", WINDOW_FREERATIO);
imshow("二值化", binary);
}
顶帽:原图 - 开操作后的图
1️⃣:得到开运算消除的区域(检查开运算效果)
2️⃣:校正不均匀光照的影响(用于暗背景上的亮物体,去光差)黑帽:闭操作后的图 - 原图
1️⃣:闭运算是去噪点的过程,所以黑帽操作实质上保留的是噪点的部分。
2️⃣:校正不均匀光照的影响(用于亮(白)背景上的暗物体)
注:顶帽和黑帽操作用于获取图像中的微小细节。击中击不中: 通过特定模板,仅当输入的图像中,有与模板一模一样的块时,被击中的输入图像区域才会被保留。
中击不中变换是形态学中用来检测特定形状所处位置的一个基本工具。它的原理就是使用腐蚀;如果要在一幅图像A上找到B形状的目标,我们要做的是:
首先,建立一个比B大的结构元素B1;使用B1对图像A进行腐蚀,得到图像假设为A_B1;
其次,用B减去B1,从而得到结构元素B2(B2-B);使用B2对图像A的补集进行腐蚀,得到图像假设为A_B2;
然后,A_B1与A_B2取交集;得到的结果就是B的位置。
void QuickDemo::other_method(Mat& image)
{
Mat gray;
cvtColor(image, gray, COLOR_BGR2GRAY);
Mat binary;
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
namedWindow("二值化", WINDOW_FREERATIO);
imshow("二值化", binary);
Mat dst1, dst2, dst3;
int kernel_size = 5;
Mat kernel_1 = getStructuringElement(MORPH_RECT, Size(kernel_size, kernel_size), Point(-1, -1));
//顶帽
morphologyEx(binary, dst1, MORPH_TOPHAT, kernel_1, Point(-1, -1), 1, 0);
namedWindow("顶帽", WINDOW_FREERATIO);
imshow("顶帽", dst1);
//黑帽
morphologyEx(binary, dst2, MORPH_BLACKHAT, kernel_1, Point(-1, -1), 1, 0);
namedWindow("黑帽", WINDOW_FREERATIO);
imshow("黑帽", dst2);
//击中击不中
Mat kernel_2 = getStructuringElement(MORPH_CROSS, Size(kernel_size, kernel_size), Point(-1, -1));
morphologyEx(binary, dst3, MORPH_HITMISS, kernel_2, Point(-1, -1), 1, 0);
namedWindow("击中击不中", WINDOW_FREERATIO);
imshow("击中击不中", dst3);
}
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);
使用cv2.getStructuringElement()生成不同结构的核
import cv2
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10)
get_show(kernel1)
kernel2 = cv2.getStructuringElement(cv2.MORPH_CROSS, (10, 10))
kernel3 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1000, 1000))
参考:https://blog.csdn.net/qq_33287871/article/details/112761328?ops_request_misc=&request_id=&biz_id=102&utm_term=opencv%E5%BD%A2%E6%80%81%E5%AD%A6%E5%A4%84%E7%90%86c++&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-4-112761328.142v88control_2,239v2insert_chatgpt&spm=1018.2226.3001.4187