OpenCV——Harris、Shi Tomas、自定义、亚像素角点检测

      在图像处理和与计算机视觉领域,兴趣点(interest points),或称作关键点(keypoints)、特征点(feature points) 被大量用于解决物体识别,图像识别、图像匹配、视觉跟踪、三维重建等一系列的问题。我们不再观察整幅图,而是选择某些特殊的点,然后对他们进行局部有的放矢的分析。如果能检测到足够多的这种点,同时他们的区分度很高,并且可以精确定位稳定的特征,那么这个方法就有使用价值。

图像特征类型可以被分为如下三种:

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

其中,角点是个很特殊的存在。他们在图像中可以轻易地定位,同时,他们在人造物体场景,比如门、窗、桌等出随处可见。因为角点位于两条边缘的交点处,代表了两个边缘变化的方向上的点,,所以他们是可以精确定位的二维特征,甚至可以达到亚像素的精度。且其图像梯度有很高的变化,这种变化是可以用来帮助检测角点的。需要注意的是,角点与位于相同强度区域上的点不同,与物体轮廓上的点也不同,因为轮廓点难以在相同的其他物体上精确定位。
为什么角点是特殊的?

  • 因为角点是两个边缘的连接点,它代表了两个边缘变化的方向上的点。图像梯度有很高的变化。这种变化是可以用来帮助检测角点的。

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

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

关于角点的具体描述可以有几种:

     一阶导数(即灰度的梯度)的局部最大所对应的像素点;

     两条及两条以上边缘的交点;

     图像中梯度值和梯度方向的变化速率都很高的点;

     角点处的一阶导数最大,二阶导数为零,指示物体边缘变化不连续的方向。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

/// Global variables
Mat src, src_gray,dst;
int thresh = 200;
int max_thresh = 255;

char* source_window = "Source image";
char* corners_window = "Corners detected";

/// Function header
void cornerHarris_demo(int, void*);
void cvtColor_src(Mat &src, Mat &dst);

/** @function main */
int main(int argc, char** argv)
{
	/// Load source image and convert it to gray
	src = imread("E:\VS2015Opencv\vs2015\project\picture\06.jpg");

	cvtColor(src, src_gray, CV_BGR2GRAY);
//  转换单通道
	


	/// Create a window and a trackbar
	namedWindow(source_window, CV_WINDOW_AUTOSIZE);
	createTrackbar("Threshold: ", source_window, &thresh, max_thresh, cornerHarris_demo);
	imshow(source_window , src);

    cornerHarris_demo(0, 0);

	waitKey(0);
	return(0);
}


/** @function cornerHarris_demo */
void cornerHarris_demo(int, void*)
{

	Mat dst, dst_norm, dst_norm_scaled;
	dst = Mat::zeros(src.size(), CV_32FC1);

	/// Detector parameters
	int blockSize = 2;
	int apertureSize = 3;
	double k = 0.04;

	/// Detecting corners
	cornerHarris(src_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT);

	/// Normalizing
	normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
	convertScaleAbs(dst_norm, dst_norm_scaled);

	/// Drawing a circle around corners
	for (int j = 0; j < dst_norm.rows; j++)
	{
		for (int i = 0; i < dst_norm.cols; i++)
		{
			if ((int)dst_norm.at<float>(j, i) > thresh)
			{
				circle(dst_norm_scaled, Point(i, j), 5, Scalar(0), 2, 8, 0);
			}
		}
	}
	/// Showing the result
	namedWindow(corners_window, CV_WINDOW_AUTOSIZE);
	imshow(corners_window, dst_norm_scaled);
}

现在讲解一下其中原理:

w(x,y)是窗口函数;w(x,y)也可以说是权值;在x方向和 v 方向也就是我们说的水平,竖直方向上移动;

上图中红色的窗口可以往任意一个方向上移动;来检测是否是角;

红色的窗口开始有图像的像素,移动后窗口内的图像像素变化;两者相减在平方;

前面说过角在各个方向上梯度都很大,因此我们就用(红色的窗口开始有图像的像素,移动后窗口内的图像像素变化;两者相减在平方,无论窗口移动大哪个方向)原理来找最大变化;下面是数学推导;

