OpenCV学习笔记(14):形态学滤波对图像进行边缘及角点检测

</pre><pre name="code" class="cpp">#include "stdafx.h"
#include<opencv2/opencv.hpp>
using namespace cv;

class MorphoFeatures{

private:
	//用于生成二值图像的阈值
	int threshold;
	//角点检测用到的结构元素
	Mat cross;
	Mat diamond;
	Mat square;
	Mat x;

public:
	//检测角点
	//opencv没有直接实现它,需要定义四种不同的结构元素,
	//包括:菱形,方形,十字形,x形。并在构造函数中完成
	MorphoFeatures() :threshold(-1),
		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<char>(i, i) = 1;
			x.at<char>(4 - i, i) = 1;
		}
	}

	Mat getEdges(const Mat &image)
	{
		//得到梯度图
		Mat result;
		//morphology函数配上合适的滤波器就可以实现直线检测
		morphologyEx(image, result, MORPH_GRADIENT, Mat());
		applyThreshold(result);
		return result;
	}
	void setThreshold(int t)
	{
		threshold = t;
	}
	void applyThreshold(Mat & result)
	{
		if (threshold > 0)
			cv::threshold(result, result, threshold, 255, THRESH_BINARY);
	}

	//需要连接使用这些结构元素,得到最终的角点映射图
	Mat 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 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; ++i, ++it)
		{
			if (!*it)
				circle(image, Point(i%image.step, i / image.step), 5, Scalar(0, 255, 0));
				
		}
	}
};
int _tmain(int argc, _TCHAR* argv[])
{
	//直线检测
	Mat image = imread("building.jpg", 0);
	//imshow("d", image);
	if (!image.data)
		return -1;
	MorphoFeatures morpho;
	morpho.setThreshold(40);
	Mat edges;
	edges = morpho.getEdges(image);
	imshow("d", 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("jj", image);
	waitKey(0);
	return 0;
}


首先如何理解对灰度图像进行形态学操作?

一种比较形象的方法是将灰度图像看做是“等高线”:亮的区域代表山峰,而暗的区域代表山谷,图像的边沿就对应于峭壁。如果腐蚀一幅图像,会导致山谷被扩展,而峭壁减少了。相反的,如果膨胀一幅图像,峭壁则会增加。但是这两种情况下,中间的部分(大片的谷底和高原)基本保持不变。

在上述理解的基础上,如果我们对图像的腐蚀和膨胀的结果做差,就能提取图像的边界:因为边界区域,二者完全不同。(实际上,我们也可以用腐蚀或者膨胀的结果与源图像做差得出类似结果,但提取的边界会比较细)。可以看出,结构元越大,边界越粗。在OpenCV中,将形态学操作函数morphologyEx 的第4个参数设为MORPH_GRADIENT,就能完成上述工作。


开运算可以 用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。

闭运算能够排除小型黑洞(黑色区域)

形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差,对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓

顶帽运算(Top Hat)为原图像与“开运算“的结果图之差,因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差,黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以,黑帽运算用来分离比邻近点暗一些的斑块。

void cv::morphologyEx( InputArray _src, OutputArray _dst, int op,
                       InputArray kernel, Point anchor, int iterations,
                       int borderType, const Scalar& borderValue )
{
    Mat src = _src.getMat(), temp;
    _dst.create(src.size(), src.type());
    Mat dst = _dst.getMat();

    switch( op )
    {
    case MORPH_ERODE:
        erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
        break;
    case MORPH_DILATE:
        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
        break;
    case MORPH_OPEN:
        erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
        dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );
        break;
    case CV_MOP_CLOSE:
        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
        erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );
        break;
    case CV_MOP_GRADIENT:
        erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
        dst -= temp;
        break;
    case CV_MOP_TOPHAT:
        if( src.data != dst.data )
            temp = dst;
        erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
        dilate( temp, temp, kernel, anchor, iterations, borderType, borderValue );
        dst = src - temp;
        break;
    case CV_MOP_BLACKHAT:
        if( src.data != dst.data )
            temp = dst;
        dilate( src, temp, kernel, anchor, iterations, borderType, borderValue );
        erode( temp, temp, kernel, anchor, iterations, borderType, borderValue );
        dst = temp - src;
        break;
    default:
        CV_Error( CV_StsBadArg, "unknown morphological operation" );
    }
}

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
  • 第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
  • 第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
    • MORPH_OPEN – 开运算(Opening operation)
    • MORPH_CLOSE – 闭运算(Closing operation)
    • MORPH_GRADIENT -形态学梯度(Morphological gradient)
    • MORPH_TOPHAT - “顶帽”(“Top hat”)
    • MORPH_BLACKHAT - “黑帽”(“Black hat“)

  • 第四个参数,InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。关于getStructuringElement我们上篇文章中讲过了,这里为了大家参阅方便,再写一遍:

其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:

    • 矩形: MORPH_RECT
    • 交叉形: MORPH_CROSS
    • 椭圆形: MORPH_ELLIPSE

而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。

getStructuringElement函数相关的调用示例代码如下:

int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
 
//获取自定义核
Mat element =getStructuringElement(MORPH_RECT,
       Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
       Point(g_nStructElementSize, g_nStructElementSize ));

调用这样之后,我们便可以在接下来调用erode、dilate或morphologyEx 函数时,kernel参数填保存getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。

  • 第五个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
  • 第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
  • 第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
  • 第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值