0%

数学形态学运算——腐蚀、膨胀、开运算、闭运算

​ 数学形态学中的二值形态学最基本的两种运算——腐蚀、膨胀。由这两个算子又可以衍生出开运算和闭运算。开运算为先腐蚀再膨胀,闭运算为先膨胀再腐蚀。

erosion_dst.jpg

dilation_dst.jpg

腐蚀

算法

描述

​ 在目标图像中标出那些与结构元素相同的子图像的原点位置的像素。

image.png

​ 把结构元素B看作一个卷积模版,每当结构元素平移到其原点位置与目标图像A中那些像素值为“1”的位置重合时,就判断被结构元素覆盖的子图像的其它像素的值是否都与结构元素相应位置的像素值相同;只有当其都相同时,就将结果图像中的那个与原点位置对应的像素位置的值置为1,否则置为0。

​ ⚠️注意:当结构元素在目标图像上平移时,结构元素中的任何元素不能超出目标图像的范围。

例子

原图像:

原图像

结构元素:

结构元素.png

腐蚀运算后的结果图像:

image.png

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;

struct Kernel{
Mat B;
int position[2]; //原点位置
}; //结构元素


/**
生成十字型结构元素
size为结构元素的行列数,position为原点位置
返回生成的结构元素
*/
Kernel getCrossKernel(int* size, int* position){
Kernel kernel;
kernel.B = Mat::zeros(size[0], size[1], CV_8U);

kernel.position[0] = position[0];
kernel.position[1] = position[1];

for (int nrow = 0; nrow < size[0]; nrow++) {
kernel.B.ptr<uchar>(nrow)[kernel.position[0]] = 255;
}
for (int ncol = 0; ncol < size[1]; ncol++) {
kernel.B.ptr<uchar>(kernel.position[1])[ncol] = 255;
}

return kernel;
}


/**
腐蚀
img为原图像,dst为腐蚀后的图像,kernel为结构元素
*/
void getErosion(Mat img, Mat &dst, Kernel kernel){
dst = img.clone();
Mat B = kernel.B;
int r_pos = kernel.position[0];
int c_pos = kernel.position[1];

// 遍历查重
for (int nrow = r_pos; nrow < img.rows-r_pos; nrow++) {
for (int ncol = c_pos; ncol < img.cols-c_pos; ncol++) {
bool bingo = true;
if (img.ptr<uchar>(nrow)[ncol] == 255) {
for (int i = 0; i < B.rows; i++) {
for (int j = 0; j < B.cols; j++) {
if (B.ptr<uchar>(i)[j] == 255) {
if (img.ptr<uchar>(i+nrow-r_pos)[j+ncol-c_pos] != 255) {
bingo = false;
break;
}
}
}
}
}
if (!bingo) { //不符合
dst.ptr<uchar>(nrow)[ncol] = 0;
}
}
}
}


int main() {
Mat img1,erosion_dst; //图像一和其腐蚀结果
img1 = imread("img1.png", IMREAD_GRAYSCALE); //获取原图的灰度图像
// 读取图片失败,则停止
if (img1.empty()) {
printf("读取图像文件失败");
system("pause");
return -1;
}

// Otsu二值化
Mat img1_twoColor;
threshold(img1, img1_twoColor, 0, 255, THRESH_OTSU);

// 建立结构元素(中心为原点位置)
int size[2] = {5, 5}; //结构元素的大小
int position[2] = {2, 2}; //原点位置
Kernel kernel = getCrossKernel(size, position);

getErosion(img1_twoColor, erosion_dst, kernel); //腐蚀

imshow("img1",img1_twoColor);
imshow("erosion_dst", erosion_dst);
imwrite("erosion_dst.jpg", erosion_dst);
waitKey(); //等待键值输入
return 0;
}

结果

原图像:

img1.png

腐蚀后的图像(十字结构元素):

erosion_dst.jpg

膨胀

算法

描述

​ 位于某个点的探针(结构元素)是否探测到物件?

image.png

过程:

  1. 求结构元素B关于原点的反射集合Bx;
  2. 每当结构元素Bx在目标图像A上平移后,结构元素Bx与其覆盖的子图像中至少有一个元素相交时,就将目标图像中与结构元素Bx的原点对应的那个位置的像素为1,否则置为0。

