C++图像的形态学操作

什么是图像的形态学操作?

    使用数学形态学的基本运算,由计算机对图像进行分析,以达到所需结果的一种技术。
形态学,morphology, 形态学最初是生物学中研究动物和植物结构的一个分支,被引入图像处理领域后,图像形态学就指以形态为基础对图像进行分析的一种方法或技术。

图像形态学操作的基本思想 :

    是用具有一定形态的结构元素去度量和提取图像中的对应形状以达到对图像分析和识别的目的。

关键点

    图像形态学操作主要是对二值图像进行操作的,来连接相邻的元素或分离成独立的元素。其次是灰度图像,但处理彩色图像几乎没有意义!

图像形态学操作主要有:

    膨胀、腐蚀、开运算、闭运算、梯度运算、礼帽运算、黑帽运算,击中与击不中变换 等等。其中膨胀和腐蚀是基本操作,后面的操作都是在膨胀和腐蚀的基础上延申而来的。

图像形态学操作有哪些应用领域

    由于对图像进行形态学操作后,可以实现比如消除噪声、提取边界、填充区域、提取连通分量、凸壳、细化、粗化等; 还可以分割出独立的图像元素,或者图像中相邻的元素;求取图像中明显的极大值区域和极小值区域;求取图像梯度等等, 所以在视觉检测、图像理解、文字识别、医学图像处理、图像压缩编码等领域有非常重要的应用。

    在进行各种形态学操作之前,先介绍一个至关重要的函数:morphologyEx函数,其利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算、形态学梯度、“顶帽”、“黑帽”等等。

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() );

参数1:InputArray src

    输入图像,图像数据类型必须为CV_8U, CV_16U, CV_16S, CV_32F or CV_64F中的一种。

参数2:OutputArray dst

    输出图像,数据类型与大小和输入图像一样。

参数3:int op

    形态学处理的类型:

MORPH_ERODE = 0:腐蚀处理

MORPH_DILATE = 1:膨胀处理

MORPH_OPEN = 2:开运算处理

MORPH_CLOSE = 3:闭运算处理

MORPH_GRADIENT = 4:形态学梯度

MORPH_TOPHAT = 5:顶帽变换

MORPH_BLACKHAT = 6:黑帽变换

MORPH_HITMISS = 7 :击中-击不中变换

参数4:InputArray kernel

    结构元矩阵

参数5:Point anchor = Point(-1,-1)

    结构元中心点, 默认值Point(-1,-1), 表示正中心

参数6:int iterations = 1

    腐蚀膨胀处理的次数,默认值为1;

如果是开运算闭运算,次数表示先腐蚀或者膨胀几次,再膨胀腐蚀几次,而不是开运算闭运算几次:

如开运算且次数为2:erode -> erode -> dilate -> dilate

参数7:int borderType = BORDER_CONSTANT

    图像边框插值类型,默认类型为固定值填充

BORDER_CONSTANT = 0:固定值 i 填充:iiiiii | abcdefgh | iiiiiii

BORDER_REPLICATE = 1:两端复制:aaaaaa | abcdefgh | hhhhhhh

BORDER_REFLECT = 2:镜像复制:fedcba | abcdefgh | hgfedcb

BORDER_WRAP = 3:去除一端的值然后复制: cdefgh | abcdefgh | abcdefg

BORDER_REFLECT_101 = 4:去除一端的值然后镜像复制: gfedcb|abcdefgh|gfedcba

BORDER_TRANSPARENT = 5:推导赋值 uvwxyz | abcdefgh | ijklmno

BORDER_REFLECT101 = BORDER_REFLECT_101: same as BORDER_REFLECT_101

BORDER_DEFAULT = BORDER_REFLECT_101: same as BORDER_REFLECT_101

BORDER_ISOLATED = 16:< do not look outside of ROI

参数8:const Scalar& borderValue = morphologyDefaultBorderValue()

    文档中说明:param borderValue Border value in case of a constant border. The default value has a special meaning.

    morphologyEx函数配合上自己定义的结构元矩阵kernel,就可以完成各种形态学操作。

以下是原始图、Sobel算子得到的梯度图以及大津法OTSU阈值分割之后的图片:
85cd3d8072b24342a30b0deef8f99895.png3408f0ed92c448f8ae262f16103bd515.png3b8b57bab9184a3eab157955e09cc943.png

其中大津法作用之后的二值图作为形态学操作的输入。

一、图像腐蚀

    顾名思义,是将物体的边缘加以腐蚀。具体的操作是拿一个宽m高n的矩形作为kernel,对图像中的每一个像素进行扫描,扫描后的图像的每个像素点和原图的位置对应,同时和扫描时kernel的中心点对应。用kernel遍历原图的所有像素,每遍历一次就将对应位置的像素点更改为kernel中的最小值。这样原图所有像素遍历一轮后,原图中突出的像素点就会被置为最小值,人类视觉看起来就是图像被腐蚀了。

Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
morphologyEx(image, image, MORPH_ERODE, element);
imshow("【腐蚀效果图】", image);

1b985b5bf69941f1b8352d1a4f6df563.png

二、图像膨胀

    膨胀和腐蚀是一对儿相反的操作,膨胀能对图像的边界进行扩展,就是将图像的轮廓加以膨胀。
操作方法与腐蚀操作一样,也是拿一个kernel,对图像的每个像素做遍历处理。不同之处在于生成的像素值不是所有像素中最小的值,而是最大的值。这样操作的结果会将图像外围的突出点连接并向外延伸。

Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));  
	//进行形态学操作
morphologyEx(image, image, MORPH_DILATE, element);   //结果保存到自身
imshow("【膨胀效果图】", image);

26c262c1d2db4008ab695240d980ebf6.png

可见 指纹内部暗点消失,边界部分有细微的扩张。

三、图像开运算、闭运算、梯度运算、顶帽运算、底帽运算

    膨胀和腐蚀是图像形态学处理的基础,将膨胀和腐蚀通过不同方式结合起来就可以得到图像开运算、闭运算、梯度运算、顶帽运算、底帽运算等运算。

①开运算

     开运算(opening operation),其实就是先腐蚀后膨胀过得过程,可以用来消除小物体,在纤细点出分离物体,平滑较大物体的边界同时并不明显改变其面积。

Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
morphologyEx(image, image, MORPH_OPEN, element);
imshow("【开运算效果图】", image);


25965ddcc6d04cc5ab1d70ee3eb5f489.png

 ②闭运算

    先膨胀后腐蚀的过程称为闭运算(closing operation), 闭运算能够排除小黑洞(黑色区域)。

Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
morphologyEx(image, image, MORPH_CLOSE, element);
imshow("【闭运算效果图】", image);

  d9283062a98b4e4da77d886992f40ec7.png  

 ③形态学梯度

    形态学梯度(morphological gradient)为膨胀图与腐蚀图之差,对二值图像进行这一操作可以将团块(blob)的边缘突出来。

Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
morphologyEx(image, image, MORPH_GRADIENT, element);
imshow("【形态学梯度效果图】", image);


5b50af41292746539d20401a12eb2f95.png

 ④顶帽运算

    顶帽运算(top hat) 又常常被译为“礼帽”运算,为原图像与“开运算”的结果图之差。因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原型轮廓周围的区域更明亮的区域。且这一操作和选择的核的大小有关。顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的时候,可以用顶帽运算进行背景提取。

Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
morphologyEx(image, image, MORPH_TOPHAT, element);
imshow("【顶帽运算效果图】",image);

add56a703ede402bb8f0c8a1994fc05f.png

 ⑤底帽运算
    黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差,黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以,黑帽运算用来分离比邻近点暗一些的斑块。

Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
morphologyEx(image, image, MORPH_BLACKHAT, element);
imshow("【黑帽运算效果图】", image);

f697d894d79c4ac288f82a9dee6a6506.png

 四、代码汇总

1.头文件,把各种操作封装到一个类中。

#include <opencv2/opencv.hpp>

using namespace cv;

class Shape_process{
public:
	void Dilation(Mat image);
	void Opening(Mat image);
	void Closing(Mat image);
	void Gradient(Mat image);
	void TopHat(Mat image);
	void BlackHat(Mat image);
	void ErodeEX(Mat image);
	Mat Sobel(Mat img);
	Mat OTSU(Mat img, int arr[]);
};

2.cpp文件,对类中方法进行定义。

#include<Test6.h>
#include<iostream>

using namespace std;

void Shape_process::Dilation(Mat image){
	namedWindow("【膨胀原始图】");
	namedWindow("【膨胀效果图】"); 
	imshow("【膨胀原始图】", image);
	Mat module = Mat::zeros(Size(image.cols, image.rows), image.type());
	//定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));  
	//进行形态学操作
	morphologyEx(image, module, MORPH_DILATE, element);
	imshow("【膨胀效果图】", module);
	waitKey(0);
}

void Shape_process::Opening(Mat image){
	namedWindow("【开运算原始图】");
	namedWindow("【开运算效果图】");
	imshow("【开运算原始图】", image);
	Mat module = Mat::zeros(Size(image.cols, image.rows), image.type());
	//定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
	morphologyEx(image, module, MORPH_OPEN, element);
	imshow("【开运算效果图】", module);
	waitKey(0);
}

void Shape_process::Closing(Mat image){
	namedWindow("【闭运算原始图】");
	namedWindow("【闭运算效果图】");
	imshow("【闭运算原始图】", image);
	Mat module = Mat::zeros(Size(image.cols, image.rows), image.type());
	//定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
	morphologyEx(image, module, MORPH_CLOSE, element);
	imshow("【闭运算效果图】", module);
	waitKey(0);
}

void Shape_process::Gradient(Mat image){
	namedWindow("【形态学梯度原始图】");
	namedWindow("【形态学梯度效果图】"); 
	imshow("【形态学梯度原始图】", image);
	Mat module = Mat::zeros(Size(image.cols, image.rows), image.type());
	//定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
	morphologyEx(image, module, MORPH_GRADIENT, element);
	imshow("【形态学梯度效果图】", module);
	waitKey(0);
}

