原理
- 腐蚀(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 dst(x,y)=min src(x+x′,y+y′) 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 dst(x,y)=max src(x+x′,y+y′) 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 //作为参数传递给回调函数的第二个形参
)