1.1 引言

       角点检测是计算机视觉中获取图像特征的一种方式,也可以称为特征点检测。广泛地应用于图像对齐、图像匹配、目标识别、三维建模等领域。

       角点通常定义为两条边的交点,更严格地说,角点的局部邻域应该具有两个不同区域的不同方向的边界。就是图像梯度在两个或多个方向上有变化,并且角点要有足够的可重复性和显著性。比如角点一定不会出现在直线上。如下图所示

1.2  Harris角点检测

1.2.1 角点检测算法思想

       角点检测算法的具体思想是使用一个固定窗口\omega (x,y)在图像上进行任意方向上的滑动,比较滑动前后窗口区域的像素灰度值变化,如果在任意方向多存在较大的灰度变化,可认为该窗口中存在角点。

E(u,v)=\sum_{x,y}^{}\omega (x,y)[I(x+u,y+v)-I(x,y)]^{2}

        该公式表征的是窗口通过平移之后,滑窗内相应位置的像素值对应相减,然后与 \omega (x,y)卷积

(u,v)是窗口的平移量,

通常的窗口函数有以下两种

泰勒展开:

E(u,v)\approx \sum_{x,y}^{}\omega (x,y)[I(x,y)+uI_{x}+vI_{y}-I(x,y)]^{2}

E(u,v)\approx \sum_{x,y}^{}\omega (x,y)[u^{2}I_{x}^{2}+2uvI_{x}I_{y}+v^{2}I_{y}^{2}]

矩阵表示

E(u,v)\approx [u,v](\sum_{x,y}^{}\omega (x,y)\begin{bmatrix} I_{x}^{2} & I_{x}I_{y}\\ I_{x}I_{y}& I_{y}^{2} \end{bmatrix})\begin{bmatrix} u\\ v \end{bmatrix}

M=\sum_{x,y}^{}\omega (x,y)\begin{bmatrix} I_{x}^{2} & I_{x}I_{y}\\ I_{x}I_{y}& I_{y}^{2} \end{bmatrix}

E(u,v)\approx [u,v]M\begin{bmatrix} u\\ v \end{bmatrix}

角点响应:

R=detM-k(tranceM)^{2}

det M=\lambda_{1} \lambda _{2}, trance M=\lambda_{1}+ \lambda _{2},其中k是常量,一般取值为0.04~0.06,这个参数仅仅是这个函数的一个系数,它的存在只是调节函数的形状而已。

  • 特征值都比较大时,即窗口中含有角点

  • 特征值一个较大,一个较小,窗口中含有边缘

  • 特征值都比较小,窗口处在平坦区域

具体的数学演算参考:参考1,参考2;

1.2.2 相关API

·void cornerHarris( InputArray src, OutputArray dst, int blockSize, int ksize, double k; int borderType=BORDER_DEFAULT)

参数含义
作用获取角点图像
输入src数据类型为 float32 的输入图像。(灰度图像,输入单通道图)
dst输出图像
blockSize角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小
ksizeSobel求导中使用的窗口大小
k计算角点响应时的参数大小
borderType边界的类型
返回值void

1.2.3 算法步骤

  1. 获取灰度图像;
  2. 使用sobel等梯度算子计算每个像素点x/y向的梯度
  3. 计算每个像素点的梯度平方I_{x}^{2} ,I_{y}^{2};
  4. 计算梯度在每个像素点的和;
  5. 构建M矩阵并计算角点响应;
  6. 设置阈值找出可能点并进行非极大值抑制,得到角点;

1.2.4 代码示例

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

Mat src, gray;
int thresh = 145;
int threshMax = 255;
const char* outputWin = "HarrisCorner";
void HarrisCallback(int,void*)
{
	Mat HarrisDst, normDst,normScaleDst;
	HarrisDst = Mat::zeros(src.size(),CV_32FC1);

	int blockSize = 2;
	int ksize = 3;
	double k = 0.04;
	cornerHarris(gray, HarrisDst,2,3,0.04);
	normalize(HarrisDst, normDst,0,255,NORM_MINMAX,CV_32FC1,Mat());
	//取绝对值
	convertScaleAbs(normDst, normScaleDst);

	//根据阈值选择角点
	Mat resImg = src.clone();
	for (int i = 0; i < resImg.rows; i++)
	{
		uchar* currentRow = normScaleDst.ptr(i);
		for (int j = 0; j < resImg.cols; j++)
		{
			int value = (int)*currentRow;
			if(value>thresh)
			{
				circle(resImg,Point(j,i),2,Scalar(0,0,255),2);
			}
			else;
			currentRow++;
		}
	}
	imshow(outputWin, resImg);

}

int main(int argc, char**argv)
{
	
	src = imread("D:/testimg/CSet12/House.png");
	if (src.empty())
	{
		cout << "the srcImg could not get" << endl;
		return -1;
	}
	namedWindow("input",WINDOW_AUTOSIZE);
	imshow("input",src);

	namedWindow(outputWin, WINDOW_AUTOSIZE);
	
	cvtColor(src,gray,COLOR_BGR2GRAY);

	createTrackbar("Threshold",outputWin,&thresh,threshMax, HarrisCallback);
	HarrisCallback(0,0);

	waitKey(0);
	return 0;
}

1.3 Shi-Tomasi角点检测

1.3.1 算法思想

        跟Harris角点检测的理论基本一致,只是在使用矩阵特征值\lambda _{1}\lambda _{2}计算角度响应的时候有所不同,其其角度响应公式如下:

R=min(\lambda _{1},\lambda _{2})

1.3.2 相关API

·void goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners,double qualityLevel,double minDistance,InputArray mask=noArray(), int blockSize=3,bool usrHarrisDetector=false,duoble harrisk=0.04)

参数含义
作用获取图像角点
输入image8位或32位浮点型输入图像,单通道
corners检测出的角点
maxCorners角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点
qualityLevel角点的品质因子,表示最小可接受的向量值
minDistance两个角点之间最小的距离
mask指定感兴趣区,如不需在整幅图上寻找角点,则用此参数指定ROI
blockSize角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小
usrHarrisDetector指示是否使用Harris角点检测,如不指定,则计算shi-tomasi角点
harriskHarris角点检测需要的k值
返回值void

1.3.3 代码示例

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

Mat src, gray;
int cornerNum = 20;
int cornerMax = 200;
const char* outputWin = "ShiTomasi";
void ShiTomasiCallback(int,void*)
{	
	vector<Point2f> cornerVec;
	goodFeaturesToTrack(gray, cornerVec, cornerNum,0.01,8,Mat(),3,false,0.04);

	//根据阈值选择角点
	Mat resImg = src.clone();
	for (int i = 0; i < cornerVec.size(); i++)
	{
		circle(resImg, cornerVec[i], 2, Scalar(0, 0, 255), 2);
	}
	imshow(outputWin, resImg);

}

int main(int argc, char**argv)
{
	
	src = imread("D:/testimg/CSet12/House.png");
	if (src.empty())
	{
		cout << "the srcImg could not get" << endl;
		return -1;
	}
	namedWindow("input",WINDOW_AUTOSIZE);
	imshow("input",src);

	namedWindow(outputWin, WINDOW_AUTOSIZE);
	
	cvtColor(src,gray,COLOR_BGR2GRAY);

	createTrackbar("Threshold",outputWin,&cornerNum, cornerMax, ShiTomasiCallback);
	ShiTomasiCallback(0, 0);

	waitKey(0);
	return 0;
}

1.4 自定义角点检测器

1.4.1 概述

       自定义角点检测是基于Harris和Shi-Tomasi角点检测,首先通过计算矩阵M得到\lambda _{1}\lambda _{2}两个特征值而获取角点响应值。然后自己设置阈值实现计算出阈值得到有效响应值的角点位置。

1.4.2 相关API

·void cornerEigenValsAndVecs( InputArray src, OutputArray dst, int blockSize=3,int ksize,int borderType=BORDER_DEFAULT)

EigenVals--\lambda _{1}/\lambda _{2}Vecs

参数含义
作用通过计算自相关矩阵M求出其特征值\lambda _{1}/\lambda _{2},用于自定义Harris
输入src数据类型为 float32 的输入图像。(灰度图像,输入单通道图)
dst输出图像(宽度为输入图像的6倍)
blockSize角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小
ksize梯度算子的核尺寸大小
borderType边界的类型
返回值void

