1、开运算
开运算:先腐蚀后膨胀,表达公式为:
dst = open(src, element) = dilate(erode(src, element), element)
开运算可以用来消除图像中的细小对象(前提是黑色为类背景,类白色为前景),在纤细点处分离物体和平滑较大物体的边界而又不明显改变其面积和形状。
例如,看看下面的例子。左边的图像是原始图像,右边的图像是应用开运算变换后的结果。我们可以看到白色小点消失了。
OpenCV中,使用函数cv::morphologyEx实现开运算,其函数原型为:
void cv::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() )
函数cv::morphologyEx可以使用腐蚀和膨胀作为基本操作来执行高级形态学操作。任何操作都可以就地完成。对于多通道图像,每个通道都是独立处理的。通过指定不同的op实现不同的形态学操作,op的取值见下表:
参数取值 | 取值说明 |
---|---|
MORPH_OPEN | 开运算,dst=open(src,element)=dilate(erode(src,element)) |
MORPH_CLOSE | 闭运算,dst=close(src,element)=erode(dilate(src,element)) |
MORPH_GRADIENT | 形态学梯度,dst=morph_grad(src,element)=dilate(src,element)−erode(src,element) |
MORPH_TOPHAT | 顶帽,dst=tophat(src,element)=src−open(src,element) |
MORPH_BLACKHAT | 黑帽,dst=blackhat(src,element)=close(src,element)−src |
MORPH_HITMISS | “hit or miss”,只支持CV_8UC1二进制图像 |
可见,对于形态学操作的其它操作,也只需要改变op的值即可,所以后面的相关操作就只是简单地介绍原理,相应的函数API不再赘述。
注意: 迭代次数是应用腐蚀或膨胀操作的次数。例如,具有2次迭代的开运算(MORPH_OPEN)等效于依次应用:腐蚀->腐蚀->膨胀->膨胀(而不是腐蚀->膨胀->腐蚀->膨胀)。
#include
using namespace cv;
using namespace std;
int main(int argc,char **argv)
{
char fileName[] = "O:\\CSDN\\MORPH.png";
Mat src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
cout << "couldn't open image: " << fileName << ",please check out!\n";
system("pause");
return EXIT_FAILURE;
}
// 定义结构元素,可以自行尝试不同的SIZE,去除小物体(噪声)的效果不同
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
Mat dst;
// 执行开运算
morphologyEx(src, dst, MORPH_OPEN, kernel);
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 200, 200);
namedWindow("MORPH_OPEN", WINDOW_AUTOSIZE);
moveWindow("MORPH_OPEN", src.cols + 200, 200);
imshow("Original", src);
imshow("MORPH_OPEN", dst);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
对比右边开运算的结果,很多较小的图形已经被去除,当然也还有一些没去掉,这个就与Size的取值有关,可以自行尝试。
2、闭运算
闭运算:先膨胀后腐蚀。其公式为:
dst=close(src,element)=erode(dilate(src,element))
闭运算可以用来填充目标内部的细小孔洞(fill hole),将断开的邻近目标连接,在不明显改变物体面积和形状的情况下平滑其边界。
如,闭运算可以达到下图的效果:
#include
using namespace cv;
using namespace std;
int main(int argc,char **argv)
{
char fileName[] = "O:\\CSDN\\MORPH.png";
Mat src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
cout << "couldn't open image: " << fileName << ",please check out!\n";
system("pause");
return EXIT_FAILURE;
}
Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));
Mat dst;
morphologyEx(src, dst, MORPH_CLOSE, kernel);
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 200, 200);
namedWindow("MORPH_CLOSE", WINDOW_AUTOSIZE);
moveWindow("MORPH_CLOSE", src.cols + 200, 200);
imshow("Original", src);
imshow("MORPH_CLOSE", dst);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
从上图可以看出,许多的黑色小孔已经被堵上,但比较大的空是难以完全填充上的。
3、形态学梯度
形态学梯度操作能描述图像亮度变化的剧烈程度;当我们想要突出高亮区域的外围时,则可以选用形态学梯度来突出边缘,可以保留物体的边缘轮廓。
常见的几种梯度:
如使用基本梯度可以得到如下效果:
#include
using namespace cv;
using namespace std;
int main(int argc,char **argv)
{
char fileName[] = "O:\\CSDN\\MORPH.png";
Mat src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
cout << "couldn't open image: " << fileName << ",please check out!\n";
system("pause");
return EXIT_FAILURE;
}
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
Mat dst;
morphologyEx(src, dst, MORPH_GRADIENT, kernel);
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 200, 200);
namedWindow("MORPH_GRADIENT", WINDOW_AUTOSIZE);
moveWindow("MORPH_GRADIENT", src.cols + 200, 200);
imshow("Original", src);
imshow("MORPH_GRADIENT", dst);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
4、顶帽
顶帽是原图与原图的开运算的差值图像。开运算放大了裂缝或者局部低亮度的区域,所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。TopHat运算一般用来分离比邻近点亮一些的斑块,可以使用这个运算提取背景。
其公式为:
dst=tophat(src,element)=src−open(src,element)
#include
using namespace cv;
using namespace std;
int main(int argc,char **argv)
{
char fileName[] = "O:\\CSDN\\MORPH.png";
Mat src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
cout << "couldn't open image: " << fileName << ",please check out!\n";
system("pause");
return EXIT_FAILURE;
}
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
Mat dst;
morphologyEx(src, dst, MORPH_TOPHAT, kernel);
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 200, 200);
namedWindow("MORPH_TOPHAT", WINDOW_AUTOSIZE);
moveWindow("MORPH_TOPHAT", src.cols + 200, 200);
imshow("Original", src);
imshow("MORPH_TOPHAT", dst);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
黑帽是闭运算结果与原图的差值图像。黑帽运算的结果突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
其公式为:
dst=blackhat(src,element)=close(src,element)−src
#include
using namespace cv;
using namespace std;
int main(int argc,char **argv)
{
char fileName[] = "O:\\CSDN\\MORPH.png";
Mat src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
cout << "couldn't open image: " << fileName << ",please check out!\n";
system("pause");
return EXIT_FAILURE;
}
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
Mat dst;
morphologyEx(src, dst, MORPH_BLACKHAT, kernel);
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 200, 200);
namedWindow("MORPH_BLACKHAT", WINDOW_AUTOSIZE);
moveWindow("MORPH_BLACKHAT", src.cols + 200, 200);
imshow("Original", src);
imshow("MORPH_BLACKHAT", dst);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
#include
using namespace cv;
using namespace std;
string TYPE_BAR = "Morph Type:\n 0:Open\n 1:Close\n 2:Gradient\n 3:TopHat\n 4:BlackHat";
void dealMorphologyEx(int, void* img);
int main(int argc,char **argv)
{
char fileName[] = "O:\\CSDN\\MORPH.png";
Mat src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
cout << "couldn't open image: " << fileName << ",please check out!\n";
system("pause");
return EXIT_FAILURE;
}
namedWindow("Original", WINDOW_AUTOSIZE);
moveWindow("Original", 200, 200);
namedWindow("MORPH", WINDOW_AUTOSIZE);
moveWindow("MORPH", src.cols + 200, 200);
int Type = 0;
int kernelSize = 0;
int iteration = 0;
createTrackbar(TYPE_BAR, "MORPH", &Type, 4, 0, 0);
createTrackbar("Kernel Size:", "MORPH", &kernelSize, 21, dealMorphologyEx, (void*)&src);
createTrackbar("Iteration times:", "MORPH", &iteration, 4, 0, 0);
imshow("MORPH", src);
imshow("Original", src);
waitKey(0);
system("pause");
return EXIT_SUCCESS;
}
void dealMorphologyEx(int, void* img)
{
int type = getTrackbarPos(TYPE_BAR, "MORPH") + 2;
int kernelSize = getTrackbarPos("Kernel Size:", "MORPH");
int iteration = getTrackbarPos("Iteration times:", "MORPH") + 1;
Mat src = ((Mat*)img)->clone();
Mat dst;
Mat kernel = getStructuringElement(MORPH_RECT, Size(2 * kernelSize + 1, 2 * kernelSize + 1));
morphologyEx(src, dst, type, kernel, Point(-1, -1), iteration);
imshow("MORPH", dst);
}
运行上面程序,便可以自由选择形态学操作了,可以根据需要设定kernelSize和迭代次数,当然也可以自行添加结构元素类型。
注意上图红色框中出现了一个问题,跨行的trackbar名称不能正确显示,本人也一直在寻找解决方案,知道问题的小伙伴可以私信我,谢谢。