形态学:腐蚀与膨胀

官网教程

原理

在这里插入图片描述

  • 腐蚀(erosion)
    定义一个卷积核,当卷积核在原始图像上滑动的时候,每次锚点位置的像素值取卷积核覆盖的最小值:

  element(x′,y′) 是卷积核: d s t ( x , y ) = m i n   s r c ( x + x , y + y )          e l e m e n t ( x , y ) 0 dst(x,y)=min \space src(x+x′,y+y′) \space \space \space \space\space \space \space \space element(x′,y′)≠0


解释:卷积核必须全部被白色覆盖,最小值才是255,否则只要有黑色在卷积核内最小值就是0。最终得到得到结果图如下:
在这里插入图片描述

  • 膨胀(dilation)
    定义一个卷积核,当卷积核在原始图像上滑动的时候,每次锚点位置的像素值取卷积核覆盖的最大值:

  element(x′,y′) 是卷积核: d s t ( x , y ) = m a x   s r c ( x + x , y + y )          e l e m e n t ( x , y ) 0 dst(x,y)=max \space src(x+x′,y+y′) \space \space \space \space\space \space \space \space element(x′,y′)≠0


解释:卷积核只要和白色有交界,最大值就是255,只有当卷积核全部在黑色区域,最大值才是0。最终得到得到结果图如下:
在这里插入图片描述

  • 代码示例(自己)
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <vector>
#include <algorithm>


using namespace std;
using namespace cv;


//得到一幅图中在卷积核覆盖区域的最大或最小值
//row 、col : 锚点在图像中的位置
//size: 卷积核的大小
//flag: 返回的是最大值(0)还是最小值(1),
uchar GetValue(const Mat& image, int row, int col, int size,int flag) 
{
	vector<uchar> v;
	int count = (size-1 ) / 2;
	int start_row = row - count;
	int start_col = col - count;

	for (int i = 0; i < size; i++)
	{
		for (int j = 0; j < size; j++)
		{
			v.push_back(image.at<uchar>(start_row + i, start_col + j));
		}
	}

	sort(v.begin(), v.end());

	if (flag==0)
	    return v.front();
	else
		return v.back();
}



//腐蚀操作
void myerode(Mat& src, Mat&dst, const Mat& element)
{
	int count = element.cols-1; //col或者row两边没有遍历的大小
	int halfcount = count / 2;//col或者row一边没有遍历的大小
	int numofcol = src.cols - count; //需要遍历的cols的数量
	int numofrow = src.rows - count;//需要遍历的rows的数量
	int size = element.cols; 


	for (int i = 0; i < numofrow; i++)
	  for (int j = 0; j < numofcol; j++)
	  {
			uchar tmp = GetValue(src, i + halfcount, j + halfcount, size,0);//从(2,2)开始遍历,到(src.rows-2,src.cols-2)结束
			dst.at<uchar>((i + halfcount), (j + halfcount)) = tmp;
	  }

}


//膨胀操作
void mydilate(Mat& src, Mat&dst, const Mat& element)
{
	int count = element.cols - 1;
	int halfcount = count / 2;
	int numofcol = src.cols - count;
	int numofrow = src.rows - count;
	int size = element.cols;

	for (int i = 0; i < numofrow; i++)
	  for (int j = 0; j < numofcol; j++)
	  {
		uchar tmp = GetValue(src, i + halfcount, j + halfcount, size,1);
		dst.at<uchar>((i + halfcount), (j + halfcount)) = tmp;
	  }

}

int main(void)
{
	Mat src = cv::imread("1.png", cv::IMREAD_GRAYSCALE);
	Mat dst1(src.size(), src.type());
	Mat dst2(src.size(), src.type());
	Mat_<uchar>element(5, 5);
		
	  element<< 0, 0, 1, 0, 0,
				0, 0, 1, 0, 0,
				1, 1, 1, 1, 1,
				0, 0, 1, 0, 0,
				0, 0, 1, 0, 0;



	  myerode(src, dst1, element);
	  mydilate(src, dst2, element);

	  imshow("src", src);
	  imshow("erosion demo", dst1);
	  imshow("dilation demo", dst2);


	waitKey(0);
	return 0;
}



结果:
在这里插入图片描述


官方教程代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

Mat src,erosion_dst,dilation_dst;

int erosion_elem = 0;
int erosion_size = 0;
int dilation_elem = 0;
int dilation_size = 0;

int const max_elem = 2;
int const max_kernel_size = 21;


void Erosion(int, void*);
void Dilation(int, void*);

