企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
##形态学及边缘角点检测 形态学滤波理论于上世纪90年代提出,目前被广泛用于分析及处理离散图像。其基本运算有4个: 膨胀、腐蚀、开启和闭合, 它们在二值图像和灰度图像中各有特点。基于这些基本运算还可推导和组合成各种数学形态学实用算法,用它们可以进行图像形状和结构的分析及处理,包括图像分割、特征抽取、边缘检测、 图像滤波、图像增强和恢复等。数学形态学方法利用一个称作结构元素的“探针”收集图像的信息,当探针在图像中不断移动时, 便可考察图像各个部分之间的相互关系,从而了解图像的结构特征。数学形态学基于探测的思想,与人的FOA(Focus Of Attention)的视觉特点有类似之处。其中最重要的结构元素,可直接携带知识(形态、大小、甚至加入灰度和色度信息)来探测、研究图像的结构特点。鉴于研究所需,记录一些知识点,开发平台为OpenCV2.4.9+Qt5.3.2。 一:图像腐蚀、膨胀和开闭运算 这些运算的基本公式和原理参考:[http://blog.csdn.net/liyuefeilong/article/details/43374777](http://blog.csdn.net/liyuefeilong/article/details/43374777)  图像的腐蚀:替换为当前像素位像素集合中的最小像素值,函数为cv::erode  图像的膨胀:替换为当前像素位像素集合中的最大像素值,函数为cv::dilate  图像的开运算:先腐蚀后膨胀,函数为cv::morphologyEx,对应的参数为MORPH_CLOSE  图像的闭运算:先膨胀后腐蚀,函数为cv::morphologyEx,对应的参数为MORPH_OPEN  如morphologyEx(image, opened, cv::MORPH_OPEN, element2, cv::Point(-1,-1), 1); 中,输入图像为image,输出图像为opened,执行开操作,结构元素为element2,原点参数cv::Point(-1,-1)表示原点位于矩阵的中心(默认),最后的1则表示对图像的操作次数(注:对一幅图像多次使用开操作和闭操作时效果不会有改善,这些运算是等幂的)。形态学滤波本是基于二值图像上,但以上这些运算同样适用于灰度图像。 新建一个Qt控制台应用,创建一个类:MorphoFeatures: ~~~ #ifndef MORPHOFEATURES_H #define MORPHOFEATURES_H #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> class MorphoFeatures { public: void fourFunctions(cv::Mat &image); // 腐蚀、膨胀、开操作、闭操作 } #endif // MORPHOFEATURES_H ~~~ ~~~ void MorphoFeatures::fourFunctions(cv::Mat &image) { // 腐蚀运算,替换为当前像素位像素集合中的最小像素值 cv::Mat eroded; cv::erode(image, eroded, cv::Mat()); // 膨胀运算,替换为当前像素位像素集合中的最大像素值 cv::Mat dilated; cv::dilate(image, dilated, cv::Mat()); // 闭运算,先膨胀后腐蚀 cv::Mat closed; cv::Mat element1(3, 3, CV_8U, cv::Scalar(1)); cv::morphologyEx(image, // 输入图像 closed, // 输出图像 cv::MORPH_CLOSE, // 指定操作 element1, // 结构元素设置 cv::Point(-1,-1), // 操作的位置 1); // 操作的次数 //开运算,先腐蚀后膨胀 cv::Mat opened; cv::Mat element2(3, 3, CV_8U, cv::Scalar(1)); cv::morphologyEx(image, opened, cv::MORPH_OPEN, element2, cv::Point(-1,-1), 1); cv::namedWindow("Eroded Image"); cv::imshow("Eroded Image", eroded); cv::namedWindow("Dilated Image"); cv::imshow("Dilated Image", dilated); cv::namedWindow("Orginal Image"); cv::imshow("Orginal Image", image); cv::namedWindow("Closed Image"); cv::imshow("Closed Image", closed); cv::namedWindow("Opened Image"); cv::imshow("Opened Image", opened); } ~~~ 得出四种操作的处理效果:  ![](https://box.kancloud.cn/2015-12-30_5683a74add9c8.jpg) ![](https://box.kancloud.cn/2015-12-30_5683a74b1a1d1.jpg) 这里你会觉得腐蚀与膨胀、开操作与闭操作的效果和期望是相反的。这是因为我们认为图像素材中黑色字体是前景,白色为背景。而一般的,形态学规定用高像素表示前景物体,用低像素表示背景,因此**使用这些基本运算之前,可以根据实际情况给原图像取反。** 二、利用形态学滤波进行边缘检测 形态学滤波利用梯度进行边缘检测,其原理是计算膨胀后的图像和腐蚀后的图像的差值,由于两个变换后的图像不同之处主要在边缘处,图像边缘将通过求差得到强化。函数为morphologyEx,参数为MORPH_GRADIENT。若架构元素尺寸越大,检测出的边缘越厚。最简单的边缘检测运算是用原图减去腐蚀后的图像,或者用膨胀后的图像减去原图或腐蚀图像,效果很直观,缺点是得到的边缘较薄。 以下给出形态学滤波进行边缘检测的基本方法:  在class MorphoFeatures中添加几个函数: ~~~ public: cv::Mat getEdges(const cv::Mat &image); void setThreshold(int gate); // 设定阈值 private: void applyThreshold(cv::Mat &result); ~~~ 在morphofeatures.cpp中添加: ~~~ void MorphoFeatures::setThreshold(int gate) { threshold = gate; } cv::Mat MorphoFeatures::getEdges(const cv::Mat &image) { // 得到梯度图 cv::Mat result; cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat()); // 阈值化以得到二值图像 applyThreshold(result); return result; } void MorphoFeatures::applyThreshold(cv::Mat &result) { // 使用阈值化 if(threshold > 0) { cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY); } } ~~~ 简单修改main函数: ~~~ #include <QCoreApplication> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include "morphofeatures.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MorphoFeatures h; cv::Mat image = cv::imread("c:\\gray.jpg"); // h.fourFunctions(image); // 边缘检测 h.setThreshold(80); // 设定阈值 cv::Mat result = h.getEdges(image); cv::namedWindow("Input Image"); cv::imshow("Input Image",image); cv::namedWindow("Edge"); cv::imshow("Edge",result); return a.exec(); } ~~~ 效果:  ![](https://box.kancloud.cn/2015-12-30_5683a74b46d7d.jpg) ![](https://box.kancloud.cn/2015-12-30_5683a74b5ee7e.jpg) 效果差强人意……除此之外,还可以用sobel算子、Canny算子等对图像进行边缘检测,这些方法都可以通过改变结构元素来实现,如下图所示为几种3*3的Sobel算子。基于Sobel算子的边缘检测见:[http://blog.csdn.net/liyuefeilong/article/details/43452711](http://blog.csdn.net/liyuefeilong/article/details/43452711)  ![](https://box.kancloud.cn/2015-12-30_5683a74b7ed27.jpg) 另外一个应用是对图像进行形态学tophat变换,用h表示,定义为:  ![](https://box.kancloud.cn/2015-12-30_5683a74b925f8.jpg)  其中,f是输入图像,B是结构元素函数。tophat变换对于增强灰度图像的阴影细节很有用处。 三、利用形态学滤波进行图像角点检测 这里使用四种不同的结构元素检测图像角点,分别为十字型、菱型、x型和方形元素,尺寸规定为5*5。与边缘检测不同,角点的检测复杂。运算过程主要分三步: 第一步,先用十字形的结构元素膨胀原图像,这种情况下只会在边缘处“扩张”,角点不发生变化。接着用菱形的结构元素腐蚀原图像,只有拐角处才会被“收缩”,而直线边缘不发生变化。 第二步,用X型的结构元素膨胀原图像,角点膨胀的比边要多。这样第二次用方块腐蚀时,角点恢复原状,而边要腐蚀的更多。 第三步,将一二步的两幅输出图像相减,结果只保留了各个拐角处的细节。 首先在类MorphoFeatures.h中添加: ~~~ public: cv::Mat getCorners(const cv::Mat &image); // 角点检测函数 void drawOnImage(const cv::Mat &binary, cv::Mat &image); // 在角点处标记圆圈 // 以下构造四种不同的结构元素用来检测灰度图像的角点 MorphoFeatures():threshold(-1), cross(5,5,CV_8U,cv::Scalar(0)), diamond(5,5,CV_8U,cv::Scalar(1)), square(5,5,CV_8U,cv::Scalar(1)), x(5,5,CV_8U,cv::Scalar(0)) { // 5*5的十字形元素 for (int i=0; i<5; i++) { cross.at<uchar>(2,i)= 1; cross.at<uchar>(i,2)= 1; } // 5*5的菱形元素 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; // 5*5的x型元素 for (int i=0; i<5; i++) { x.at<uchar>(i,i)= 1; x.at<uchar>(4-i,i)= 1; } } ~~~ 接着,在morphofeatures.cpp中添加: ~~~ cv::Mat MorphoFeatures::getCorners(const cv::Mat &image) { cv::Mat result; // 十字膨胀 cv::dilate(image, result, cross); // 棱形腐蚀 形态学函数支持原地操作 cv::erode(result, result, diamond); cv::Mat result2; // x型膨胀 cv::dilate(image, result2, x); // 方形腐蚀 cv::erode(result2, result2, square); // 对result和result2这两张图像相减,得到焦点图像 cv::absdiff(result2, result, result); // 阈值化,得到二值图像 applyThreshold(result); cv::namedWindow("Corners"); cv::imshow("Corners", result); return result; } void MorphoFeatures::drawOnImage(const cv::Mat &binary, cv::Mat &image) { cv::Mat_<uchar>::const_iterator it = binary.begin<uchar>(); cv::Mat_<uchar>::const_iterator itend = binary.end<uchar>(); for(int i=0; it!=itend; ++it, ++i) { if(*it) // 若该像素被标定为角点则画白色圈圈 { cv::circle(image, cv::Point(i%image.step, i/image.step), 5, cv::Scalar(255, 255, 255)); } } } ~~~ 在main.cpp中简单添加: ~~~ cv::Mat corners; corners = h.getCorners(image); h.drawOnImage(corners, image); cv::namedWindow("Corners On Image"); cv::imshow("Corners On Image", image); ~~~ 效果: ![](https://box.kancloud.cn/2015-12-30_5683a74ba1911.jpg) ![](https://box.kancloud.cn/2015-12-30_5683a74bb6f78.jpg) 在这里,需要把输入图像转化为二值图像,因此阈值的选择会影响角点检测效果,如下图所示: ![](https://box.kancloud.cn/2015-12-30_5683a74be8087.jpg) ![](https://box.kancloud.cn/2015-12-30_5683a74c22e82.jpg) 尽管角点检测效果有好有坏,不过实现该方法,对于理解腐蚀、膨胀、开操作、闭操作有很好的帮助。 欢迎转载或分享,但请务必声明文章出处~