void Shape_process::TopHat(Mat image){
	namedWindow("【顶帽运算原始图】");
	namedWindow("【顶帽运算效果图】");
	imshow("【顶帽运算原始图】", image);
	Mat module = Mat::zeros(Size(image.cols, image.rows), image.type());
	//定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
	morphologyEx(image, module, MORPH_TOPHAT, element);
	imshow("【顶帽运算效果图】", module);

	waitKey(0);
}

void Shape_process::BlackHat(Mat image){
	namedWindow("【黑帽运算原始图】");
	namedWindow("【黑帽运算效果图】"); 
	imshow("【黑帽运算原始图】", image);
	Mat module = Mat::zeros(Size(image.cols, image.rows), image.type());
	//定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
	morphologyEx(image, module, MORPH_BLACKHAT, element);
	imshow("【黑帽运算效果图】", module);
	waitKey(0);
}

void Shape_process::ErodeEX(Mat image){
	namedWindow("【腐蚀原始图】");
	namedWindow("【腐蚀效果图】"); 
	imshow("【腐蚀原始图】", image);
	Mat module = Mat::zeros(Size(image.cols, image.rows), image.type());
	//定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	//进行形态学操作
	morphologyEx(image, module, MORPH_ERODE, element);
	imshow("【腐蚀效果图】", module);
	waitKey(0);
}

Mat Shape_process::Sobel(Mat img) {                   // 基于Prewitt算子的阈值分割
	Mat Sobel_Ojld = Mat::zeros(Size(img.cols, img.rows), img.type());  //用于计算欧几里得距离的空白图像
	for (int row = 1; row < img.rows - 1; row++) {
		for (int col = 1; col < img.cols - 1; col++) {
			Sobel_Ojld.at<uchar>(row, col) = saturate_cast<uchar>(sqrt(pow(img.at<uchar>(row - 1, col + 1) - img.at<uchar>(row - 1, col - 1) + 2 * img.at<uchar>(row, col + 1) - 2 * img.at<uchar>(row, col - 1) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row + 1, col - 1), 2) + pow(img.at<uchar>(row + 1, col - 1) - img.at<uchar>(row - 1, col - 1) + 2 * img.at<uchar>(row + 1, col) - 2 * img.at<uchar>(row - 1, col) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row - 1, col + 1), 2)));
		}
	}
	imshow("Sobel图像(欧几里得距离)", Sobel_Ojld);
	int pixel_num[256] = { 0 };            //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			pixel_num[Sobel_Ojld.at<uchar>(row, col)] += 1;   //遍历到的像素值作为索引,次数+1
		}
	}

	imshow("欧几里得阈值分割", Sobel_Ojld);

	Mat m1 = OTSU(Sobel_Ojld, pixel_num);     // OTSU阈值分割
	return m1;
}

Mat Shape_process::OTSU(Mat img, int arr[]) {   // 需要输入待处理的图像 和 直方图像素个数数组
	int N = img.rows * img.cols;    // 统计输入图像的像素个数N
	double pro[256] = { 0 };            // 定义概率数组
	for (int i = 0; i < 256; i++) {
		pro[i] = arr[i] / N;            // 得到每个像素值的概率
	}
	double delta = 0, w0 = 0, w1 = 0, u0 = 0, u1 = 0, v = 0;           // 变量初始化为0
	int T = 0, thresh = 0;
	while (T <= 255) {
		for (int i = 0; i <= T; i++) {
			w0 += pro[i];                       // C0的概率
		}
		w1 = 1 - w0;                            // C1的概率
		for (int i = 0; i <= 255; i++) {
			if (i <= T) {
				u0 += pro[i] * i;                     // C0的平均值
			}
			else {
				u1 += pro[i] * i;                     // C1的平均值
			}
			v = w0 * w1 * pow(u1 - u0, 2);
			if (v > delta) {
				delta = v;
				thresh = T;
			}
			T += 1;
		}
	}
    //由最佳像素阈值进行二值分割
	Mat OTSU = Mat::zeros(Size(img.cols, img.rows), img.type());
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			if (img.at<uchar>(row, col) > thresh) {
				OTSU.at<uchar>(row, col) = 255;
			}
		}
	}
	return OTSU;
}

3.cpp文件,进行各种形态学操作。

#include <opencv2/opencv.hpp>
#include <Test6.h>

using namespace cv;

int main() {
	Mat Gray = imread("C:\\Users\\LLLiePP\\Desktop\\zhiwen.png", IMREAD_GRAYSCALE);
    if (Gray.empty())return -1;       //判断图片是否引入出错

	Shape_process test;
	Mat otsu = test.Sobel(Gray);

	test.Dilation(otsu);
	test.Opening(otsu);
	test.Closing(otsu);
	test.Gradient(otsu);
	test.TopHat(otsu);
	test.BlackHat(otsu);
	test.ErodeEX(otsu);

	return 0;
};

猜你喜欢

转载自blog.csdn.net/m0_64007201/article/details/128360677