·void cornerMinEigenVal( InputArray src, OutputArray dst, int blockSize=3,int ksize=3,int borderType=BORDER_DEFAULT)

参数含义
作用通过计算自相关矩阵M获取最小特征值,用于Shi-Tomasi
输入src数据类型为 float32 的输入图像。(灰度图像,输入单通道图)
dst输出图像(大小同输入图像)
blockSize角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小
ksize梯度算子的核尺寸大小
borderType边界的类型
返回值void

1.4.3 代码示例

* 自定义Harris检测

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace cv;
using namespace std;

Mat src, gray;
double RMin,RMax;
int qualityLevel = 30;
int countMax = 100;
Mat cornerEigenR;
const char* outputWin = "ShiTomasi";
void ThresholdCallback(int,void*)
{	
	if (qualityLevel < 10)
	{
		qualityLevel = 10;
	}
	else;

	//根据阈值选择角点
	Mat resImg = src.clone();
	float vTemp = RMin + (((double)qualityLevel)/countMax)*(RMax-RMin);
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			float value = cornerEigenR.at<float>(i,j);
			if (value > vTemp)
			{
				circle(resImg,Point(j,i),2,Scalar(0,0,255),2,8,0);
			}
		}
	}
	imshow(outputWin, resImg);

}

int main(int argc, char**argv)
{
	
	src = imread("D:/testimg/CSet12/House.png");
	if (src.empty())
	{
		cout << "the srcImg could not get" << endl;
		return -1;
	}
	namedWindow("input",WINDOW_AUTOSIZE);
	imshow("input",src);

	namedWindow(outputWin, WINDOW_AUTOSIZE);
	
	cvtColor(src,gray,COLOR_BGR2GRAY);
	//计算特征值
	int blockSize = 3;
	int ksize = 3;
	Mat cornerEigen;
	cornerEigen = Mat::zeros(src.size(),CV_32FC(6));
	cornerEigenValsAndVecs(gray, cornerEigen,blockSize,ksize);
	//计算角点响应R=λ1*λ2-k(λ1+λ2)^2
	double k = 0.04;
	cornerEigenR = Mat::zeros(src.size(),CV_32FC1);
	for (int i = 0; i < cornerEigen.rows; i++)
	{
		for (int j = 0; j < cornerEigen.cols; j++)
		{
			double lambda1 = cornerEigen.at<Vec6f>(i,j)[0];
			double lambda2 = cornerEigen.at<Vec6f>(i, j)[1];
			cornerEigenR.at<float>(i, j) = lambda1*lambda2 - k*pow((lambda1+ lambda2),2);
		}
	}

	minMaxLoc(cornerEigenR,&RMin,&RMax,0,0,Mat());

	createTrackbar("Threshold",outputWin,&qualityLevel, countMax, ThresholdCallback);
	ThresholdCallback(0, 0);

	waitKey(0);
	return 0;
}

* 自定义Shi-Tomasi检测

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace cv;
using namespace std;

Mat src, gray;
double RMin,RMax;
int qualityLevel = 30;
int countMax = 100;
Mat cornerMin;
const char* outputWin = "ShiTomasi";
void ThresholdCallback(int,void*)
{	
	if (qualityLevel <5)
	{
		qualityLevel = 5;
	}
	else;

	//根据阈值选择角点
	Mat resImg = src.clone();
	float vTemp = RMin + (((double)qualityLevel)/countMax)*(RMax-RMin);
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			float value = cornerMin.at<float>(i,j);
			if (value > vTemp)
			{
				circle(resImg,Point(j,i),2,Scalar(0,0,255),2,8,0);
			}
		}
	}
	imshow(outputWin,resImg);

}