int main(void)
{
	
	src = cv::imread("../res/cat.jpg",cv::IMREAD_UNCHANGED);
	if(src.empty())
	{
		cout << "load the picture failed"<< endl;
		return -1;
	}

	namedWindow("Erosion Demo",WINDOW_AUTOSIZE);	
	namedWindow("Dilation Demo",WINDOW_AUTOSIZE);
	moveWindow("Dilation Demo",src.cols,0);

//腐蚀:选择卷积核形状的滑块
	createTrackbar("Element:\n 0: Rect \n 1:Cross \n 2: Ellipse","Erosion Demo",
				   &erosion_elem,max_elem,
				   Erosion);
//腐蚀:选择卷积核大小的滑块
	createTrackbar("Kernel size:\n 2n+1","Erosion Demo",
				   &erosion_size,max_kernel_size,
				   Erosion);

//膨胀:选择卷积核形状的滑块
	createTrackbar("Element:\n 0:Rect \n 1:Cross \n 2:Ellipse","Dilation Demo",
					&dilation_elem,max_elem,
					Dilation);
//膨胀:选择卷积核大小的滑块
	createTrackbar("Kernel size :\n 2n+1","Dilation Demo",
					&dilation_size, max_kernel_size,
					Dilation);

	Erosion(0,NULL);
	Dilation(0,NULL);
	
	waitKey(0);

	return 0;
};


void Erosion(int, void*) //滑块改变的时候,将调用此函数,erosion_size、erosion_elem 全局变量将跟新再传入
{
	int erosion_type=0;
	if(erosion_elem==0){erosion_type = cv::MORPH_RECT;}
	else if(erosion_elem == 1){erosion_type = cv::MORPH_CROSS;}
	else if(erosion_elem == 2){erosion_type = cv::MORPH_ELLIPSE;}

	Mat element = cv::getStructuringElement(erosion_type,
											Size(2*erosion_size+1,2*erosion_size+1),
											Point(-1,-1));

	cv::erode(src,erosion_dst,element);
	imshow("Erosion Demo",erosion_dst);
	
}


//滑块改变的时候,将调用此函数,dilation_size、dilation_elem 全局变量将跟新再传入
void Dilation(int, void*)
{

	int dilation_type = 0;
	if(dilation_elem == 0) {dilation_type = cv::MORPH_RECT;}
	else if(dilation_elem ==1) { dilation_type = cv::MORPH_CROSS; }
	else if(dilation_elem == 2) {dilation_type = cv::MORPH_ELLIPSE;}

	Mat element = cv::getStructuringElement(dilation_type,
											Size(2*dilation_size+1,2*dilation_size+1),
											Point(-1,-1));

	cv::dilate(src,dilation_dst,element);
	imshow("Dilation Demo",dilation_dst);

}

原图:
在这里插入图片描述

结果:左边是腐蚀,右边是膨胀
在这里插入图片描述



OpenCV API说明

  • cv::erode():主要前三个参数,后面默认
    void cv::erode ( InputArray src,     //源图像
    OutputArray dst,        //输出图像
    InputArray kernel,        //卷积核(可以使用cv::getStructuringElement创造)
    Point anchor = Point(-1,-1),    // 卷积核的锚点
    int iterations = 1,            // 腐蚀使用的次数,默认1即可
    int borderType = BORDER_CONSTANT,    //边界的处理类型
    const Scalar & borderValue = morphologyDefaultBorderValue()     //如果边界使用的是:borderType = BORDER_CONSTANT,这个参数就是边界值
    )

  • cv::dilate():主要前三个参数,后面默认
    void cv::dilate ( InputArray src,    //源图像
    OutputArray dst,        //输出图像
    InputArray kernel,        //卷积核(可以使用cv::getStructuringElement创造)
    Point anchor = Point(-1,-1),    // 卷积核的锚点
    int iterations = 1,            // 膨胀使用的次数,
    int borderType = BORDER_CONSTANT,    //边界的处理类型
    const Scalar & borderValue = morphologyDefaultBorderValue()     //如果边界使用的是:borderType = BORDER_CONSTANT,这个参数就边界值
    )

  • cv::getStructuringElement():返回一个特定形状、大小的卷积核供腐蚀膨胀使用
    Mat cv::getStructuringElement (
    int shape,        //卷积核的形状:MORPH_RECT、 MORPH_CROSS 、MORPH_ELLIPSE
    Size ksize,            //卷积核的大小(Size(x,y))
    Point anchor = Point(-1,-1)         //卷积核的锚点,默认 (−1,−1) 意味着中心点,注意:只有cross-shaped形状的卷积核依赖锚点
    )

  • createTrackbar():给窗口创建一个滑动条
    int cv::createTrackbar (
    const String & trackbarname,     // 创建的trackbar的名字
    const String & winname,         //trackbar的父窗口的名字
    int * value,         //初始滑块的位置
    int count,        //滑块的最大值,最小是总是0
    TrackbarCallback onChange = 0,     //指向回调函数的指针(传入函数名即可),每当滑块的位置改变的时候,调用此函数(函数应该定义成:void Foo(int,void*)),传入当前滑块的位置给第一个形参
    void * userdata = 0         //作为参数传递给回调函数的第二个形参
    )


猜你喜欢

转载自blog.csdn.net/zzyczzyc/article/details/84929959