OpenCV C++ 形态学分析:从基础操作到高级应用
形态学分析是基于形状的图像处理方法,通过预设的结构元素对图像进行操作,实现对目标形态的调整与特征提取。本章将系统讲解形态学的核心操作 —— 腐蚀与膨胀,以及由它们组合而成的开操作、闭操作、形态学梯度、顶帽与黑帽等高级应用,结合实例展示这些技术在噪声去除、边缘提取、形状分析等场景的实用价值。
一、腐蚀与膨胀
腐蚀与膨胀是形态学分析的基础操作,二者互为对偶,通过与结构元素的相互作用改变图像中前景物体的形态。
1.1 腐蚀操作 (Erosion)
腐蚀操作的核心是缩小前景物体,它会消除物体边缘的像素,使物体边界向内收缩,常用于去除小噪声、分离粘连物体。
原理:对于图像中的每个像素,当结构元素完全包含于前景区域时,该像素保留为前景,否则变为背景。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;// 自定义腐蚀操作(4连通)
Mat erodeCustom(const Mat& binary, int ksize = 3) {CV_Assert(binary.type() == CV_8UC1);int rows = binary.rows;int cols = binary.cols;Mat result = Mat::zeros(rows, cols, CV_8UC1);int center = ksize / 2;// 创建矩形结构元素Mat kernel = Mat::ones(ksize, ksize, CV_8UC1);// 遍历图像(边界不处理)for (int i = center; i < rows - center; ++i) {for (int j = center; j < cols - center; ++j) {if (binary.at<uchar>(i, j) == 255) {bool allForeground = true;// 检查结构元素覆盖的所有像素for (int m = 0; m < ksize; ++m) {for (int n = 0; n < ksize; ++n) {if (kernel.at<uchar>(m, n) == 1) {int x = i + m - center;int y = j + n - center;if (binary.at<uchar>(x, y) == 0) {allForeground = false;break;}}}if (!allForeground) break;}if (allForeground) {result.at<uchar>(i, j) = 255;}}}}return result;
}// 使用OpenCV内置腐蚀函数
Mat erodeOpenCV(const Mat& binary, int ksize = 3, int iterations = 1) {Mat result;// 创建结构元素(矩形、椭圆或十字形)Mat kernel = getStructuringElement(MORPH_RECT, Size(ksize, ksize));// 应用腐蚀操作erode(binary, result, kernel, Point(-1, -1), iterations);return result;
}
1.2 膨胀操作 (Dilation)
膨胀操作与腐蚀相反,它会扩大前景物体,使物体边界向外扩张,常用于填补物体内部的小空洞、连接断裂的物体。
原理:对于图像中的每个像素,当结构元素与前景区域有交集时,该像素变为前景,否则保持背景。
// 自定义膨胀操作(4连通)
Mat dilateCustom(const Mat& binary, int ksize = 3) {CV_Assert(binary.type() == CV_8UC1);int rows = binary.rows;int cols = binary.cols;Mat result = Mat::zeros(rows, cols, CV_8UC1);int center = ksize / 2;Mat kernel = Mat::ones(ksize, ksize, CV_8UC1);for (int i = center; i < rows - center; ++i) {for (int j = center; j < cols - center; ++j) {if (binary.at<uchar>(i, j) == 0) {bool hasForeground = false;// 检查结构元素覆盖的区域是否有前景像素for (int m = 0; m < ksize; ++m) {for (int n = 0; n < ksize; ++n) {if (kernel.at<uchar>(m, n) == 1) {int x = i + m - center;int y = j + n - center;if (binary.at<uchar>(x, y) == 255) {hasForeground = true;break;}}}if (hasFo