为便于理解,先简要介绍角点的概念和角点检测背景
一、角点及角点检测背景:
角点概念:
角点,通常可理解为两条边的角点,也可理解为像素值在多个方向有显著变化的点或局部区域内某个属性明显的点。如多个轮廓的交界处的点、轮廓边缘凸出的点等。对于角点的具体定义,目前并没有一个明确的概念,只需理解其大致含义即可。以下是对角点的一些描述,帮助理解:
(1)、一阶导数(灰度的梯度)的局部最大所对应的像素点;
(2)、两条及两条以上边缘的交点;
(3)、图像中梯度值和梯度方向的变化率都很高的点
(4)、角点处的一阶导数最大、二阶导数为零,指物体边缘变化不连续的方向。
角点检测背景:
目前角点检测可分为三类,基于灰度图的角点检测、基于二值化图像的角点检测和基于轮廓曲线的角点检测。博主目前受实力限制,仅了解基于灰度图的Harris角点检测,后续实力有变,再续更。
二、Harris角点检测原理:
概念:
Harris角点检测可理解为以像素点为中心的窗口向多个方向移动,通过窗口像素值有无显著变化判断有无角点。下面上图,便于理解:
窗口多个方向移动,像素值无 窗口多个方向移动,像素值 窗口多个方向移动,像素值
显著变化 仅在竖直方向有明显变化 在多个方向有明显变化
算法:
对I(x,y)图像中的像素点(x,y),进行自相关平移w(x+∆x、y+∆y)得到自相关函数:
c(x,y,∆x,∆y) = ∑wh(x,y)(I(x,y)-I(x+∆x,y+∆y))2
其中 ∑w表示窗口内的点,h(x,y)表示加权函数,可为高斯函数或常数等,感兴趣可取下方源代码,根据自己喜好进行修改。
有泰勒公式可得:
I(x+∆x,y+∆y) = I(x,y)+∆xIx(x,y)+∆yIy(x,y)+p ≈ I(x,y)+∆xIx(x,y)+∆yIy(x,y)
代入上式(为便于理解和方便(懒),加权函数暂时忽略):
c(x,y,∆x,∆y) = ∑w(I(x,y)-I(x+∆x,y+∆y))2 ≈ ∑w((∆xIx(x,y))2+2∆x∆yIx(x,y)Iy(x,y)+(∆yIy(x,y))2)
由于某园对公式输入不太友好,下面取道友的图一用(道友图中,w(x,y)表示加权函数):
c(x,y,∆x,∆y)为自相关函数,若窗口内的像素值无变化时,该函数值一般为定值,表示为椭圆形式。若窗口内的像素值有变化时,一般为Ix或Iy的变化,也可表示为M的特征值(原因,建议移步bilibli线代教程)即下图中的λmax,λmin的变化:
进一步解释(1表示水平,2表示竖直):
为便于表示结果和更好的编程,Harris原理中定义了角点响应函数R,通过R的大小判断像素值是否为角点。R取决于M的特征值,下图为R的数学定义:
trace表示为矩阵的迹,det为矩阵的行列式(矩阵的迹:主对角线上的值相加即所有特征值的和),k为经验常数,一般取0.04~0.06。
对R:
R = λ1λ2-k(λ1+λ2)2
当λ1和λ2均很小时,由上式分析可知,|R|很小,该区域像素值无明显变化
当λ1>>λ2或λ2>>λ1时,上式左边小于右边(远大于!!!),故R为负值,区域为边缘区域
当λ1和λ2均比较大时,R也很大,区域含角点,也即所取点
最后,Harris将窗口内多个方向的R进行加权平均,得到一个值,通过判断该值的大小,确定角点。
三、小结:
Harris角点检测可分为5个步骤:
1.计算图像x和y两个方向梯度
2.计算图像的两个方向梯度的平方和乘积:I2x = Ix*Ix ,IxIy = Ix*Iy, I2y = Iy*Iy
3.对窗口W进行加权得到W矩阵: A = ∑W I2x*h(x,y) B = ∑W I2y *h(x,y) C = ∑W IxIy*h(x,y)
4.计算每个像素点(x,y)处的Harris响应值R;
5.过滤大于某一阈值t的R值;
Harris角点的性质(提升篇,均不讲解,自己感兴趣可详细网上了解或代码实现):
1.参数k(k大于等于0)的影响:
R = λ1λ2-k(λ1+λ2)2
假设λ2 = n λ1,对上式化简可得:R = λ21 ( n - k(1+n)2 )
下面分R大于0和小于0分析:
大于0: k<=n/(n+1)2 <= 某一个值(不再计算)
小于0: n/(n+1)2=<k
2.Harris角点检测对亮度和对比度的变化不灵敏:
Harris角点检测时,涉及微积分算子对图像进行微分计算,微分运算对图像密度的拉伸或收缩和对亮度的抬高不敏感,即对Harris响应的极值位置无影响,仅影响极值的大小,如下图(仅为提升,其中原理,感兴趣可自己查询):
3.Harris角点检测算子具有旋转不变性:
Harriis角点检测为一个点窗口周围的像素值梯度的加权,图像旋转时,特征值不发生变化。
4.Harris角点检测具有尺度不变性:
对同一图像,在检测窗口尺寸不变的前提下,Harris角点检测的特征值不发生变化。
四、代码应用:
OpenCV内的API:
void goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance,InputArray mask=noArray(), int blockSize=3, bool useHarrisDetector=false, double k=0.04 ); //参数分别为:输入图像,输出角点,检测到的角点的数量的最大值,角点的质量等级(可以看做R的一个转化), 两个角点间的最小距离,检测区域(即有无roi区域),窗口的大小,是否使用Harris角点检测(False为Shi-Tomasi算子)
参数k
样本:
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace cv; using namespace std; Mat image; Mat imageGray; int thresh=200; int MaxThresh=255; void Trackbar(int,void*); //阈值控制 int main() { image=imread("Test.bmp"); cvtColor(image,imageGray,CV_RGB2GRAY); GaussianBlur(imageGray,imageGray,Size(5,5),1); // 滤波 namedWindow("Corner Detected"); createTrackbar("threshold:","Corner Detected",&thresh,MaxThresh,Trackbar); imshow("Corner Detected",image); Trackbar(0,0); waitKey(); return 0; } void Trackbar(int,void*) { Mat dst,dst8u,dstshow,imageSource; dst=Mat::zeros(image.size(),CV_32FC1); imageSource=image.clone(); cornerHarris(imageGray,dst,3,3,0.04,BORDER_DEFAULT); normalize(dst,dst8u,0,255,CV_MINMAX); //归一化 convertScaleAbs(dst8u,dstshow); imshow("dst",dstshow); //dst显示 for(int i=0;i<image.rows;i++) { for(int j=0;j<image.cols;j++) { if(dstshow.at<uchar>(i,j)>thresh) //阈值判断 { circle(imageSource,Point(j,i),2,Scalar(0,0,255),2); //标注角点 } } } imshow("Corner Detected",imageSource); }
源代码(后续再做更新):
//--------------------【cornerHarris源码分析】------------------------------------ //Harries角点实现函数,截取cornerHarries中的关键代码做了简化 void myHarries( const Mat& src, Mat& eigenv, int block_size, int aperture_size, double k=0.) { eigenv.create(src.size(), CV_32F); Mat Dx, Dy; //soble operation get Ix, Iy Sobel(src, Dx, CV_32F, 1, 0, aperture_size); Sobel(src, Dy, CV_32F, 0, 1, aperture_size); //get covariance matrix Size size = src.size(); Mat cov(size, CV_32FC3); //创建一个三通道cov矩阵分别存储[Ix*Ix, Ix*Iy, Iy*Iy]; for( int i=0; i < size.height;i++) { float* cov_data = cov.ptr<float>(i); const float* dxdata = Dx.ptr<float>(i); const float* dydata = Dy.ptr<float>(i); for( int j=0; j < size.width; j++ ) { float dx = dxdata[j]; float dy = dydata[j]; cov_data[j*3] = dx * dx; //即 Ix*Ix cov_data[j*3 + 1] = dx*dy; //即 Ix*Iy cov_data[j*3 + 2] = dy*dy; //即 Iy*Iy } } //方框滤波W(x,y)卷积, 也可用高斯核加权... //W(Y,Y)与矩阵cov卷积运算得到 H 矩阵,后面通过H矩阵的特征值决定是否是角点 boxFilter(cov, cov, cov.depth(), Size(block_size,block_size),Point(-1,-1),false); //cale Harris size = cov.size(); if( cov.isContinuous() && eigenv.isContinuous()) { size.width *= size.width; size.height = 1; //cout << "yes"<<endl; } //此处计算响应R= det(H) - k*trace(H)*trace(H); for (int i = 0; i < size.height; i++) { const float* covPtr = cov.ptr<float>(i); float* dstPtr = eigenv.ptr<float>(i); for( int j = 0; j < size.width; j++) { float a = covPtr[j*3]; float b = covPtr[j*3 + 1]; float c = covPtr[j*3 + 2]; //根据公式 R = det(H) - k*trace(H)*trace(H); dstPtr[j] = (float)(a*c - b*b - k*(a + c)(a + c)); } } double max, min; minMaxLoc(eigenv, &min, &max); //cout<< max << endl; double threshold = 0.1*max; cv::threshold(eigenv, eigenv,threshold, 1, CV_THRESH_BINARY); //eigenv的类型是CV_32F, imshow("eigenv", eigenv); }