首页 > 代码库 > 【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】基于形态学运算的图像变换