形态学操作
形态学操作
简介
- 在有些情况下,相比于图像中物体的纹理信息,物体的形状与位置信息对我们更加重要,因此可以将物体的内部信息忽略,以形态为基础对图像进行描述和分析
- 图像形态学用具有一定形态的结构元素度量和提取图像中的对应形状,以达到对图像分析和识别的目的
- 简而言之:一组基于形状处理图像的操作。 形态学操作将结构元素应用于输入图像并生成输出图像
连通域
- 图像的连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域
腐蚀(Erosion)
简介
- 图像的腐蚀过程与图像的卷积操作类似,都需要模板矩阵来控制运算的结果,在图像的腐蚀和膨胀中,这个模板矩阵称为结构元素
- 在定义结构元素之后,将结构元素绕着中心点旋转180o,之后将结构元素的中心点依次放到图像中每一个非零元素处,如果此时结构元素内所有的元素所覆盖的图像像素值均不为0,那么保留结构元素中心点对应的图像像素,否则将删除结构元素中心点对应的像素
- 图像形态学腐蚀可以将细小的噪声区域去除,但是会将图像主要区域的面积缩小,造成主要区域的形状发生改变
结构元素
- 图像腐蚀过程中使用的结构元素可以根据需求自己生成,但是为了研究人员的使用方便,OpenCV提供了
getStructuringElement()
函数用于生成常用的矩形结构元素、十字结构元素和椭圆结构元素
Mat cv::getStructuringElement(shape, ksize)
-
shape:生成结构元素的种类,可以选择的参数及其含义如下
标志参数 简记 作用 MORPH_RECT 0 矩形结构元素,所有元素都为1 MORPH_CROSS 1 十字结构元素,中间的列和行元素为1 MORPH_ELLIPSE 2 椭圆结构元素,矩形的內接椭圆元素为1 -
ksize:结构元素的尺寸。一般情况下,当结构元素的种类相同时,结构元素的尺寸越大,腐蚀效果越明显
实现
OpenCV 4 提供了用于图像腐蚀的erode()
函数
void cv::erode(src, dst, kernel, anchor, iterations)
- src:输入的待腐蚀图像,图像的通道数可以是任意的,但类型必须是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一
- dst:腐蚀后的输出图像,与输入图像src具有相同的尺寸和数据类型
- kernel:用于腐蚀操作的结构元素,可以自己定义,也可以用
getStructuringElement()
函数生成 - anchor:中心点在结构元素中的位置,默认参数为结构元素的几何中心点
- iterations:腐蚀的次数,默认值为1
示例代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
// 生成用于腐蚀的原图
Mat_<uchar> src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 0, 0, 0, 0, 0);
Mat kernel;
kernel = getStructuringElement(1, Size(3, 3)); // 十字结构元素
Mat_<uchar> erodeSrc; // 存放腐蚀后的图像
erode(src, erodeSrc, kernel);
namedWindow("src", WINDOW_GUI_NORMAL);
namedWindow("erodeSrc", WINDOW_GUI_NORMAL);
imshow("src", src);
imshow("erodeSrc", erodeSrc);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
膨胀(Dilation)
简介
- 相比于图像腐蚀,图像膨胀是其相反的过程。与图像腐蚀相似,图像膨胀同样需要结构元素用于控制图像膨胀的效果
- 在定义结构元素之后,将结构元素的中心点依次放到图像中每一个非零元素处,如果原图中某个元素被结构元素覆盖,但是该像素的像素值不与结构元素中心点对应的像素点的像素值相同,那么将原图中该像素的像素值修改为结构元素中心点对应点的像素值
- 图像形态学膨胀可以扩充每一个区域的面积,填充较小的空洞,但是会增加噪声的面积
实现
OpenCV 4 提供了用于图像膨胀的dilate()
函数
void cv::dilate(src, dst, kernel, anchor, iterations)
- src:输入的待膨胀图像,图像的通道数可以是任意的,但类型必须是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一
- dst:膨胀后的输出图像,与输入图像src具有相同的尺寸和数据类型
- kernel:用于膨胀操作的结构元素,可以自己定义,也可以用
getStructuringElement()
函数生成 - anchor:中心点在结构元素中的位置,默认参数为结构元素的几何中心点
- iterations:膨胀的次数,默认值为1
示例代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
// 生成用于膨胀的原图
Mat_<uchar> src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 0, 0, 0, 0, 0);
Mat kernel;
kernel = getStructuringElement(1, Size(3, 3)); // 十字结构元素
Mat_<uchar> dilateSrc; // 存放膨胀后的图像
dilate(src, dilateSrc, kernel);
namedWindow("src", WINDOW_GUI_NORMAL);
namedWindow("dilateSrc", WINDOW_GUI_NORMAL);
imshow("src", src);
imshow("dilateSrc", dilateSrc);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
开运算(Opening)
简介
- 图像开运算可以去除图像中的噪声,消除较小连通域,保留较大连通域,同时能够在两个物体纤细的连接处将两个物体分离,并且在不明显改变较大连通域面积的同时能够平滑连通域的边界
- 开运算是图像腐蚀和膨胀操作的结合,首先对图像进行腐蚀,消除图像中的噪声和较小的连通域,之后通过膨胀运算弥补较大连通域因腐蚀而造成的面积减小
实现
OpenCV 4 提供了图像腐蚀和膨胀运算不同组合形式的morphologyEx()
函数,以实现图像的开运算、闭运算、形态学梯度、顶帽运算、黑帽运算,以及击中击不中变换
void cv::morphologyEx(src, dst, op, kernel, anchor, iterations)
-
src:输入图像,图像的通道数可以是任意的,但类型必须是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一
-
dst:形态学操作后的输出图像,与输入图像具有相同的尺寸和数据类型
-
op:形态学操作类型的标志,可以选择的标志及其含义如下
标志参数 简记 含义 MORPH_DILATE 1 图像膨胀 MORPH_OPEN 2 开运算 MORPH_CLOSE 3 闭运算 MORPH_GRADIENT 4 形态学梯度 MORPH_TOPHAT 5 顶帽运算 MORPH_BLACKHAT 6 黑帽运算 MORPH_HITMISS 7 击中击不中运算 -
kernel:结构元素,可以自己定义,也可以用
getStructuringElement()
函数生成 -
anchor:中心点在结构元素中的位置,默认参数为结构元素的几何中心点
-
iterations:处理的次数,默认值为1
示例代码:
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
namedWindow("src", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("src", src);
// 3x3 矩形结构元素
Mat kernel = getStructuringElement(0, Size(3, 3));
// 对二值化矩阵进行开运算
Mat open;
morphologyEx(src, open, MORPH_OPEN, kernel);
namedWindow("Opening", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("Opening", open);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
闭运算(Closing)
简介
- 图像闭运算可以去除连通域内的小型空洞,平滑物理轮廓,连接两个临近的连通域
- 闭运算是图像腐蚀和膨胀操作的结合,首先对图像进行膨胀,填充连通域内的小型空洞,扩大连通域的边界,将临近的两个连通域连接,之后通过腐蚀运算减少由膨胀运算引起的连通域边界的扩大以及面积增加
实现
示例代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
namedWindow("src", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("src", src);
// 3x3 矩形结构元素
Mat kernel = getStructuringElement(0, Size(3, 3));
// 对二值化矩阵进行闭运算
Mat close;
morphologyEx(src, close, MORPH_CLOSE, kernel);
namedWindow("Closing", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("Closing", close);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
形态学梯度(Morphological Gradient)
简介
- 形态学梯度能够描述目标的边界,根据图像腐蚀和膨胀与原图之间的关系计算得到,形态学梯度可以分为基本梯度、内部梯度和外部梯度
- 基本梯度是原图像膨胀后图像与腐蚀后图像间的差值图像,内部梯度图像是原图像与腐蚀后图像间的差值图像,外部梯度是膨胀后图像与原图像间的差值图像
注,morphologyEx()
函数仅可实现图像的基本梯度,如果需要计算图像的内部梯度或外部梯度,需要自己通过编程实现
实现
示例代码:
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
namedWindow("src", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("src", src);
// 3x3 矩形结构元素
Mat kernel = getStructuringElement(0, Size(3, 3));
// 对二值化矩阵进行形态学梯度运算
Mat gradient;
morphologyEx(src, gradient, MORPH_GRADIENT, kernel);
namedWindow("Morphological Gradient", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("Morphological Gradient", gradient);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
顶帽运算(Top Hat)
简介
- 图像顶帽运算是原图像与开运算结果之间的差值,往往用来分离比邻近点亮一些的斑块
- 顶帽运算先对图像进行开运算,之后从原图像中减去开运算计算的结果
实现
示例代码:
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
namedWindow("src", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("src", src);
// 3x3 矩形结构元素
Mat kernel = getStructuringElement(0, Size(3, 3));
// 对二值化矩阵进行顶帽运算
Mat tophat;
morphologyEx(src, tophat, MORPH_TOPHAT, kernel);
namedWindow("Top Hat", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("Top Hat", tophat);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
黑帽运算(Black Hat)
简介
- 黑帽运算是原图像与顶帽运算结果之间的差值,往往用来分离比邻近点暗一些的斑块
- 黑帽运算先对图像进行闭运算,之后从闭运算结果中减去原图像
实现
示例代码:
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
namedWindow("src", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("src", src);
// 3x3 矩形结构元素
Mat kernel = getStructuringElement(0, Size(3, 3));
// 对二值化矩阵进行黑帽运算
Mat blackhat;
morphologyEx(src, blackhat, MORPH_BLACKHAT, kernel);
namedWindow("Black Hat", WINDOW_NORMAL); // 可以自由调节显示图像的尺寸
imshow("Black Hat", blackhat);
waitKey(0);
destroyAllWindows();
return 0;
}
击中不击中变换(Hit-or-Miss)
简介
- 击中不击中变换是比图像腐蚀要求更加苛刻的一种形态学操作,图像腐蚀只需要图像能够将结构元素中所有非零元素包含,但是击中不击中变换要求原图像中需要存在与结构元素一模一样的结构,即结构元素中非零元素也需要同时被考虑
PS:在使用矩形结构元素时,击中击不中变换与图像的腐蚀结果相同
实现
示例代码:
#include <opencv2/opencv.hpp>
const int rate = 50;
using namespace cv;
int main()
{
Mat input_image = (Mat_<uchar>(8, 8) <<
0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 0, 0, 0, 255,
0, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 0, 255, 0, 0,
0, 0, 255, 0, 0, 0, 0, 0,
0, 0, 255, 0, 0, 255, 255, 0,
0, 255, 0, 255, 0, 0, 255, 0,
0, 255, 255, 255, 0, 0, 0, 0);
Mat kernel = (Mat_<int>(3, 3) <<
0, 1, 0,
1, -1, 1,
0, 1, 0);
Mat output_image;
morphologyEx(input_image, output_image, MORPH_HITMISS, kernel);
kernel = (kernel + 1) * 127;
kernel.convertTo(kernel, CV_8U);
resize(kernel, kernel, Size(), rate, rate, INTER_NEAREST);
imshow("kernel", kernel);
moveWindow("kernel", 0, 0);
resize(input_image, input_image, Size(), rate, rate, INTER_NEAREST);
imshow("Original", input_image);
moveWindow("Original", 0, 200);
resize(output_image, output_image, Size(), rate, rate, INTER_NEAREST);
imshow("Hit or Miss", output_image);
moveWindow("Hit or Miss", 500, 200);
waitKey(0);
return 0;
}
运行结果: