【OpenCV十六新手教程】OpenCV角检测Harris角点检测


本系列文章由@浅墨_毛星云 出品。转载请注明出处。  

文章链接: http://blog.csdn.net/poem_qianmo/article/details/29356187

作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

知乎:http://www.zhihu.com/people/mao-xing-yun

邮箱: happylifemxy@163.com

写作当前博文时配套使用的OpenCV版本号: 2.4.9


本篇文章中,我们一起探讨了OpenCV中Harris角点检測相关的知识点。学习了OpenCV中实现Harris角点检測的cornerHarris函数的用法。此博文一共同拥有两个配套的麻雀虽小但五脏俱全的演示样例程序,其经过浅墨具体凝视过的代码都在文中贴出。且文章最后提供了综合演示样例程序的下载。

依旧是先看看程序执行截图:

 

 





一、引言:关于兴趣点(interest points)

 



在图像处理和与计算机视觉领域,兴趣点(interest points),或称作关键点(keypoints)、特征点(feature points) 被大量用于解决物体识别,图像识别、图像匹配、视觉跟踪、三维重建等一系列的问题。我们不再观察整幅图,而是选择某些特殊的点,然后对他们进行局部有的放矢的分析。

假设能检測到足够多的这样的点。同一时候他们的区分度非常高。而且能够精确定位稳定的特征。那么这种方法就有使用价值。

 

图像特征类型能够被分为例如以下三种:

  • <1>边缘
  • <2>角点 (感兴趣关键点)
  • <3>斑点(Blobs)(感兴趣区域)

当中,角点是个非常特殊的存在。他们在图像中能够轻易地定位,同一时候,他们在人造物体场景,比方门、窗、桌等出随处可见。由于角点位于两条边缘的交点处,代表了两个边缘变化的方向上的点,,所以他们是能够精确定位的二维特征,甚至能够达到亚像素的精度。且其图像梯度有非常高的变化,这样的变化是能够用来帮助检測角点的。

须要注意的是,角点与位于同样强度区域上的点不同。与物体轮廓上的点也不同,由于轮廓点难以在同样的其它物体上精确定位。

 

 




二、角点检測算法的分类




在当前的图像处理领域。角点检測算法可归纳为三类:


  • <1>基于灰度图像的角点检測
  • <2>基于二值图像的角点检測
  • <3>基于轮廓曲线的角点检測

而基于灰度图像的角点检測又可分为基于梯度、基于模板和基于模板梯度组合三类方法,当中基于模板的方法主要考虑像素领域点的灰度变化,即图像亮度的变化。将与邻点亮度对照足够大的点定义为角点。常见的基于模板的角点检測算法有Kitchen-Rosenfeld角点检測算法,Harris角点检測算法、KLT角点检測算法及SUSAN角点检測算法。和其它角点检測算法相比,SUSAN角点检測算法具有算法简单、位置准确、抗噪声能力强等特点。





三、角点的定义



“假设某一点在随意方向的一个微小变动都会引起灰度非常大的变化。那么我们就把它称之为角点”

角点检測(Corner Detection)是计算机视觉系统中用来获得图像特征的一种方法,广泛应用于运动检測、图像匹配、视频跟踪、三维建模和目标识别等领域中。

也称为特征点检測。

角点通常被定义为两条边的交点。更严格的说。角点的局部邻域应该具有两个不同区域的不同方向的边界。而实际应用中,大多数所谓的角点检測方法检測的是拥有特定特征的图像点,而不不过“角点”。

这些特征点在图像中有具体的坐标,并具有某些数学特征,如局部最大或最小灰度、某些梯度特征等。

现有的角点检測算法并非都十分的健壮。非常多方法都要求有大量的训练集和冗余数据来防止或降低错误特征的出现。

另外。角点检測方法的一个非常重要的评价标准是其对多幅图像中同样或类似特征的检測能力,而且能够应对光照变化、图像旋转等图像变化。

在我们解决这个问题时,往往希望找到特征点,“特征”顾名思义,指能描写叙述物体本质的东西。另一种解释就是这个特征微小的变化都会对物体的某一属性产生重大的影响。而角点就是这样的特征。

观察日常生活中的“角落”就会发现。“角落”能够视为全部平面的交汇处。或者说是全部表面的发起处。

假设我们要改变一个墙角的位置,那么由它而出发的平面势必都要有非常大的变化。

所以,这就引出了图像角点的定义。


我们知道,特征检測与匹配是计算机视觉应用中非常重要的一部分,这须要寻找图像之间的特征建立相应关系。图像中的点作为图像的特殊位置,是非经常常使用的一类特征,点的局部特征也能够叫做“关键特征点”(keypoint feature),或“兴趣点”(interest point)。或“角点”(conrner)。

 

另外,关于角点的具体描写叙述能够有几种:

  • 一阶导数(即灰度的梯度)的局部最大所相应的像素点;
  • 两条及两条以上边缘的交点。
  • 图像中梯度值和梯度方向的变化速率都非常高的点。
  • 角点处的一阶导数最大,二阶导数为零,指示物体边缘变化不连续的方向。

 

 

 


四、cornerHarris函数具体解释



cornerHarris 函数用于在OpenCV中执行Harris角点检測算子处理图像。

和cornerMinEigenVal( )以及cornerEigenValsAndVecs( )函数类似,cornerHarris 函数对于每个像素(x,y)在邻域内,计算2x2梯度的协方差矩阵。接着它计算例如以下式子:


 

即能够找出输出图中的局部最大值,即找出了角点。


其函数原型和參数解析:

C++: void cornerHarris(InputArray src,OutputArray dst, int blockSize, int ksize, double k, intborderType=BORDER_DEFAULT )

  • 第一个參数,InputArray类型的src,输入图像,即源图像,填Mat类的对象就可以,且需为单通道8位或者浮点型图像。

  • 第二个參数,OutputArray类型的dst。函数调用后的运算结果存在这里,即这个參数用于存放Harris角点检測的输出结果,和源图片有一样的尺寸和类型。
  • 第三个參数,int类型的blockSize,表示邻域的大小,很多其它的具体信息在cornerEigenValsAndVecs()中有讲到。

  • 第四个參数。int类型的ksize,表示Sobel()算子的孔径大小。
  • 第五个參数,double类型的k,Harris參数。
  • 第六个參数。int类型的borderType,图像像素的边界模式,注意它有默认值BORDER_DEFAULT。更具体的解释,參考borderInterpolate( )函数。

 接着我们一起过一遍稍后须要用到的Threshold函数的解析,然后看一个以cornerHarris为核心的演示样例程序。





五、Threshold函数具体解释

 

 

函数Threshold( ) 对单通道数组应用固定阈值操作。该函数的典型应用是对灰度图像进行阈值操作得到二值图像。(另外,compare( )函数也能够达到此目的) 或者是去掉噪声,比如过滤非常小或非常大象素值的图像点。


C++: double threshold(InputArray src,OutputArray dst, double thresh, double maxval, int type)

  • 第一个參数,InputArray类型的src,输入数组,填单通道 , 8或32位浮点类型的Mat就可以。

  • 第二个參数。OutputArray类型的dst,函数调用后的运算结果存在这里,即这个參数用于存放输出结果,且和第一个參数中的Mat变量有一样的尺寸和类型。
  • 第三个參数,double类型的thresh,阈值的具体值。
  • 第四个參数,double类型的maxval,当第五个參数阈值类型type取 CV_THRESH_BINARY 或CV_THRESH_BINARY_INV 阈值类型时的最大值.
  • 第五个參数,int类型的type。阈值类型,。threshold( )函数支持的对图像取阈值的方法由其确定,具体用法例如以下图:


