首页 > 代码库 > 【OpenCV学习笔记 008】基于形态学运算的图像变换

【OpenCV学习笔记 008】基于形态学运算的图像变换

一、形态学滤波对图像进行腐蚀、膨胀运算

1.概念及原理

(1)腐蚀和膨胀是形态学中最基本的运算,而结构元素又是数学形态学中最基本的工具。结构元素可以简单理解为像素的结构以及一个原点。使用形态学滤波就是对像素的每个元素应用这个结构,当结构元素的原点和像素对齐时,它与图像的相交部分定义了一组进行形态学运算的像素。结构元素可以是任何形状,我们一般使用简单的方形、圆形、或菱形,原点即位于中心位置。

(2)腐蚀替换当前像素位像素集合中找到的最小像素值,膨胀则相反。为了想象出两个运算的效果,可以考虑背景(黑色)和前景(白色)。腐蚀情况下,给定像素的结构元素接触到背景,该像素被置为背景。膨胀则是给定像素位置接触到前景物体,该像素被置为白色。因此,腐蚀后的图像尺寸变小,膨胀即相反。

2.实验

由于形态学滤波通常使用二值图像,所以我们先把图像转换为二值图像然后对图像进行腐蚀和膨胀。

技术分享

以下实验都是对该二值图像进行处理。

源码示例

#include<iostream>    
#include <opencv2/core/core.hpp>    
#include <opencv2/highgui/highgui.hpp> 
#include <opencv/cv.h>

using namespace std;
using namespace cv;

Mat g_pGrayImage;
Mat g_pBinaryImage;

int main(){

	// 从文件中加载原图  
	Mat pSrcImage = cvLoadImage("horse.jpg", CV_LOAD_IMAGE_UNCHANGED);
	// 转为灰度图  
	g_pGrayImage.create(pSrcImage.rows, pSrcImage.cols, CV_8U);
	cvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY);
	// 创建二值图  
	g_pBinaryImage.create(g_pGrayImage.rows, g_pGrayImage.cols, CV_8U);
	// 转为二值图  
	threshold(g_pGrayImage, g_pBinaryImage, 100, 255, CV_THRESH_BINARY);
	//显示二值图像
	cvNamedWindow("BinaryImage");
	imshow("BinaryImage", g_pBinaryImage);

	//腐蚀图像
	Mat eroded;	//目标图像
	erode(g_pBinaryImage, eroded, Mat());
	//显示腐蚀后的图像
	cvNamedWindow("Eroded Image");
	imshow("Eroded Image", eroded);

	//膨胀图像
	Mat dilated;	//目标图像
	dilate(g_pBinaryImage, dilated, Mat());
	//显示膨胀后的图像
	cvNamedWindow("Dilated Image");
	imshow("Dilated Image", dilated);
	
	waitKey(0);
	system("pause");
	return 0;
}
运行效果

技术分享

二、形态学滤波对图像进行开闭运算

1.概念及原理

(1)闭运算定义为对图像进行先膨胀再腐蚀,开运算定义为对图像进行先腐蚀再膨胀

(2)闭滤波器可以填充白色前景物中的小洞,开滤波器可以移除场景中比较小的物体。这些滤波器通常在物体检测中使用,闭滤波器可以将误分割成碎片的物体重新连接,而开滤波器可以处理图像噪点引起的小像素块。

2.实验

通过合适的参数调用cv::morphologyEx 使用更高级的形态学滤波。

源码示例

#include<iostream>    
#include <opencv2/core/core.hpp>    
#include <opencv2/highgui/highgui.hpp> 
#include <opencv/cv.h>

using namespace std;
using namespace cv;

Mat g_pGrayImage;
Mat g_pBinaryImage;