由于角点代表了图像像素梯度变化,我们将寻找这个”变化”。
考虑到一个灰度图像 I. 划动窗口 w(x,y) (with displacements u 在x方向和 v 方向) I 计算像素灰度变化。     
其中:
w(x,y) is the window at position (x,y)
I(x,y) is the intensity at (x,y)
I(x+u,y+v) is the intensity at the moved window (x+u,y+v)


为了寻找带角点的窗口,我们搜索像素灰度变化较大的窗口。于是, 我们期望最大化以下式子:

使用 泰勒(Taylor)展开式:

具体的可以看数学推导(如果还不懂,可以去学学泰勒展开,治理主要用的是一阶泰勒展开)
式子可以展开为:
将上式表达为矩阵的形式,表达式可以写为:
表示为:
因此我们有等式:
每个窗口中计算得到一个值。这个值决定了这个窗口中是否包含了角点:
其中:
一个窗口,它的分数 R 大于一个特定值,这个窗口就可以被认为是”角点”
上面E(u,v)可以看出,无论u,v取何值,我们需要E尽可能的大;

线代理论中实对称矩阵可以正交相似对角化,如下面理论;可以看出E(u,v)是一个椭圆,若E尽可能的大,则λi要尽可能大;

以下图中的λi是椭圆的半长轴(及开根号,椭圆公式);所以当λi两个都很大时,是角及corner,一个大一个小是边;具体看下图中英文标识。

corner:在水平、竖直两个方向上变化均较大的点,即Ix、Iy都较大; 
 edge :仅在水平、或者仅在竖直方向有较大的点,即Ix和Iy只有其一较大 ;
  flat   : 在水平、竖直方向的变化量均较小的点,即Ix、Iy都较小;

讲到这里,大家基本原理懂了,那如何判别两个特征根都很大呢?

首先矩阵行列式的求法:,只有两个都大时,det(M)才会大;k一般去为0.05;

也是两个都大时,其平方也会大;

最重要的是求每个像素的M矩阵,如下面步骤:

最后最重要的一点;M的求法是不会变得,不同的角点检测方法最后区别就在于R的求法,也就是如何判断特征根都很大的。

可参考:思维之际Harris角点

void cornerHarris_demo(int, void*)
{

	Mat dst, dst_norm, dst_norm_scaled;
	dst = Mat::zeros(src.size(), CV_32FC1);

	/// Detector parameters
	int blockSize = 2;
	int apertureSize = 3;
	double k = 0.04;

	/// Detecting corners
	cornerHarris(src_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT);

	/// Normalizing
	normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
	convertScaleAbs(dst_norm, dst_norm_scaled);

	/// Drawing a circle around corners
	for (int j = 0; j < dst_norm.rows; j++)
	{
		for (int i = 0; i < dst_norm.cols; i++)
		{
			if ((int)dst_norm.at<float>(j, i) > thresh)
			{
				circle(dst_norm_scaled, Point(i, j), 5, Scalar(0), 2, 8, 0);
			}
		}
	}

 

 Shi-Tomasi 算法是Harris 算法的改进。Harris 算法最原始的定义是将黑塞矩阵

 

 M的行列式值与 M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进的方法,若两个特征值中较小的一个大于最小阈值,则会得到强角点。二者原理一模一样,只是计算角点量的方式不一样。

#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;

#define WINDOW_NAME "【Shi-Tomasi角点检测】" 
Mat g_srcImage, g_grayImage;
int g_maxCornerNumber = 33;
int g_maxTrackbarNumber = 500;
RNG g_rng(12345);//初始化随机数生成器


				 //-----------------------------【on_GoodFeaturesToTrack( )函数】----------------------------
				 //          描述:响应滑动条移动消息的回调函数
				 //----------------------------------------------------------------------------------------------
void on_GoodFeaturesToTrack(int, void*)
{
	//【1】对变量小于等于1时的处理
	if (g_maxCornerNumber <= 1) { g_maxCornerNumber = 1; }

	//【2】Shi-Tomasi算法(goodFeaturesToTrack函数)的参数准备
	vector<Point2f> corners;
	double qualityLevel = 0.01;//角点检测可接受的最小特征值
	double minDistance = 10;//角点之间的最小距离
	int blockSize = 3;//计算导数自相关矩阵时指定的邻域范围
	double k = 0.04;//权重系数
	Mat copy = g_srcImage.clone();    //复制源图像到一个临时变量中,作为感兴趣区域

									  //【3】进行Shi-Tomasi角点检测
	goodFeaturesToTrack(g_grayImage,//输入图像
		corners,//检测到的角点的输出向量
		g_maxCornerNumber,//角点的最大数量
		qualityLevel,//角点检测可接受的最小特征值
		minDistance,//角点之间的最小距离
		Mat(),//感兴趣区域
		blockSize,//计算导数自相关矩阵时指定的邻域范围
		false,//不使用Harris角点检测
		k);//权重系数


		   //【4】输出文字信息
	cout << "	>此次检测到的角点数量为:" << corners.size() << endl;

	//【5】绘制检测到的角点
	int r = 4;
	for (int i = 0; i < corners.size(); i++)
	{
		//以随机的颜色绘制出角点
		circle(copy, corners[i], r, Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255),
			g_rng.uniform(0, 255)), -1, 8, 0);
	}

	//【6】显示(更新)窗口
	imshow(WINDOW_NAME, copy);
}