而图形化的阈值描写叙述例如以下图:


  

解说完这两个函数,让我们看一个调用演示样例程序:

 

//-----------------------------------【头文件包括部分】---------------------------------------  
//      描写叙述:包括程序所依赖的头文件  
//----------------------------------------------------------------------------------------------   
#include <opencv2/opencv.hpp>  
#include <opencv2/imgproc/imgproc.hpp>  

//-----------------------------------【命名空间声明部分】---------------------------------------  
//      描写叙述:包括程序所使用的命名空间  
//-----------------------------------------------------------------------------------------------   
using namespace cv;  

int main()  
{  
	//以灰度模式加载图像并显示
	Mat srcImage = imread("1.jpg", 0);  
	imshow("原始图", srcImage);  

	//进行Harris角点检測找出角点
	Mat cornerStrength;  
	cornerHarris(srcImage, cornerStrength, 2, 3, 0.01);

	//对灰度图进行阈值操作,得到二值图并显示  
	Mat harrisCorner;  
	threshold(cornerStrength, harrisCorner, 0.00001, 255, THRESH_BINARY);  
	imshow("角点检測后的二值效果图", harrisCorner);  

	waitKey(0);  
	return 0;  
}  


执行截图:

   


 

 

 

 

 

六、本文相关核心函数在OpenCV中的实现源代码

 


这个部分贴出OpenCV中本文相关函数的源代码实现细节,来给想了解实现细节的小伙伴们參考。浅墨临时不在源代码的细节上挖深作具体凝视。


 

6.1 OpenCV2.X中cornerHarris函数源代码


源代码路径: …opencv\sources\modules\imgproc\src\corner.cpp

 

void cv::cornerHarris( InputArray _src,OutputArray _dst, int blockSize, int ksize, double k, int borderType )
{
   Mat src = _src.getMat();
   _dst.create( src.size(), CV_32F );
   Mat dst = _dst.getMat();
   cornerEigenValsVecs( src, dst, blockSize, ksize, HARRIS, k, borderType);
}

 

可见cornerHarris内部事实上是调用了cornerEigenValsVecs函数,我们看看事实上现源代码:

 

static void
cornerEigenValsVecs( const Mat& src,Mat& eigenv, int block_size,
                     int aperture_size, intop_type, double k=0.,
                     intborderType=BORDER_DEFAULT )
{
#ifdef HAVE_TEGRA_OPTIMIZATION
   if (tegra::cornerEigenValsVecs(src, eigenv, block_size, aperture_size,op_type, k, borderType))
       return;
#endif
 
   int depth = src.depth();
   double scale = (double)(1 << ((aperture_size > 0 ?aperture_size : 3) - 1)) * block_size;
   if( aperture_size < 0 )
       scale *= 2.;
   if( depth == CV_8U )
       scale *= 255.;
   scale = 1./scale;
 
   CV_Assert( src.type() == CV_8UC1 || src.type() == CV_32FC1 );
 
   Mat Dx, Dy;
   if( aperture_size > 0 )
    {
       Sobel( src, Dx, CV_32F, 1, 0, aperture_size, scale, 0, borderType );
       Sobel( src, Dy, CV_32F, 0, 1, aperture_size, scale, 0, borderType );
    }
   else
    {
       Scharr( src, Dx, CV_32F, 1, 0, scale, 0, borderType );
       Scharr( src, Dy, CV_32F, 0, 1, scale, 0, borderType );
    }
 
   Size size = src.size();
   Mat cov( size, CV_32FC3 );
   int i, j;
 
   for( i = 0; i < size.height; i++ )
    {
       float* cov_data = (float*)(cov.data + i*cov.step);
       const float* dxdata = (const float*)(Dx.data + i*Dx.step);
       const float* dydata = (const float*)(Dy.data + i*Dy.step);
 
        for( j = 0; j < size.width; j++ )
       {
           float dx = dxdata[j];
           float dy = dydata[j];
 
           cov_data[j*3] = dx*dx;
           cov_data[j*3+1] = dx*dy;
           cov_data[j*3+2] = dy*dy;
       }
    }
 
   boxFilter(cov, cov, cov.depth(), Size(block_size, block_size),
       Point(-1,-1), false, borderType );
 
   if( op_type == MINEIGENVAL )
       calcMinEigenVal( cov, eigenv );
   else if( op_type == HARRIS )
       calcHarris( cov, eigenv, k );
   else if( op_type == EIGENVALSVECS )
       calcEigenValsVecs( cov, eigenv );
}
 
}


 

 

6.1 OpenCV2.X中Threshold函数源代码


路径:…opencv\sources\modules\imgproc\src\thresh.cpp

 

double cv::threshold( InputArray _src,OutputArray _dst, double thresh, double maxval, int type )
{
   Mat src = _src.getMat();
   bool use_otsu = (type & THRESH_OTSU) != 0;
    type &= THRESH_MASK;
 
   if( use_otsu )
    {
       CV_Assert( src.type() == CV_8UC1 );
       thresh = getThreshVal_Otsu_8u(src);
    }
 
   _dst.create( src.size(), src.type() );
   Mat dst = _dst.getMat();
 
   if( src.depth() == CV_8U )
    {
        int ithresh = cvFloor(thresh);
       thresh = ithresh;
       int imaxval = cvRound(maxval);
       if( type == THRESH_TRUNC )
           imaxval = ithresh;
       imaxval = saturate_cast<uchar>(imaxval);
 
       if( ithresh < 0 || ithresh >= 255 )
       {
           if( type == THRESH_BINARY || type == THRESH_BINARY_INV ||
                ((type == THRESH_TRUNC || type== THRESH_TOZERO_INV) && ithresh < 0) ||
                (type == THRESH_TOZERO&& ithresh >= 255) )
           {
               int v = type ==THRESH_BINARY ?

(ithresh >= 255 ?

0 : imaxval) : type ==THRESH_BINARY_INV ? (ithresh >= 255 ? imaxval : 0) : /*type == THRESH_TRUNC?

imaxval :*/ 0; dst.setTo(v); } else src.copyTo(dst); return thresh; } thresh = ithresh; maxval = imaxval; } else if( src.depth() == CV_16S ) { int ithresh = cvFloor(thresh); thresh = ithresh; int imaxval = cvRound(maxval); if( type == THRESH_TRUNC ) imaxval = ithresh; imaxval = saturate_cast<short>(imaxval); if( ithresh < SHRT_MIN || ithresh >= SHRT_MAX ) { if( type == THRESH_BINARY || type == THRESH_BINARY_INV || ((type == THRESH_TRUNC || type== THRESH_TOZERO_INV) && ithresh < SHRT_MIN) || (type == THRESH_TOZERO&& ithresh >= SHRT_MAX) ) { int v = type == THRESH_BINARY ?(ithresh >= SHRT_MAX ? 0 : imaxval) : type == THRESH_BINARY_INV ?(ithresh >= SHRT_MAX ? imaxval : 0) : /*type == THRESH_TRUNC ?

imaxval :*/ 0; dst.setTo(v); } else src.copyTo(dst); return thresh; } thresh = ithresh; maxval = imaxval; } else if( src.depth() == CV_32F ) ; else CV_Error( CV_StsUnsupportedFormat, "" ); parallel_for_(Range(0, dst.rows), ThresholdRunner(src, dst,thresh, maxval, type), dst.total()/(double)(1<<16)); return thresh; }



另外在贴上与之相关的自适应阈值操作函数的源代码adaptiveThreshold:

 