int main(int argc, char**argv)
{
	
	src = imread("D:/testimg/CSet12/House.png");
	if (src.empty())
	{
		cout << "the srcImg could not get" << endl;
		return -1;
	}
	namedWindow("input",WINDOW_AUTOSIZE);
	imshow("input",src);

	namedWindow(outputWin, WINDOW_AUTOSIZE);
	
	cvtColor(src,gray,COLOR_BGR2GRAY);
	//计算特征值
	int blockSize = 3;
	int ksize = 3;

	//计算最小特征值
	cornerMin = Mat::zeros(src.size(), CV_32FC1);
	cornerMinEigenVal(gray, cornerMin, blockSize, ksize,4);
	//获取这个图的特征值最大最小值
	minMaxLoc(cornerMin,&RMin,&RMax,0,0,Mat());

	createTrackbar("quality",outputWin,&qualityLevel, countMax, ThresholdCallback);
	ThresholdCallback(0, 0);

	waitKey(0);
	return 0;
}

1.5 亚像素级角点检测

1.5.1 概述

通过进行亚像素级别的角点检测可以提高角点的检测精准度,因为角点的位置不会是一个准确的像素点。可以通过插值算法、基于图像矩计算、曲线拟合(高斯曲面、多项式、椭圆曲面)

1.5.2 相关API

·void cornerSubPix( InputArray image, InputOutputArray corners, Size winSize,Size zeroZone,TermCirteria criteria)

参数含义
作用对检测到的角点作进一步的优化计算,使角点的精度达到亚像素级别
输入image输入图像
corners角点(输入检测到的角点-优化后的角点输出)
winSize计算亚像素角点时考虑的区域的大小
zeroZone类似于winSize,但是总是具有较小的范围,通常忽略(即Size(-1, -1))
criteria计算亚像素时停止迭代的标准,可选的值有TermCriteria::MAX_ITER 、TermCriteria::EPS(可以是两者其一,或两者均选),前者表示迭代次数达到了最大次数时停止,后者表示角点位置变化的最小值已经达到最小时停止迭代。二者均使用cv::TermCriteria()构造函数进行指定。
返回值void

1.5.3 代码示例

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace cv;
using namespace std;

Mat src, gray;
int cornerNum = 30;
int cornerMax = 80;
const char* outputWin = "SubPixel Corner";
void SubpixelCallback(int,void*)
{	
	if (cornerNum <5)
	{
		cornerNum = 5;
	}
	else;

	//利用Shi-Tomasi获取角点
	vector<Point2f> cornerVec;
	int blockSize = 3;
	int ksize = 3;
	double qulitylevel = 0.01;
	double mindistance = 8;
	goodFeaturesToTrack(gray, cornerVec, cornerNum, qulitylevel, mindistance,Mat(),3,false,0.04);
	printf("corners num = %d\n", cornerVec.size());
	Mat resImg = src.clone();
	for (int i = 0; i < cornerVec.size(); i++)
	{
		circle(resImg, cornerVec[i], 2, Scalar(0, 0, 255), 2, 8, 0);
	}

	//拟合找到亚像素位置
	Size winSize = Size(5, 5);
	Size zerozone = Size(-1,-1);
	//迭代求解停止条件
	TermCriteria tc = TermCriteria(TermCriteria::EPS+ TermCriteria::MAX_ITER,40,0.001);
	cornerSubPix(gray,cornerVec,winSize,zerozone,tc);
	for (int i = 0; i < cornerVec.size(); i++)
	{
		printf("%d Point: x: %f, y: %f\n", i,cornerVec[i].x, cornerVec[i].y);
	}

	imshow(outputWin,resImg);
}

int main(int argc, char**argv)
{
	
	src = imread("D:/testimg/CSet12/House.png");
	if (src.empty())
	{
		cout << "the srcImg could not get" << endl;
		return -1;
	}
	namedWindow("input",WINDOW_AUTOSIZE);
	imshow("input",src);

	namedWindow(outputWin, WINDOW_AUTOSIZE);
	
	cvtColor(src,gray,COLOR_BGR2GRAY);

	createTrackbar("cornerNum",outputWin,&cornerNum, cornerMax, SubpixelCallback);
	SubpixelCallback(0, 0);

	waitKey(0);
	return 0;
}

亚像素检测到的角点位置

参考:

1.机器视觉学习(五)局部特征点检测 - 知乎

2.图像处理基础(七)Harris 角点检测 - 知乎

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