static void ShowHelpText()
{
	//输出欢迎信息和OpenCV版本
	printf("

			非常感谢购买《OpenCV3编程入门》一书!
");
	printf("

			此为本书OpenCV2版的第87个配套示例程序
");
	printf("

			   当前使用的OpenCV版本为:" CV_VERSION);
	printf("

  ----------------------------------------------------------------------------
");
	//输出一些帮助信息
	printf("


	欢迎来到【Shi-Tomasi角点检测】示例程序
");
	printf("
	请调整滑动条观察图像效果

");

}

void main()
{
	system("color 2F");
	ShowHelpText();

	//【1】载入源图像并将其转换为灰度图
	g_srcImage = imread("E:\VS2015Opencv\vs2015\project\picture\06.jpg", 1);
	cvtColor(g_srcImage, g_grayImage, CV_BGR2GRAY);

	//【2】创建窗口和滑动条,并进行显示和回调函数初始化
	namedWindow(WINDOW_NAME, CV_WINDOW_AUTOSIZE);
	createTrackbar("最大角点数", WINDOW_NAME, &g_maxCornerNumber, g_maxTrackbarNumber, on_GoodFeaturesToTrack);
	imshow(WINDOW_NAME, g_srcImage);
	on_GoodFeaturesToTrack(0, 0);

	waitKey(0);
}

自定义角点检测器简介:

  • 基于Harris与Shi-Tomasi角点检测

  • 首先通过计算矩阵M得到lamda1和lamda2两个特征值根据他们得到角点响应值

  • 然后自己设置阈值实现计算出阈值得到有效响应值的角点设置

cornerEigenValsAndVecs()函数在角点检测中计算扫描图像块的特征向量与特征值,其函数原型如下:

C++: void cornerEigenValsAndVecs(
       InputArray src,       --单通道输入8位或浮点图像
       OutputArray dst,    --输出图像,同源图像或CV_32FC(6)
       int blockSize,         --邻域大小值
       int apertureSize,    --Sobel算子的参数
       int borderType=BORDER_DEFAULT --像素外插方法
)//对应于Harris

borderType:像素扩展的方法,因为在滤波处理的过程中会扩展图像边缘,每扩张一个边界像素,都需要计算出该像素点在原图中的位置,这个功能被提炼出来就变成了borderInterpolate()函数。该函数输入一个点坐标,返回他在原图中的坐标;可参考:borderInterpolate()函数。

 下面我们采用cornerEigenValsAndVecs()函数和minMaxLoc()函数,根据其原理来编写Harris角点检测的实现代码,示例如下:

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

using namespace cv;
using namespace std;

// 定义全局变量
const string harris_winName = "自定义角点检测";
Mat src_img, gray_img;                       // src_img表示原图, gray_img表示灰度图
Mat harris_dst_img, harris_response_img;     // harris_dst_img存储自相关矩阵M的特征值和特征向量,harris_response_img存储响应函数的结果

double min_respense_value;			  // 响应函数的结果矩阵中的最小值
double max_respense_value;			  // 响应函数的结果矩阵中的最大值

int qualityValue = 30;
int max_qualityValue = 100;              // 通过qualityValue/max_qualityValue的结果作为qualitylevel来计算阈值
RNG  random_number_generator;             // 定义一个随机数发生器
void self_defining_Harris_Demo(int, void*);      //TrackBar回调函数声明

												 // 主函数
int main()
{
	src_img = imread("E:\VS2015Opencv\vs2015\project\picture\06.jpg");
	if (src_img.empty())
	{
		printf("could not load the image...
");
		return -1;
	}
	namedWindow("原图", CV_WINDOW_AUTOSIZE);
	imshow("原图", src_img);
	cvtColor(src_img, gray_img, COLOR_BGR2GRAY);      //将彩色图转化为灰度图

													  // 计算特征值
	int blockSize = 3;
	int ksize = 3;
	double k = 0.04;
	harris_dst_img = Mat::zeros(src_img.size(), CV_32FC(6));
	// 目标图像harris_dst_img存储自相关矩阵M的特征值和特征向量,
	// 并将它们以(λ1, λ2, x1, y1, x2, y2)的形式存储。其中λ1, λ2是M未经过排序的特征值;
	// x1, y1是对应于λ1的特征向量;x2, y2是对应于λ2的特征向量。
	// 因此目标矩阵为6通道,即 CV_32FC(6)的矩阵。

	harris_response_img = Mat::zeros(src_img.size(), CV_32FC1);
	// harris_response_img用来存储通过每个像素值所对应的自相关矩阵所计算得到的响应值

	cornerEigenValsAndVecs(gray_img, harris_dst_img, blockSize, ksize, 4);
	// 该函数用来计算每个像素值对应的自相关矩阵的特征值和特征向量

	// 计算响应函数值
	for (int row = 0; row < harris_dst_img.rows; ++row)
	{
		for (int col = 0; col < harris_dst_img.cols; ++col)
		{
			double eigenvalue1 = harris_dst_img.at<Vec6f>(row, col)[0];     // 获取特征值1
			double eigenvalue2 = harris_dst_img.at<Vec6f>(row, col)[1];		// 获取特征值2
			harris_response_img.at<float>(row, col) = eigenvalue1* eigenvalue2 - k*pow((eigenvalue1 + eigenvalue2), 2);
			// 通过响应公式R=λ1*λ2 - k*(λ1+λ2)*(λ1+λ2)来计算每个像素对应的响应值
		}
	}
	minMaxLoc(harris_response_img, &min_respense_value, &max_respense_value, 0, 0, Mat());   // 寻找响应矩阵中的最小值和最大值
	namedWindow(harris_winName, CV_WINDOW_AUTOSIZE);
	createTrackbar("Quality Value:", harris_winName, &qualityValue, max_qualityValue, self_defining_Harris_Demo);    //创建TrackBar
	self_defining_Harris_Demo(0, 0);

	waitKey(0);
	return 0;
}


//  回调函数实现
void self_defining_Harris_Demo(int, void*)
{
	if (qualityValue < 10)
	{
		qualityValue = 10;       // 控制qualitylevel的下限值
	}
	Mat result_img = src_img.clone();    // 输出图像
	float threshold_value = min_respense_value + (((double)qualityValue) / max_qualityValue)*(max_respense_value - min_respense_value);
	for (int row = 0; row <result_img.rows; row++)
	{
		for (int col = 0; col < result_img.cols; col++)
		{
			float resp_value = harris_response_img.at<float>(row, col);
			if (resp_value > threshold_value)
			{
				circle(result_img, Point(col, row), 2, Scalar(random_number_generator.uniform(0, 255),
					random_number_generator.uniform(0, 255), random_number_generator.uniform(0, 255)), 2, 8, 0);
			}
		}
	}
	imshow(harris_winName, result_img);
}

erMinEigenVal()函数在角点检测中计算梯度矩阵的最小特征值,其函数原型如下

C++: void cornerMinEigenVal(
       InputArray src,     --单通道输入8位或浮点图像
       OutputArray dst,  --图像存储的最小特征值。类型为CV_32FC1
       int blockSize,       --邻域大小值
       int apertureSize=3,   --Sobel算子的参数
       int borderType=BORDER_DEFAULT  --像素外插方法
}//对应Shi-Tomasi

函数参数

函数参数说明中除了dst必须为CV_32FC1类型以外,其它与cornerEigenValsAndVecs()函数的一致。

函数功能

功能与cornerEigenValsAndVecs()函数相似,但是它只计算导数协方差矩阵的最小特征值,

按照cornerEigenValsAndVecs()函数给定的特征值λ1, λ2来说就是min(λ1, λ2)。


采用cornerMinEigenVal()函数和minMaxLoc()函数结合来模拟Shi-Tomasi角点检测的代码示例如下;(参考:定制化创建角点检测子

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

using namespace cv;
using namespace std;

// 定义全局变量
const string ShiTomasi_winName = "Custom Shi-Tomasi Corners Detector";
Mat src_img, gray_img;                  // src_img表示原图, gray_img表示灰度图
Mat ShiTomasi_dst_img;                  // ShiTomasi_dst_img用来存储每个像素对应的自相关矩阵的最小特征值
double min_ShiTomasi_value;            // 最小特征矩阵中的最小值
double max_ShiTomasi_value;		      // 最小特征矩阵中的最大值
int ShiTomasi_qualityValue = 30;
int max_qualityValue = 100;
RNG  random_number_generator;                        // 定义一个随机数发生器
void self_defining_ShiTomasi_Demo(int, void*);      //TrackBar回调函数声明

													// 主函数
int main()
{
	src_img = imread("E:\VS2015Opencv\vs2015\project\picture\06.jpg");
	if (src_img.empty())
	{
		printf("could not load the image...
");
		return -1;
	}
	namedWindow("原图", CV_WINDOW_AUTOSIZE);
	imshow("原图", src_img);
	cvtColor(src_img, gray_img, COLOR_BGR2GRAY);      //将彩色图转化为灰度图

													  // 计算特征值
	int blockSize = 3;
	int ksize = 3;

	// 计算最小特征值
	ShiTomasi_dst_img = Mat::zeros(src_img.size(), CV_32FC1);
	cornerMinEigenVal(gray_img, ShiTomasi_dst_img, blockSize, ksize, 4);            // 计算每个像素对应的自相关矩阵的最小特征值
	minMaxLoc(ShiTomasi_dst_img, &min_ShiTomasi_value, &max_ShiTomasi_value, 0, 0, Mat());           //计算最小最大值
	namedWindow(ShiTomasi_winName, CV_WINDOW_AUTOSIZE);
	createTrackbar("Quality:", ShiTomasi_winName, &ShiTomasi_qualityValue, max_qualityValue, self_defining_ShiTomasi_Demo);
	self_defining_ShiTomasi_Demo(0, 0);

	waitKey(0);
	return 0;
}


//  回调函数实现
void self_defining_ShiTomasi_Demo(int, void*)
{
	if (ShiTomasi_qualityValue < 10)
	{
		ShiTomasi_qualityValue = 10;       // 控制qualitylevel的下限值
	}
	Mat result_img = src_img.clone();    // 输出图像
	float threshold_value = min_ShiTomasi_value + (((double)ShiTomasi_qualityValue) / max_qualityValue)*(max_ShiTomasi_value - min_ShiTomasi_value);
	for (int row = 0; row <result_img.rows; row++)
	{
		for (int col = 0; col < result_img.cols; col++)
		{
			float resp_value = ShiTomasi_dst_img.at<float>(row, col);
			if (resp_value > threshold_value)
			{
				circle(result_img, Point(col, row), 2, Scalar(random_number_generator.uniform(0, 255),
					random_number_generator.uniform(0, 255), random_number_generator.uniform(0, 255)), 2, 8, 0);
			}
		}
	}
	imshow(ShiTomasi_winName, result_img);
}

 

亚像素角点检测需要先运行常规的角点检测,得到整数表示的角点坐标。然后算法对每个角点做细化,得到实数表示的角点坐标

第一步:goodFeaturesToTrack()    //检测角点

第二步:TermCriteria()            //设置迭代算法的终止条件
TermCriteria(int type,int max_iter, double epsilon);
参数
type:终止条件类型;
CV_TERMCRIT_ITER--max_iter达到最大值后停止算法;
CV_TERMCRIT_EPS--当算法依赖的精确度低于epsilon后,停止算法;
CV_TERMCRIT_ITER+CV_TERMCRIT_EPS--当max_iter达到最大值或算法依赖的精确度低于epsilon任一个满足时,停止算法;
max_iter:最大迭代次数;
epsilon:要求精度;

第三步:cornerSubPix()         //细化角点位置;
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria);
参数:
image:输入图像;
corners:初始化输入角点的坐标,为输出提供细化的坐标;
winSize:搜索窗口的边长的一半;
zeroZone:搜索区域中间的死区的一半大小,对它在下边的求和公式不计算,有时候它用来避免可能的自相关矩阵的奇异性,(-1,-1)用来表明这里没有这样的规模;
criteria:角点细化的迭代过程的终止条件;

 可参考:亚像素角点检测

相关代码和结果

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

using namespace cv;
using namespace std;

//描述:定义一些辅助宏

#define WINDOW_NAME "【亚像素角点检测】"

//全局变量声明
Mat g_srcImage;
Mat g_grayImage;
int g_maxCornerNumber = 33;
int g_maxTrackbarNumber = 500;
RNG g_rng(12345);

//回调函数
void on_GoodFeaturesToTrack(int, void *)
{
	if (g_maxCornerNumber <= 1)
	{
		g_maxCornerNumber = 1;
	}
	//shi-tomasi算法
	vector<Point2f> corner;
	double qualityLevel = 0.01;     //角点检测可接收的最小特征
	double minDistance = 10;        //角点之间的最小的距离
	int blockSize = 3;              //计算导数自相关矩阵时指定的邻域范围
	double k = 0.04;                //权重系数
	Mat copy = g_srcImage.clone();
	//进行角点检测
	goodFeaturesToTrack(g_grayImage, corner, g_maxCornerNumber, qualityLevel, minDistance, Mat(), blockSize, false, k);
	//输出文字信息
	cout << ">此次检测到的角点数量为:" << corner.size() << endl;
	//绘制检测到的角点
	Size winSize = Size(5, 5);
	Size zeroZone = Size(-1, -1);
	TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
	cornerSubPix(g_grayImage, corner, winSize, zeroZone, criteria);
	for (int i = 0; i < corner.size(); i++)
	{
		cout << "	>>精度角点坐标[" << i << "](" << corner[i].x << "," << corner[i].y << ")" << endl;
	}
	int r = 4;
	for (unsigned int i = 0; i < corner.size(); i++)
	{
		circle(copy, corner[i], r, Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255)), -1, 8, 0);
	}
	//显示更新窗口
	imshow(WINDOW_NAME, copy);
}
int main()
{
	//载入原始图像
	g_srcImage = imread("E:\VS2015Opencv\vs2015\project\picture\06.jpg", 1);
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);

	//创建窗口和滑动条
	namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);
	createTrackbar("最大角点数:", WINDOW_NAME, &g_maxCornerNumber, g_maxTrackbarNumber, on_GoodFeaturesToTrack);
	imshow(WINDOW_NAME, g_srcImage);
	on_GoodFeaturesToTrack(0, 0);
	waitKey(0);
	return 0;
}

  

原文链接:https://blog.csdn.net/poem_qianmo/article/details/29356187

代码参考:https://blog.csdn.net/poem_qianmo/article/category/1923021和http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/features2d/trackingmotion/good_features_to_track/good_features_to_track.html#good-features-to-track

参考:OpenCV学习笔记(十一)——自定

 harris corner detection(角点检测)原理视频

原文地址:https://www.cnblogs.com/fcfc940503/p/11332104.html