形态学分析是基于形状的图像处理方法,通过预设的结构元素对图像进行操作,实现对目标形态的调整与特征提取。本章将系统讲解形态学的核心操作 —— 腐蚀与膨胀,以及由它们组合而成的开操作、闭操作、形态学梯度、顶帽与黑帽等高级应用,结合实例展示这些技术在噪声去除、边缘提取、形状分析等场景的实用价值。
一、腐蚀与膨胀
腐蚀与膨胀是形态学分析的基础操作,二者互为对偶,通过与结构元素的相互作用改变图像中前景物体的形态。
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