⚠️注意:

  1. 当结构元素中原点位置的值是0时,仍把它看作是0;而不再把它看作是1。
  2. 当结构元素在目标图像上平移时,允许结构中的非原点像素超出目标图像范围。

例子

  • 结构元素原点为1时,

​ 原图像:

image.png

​ 结构元素:

image.png

​ 结构元素的反射:

image.png

​ 膨胀运算后的结果图像:

image.png

  • 结构元素原点为0时,

​ 原图像:

image.png

​ 结构元素:

image.png

​ 结构元素的反射:

image.png

​ 膨胀运算后的结果图像:

image.png

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;

struct Kernel{
Mat B;
int position[2]; //原点位置
}; //结构元素


/**
生成矩形结构元素
size为结构元素的大小,position为原点位置
返回生成的结构元素
*/
Kernel getRectKernel(int* size, int* position) {
Kernel kernel;
kernel.B = Mat::ones(size[0], size[1], CV_8U);
for (int nrow = 0; nrow < kernel.B.rows; nrow++) {
for (int ncol = 0; ncol < kernel.B.cols; ncol++) {
kernel.B.ptr<uchar>(nrow)[ncol] = 255;
}
}
kernel.position[0] = position[0];
kernel.position[1] = position[1];

return kernel;
}


/**
膨胀
img为原图像,dst为膨胀后的图像,kernel为结构元素
*/
void getDilation(Mat img, Mat &dst, Kernel kernel) {
dst = img.clone();
Mat B = kernel.B;

// 结构元素反射
Mat B_x = Mat::zeros(B.cols, B.rows, CV_8U);
for (int nrow = 0; nrow < B.rows; nrow++) {
for (int ncol = 0; ncol < B.cols; ncol++) {
B_x.ptr<uchar>(ncol)[nrow] = B.ptr<uchar>(nrow)[ncol];
}
}
int r_pos = kernel.position[1];
int c_pos = kernel.position[0];

// 检验图像的每一个点
for (int nrow = 0; nrow < img.rows; nrow++) {
for (int ncol = 0; ncol < img.cols; ncol++) {
bool bingo = false;

// 检测结构元素中是否有符合的1
for (int i = 0; i < B_x.rows; i++) {
for (int j = 0; j < B_x.cols; j++) {
// 图像中的位置(有可能越界)
int row = nrow - r_pos + i;
int col = ncol - c_pos + j;

if (B_x.ptr<uchar>(i)[j] == 255) {
if (row >= 0 && row < img.rows && col >= 0 && col < img.cols) {
if (img.ptr<uchar>(row)[col] == 255) {
bingo = true;
break;
}
}
}
}
if (bingo) {
break;
}
}

if (bingo) { //如果符合,即存在重合的1,则置1
dst.ptr<uchar>(nrow)[ncol] = 255;
} else { //如果没有符合的,则置0
dst.ptr<uchar>(nrow)[ncol] = 0;
}
}
}
}


int main() {
Mat img2,dilation_dst; //图像二和其膨胀结果
img2 = imread("img2.png", IMREAD_GRAYSCALE); //获取原图的灰度图像
// 读取图片失败,则停止
if (img2.empty()) {
printf("读取图像文件失败");
system("pause");
return -1;
}

// Otsu二值化
Mat img2_twoColor;
threshold(img2, img2_twoColor, 0, 255, THRESH_OTSU);

// 矩形结构元素
int size[2] = {7, 7}; //结构元素的大小
int position[2] = {0, 0}; //原点位置
Kernel kernel = getRectKernel(size, position);

getDilation(img2_twoColor, dilation_dst, kernel); //膨胀

imshow("img2",img2_twoColor);
imshow("dilation_dst", dilation_dst);
imwrite("dilation_dst.jpg", dilation_dst);
waitKey(); //等待键值输入
return 0;
}

结果

原图像:

img2.png

膨胀后的图像(矩形结构元素):

dilation_dst.jpg

开运算

​ 开运算为先腐蚀再膨胀。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;

struct Kernel{
Mat B;
int position[2]; //原点位置
}; //结构元素