int main(){

	// 从文件中加载原图  
	Mat pSrcImage = cvLoadImage("horse.jpg", CV_LOAD_IMAGE_UNCHANGED);
	// 转为灰度图  
	g_pGrayImage.create(pSrcImage.rows, pSrcImage.cols, CV_8U);
	cvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY);
	// 创建二值图
	g_pBinaryImage.create(g_pGrayImage.rows, g_pGrayImage.cols, CV_8U);
	// 转为二值图  
	threshold(g_pGrayImage, g_pBinaryImage, 100, 255, CV_THRESH_BINARY);
	//显示二值图像
	cvNamedWindow("BinaryImage");
	imshow("BinaryImage", g_pBinaryImage);

	//闭运算图像,使用5*5的结构元素使得滤波效果更明显
	Mat element5(5, 5, CV_8U, Scalar(1));
	Mat closed;
	morphologyEx(g_pBinaryImage, closed, MORPH_CLOSE, element5);

	//显示闭运算后的图像
	cvNamedWindow("闭运算图像");
	imshow("闭运算图像", closed);

	//开运算图像
	Mat opened;	//目标图像
	morphologyEx(g_pBinaryImage, opened, MORPH_OPEN, element5);
	//显示开运算后的图像
	cvNamedWindow("开运算图像");
	imshow("开运算图像", opened);
	
	waitKey(0);
	system("pause");
	return 0;
}
程序运行后的效果

技术分享

三、形态学滤波对图像进行边缘及角点检测

实验一

检测灰度图中的直线。

源码示例

#include<iostream>    
#include <opencv2/core/core.hpp>    
#include <opencv2/highgui/highgui.hpp> 
#include <opencv/cv.h>

using namespace std;
using namespace cv;

class MorphoFeatures{

private:
	//用于生成二值图像的阈值
	int ithreshold;
	//角点检测中用到的结构元素
	Mat cross;
	Mat diamond;
	Mat square;
	Mat x;
	void applyThreshold(Mat &result);
public:
	Mat getEdges(const Mat &image);
	void setThreshold(float t);
};

//获取二值的边缘图像
void MorphoFeatures::applyThreshold(Mat &result){

	//使用阈值化
	if (ithreshold > 0)
	{
		threshold(result, result, ithreshold, 255, THRESH_BINARY);
	}
}

//morphologyEx + 合适的滤波器实现直线的检测
Mat MorphoFeatures::getEdges(const Mat &image){

	//得到梯度图
	Mat result;
	morphologyEx(image, result, MORPH_GRADIENT, Mat());
	//阈值化以得到二值图像
	applyThreshold(result);
	return result;
}

//设置直方图的阈值[0,1]
void MorphoFeatures::setThreshold(float t){

	ithreshold = t;
}


int main(){

	Mat image = cvLoadImage("floor.jpg",0);
	MorphoFeatures morpho;
	morpho.setThreshold(80);
	//获取边缘
	Mat edges;
	edges = morpho.getEdges(image);

	namedWindow("floor");
	imshow("floor",image);
	namedWindow("edges");
	imshow("edges", edges);
	waitKey(0);
	system("pause");
	return 0;
}
检测后的效果

技术分享

实验二

检测灰度图中的角点

角点检测使用四种不同的结构元素检测图像角点,分别为十字型、菱型、x型和方形元素,尺寸规定为5*5。与边缘检测不同,角点的检测复杂。运算过程主要分三步:

第一步,先用十字形的结构元素膨胀原图像,这种情况下只会在边缘处“扩张”,角点不发生变化。接着用菱形的结构元素腐蚀原图像,只有拐角处才会被“收缩”,而直线边缘不发生变化。

第二步,用X型的结构元素膨胀原图像,角点膨胀的比边要多。这样第二次用方块腐蚀时,角点恢复原状,而边要腐蚀的更多。

第三步,将一二步的两幅输出图像相减,结果只保留了各个拐角处的细节。

源码示例

#include<iostream>    
#include <opencv2/core/core.hpp>    
#include <opencv2/highgui/highgui.hpp> 
#include <opencv/cv.h>

using namespace std;
using namespace cv;
class MorphoFeatures{

private:
	//用于生成二值图像的阈值
	int ithreshold;
	//角点检测中用到的结构元素
	Mat cross;
	Mat diamond;
	Mat square;
	Mat x;
	void applyThreshold(Mat &result);
public:
	MorphoFeatures():ithreshold(50), cross(5, 5, CV_8U, Scalar(0)), diamond(5, 5, CV_8U, Scalar(1)), square(5, 5, CV_8U, Scalar(1)),x(5,5,CV_8U,Scalar(0)){
	
		//创建十字形元素
		for (int i = 0; i < 5; i++)
		{
			cross.at<uchar>(2, i) = 1;
			cross.at<uchar>(i, 2) = 1;
		}

		//创建菱形元素
		diamond.at<uchar>(0, 0) = 0;
		diamond.at<uchar>(0, 1) = 0;
		diamond.at<uchar>(1, 0) = 0;
		diamond.at<uchar>(4, 4) = 0;
		diamond.at<uchar>(3, 4) = 0;
		diamond.at<uchar>(4, 3) = 0;
		diamond.at<uchar>(4, 0) = 0;
		diamond.at<uchar>(4, 1) = 0;
		diamond.at<uchar>(3, 0) = 0;
		diamond.at<uchar>(0, 4) = 0;
		diamond.at<uchar>(0, 3) = 0;
		diamond.at<uchar>(1, 4) = 0;

		//创建x形元素
		for (int i = 0; i < 5; i++)
		{
			x.at<uchar>(i, i) = 1;
			x.at<uchar>(4-i, i) = 1;
		}

	}

	Mat getEdges(const Mat &image);
	void setThreshold(float t);
	Mat getCorners(const Mat&image);
	void drawOnImage(const Mat &binary, Mat &image);
};

//获取二值的边缘图像
void MorphoFeatures::applyThreshold(Mat &result){

	//使用阈值化
	if (ithreshold > 0)
	{
		threshold(result, result, ithreshold, 255, THRESH_BINARY);
	}
}

//morphologyEx + 合适的滤波器实现直线的检测
Mat MorphoFeatures::getEdges(const Mat &image){

	//得到梯度图
	Mat result;
	morphologyEx(image, result, MORPH_GRADIENT, Mat());
	//阈值化以得到二值图像
	applyThreshold(result);
	return result;
}

//设置直方图的阈值[0,1]
void MorphoFeatures::setThreshold(float t){

	ithreshold = t;
}

//连接使用这些结构以得到最终的角点映射图
Mat MorphoFeatures::getCorners(const Mat&image){

	Mat result;
	//十字形膨胀
	dilate(image, result, cross);
	//菱形腐蚀
	erode(result, result, diamond);
	Mat result2;
	//X形膨胀
	dilate(image, result2, x);
	//方形腐蚀
	erode(result2, result2, square);
	//通过对两张图像做差值得到角点图像
	absdiff(result2, result, result);
	//阈值化以得到二值图像
	applyThreshold(result);

	return result;
}

//在二值图像中的每个检测点上绘制一个圆  更好的可视化结果
void MorphoFeatures::drawOnImage(const Mat &binary, Mat &image){

	Mat_<uchar>::const_iterator it = binary.begin<uchar>();
	Mat_<uchar>::const_iterator itend = binary.end<uchar>();

	//遍历每个像素
	for (int i = 0; it != itend; ++it, ++i)
	{
		if (*it)
			circle(image, Point(i%image.step, i/image.step), 5, Scalar(255, 0, 0));
	}
}

int main(){

	Mat image = cvLoadImage("floor.jpg");
	Mat grayImage;
	cvtColor(image, grayImage,CV_BGR2GRAY);

	//显示原图
	namedWindow("Image");
	imshow("Image", grayImage);

	MorphoFeatures morpho;
	//得到角点
	Mat corners;
	corners = morpho.getCorners(grayImage);
	//在图像中显示角点
	morpho.drawOnImage(corners, grayImage);

	namedWindow("Corners on Image");
	imshow("Corners on Image", grayImage);

	waitKey(0);
	return 0;
}
角点检测效果图

技术分享

这里需要注意由于要把输入图像转化为二值图像,因此阈值的选择会影响角点检测效果,在此可以输入不同的阈值查看角点检测效果。

四、分水岭算法对图像进行分割

1.概念及原理

(1)分水岭分割方法是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。

实验

实验主要分为

1.对二值图像进行反转,因为我们一般用255白色表示前景 0黑色表示背景

2.有二值图腐蚀移除噪点和微小物体 获得前景

3.膨胀二值图获取背景

4.前景与背景相加得到markers标记图像

5.设置markers标记进行分水岭分割

源码示例

#include<iostream>    
#include <opencv2/core/core.hpp>    
#include <opencv2/highgui/highgui.hpp> 
#include <opencv/cv.h>

using namespace std;
using namespace cv;

