若图像中某一点的像素在任意方向上的一个微小变动都会导致灰度值的很大变化,那么我们就称这一点为角点,又叫关键点,特征点,他被大量用于解决无题识别,图像识别,视觉跟踪,3D重建等一系列的问题.
如果能检测到足够多的这种点,同时他们的区分度很高,并且可以精确定位稳定的特征,那么角点检测就很有实用价值.
针对一幅图像而言,图像的特征一般分为边缘特征,角点特征,斑点特征,角点一般是位于两条边缘的交点处,代表了两个边缘变化方向上的点,所以他们是可以精确定位的图像特征,甚至可以达到亚像素的级别.
角点的具体描述包括1.图像一阶导数的局部最大值所对应的像素点,2.两条或者以上边缘的交点,3.图像中梯度值和变化方向都很大,值得关注的点4.角点处的一阶导数最大,二阶导数为0,他指出了物体边缘变化不连续的方向.
角点的检测算法一般可以分为三类1.基于灰度图像的角点检测,2.基于阈值图像的角点检测3.基于轮廓曲线的角点检测.
一.harris基于灰度图像的角点检测
该算法稳定性高,尤其对于L形式的角点,检测精度高,但是由于计算过程中使用了高斯滤波,运算速度相对较慢,而且角点信息可能有丢失,或者是偏移现象,角点提取的时候有时有聚簇现象.
API:void cornerHarris(源图像,输出角点图像,int 高斯滤波邻域大小,int sobel求导算子孔径,double harris参数K,int 边界模式)
注:源图像是灰度图像,八位单通道或者浮点型,运算结果和源图像有一样的尺寸和类型,边界模式默认为BORDER_DEFAULT,对于输出的角点图像进行二值化处理就能很明显的看到角点,同时,对输出图像进行阈值处理,控制阈值,可以控制角点相应的强度值,达到角点筛选的目的.
实际使用例程如下
//harris角点检测 //基于灰度图像的焦点检测,但是因为使用了高斯滤波,会有焦点缺少和聚簇现象 //但是对L型角点的检测精度高,稳定性高 Mat srcImage,srcGrayImage; const int g_thresholdLowMax = 170; int g_thresholdLowValue; void onTrackBarLowThreshold(int pos,void* userData); int main(int argc,char* argv[]) { srcImage = imread("F:\opencv\OpenCVImage\harrisCorner.jpg"); if(srcImage.channels() == 3) { cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY); } else { srcImage.copyTo(srcGrayImage); } namedWindow("src image"); namedWindow("corner image"); g_thresholdLowValue = 10; createTrackbar("threshold low value", "src image", &g_thresholdLowValue, g_thresholdLowMax,onTrackBarLowThreshold,0); onTrackBarLowThreshold(50, 0); //imshow("src image", srcImage); moveWindow("src image", 0, 0); moveWindow("corner image", srcImage.cols, 0); waitKey(0); return 0; } void onTrackBarLowThreshold(int pos,void* userData) { Mat dstImage,normalImage,scaleImage; Mat srcImageShow; dstImage = Mat::zeros(srcGrayImage.size(), CV_32FC1); srcImageShow = srcImage.clone(); cornerHarris(srcGrayImage, dstImage, 2, 3, 0.04,BORDER_DEFAULT); normalize(dstImage, normalImage, 0, 255,NORM_MINMAX,CV_32FC1,Mat()); //convertScaleAbs(normalImage, scaleImage); scaleImage = Mat(normalImage.rows,normalImage.cols,CV_8UC1,Scalar::all(255)); for(int i = 0 ; i < normalImage.rows;i++) { for(int j = 0; j < normalImage.cols;j++) { if((int)normalImage.at<float>(i,j) > g_thresholdLowValue+80) { circle(srcImageShow, Point(i,j), 3, Scalar(10,255,255),2,8,0); circle(scaleImage, Point(i,j), 3, Scalar::all(0),2,8,0); } } } imshow("src image", srcImageShow); imshow("corner image", scaleImage); }
二.shi_Tomasi角点检测
该算法是对harris算法的改进,通过矩阵行列式的迹的插值,来计算出图像中的强角点
API:void goodFeatureToTrack(输入图像,输出角点向量,int 角点最大值指定,double 角点可以接受的最小特征值,double 角点之间的最小距离,inputarray 输入图像的可选的ROI掩码,int 导数自相关矩阵指定邻域范围,bool 是否使用harris角点检测,double hessian自相关矩阵行列式的权重系数)
注:角点检测时候的最小特征值一般为0.1或者0.01,最大不超过1,ROI区域掩码默认为noarray,也就是没有指定ROI,导数自相关矩阵的领域范围默认为3,是否使用harris角点检测默认为false,不使用, hessian自相关矩阵行列式的权重系数默认值为0.04,输出角点的向量特征为vector<point2f>corner.
该算法的演示如下 //shi-tomasi 强角点检测 可变数据为 角点最大数目 Mat srcImage,srcGrayImage,srcCopyImage,dstImage; vector<Point2f>cornerPoints; const int g_cornerPointNumMax = 100; int g_cornerPointValue; void onTrackBarCornerNum(int pos,void* userData); int main(int argc,char* argv[]) { srcImage = imread("F:\opencv\OpenCVImage\shiTomasi.jpg"); if(srcImage.channels() == 3) { cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY); } else { srcGrayImage = srcImage.clone(); } namedWindow("src image"); namedWindow("dst image"); g_cornerPointValue = 30; createTrackbar("corner num value", "src image", &g_cornerPointValue, g_cornerPointNumMax,onTrackBarCornerNum,0); onTrackBarCornerNum(g_cornerPointValue, 0); moveWindow("src image", 0, 0); moveWindow("dst image", srcImage.cols, 0); waitKey(); return 0; } void onTrackBarCornerNum(int pos,void* userData) { srcCopyImage = srcImage.clone(); dstImage = srcGrayImage.clone(); double qualityLevel= 0.01; double min_distance = 10; int blockSize = 3; double k = 0.04; goodFeaturesToTrack(srcGrayImage, cornerPoints, g_cornerPointValue, qualityLevel, min_distance,noArray(),blockSize,false,k); int radius = 5; for(int i = 0; i < cornerPoints.size(); i++) { circle(srcCopyImage, cornerPoints[i],radius, Scalar(0,0,255),-1,8,0); circle(dstImage, cornerPoints[i], radius, Scalar(255),1,8,0); } imshow("src image", srcCopyImage); imshow("dst image", dstImage); }
三.亚像素级别角点检测
在实际应用中,比如摄像机的标定的时候,我们需要的特征点坐标不能仅仅是整数值,有时需要是实数值,这就需要用到亚像素级别的角点检测,检测出一些高精度角点,该方法在摄像机标定,跟踪并重建摄像机轨迹方面或者重建跟踪目标的3D模型的时候,是一个基本的测量值,很关键.
API:void cornerSubpix(源图像,输入角点的初始坐标和精确的输出坐标,size 搜索窗口的一半尺寸,size 死区的一半尺寸,Term_Criteria 迭代数目和精确度的混合结构体)
注:搜索窗口的实际尺寸为一般尺寸*2+1,保证是奇数,死区是值不对搜索区的中央位置做求和运算的区域,(-1,-1)表示不设死区,但是没有死区可能带来自相关矩阵的奇异性,导致结果误差.Term_Criteria::EPS代表迭代数量, Term_Criteria::MAX_ITER代表需要的角点精确度
实际使用代码如下
//亚像素级角点检测
//在原来的强角点检测的基础上对检测出来的强角点进行亚像素级别的检测,并输出角点位置
Mat srcImage,srcGrayImage,srcCopyImage,dstImage; vector<Point2f>cornerPoints; const int g_cornerPointNumMax = 100; int g_cornerPointValue; void onTrackBarCornerNum(int pos,void* userData); int main(int argc,char* argv[]) { srcImage = imread("F:\opencv\OpenCVImage\cornerSubpix.jpg"); if(srcImage.channels() == 3) { cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY); } else { srcGrayImage = srcImage.clone(); } namedWindow("src image"); namedWindow("dst image"); g_cornerPointValue = 30; createTrackbar("corner num value", "src image", &g_cornerPointValue, g_cornerPointNumMax,onTrackBarCornerNum,0); onTrackBarCornerNum(g_cornerPointValue, 0); moveWindow("src image", 0, 0); moveWindow("dst image", srcImage.cols, 0); waitKey(); return 0; } void onTrackBarCornerNum(int pos,void* userData) { srcCopyImage = srcImage.clone(); dstImage = srcGrayImage.clone(); double qualityLevel= 0.01; double min_distance = 10; int blockSize = 3; double k = 0.04; goodFeaturesToTrack(srcGrayImage, cornerPoints, g_cornerPointValue, qualityLevel, min_distance,noArray(),blockSize,false,k); int radius = 5; for(int i = 0; i < cornerPoints.size(); i++) { circle(srcCopyImage, cornerPoints[i],radius, Scalar(0,0,255),-1,8,0); circle(dstImage, cornerPoints[i], radius, Scalar(255),1,8,0); } printf("当前检测到的看角点数量为%lu ",cornerPoints.size()); Size winSize = Size(5,5);//搜索窗口的一半尺寸 Size zeroSize = Size(-1,-1);//死区尺寸,-1,-1表示无死区 TermCriteria criteria = TermCriteria(TermCriteria::EPS+TermCriteria::MAX_ITER, 40, 0.01);//迭代数 40 精确度0.01 cornerSubPix(srcGrayImage, cornerPoints, winSize, zeroSize, criteria); for(int i = 0 ; i < cornerPoints.size();i++) { cout<<"第"<<i<<"个角点为 "<<"x = "<<cornerPoints[i].x<<" "<<"y = "<<cornerPoints[i].y<<" "; } imshow("src image", srcCopyImage); imshow("dst image", dstImage); }