腐蚀
定义B对A的腐蚀为:
A⊖B={z∣(B)z⊆A}
其中,A是前景像素的一个集合,B是一个结构元,z项是前景像素值。
腐蚀的目的是去除图像中的某些部分以及会缩小细化目标。
但是,对于白色背景,黑色目标变大;对于黑色背景,白色目标变小。
可以理解为结构元像素是白色的,腐蚀是腐蚀图像中的白色像素,白色像素被腐蚀,则黑色元素膨胀。
代码如下:
相关函数有:
1 2 3 4 5
| double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);
|
参数列表有:
- src:输入数组(多通道,8位或32位浮点)。
- dst:与src相同大小和类型、相同通道数的输出数组。
- thresh:阈值.
- maxval:dst图像中的最大值。
- type:阈值。
type可选有:
dst(x,y)={maxval,if src(x,y)>thresh0,otherwise
cv::THRESH_BINARY_INV
,效果:
dst(x,y)={0,if src(x,y)>threshmaxval,otherwise
dst(x,y)={threshold,if src(x,y)>threshsrc(x,y),otherwise
dst(x,y)={src(x,y),if src(x,y)>thresh0,otherwise
cv::THRESH_TOZERO_INV
,效果:
dst(x,y)={0,if src(x,y)>threshsrc(x,y),otherwise
1 2 3
| Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
|
参数列表有:
- shape:结构元形状,0表示矩形,1表示十字架,2表示椭圆。
- ksize:结构元大小。
- anchor:结构元中心点所在位置。
1 2 3 4 5 6 7
| void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue());
|
参数列表有:
- src:输入图像,通道的数量可以是任意的,但深度应该是CV_8U, CV_16U, CV_16S, CV_32F或CV_64F。
- dst:输出与src相同大小和类型的图像。
- kernel:用于腐蚀的结构元。
- anchor:中心点在元素中的位置。
- iterations:应用侵蚀的次数。
- borderType:推断图像外部像素的边界模式。
- borderValue:当边界为常数时的边界值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <opencv2/opencv.hpp>
int main() { cv::Mat input = cv::imread("src/test.jpg", 0); cv::Mat image_bw, image_erosion;
cv::threshold(input, image_bw, 100, 255, cv::THRESH_BINARY); cv::Mat se = cv::getStructuringElement(0, cv::Size(3, 3)); cv::erode(image_bw, image_erosion, se, cv::Point(-1, -1), 3);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::imshow("before", input);
cv::namedWindow("二值化后", cv::WINDOW_NORMAL); cv::imshow("二值化后", image_bw);
cv::namedWindow("after", cv::WINDOW_NORMAL); cv::imshow("after", image_erosion);
cv::waitKey(0); return 0; }
|
效果展示:

膨胀
定义B对A的膨胀为:
A⊕B={z∣[(B^)z∩A]⊆A}
其中,A是前景像素的一个集合,B是一个结构元,z项是前景像素值。
膨胀的目的是增大图像中的目标,或者填充、连接某些目标。
但是,对于白色背景,黑色目标变小;对于黑色背景,白色目标变大。
可以理解为结构元像素是白色的,膨胀是膨胀图像中的白色像素,白色像素被膨胀,则黑色元素腐蚀。
代码如下:
相关函数有:
1 2 3 4 5 6 7
| void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue());
|
参数列表有:
- src:输入图像,通道的数量可以是任意的,但深度应该是CV_8U, CV_16U, CV_16S, CV_32F或CV_64F。
- dst:输出与src相同大小和类型的图像。
- kernel:用于腐蚀的结构元。
- anchor:中心点在元素中的位置。
- iterations:应用侵蚀的次数。
- borderType:推断图像外部像素的边界模式。
- borderValue:当边界为常数时的边界值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <opencv2/opencv.hpp>
int main() { cv::Mat input = cv::imread("src/test.jpg", 0); cv::Mat image_bw, image_dilate;
cv::threshold(input, image_bw, 100, 255, cv::THRESH_BINARY); cv::Mat se = cv::getStructuringElement(0, cv::Size(3, 3)); cv::dilate(image_bw, image_dilate, se, cv::Point(-1, -1), 3);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::imshow("before", input);
cv::namedWindow("二值化后", cv::WINDOW_NORMAL); cv::imshow("二值化后", image_bw);
cv::namedWindow("after", cv::WINDOW_NORMAL); cv::imshow("after", image_dilate);
cv::waitKey(0); return 0; }
|
效果展示:

开运算与闭运算
结构元B对集合A的开运算定义为:
A∘B=(A⊖B)⊕B
即B对A先腐蚀,接着B对腐蚀结果膨胀。
- 作用是:平滑物体轮廓、断开狭窄的狭颈、消除细长的突出和物体。
结构元B对集合A的闭运算定义为:
A∙B=(A⊕B)⊖B
即B对A先膨胀,接着B对膨胀结果腐蚀。
- 作用是:弥合狭窄的狭颈或断裂处、消除小孔、填补轮廓缝隙。
代码如下:
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
| #include <opencv2/opencv.hpp>
void imageOpenOperation(cv::Mat input, cv::Mat &output) { output = input.clone(); cv::Mat se = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::erode(input, output, se); cv::dilate(output, output, se); }
void imageCloseOperation(cv::Mat input, cv::Mat &output) { output = input.clone(); cv::Mat se = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::dilate(input, output, se); cv::erode(output, output, se); }
int main() { cv::Mat input = cv::imread("src/pic2.png", 0); cv::Mat img_bw, result1, result2; cv::threshold(input, img_bw, 100, 255, cv::THRESH_BINARY);
imageOpenOperation(img_bw, result1); imageCloseOperation(img_bw, result2);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::namedWindow("开运算后", cv::WINDOW_NORMAL); cv::namedWindow("闭运算后", cv::WINDOW_NORMAL);
cv::imshow("before", img_bw); cv::imshow("开运算后", result1); cv::imshow("闭运算后", result2);
cv::waitKey(0); return 0; }
|

morphologyEx() 函数的运用
膨胀腐蚀、开运算闭运算、击中-击不中变换、顶帽变换与底帽变换、形态学梯度
相关函数原型
1 2 3 4 5 6 7 8
| void 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() );
|
参数如下:
MORPH_ERODE = 0:腐蚀处理
MORPH_DILATE = 1:膨胀处理
MORPH_OPEN = 2:开运算处理
MORPH_CLOSE = 3:闭运算处理
MORPH_GRADIENT = 4:形态学梯度
MORPH_TOPHAT = 5:顶帽变换
MORPH_BLACKHAT = 6:黑帽变换
MORPH_HITMISS = 7 :击中-击不中变换
-
kernel:结构元矩阵。
-
iterations:腐蚀膨胀处理的次数,默认值为1;如果是开运算闭运算,次数表示先腐蚀或者膨胀几次,再膨胀腐蚀几次,而不是开运算闭运算几次。
-
borderType:图像边框插值类型,默认类型为固定值填充。
-
borderValue:边界值(如果是恒定边界)。默认值具有特殊含义。
进行腐蚀膨胀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <opencv2/opencv.hpp>
int main() { cv::Mat input = cv::imread("src/pic2.png", 0); cv::Mat img_bw, result1, result2; cv::threshold(input, img_bw, 100, 255, cv::THRESH_BINARY);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::morphologyEx(img_bw, result1, 0, kernel); cv::morphologyEx(img_bw, result2, 1, kernel);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::namedWindow("腐蚀结果", cv::WINDOW_NORMAL); cv::namedWindow("膨胀结果", cv::WINDOW_NORMAL);
cv::imshow("before", img_bw); cv::imshow("腐蚀结果", result1); cv::imshow("膨胀结果", result2);
cv::waitKey(0); return 0; }
|
效果展示:

进行开运算和闭运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <opencv2/opencv.hpp>
int main() { cv::Mat input = cv::imread("src/pic2.png", 0); cv::Mat img_bw, result1, result2; cv::threshold(input, img_bw, 100, 255, cv::THRESH_BINARY);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::morphologyEx(img_bw, result1, 2, kernel); cv::morphologyEx(img_bw, result2, 3, kernel);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::namedWindow("开运算结果", cv::WINDOW_NORMAL); cv::namedWindow("闭运算结果", cv::WINDOW_NORMAL);
cv::imshow("before", img_bw); cv::imshow("开运算结果", result1); cv::imshow("闭运算结果", result2);
cv::waitKey(0); return 0; }
|
效果展示:

击中-击不中变换
原理:
I⊛B={z∣(B)z⊆I}
可以检测图像中满足结构元B的所有像素点,如:

图中结构元B中深色像素为前景,白色表示为背景, ×
表示不关心像素(即不影响结果)。
击中-击不中变换是形状检测的基本工具,寻找与结构元相符合的像素。
在击中击不中变换的结构元中,-1表示背景,1表示前景,0表示不关心。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <opencv2/opencv.hpp>
int main() { cv::Mat input = cv::imread("src/rice.jpg", 0); cv::Mat img_bw, result; cv::threshold(input, img_bw, 100, 255, cv::THRESH_BINARY);
cv::Mat kernel = (cv::Mat_<float>(3, 3) << 1, 1, -1, 1, 1, -1, 1, 1, -1);
cv::morphologyEx(img_bw, result, 7, kernel);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::namedWindow("击中-击不中变换结果", cv::WINDOW_NORMAL);
cv::imshow("before", img_bw); cv::imshow("击中-击不中变换结果", result);
cv::waitKey(0); return 0; }
|
效果展示:

顶帽变换与底帽变换
顶帽变换(礼帽变换)
-
是原图像与开运算结果图之差。
-
作用:提取出细线状的部分或者噪声。
底帽变换(黑帽变换)
-
是闭运算与原图像结果图之差。
-
作用:得到图像内部的小孔,或前景色的小黑点。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <opencv2/opencv.hpp>
int main() { cv::Mat input = cv::imread("src/rice.jpg", 0); cv::Mat img_bw, result1, result2; cv::threshold(input, img_bw, 100, 255, cv::THRESH_BINARY); cv::Mat kernel = cv::getStructuringElement(0, cv::Size(5, 5));
cv::morphologyEx(img_bw, result1, 5, kernel); cv::morphologyEx(img_bw, result2, 6, kernel);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::namedWindow("顶帽变换结果", cv::WINDOW_NORMAL); cv::namedWindow("底帽变换结果", cv::WINDOW_NORMAL);
cv::imshow("before", img_bw); cv::imshow("顶帽变换结果", result1); cv::imshow("底帽变换结果", result2);
cv::waitKey(0); return 0; }
|
效果截图:

形态学梯度
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <opencv2/opencv.hpp>
int main() { cv::Mat input = cv::imread("src/pic2.png", 0); cv::Mat img_bw, result; cv::threshold(input, img_bw, 100, 255, cv::THRESH_BINARY_INV); cv::Mat kernel = cv::getStructuringElement(0, cv::Size(5, 5));
cv::morphologyEx(img_bw, result, 4, kernel);
cv::namedWindow("before", cv::WINDOW_NORMAL); cv::namedWindow("形态学梯度结果", cv::WINDOW_NORMAL);
cv::imshow("before", img_bw); cv::imshow("形态学梯度结果", result);
cv::waitKey(0); return 0; }
|
效果截图:

连通域分析
连通域:指图像中具有相同像素值且位置相邻的像素组成的区域。
connectedComponents 函数
1 2 3 4
| int connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S);
|
参数如下:
代码如下:
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
| #include <opencv2/opencv.hpp> #include <iostream>
int main() { cv::Mat input = cv::imread("src/rice.jpg"); cv::Mat img_bw, connectimg; cv::cvtColor(input, img_bw, cv::COLOR_BGR2GRAY); cv::threshold(img_bw, img_bw, 100, 255, cv::THRESH_BINARY_INV);
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); cv::erode(img_bw, img_bw, element, cv::Point(-1, -1), 2); cv::dilate(img_bw, img_bw, element, cv::Point(-1, -1), 2);
int num = cv::connectedComponents(img_bw, connectimg, 8, CV_16U); std::cout << "连通域个数:" << num << std::endl;
cv::namedWindow("原图", cv::WINDOW_NORMAL); cv::imshow("原图", img_bw);
cv::Mat result = cv::Mat::zeros(img_bw.size(), CV_8UC3); std::vector<cv::Vec3b> color; color.push_back(cv::Vec3b(0, 0, 255)); color.push_back(cv::Vec3b(0, 255, 0)); color.push_back(cv::Vec3b(255, 0, 0)); color.push_back(cv::Vec3b(0, 255, 255)); color.push_back(cv::Vec3b(255, 255, 0));
for (int i = 0; i < result.cols; i ++) { for (int j = 0; j < result.rows; j ++) { int label = connectimg.at<uint16_t>(i, j); if (label == 0) { continue; } result.at<cv::Vec3b>(i, j) = color[label % 5]; } }
cv::namedWindow("标记连通域后", cv::WINDOW_NORMAL); cv::imshow("标记连通域后", result);
cv::waitKey(0); return 0; }
|
效果截图:

注:黑色也算一个连通域
connectedComponentsWithStats 函数
函数:
1 2 3 4 5 6
| int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity = 8, int ltype = CV_32S);
|
参数如下:
-
image:要标记的8位单通道图像
-
labels:目标标签图像
-
stats:每个标签的统计输出,是一个5列的矩阵,每一行对应每个连通区域的外接矩形的左上角坐标x、y,以及外接矩形的宽高width、height和面积area。
-
centroids:连通域中心点,数据类型CV_64F。
-
connectivity:8或4分别用于八连通域和四连通域。
-
ltype输出图像标签类型。目前支持CV_32S和CV_16U。
-
返回连通域个数。
其中, stats包含了标签为i的连通域的一些信息,可以如下访问标签为i的连通域的面积:
1
| stats.at<int>(i, CC_STAT_AREA)
|
连通域外接矩形的参数:
1 2 3 4 5 6 7 8
| x = stats.at<int>(max_idx, cv::CC_STAT_LEFT);
y = stats.at<int>(max_idx, cv::CC_STAT_TOP);
h = stats.at<int>(max_idx, cv::CC_STAT_HEIGHT);
w = stats.at<int>(max_idx, cv::CC_STAT_WIDTH);
|