void cv::adaptiveThreshold( InputArray_src, OutputArray _dst, double maxValue,
                            int method, inttype, int blockSize, double delta )
{
   Mat src = _src.getMat();
   CV_Assert( src.type() == CV_8UC1 );
   CV_Assert( blockSize % 2 == 1 && blockSize > 1 );
   Size size = src.size();
 
   _dst.create( size, src.type() );
   Mat dst = _dst.getMat();
 
   if( maxValue < 0 )
    {
       dst = Scalar(0);
       return;
    }
 
   Mat mean;
 
   if( src.data != dst.data )
       mean = dst;
 
   if( method == ADAPTIVE_THRESH_MEAN_C )
       boxFilter( src, mean, src.type(), Size(blockSize, blockSize),
                   Point(-1,-1), true,BORDER_REPLICATE );
   else if( method == ADAPTIVE_THRESH_GAUSSIAN_C )
       GaussianBlur( src, mean, Size(blockSize, blockSize), 0, 0,BORDER_REPLICATE );
   else
       CV_Error( CV_StsBadFlag, "Unknown/unsupported adaptive thresholdmethod" );
 
   int i, j;
   uchar imaxval = saturate_cast<uchar>(maxValue);
   int idelta = type == THRESH_BINARY ?

cvCeil(delta) : cvFloor(delta); uchar tab[768]; if( type == CV_THRESH_BINARY ) for( i = 0; i < 768; i++ ) tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0); else if( type == CV_THRESH_BINARY_INV ) for( i = 0; i < 768; i++ ) tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0); else CV_Error( CV_StsBadFlag, "Unknown/unsupported threshold type"); if( src.isContinuous() && mean.isContinuous() &&dst.isContinuous() ) { size.width *= size.height; size.height = 1; } for( i = 0; i < size.height; i++ ) { const uchar* sdata = src.data + src.step*i; const uchar* mdata = mean.data + mean.step*i; uchar* ddata = dst.data + dst.step*i; for( j = 0; j < size.width; j++ ) ddata[j] = tab[sdata[j] - mdata[j] + 255]; } }



 

 



七、综合演示样例部分




本次综合演示样例为调节滚动栏来控制阈值,以控制的harris检測角点的数量。一共同拥有三个图片窗体。分别为显示原始图的窗体。包括滚动栏的彩色效果图窗体,以及灰度图效果图窗体。

废话不多说。让我们一起来赞赏具体凝视过后的完整源代码:

 

//-----------------------------------【程序说明】----------------------------------------------
//		程序名称::《【OpenCV新手教程之十六】OpenCV角点检測之Harris角点检測》 博文配套源代码 
//		开发所用IDE版本号:Visual Studio 2010
//		开发所用OpenCV版本号:	2.4.9
//		2014年6月8日 Created by 浅墨
//		浅墨的微博:@浅墨_毛星云 http://weibo.com/1723155442
//		浅墨的知乎:http://www.zhihu.com/people/mao-xing-yun
//		浅墨的豆瓣:http://www.douban.com/people/53426472/
//----------------------------------------------------------------------------------------------

//-----------------------------------【头文件包括部分】---------------------------------------
//		描写叙述:包括程序所依赖的头文件
//---------------------------------------------------------------------------------------------- 
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

//-----------------------------------【命名空间声明部分】--------------------------------------
//		描写叙述:包括程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
using namespace std;

//-----------------------------------【宏定义部分】--------------------------------------------  
//  描写叙述:定义一些辅助宏  
//------------------------------------------------------------------------------------------------  
#define WINDOW_NAME1 "【程序窗体1】"        //为窗体标题定义的宏  
#define WINDOW_NAME2 "【程序窗体2】"        //为窗体标题定义的宏  

//-----------------------------------【全局变量声明部分】--------------------------------------
//		描写叙述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_srcImage1,g_grayImage;
int thresh = 30; //当前阈值
int max_thresh = 175; //最大阈值


//-----------------------------------【全局函数声明部分】--------------------------------------
//		描写叙述:全局函数声明
//-----------------------------------------------------------------------------------------------
void on_CornerHarris( int, void* );//回调函数
static void ShowHelpText();