/**
生成矩形结构元素
size为结构元素的大小,position为原点位置
返回生成的结构元素
*/
Kernel getRectKernel(int* size, int* position) {
Kernel kernel;
kernel.B = Mat::ones(size[0], size[1], CV_8U);
for (int nrow = 0; nrow < kernel.B.rows; nrow++) {
for (int ncol = 0; ncol < kernel.B.cols; ncol++) {
kernel.B.ptr<uchar>(nrow)[ncol] = 255;
}
}
kernel.position[0] = position[0];
kernel.position[1] = position[1];

return kernel;
}


/**
腐蚀
img为原图像,dst为腐蚀后的图像,kernel为结构元素
*/
void getErosion(Mat img, Mat &dst, Kernel kernel){
dst = img.clone();
Mat B = kernel.B;
int r_pos = kernel.position[0];
int c_pos = kernel.position[1];

// 遍历查重
for (int nrow = r_pos; nrow < img.rows-r_pos; nrow++) {
for (int ncol = c_pos; ncol < img.cols-c_pos; ncol++) {
bool bingo = true;
if (img.ptr<uchar>(nrow)[ncol] == 255) {
for (int i = 0; i < B.rows; i++) {
for (int j = 0; j < B.cols; j++) {
if (B.ptr<uchar>(i)[j] == 255) {
if (img.ptr<uchar>(i+nrow-r_pos)[j+ncol-c_pos] != 255) {
bingo = false;
break;
}
}
}
}
}
if (!bingo) { //不符合
dst.ptr<uchar>(nrow)[ncol] = 0;
}
}
}
}


/**
膨胀
img为原图像,dst为膨胀后的图像,kernel为结构元素
*/
void getDilation(Mat img, Mat &dst, Kernel kernel) {
dst = img.clone();
Mat B = kernel.B;

// 结构元素反射
Mat B_x = Mat::zeros(B.cols, B.rows, CV_8U);
for (int nrow = 0; nrow < B.rows; nrow++) {
for (int ncol = 0; ncol < B.cols; ncol++) {
B_x.ptr<uchar>(ncol)[nrow] = B.ptr<uchar>(nrow)[ncol];
}
}
int r_pos = kernel.position[1];
int c_pos = kernel.position[0];

// 检验图像的每一个点
for (int nrow = 0; nrow < img.rows; nrow++) {
for (int ncol = 0; ncol < img.cols; ncol++) {
bool bingo = false;

// 检测结构元素中是否有符合的1
for (int i = 0; i < B_x.rows; i++) {
for (int j = 0; j < B_x.cols; j++) {
// 图像中的位置(有可能越界)
int row = nrow - r_pos + i;
int col = ncol - c_pos + j;

if (B_x.ptr<uchar>(i)[j] == 255) {
if (row >= 0 && row < img.rows && col >= 0 && col < img.cols) {
if (img.ptr<uchar>(row)[col] == 255) {
bingo = true;
break;
}
}
}
}
if (bingo) {
break;
}
}

if (bingo) { //如果符合,即存在重合的1,则置1
dst.ptr<uchar>(nrow)[ncol] = 255;
} else { //如果没有符合的,则置0
dst.ptr<uchar>(nrow)[ncol] = 0;
}
}
}
}


int main() {
Mat img3, erosion_dst, opening_dst; //图像三和其开运算结果
img3 = imread("img3.png", IMREAD_GRAYSCALE); //获取原图的灰度图像
// 读取图片失败,则停止
if (img3.empty()) {
printf("读取图像文件失败");
system("pause");
return -1;
}

// Otsu二值化
Mat img3_twoColor;
threshold(img3, img3_twoColor, 0, 255, THRESH_OTSU);

// 矩形结构元素
int size[2] = {4, 4}; //结构元素的大小
int position[2] = {0, 0}; //原点位置
Kernel kernel = getRectKernel(size, position);

getErosion(img3_twoColor, erosion_dst, kernel); //腐蚀
getDilation(erosion_dst, opening_dst, kernel); //膨胀

imshow("img3",img3);
imshow("erosion_dst", erosion_dst);
imshow("opening_dst", opening_dst);
imwrite("opening_dst.jpg", opening_dst);
waitKey(); //等待键值输入
return 0;
}

结果

原图像:

image.png

开运算结果:

opening_dst.jpg

闭运算

​ 闭运算为先膨胀再腐蚀。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;

struct Kernel{
Mat B;
int position[2]; //原点位置
}; //结构元素


