首先如何理解对灰度图像进行形态学操作?
一种比较形象的方法是将灰度图像看做是“等高线”:亮的区域代表山峰,而暗的区域代表山谷,图像的边沿就对应于峭壁。如果腐蚀一幅图像,会导致山谷被扩展,而峭壁减少了。相反的,如果膨胀一幅图像,峭壁则会增加。但是这两种情况下,中间的部分(大片的谷底和高原)基本保持不变。
在上述理解的基础上,如果我们对图像的腐蚀和膨胀的结果做差,就能提取图像的边界:因为边界区域,二者完全不同。(实际上,我们也可以用腐蚀或者膨胀的结果与源图像做差得出类似结果,但提取的边界会比较细)。可以看出,结构元越大,边界越粗。在OpenCV中,将形态学操作函数morphologyEx 的第4个参数设为MORPH_GRADIENT,就能完成上述工作。
利用形态学操作获取角点稍微有一些复杂,他的基本方法是对一幅图像先腐蚀,在膨胀。但是这两次操作使用的结构元却不同。这些结构元的选取使得直线保持不变,但是由于他们各自作用的效果,角点处的边沿被影响了。我们结合一幅图来说明:
第一个方块是源图像,当被十字结构元膨胀后,除了那些十字形不能对到的角点,其他部分被扩展了。作用结果在显示在中间那个图。膨胀后的图形被菱形结构元腐蚀。腐蚀将边沿变回了原来的位置,但是把角点放得更远了,因为他们没有被膨胀。这样就获得最右边的图像,可以看到,它失去了角点。相同的过程作用于X型和方形结构元。他们两个是前两个结构元的旋转版本,会捕捉45度角点的角点。最终,两幅图像的差分将会提取角点特征。
最后我们看一下代码:
首先是定义一个类来支持我们要做的操作:
#if ! defined MORPHOF #define MORPHOF #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> using namespace cv; class MorphoFeatures { private: //产生二值图像的门限 int threshold; //结构元 Mat cross; Mat diamond; Mat square; Mat x; //对图像二值化 void applyThreshold(Mat &result) { if(threshold>0) cv::threshold(result,result,threshold,255,THRESH_BINARY_INV); } public: //构造函数 MorphoFeatures():threshold(-1), cross(5,5,CV_8U,Scalar(0)), diamond(5,5,CV_8U,Scalar(255)), square(5,5,CV_8U,Scalar(255)), x(5,5,CV_8U,Scalar(0)) { //创建十字型结构元 for(int i = 0; i < 5; i++) { cross.at<uchar>(2,i) = 255; cross.at<uchar>(i,2) = 255; } //创建菱形结构元:手动画,只需要把不是菱形的部分变白即可 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) = 255; //副对角线 x.at<uchar>(4-i,i) = 255; } } //设置门限函数 void setThreshold(int t) { threshold = t; } //获取当前门限 int getThreshold() const { return threshold; } //检测直线函数 Mat getEdges(const Mat &image) { Mat result; //获取图像的梯度 morphologyEx(image,result,cv::MORPH_GRADIENT,Mat()); //结果二值化 applyThreshold(result); return result; } //检测角点函数 Mat getCorners(const Mat &image) { Mat result; dilate(image,result,cross); erode(result,result,diamond); Mat result2; dilate(image,result2,x); erode(result2,result2,square); absdiff(result2,result,result); applyThreshold(result); return result; } //在角点处画圆 void 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)); } } }; #endif
然后看看main函数:
#include <opencv2/highgui/highgui.hpp> #include "morphoFeatures.h" int main() { Mat image = imread("D:/picture/images/building.jpg",0); if(!image.data) return -1; imshow("源图像",image); MorphoFeatures morpho; morpho.setThreshold(40); //获取边沿 Mat edges; edges = morpho.getEdges(image); imshow("边沿",edges); //获取角点 morpho.setThreshold(-1); Mat corners; corners = morpho.getCorners(image); morphologyEx(corners,corners,MORPH_TOPHAT,Mat()); threshold(corners,corners,40,255,THRESH_BINARY_INV); //imshow("角点",corners); //展示图片上的角点 morpho.drawOnImage(corners,image); imshow("图片上的角点",image); waitKey(0); return 0; }