//-----------------------------------【main( )函数】--------------------------------------------
//		描写叙述:控制台应用程序的入口函数。我们的程序从这里開始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
	//【0】改变console字体颜色
	system("color 3F");  

	//【0】显示帮助文字
	ShowHelpText();

	//【1】加载原始图并进行克隆保存
	g_srcImage = imread( "1.jpg", 1 );
	 if(!g_srcImage.data ) { printf("读取图片错误,请确定文件夹下是否有imread函数指定的图片存在~! \n"); return false; }  
	 imshow("原始图",g_srcImage);
	g_srcImage1=g_srcImage.clone( );

	//【2】存留一张灰度图
	cvtColor( g_srcImage1, g_grayImage, CV_BGR2GRAY );

	//【3】创建窗体和滚动栏
	namedWindow( WINDOW_NAME1, CV_WINDOW_AUTOSIZE );
	createTrackbar( "阈值: ", WINDOW_NAME1, &thresh, max_thresh, on_CornerHarris );

	//【4】调用一次回调函数。进行初始化
	on_CornerHarris( 0, 0 );

	waitKey(0);
	return(0);
}

//-----------------------------------【on_HoughLines( )函数】--------------------------------
//		描写叙述:回调函数
//----------------------------------------------------------------------------------------------

void on_CornerHarris( int, void* )
{
	//---------------------------【1】定义一些局部变量-----------------------------
	Mat dstImage;//目标图
	Mat normImage;//归一化后的图
	Mat scaledImage;//线性变换后的八位无符号整型的图

	//---------------------------【2】初始化---------------------------------------
	//置零当前须要显示的两幅图,即清除上一次调用此函数时他们的值
	dstImage = Mat::zeros( g_srcImage.size(), CV_32FC1 );
	g_srcImage1=g_srcImage.clone( );

	//---------------------------【3】正式检測-------------------------------------
	//进行角点检測
	cornerHarris( g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT );

	// 归一化与转换
	normalize( dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
	convertScaleAbs( normImage, scaledImage );//将归一化后的图线性变换成8位无符号整型 

	//---------------------------【4】进行绘制-------------------------------------
	// 将检測到的,且符合阈值条件的角点绘制出来
	for( int j = 0; j < normImage.rows ; j++ )
	{ for( int i = 0; i < normImage.cols; i++ )
	{
		if( (int) normImage.at<float>(j,i) > thresh+80 )
		{
			circle( g_srcImage1, Point( i, j ), 5,  Scalar(10,10,255), 2, 8, 0 );
			circle( scaledImage, Point( i, j ), 5,  Scalar(0,10,255), 2, 8, 0 );
		}
	}
	}
	//---------------------------【4】显示终于效果---------------------------------
	imshow( WINDOW_NAME1, g_srcImage1 );
	imshow( WINDOW_NAME2, scaledImage );

}

//-----------------------------------【ShowHelpText( )函数】----------------------------------
//		描写叙述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
	//输出一些帮助信息
	printf("\n\n\n\t\t\t【欢迎来到Harris角点检測演示样例程序~】\n\n");  
	printf("\n\n\n\t请调整滚动栏观察图像效果~\n\n");
	printf("\n\n\t\t\t\t\t\t\t\t by浅墨"
		);
}


放出一些执行效果图。

首先是原始图,非常漂亮的异域建筑群:



第一组阈值效果图:

 

第二组阈值效果图:

 

第三组阈值效果图:

 

第四组阈值效果图:

 




本篇文章的配套源代码请点击这里下载:

 

【浅墨OpenCV新手教程之十六】配套源代码下载





OK。今天的内容大概就是这些,我们下篇文章见:)

版权声明:本文博主原创文章,博客,未经同意不得转载。

posted @ 2015-10-27 21:57  hrhguanli  阅读(921)  评论(0编辑  收藏  举报