/**
生成矩形结构元素
size为结构元素的大小,position为原点位置
返回生成的结构元素
*/
Kernel getRectKernel(int* size, int* position) {
Kernel kernel;
kernel.B = Mat::ones(size[0], size[1], CV_8U);
for (int nrow = 0; nrow < kernel.B.rows; nrow++) {
for (int ncol = 0; ncol < kernel.B.cols; ncol++) {
kernel.B.ptr<uchar>(nrow)[ncol] = 255;
}
}
kernel.position[0] = position[0];
kernel.position[1] = position[1];

return kernel;
}


/**
腐蚀
img为原图像,dst为腐蚀后的图像,kernel为结构元素
*/
void getErosion(Mat img, Mat &dst, Kernel kernel){
dst = img.clone();
Mat B = kernel.B;
int r_pos = kernel.position[0];
int c_pos = kernel.position[1];

// 遍历查重
for (int nrow = r_pos; nrow < img.rows-r_pos; nrow++) {
for (int ncol = c_pos; ncol < img.cols-c_pos; ncol++) {
bool bingo = true;
if (img.ptr<uchar>(nrow)[ncol] == 255) {
for (int i = 0; i < B.rows; i++) {
for (int j = 0; j < B.cols; j++) {
if (B.ptr<uchar>(i)[j] == 255) {
if (img.ptr<uchar>(i+nrow-r_pos)[j+ncol-c_pos] != 255) {
bingo = false;
break;
}
}
}
}
}
if (!bingo) { //不符合
dst.ptr<uchar>(nrow)[ncol] = 0;
}
}
}
}


/**
膨胀
img为原图像,dst为膨胀后的图像,kernel为结构元素
*/
void getDilation(Mat img, Mat &dst, Kernel kernel) {
dst = img.clone();
Mat B = kernel.B;

// 结构元素反射
Mat B_x = Mat::zeros(B.cols, B.rows, CV_8U);
for (int nrow = 0; nrow < B.rows; nrow++) {
for (int ncol = 0; ncol < B.cols; ncol++) {
B_x.ptr<uchar>(ncol)[nrow] = B.ptr<uchar>(nrow)[ncol];
}
}
int r_pos = kernel.position[1];
int c_pos = kernel.position[0];

// 检验图像的每一个点
for (int nrow = 0; nrow < img.rows; nrow++) {
for (int ncol = 0; ncol < img.cols; ncol++) {
bool bingo = false;

// 检测结构元素中是否有符合的1
for (int i = 0; i < B_x.rows; i++) {
for (int j = 0; j < B_x.cols; j++) {
// 图像中的位置(有可能越界)
int row = nrow - r_pos + i;
int col = ncol - c_pos + j;

if (B_x.ptr<uchar>(i)[j] == 255) {
if (row >= 0 && row < img.rows && col >= 0 && col < img.cols) {
if (img.ptr<uchar>(row)[col] == 255) {
bingo = true;
break;
}
}
}
}
if (bingo) {
break;
}
}

if (bingo) { //如果符合,即存在重合的1,则置1
dst.ptr<uchar>(nrow)[ncol] = 255;
} else { //如果没有符合的,则置0
dst.ptr<uchar>(nrow)[ncol] = 0;
}
}
}
}


int main() {
Mat img4, dilation_dst, closing_dst; //图像四和其闭运算结果
img4 = imread("img4.png", IMREAD_GRAYSCALE); //获取原图的灰度图像
// 读取图片失败,则停止
if (img4.empty()) {
printf("读取图像文件失败");
system("pause");
return -1;
}

// Otsu二值化
Mat img4_twoColor;
threshold(img4, img4_twoColor, 0, 255, THRESH_OTSU);

// 矩形结构元素
int size[2] = {4, 4}; //结构元素的大小
int position[2] = {0, 0}; //原点位置
Kernel kernel = getRectKernel(size, position);

getDilation(img4_twoColor, dilation_dst, kernel); //膨胀
getErosion(dilation_dst, closing_dst, kernel); //腐蚀

imshow("img4",img4);
imshow("dilation_dst", dilation_dst);
imshow("closing_dst", closing_dst);
imwrite("closing_dst.jpg", closing_dst);
waitKey(); //等待键值输入
return 0;
}

结果

原图像:

image.png

闭运算结果:

closing_dst.jpg