class WatershedSegmenter{

private:
	//用来表示标记(图)
	Mat markers;
public:
	//设置标记图
	void setMarkers(const Mat&makerImage);
	Mat process(const Mat&image);
	Mat getSegmentation();
	Mat getWatersheds();
};


void WatershedSegmenter::setMarkers(const Mat&makerImage){
	//转换为整数图像 watershed()的输入参数必须为一个32位有符号的标记,所以要先进行转换 
	makerImage.convertTo(markers, CV_32S);
}

Mat WatershedSegmenter::process(const Mat&image){
	//使用算法
	watershed(image, markers);
	return markers;
}

//以图像形式返回结果
Mat WatershedSegmenter::getSegmentation(){

	Mat temp;
	//从32S到8U(0-255)会进行饱和运算,所以像素高于255的一律复制为255
	markers.convertTo(temp, CV_8U);
	return temp;
}

//以图像的形式返回分水岭 分割线
Mat WatershedSegmenter::getWatersheds(){

	Mat temp;
	//在设置标记图像,即执行setMarkers()后,边缘的像素会被赋值为-1,其他的用正整数表示
	//下面的这个转换可以让边缘像素变为-1*255+255=0,即黑色,其余的溢出,赋值为255,即白色
	markers.convertTo(temp, CV_8U, 255, 255);
	return temp;
}

int main(){

	Mat image = cvLoadImage("group.jpg");
	Mat grayImage;
	cvtColor(image, grayImage, CV_BGR2GRAY);
	//转换为二值图
	Mat binaryImage;
	threshold(grayImage, binaryImage, 80,255, CV_THRESH_BINARY);

	// 1.二值图 这里进行了像素反转,因为一般我们用255白色表示前景(物体),用0黑色表示背景
	Mat reverseBinaryImage;
	bitwise_not(binaryImage,reverseBinaryImage);

	namedWindow("reverseBinaryImage");
	imshow("reverseBinaryImage", reverseBinaryImage);
	//2.由二值图像获得前景 腐蚀 移除噪点与微小物体
	Mat fg;
	erode(reverseBinaryImage, fg, Mat(), Point(-1, -1), 6);

	//3.识别不包含物体的背景 膨胀二值图来获取背景(只有草地,没有树林)
	Mat bg;
	dilate(reverseBinaryImage, bg, Mat(), Point(-1, -1), 6);
	threshold(bg, bg, 1, 128, THRESH_BINARY_INV);

	//4.组合这些图形成标记图形
	Mat markers(binaryImage.size(), CV_8U, Scalar(0));
	markers = fg + bg; //使用重载操作符+

	//5.创建分水岭分割对象
	WatershedSegmenter segmenter;
	//设置标记并进行处理
	segmenter.setMarkers(markers);
	segmenter.process(image);

	namedWindow("Segmentation");
	imshow("Segmentation", segmenter.getSegmentation());

	namedWindow("Watersheds");
	imshow("Watersheds", segmenter.getWatersheds());

	waitKey(0);
	return 0;
}
实验过程中的一些图片

原图和像素反转二值图

技术分享

fg前景图和bg背景图

技术分享

标记图像和边界图像

技术分享

五、GrabCut算法提取前景物体

源码示例

    #include<opencv2/core/core.hpp>  
    #include<opencv2/highgui/highgui.hpp>  
    #include<imgproc/imgproc.hpp>  
    #include<iostream>  
       
    using namespace std;  
    using namespace cv;  
      
    int main()  
    {  
        Mat image=imread("d:/test/opencv/group.jpg");  
        namedWindow("Image");  
        imshow("Image",image);  
        Rect rectangle(10,100,380,180);  
        Mat result;  
        Mat bgModel,fgModel;  
        grabCut(image,result,rectangle,bgModel,fgModel,5,GC_INIT_WITH_RECT);  
        compare(result,GC_PR_FGD,result,CMP_EQ);  
        Mat foreground(image.size(),CV_8UC3,Scalar(255,255,255));  
        image.copyTo(foreground,result);  
        namedWindow("Foreground");  
        imshow("Foreground",foreground);  
        waitKey();  
        return 0;  
    }  

提取到前景效果图

技术分享


【OpenCV学习笔记 008】基于形态学运算的图像变换