前言:
图像特征点检测广泛运用于计算机视觉处理领域,包括目标识别与跟踪、立体成像,在特征点的图像分析中,特征点提取是非常重要的步骤,其中,角点是最常见的一类点特征。前面我们介绍了用 Harris提取角点,但是提取的角点是像素级的,精度不高,若我们进行图像处理的目的不是提取用于识别的特征点而是进行几何测量,这通常需要更高的精度。
那么如何提取亚像素级角点的位置呢?在 Harris 提取角点过程中,通过两次角点筛选,剔除非角点和伪角点,利用角点响应函数执行非极大值抑制,以局部角点响应函数最大值的像素点作为初始角点,并以该初始角点为中心,以一定半径搜索角点簇,采用最小二乘法加权角点簇与待求角点的欧几里得距离,精化初始角点坐标,从而实现 Harris 亚像素角点准确快速定位。
一、原理解析
在亚像素级精度的角点检测算法中,有两种常用的方法。一种方法是从亚像素角点到周围像素点的矢量应垂直于图像的灰度梯度这个观察事实得到的,通过最小化误差函数的迭代方法来获得亚像素级精度的坐标值。另一种方法是用二次多项式去逼近周围3× 3领域内的角点反应函数,用线性解法求得亚像素级角点坐标。
(1) 第一种方法
角点位置特征:边缘的交点,且角点与边缘点的连线和边缘点的梯度方向垂直。
如上图所示,假设一个起始角点q在实际亚像素角点附近。p点在q点附近的邻域中,若p点在均匀区域内部,则p点的梯度为0;若p点在边缘上,则p点的梯度方向垂直边缘方向。如果向量q-p方向与边缘方向一致,那么q-p向量与p点的梯度向量点积运算结果为0。在初始角点(初始角点可能不在边缘上)附近我们可以收集很多组点的梯度以及相关向量q-p,此时的q就是我们所要求的更精确角点位置,那么每一组的向量点积设置为0,正是基于这个思想,将点积为0的等式组合起来形成一个系统方程,该系统方程的解就是更精确的亚像素角点位置。 将新的q点作为区域的中心,可以继续使用这个方法进行迭代,获得很高的精度。
(2)第二种方法
一个很直接的想法就是插值。用二次多项式来逼近角点反应函数,找到
的亚像素级精确位置:
用已经检测出来的角点周围的9个像素点可以建立含有
6个未知量的超定方程组。运用最小二乘法可以求解这个超定方程。亚像素级角点的
对应的是二次多项式的极大值点。为了求出这个坐标点,对二次多项式进行求导:
可以直接得到的亚像素级的坐标。
二、cornerSubPix()函数介绍
函数goodFeaturesToTrack()函数只能提供简单的像素的坐标值,也就是说,有时候会需要实数坐标值而不是整数坐标值。在OpenCV中,就提供了一个cornerSubPix()函数,用于寻找亚像素角点的位置,其函数声明如下:
其中,函数参数解释如下:
image:输入图像,即源图像;
corners:提供输入角点的初始坐标和精确的输出坐标。
winSize:Size类型,表示搜索窗口的半径。若winSize=Size(5,5),那么就表示使用(5*2+1)x(5*2+1)=11*11大小的搜索窗口。
zeroZone:Size类型,表示死区的一半尺寸。而死区为不对搜索区的中央位置做求和运算的区域,用来避免自相关矩阵出现的某些可能的奇异性。值为(-1,-1)表示没有死区。
criteria:TermCriteria类型,求角点的迭代过程的终止条件。
三、代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int max_corners = 20;
int max_count = 50;
Mat src_img, gray_img;
const string output_winName = "亚像素角点检测";
RNG random_number_generator; // 定义一个随机数发生器
void SubPixel_Demo(int, void*);
int main()
{
src_img = imread("test11.png");
if (src_img.empty())
{
printf("could not load the image...\n");
return -1;
}
namedWindow("原图", CV_WINDOW_AUTOSIZE);
imshow("原图", src_img);
cvtColor(src_img, gray_img, COLOR_BGR2GRAY);
namedWindow(output_winName, CV_WINDOW_AUTOSIZE);
createTrackbar("角点数", output_winName, &max_corners, max_count, SubPixel_Demo); //创建TrackBar
SubPixel_Demo(0, 0);
waitKey(0);
return 0;
}
void SubPixel_Demo(int, void*)
{
if (max_corners < 5)
{
max_corners = 5; //控制下限
}
vector<Point2f> corners; // corners是一个Vector,里面存放找到的角点坐标,即float的Point值
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3;
double k = 0.04;
cout << "************************************************************"<< endl;
cout << "作者;行歌" << endl;
// 调用goodFeaturesToTrack进行shi-Tomasi角点检测
goodFeaturesToTrack(gray_img, corners, max_corners, qualityLevel, minDistance, Mat(), blockSize, false, k);
cout << "角点数: " << corners.size() << endl;
Mat result_img = src_img.clone();
for (auto t = 0; t < corners.size();++t)
{
circle(result_img, corners[t], 2, Scalar(random_number_generator.uniform(0, 255),
random_number_generator.uniform(0, 255), random_number_generator.uniform(0, 255)), 2, 8, 0); // 注意corners[t]就是一个Point类型的坐标
}
imshow(output_winName, result_img);
// 参数设置
Size winSize = Size(5, 5);
Size zerozone = Size(-1, -1);
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
cornerSubPix(gray_img, corners, winSize, zerozone, criteria); // 调用cornerSubPix函数计算出亚像素角点的位置
// 输出亚像素角点信息
for (auto t = 0; t < corners.size(); ++t)
{
cout << "精确角点坐标[" << t +1 << "]" << " point[x, y] = " << corners[t].x<< " , " << corners[t].y << endl;
}
}
运行程序,如下:
拖动TrackBar,查看不同角点数设置下的效果: