一:角点检测
什么是角点,难道是角落里面的点?我们知道,比如说墙角,他有往左延申的边缘,又有往右延申的边缘,那么这样的概念同样可以帮助我们理解图像的角点检测。
其实我们人眼对于角点的识别是通过一个小窗口来实现的,如下面这张图所示,如果在各个方向上移动这个小窗口,窗口内的灰度发生了较大的变化,那么说明窗口内存在角点。
- 如果在各个方向上移动,灰度变化为0,则这一块区域是平坦区域
- 如果只有一个方向移动,灰度值几乎不变,这一块区域是边缘区域
- 如果各个方向上移动,灰度值都发生了变化,则该区域是角点区域
图像I(x,y),在点(x,y)处平移(u,v)后的自相似性,可以用灰度变化函数E(u,v)表示:
泰勒展开:
代入得到:
其中:
矩阵M的两个特征值分别代表灰度值的变化率,其与图像中的角点,边缘,平坦区域的关系如下所示:
Harris定义的角点检测响应函数为:
k的取值为一个经验常数,大致在0.04-0.06之间。
我们定义当R大于threshold,并且为局部最大值的点为角点。
Harris角点检测算子对图像亮度和对比度具有部分不变性,且具有旋转不变性,但不具有尺度不变性。
opencv中实现Harris角点检测
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat src, dst, gray_src;
int thresh_value = 130; //设置当前阈值
int max_thresh_value = 255; //设置最大阈值
void Harris_Demo(int, void*); //实现Harris角点检测函数
char *output_image = "Harris Result"; //输出图像窗口
int main(int argc, char**argv) {
//读入原始图像并显示
src = imread("D:/opencv/yuner.jpg");
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
namedWindow(output_image, CV_WINDOW_AUTOSIZE);
cvtColor(src, gray_src, CV_BGR2GRAY); //灰度图像转换
//创建滑动bar
createTrackbar("thresh", output_image, &thresh_value, max_thresh_value, Harris_Demo);
Harris_Demo(thresh_value, 0);
waitKey(0);
return(0);
}
void Harris_Demo(int, void*) {
Mat normImage, scaleImage;
//将需要显示的图置零,即清除上一次调用此函数的值
Mat ResultImage = src.clone();
dst = Mat::zeros(gray_src.size(), CV_32FC1);
//角点检测
cornerHarris(gray_src, dst, 2, 3, 0.04, BORDER_DEFAULT);
//归一化到0-255尺度下
normalize(dst, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
//将归一化后的图像线性变化为8位无符号整型
convertScaleAbs(normImage, scaleImage);
for (int i = 0; i < ResultImage.rows; i++) {
//读取像素值
uchar *currentRow = scaleImage.ptr(i);
for (int j = 0; j < ResultImage.cols; j++) {
if ((int) *currentRow > thresh_value) {
//如果像素值大于阈值,则将角点画在输出图像上
circle(ResultImage, Point(i, j), 2, (0, 0, 255),2,8,0);
}
currentRow++;
}
}
imshow(output_image, ResultImage);
}
运行程序我们可以看到如下的效果图:
、
调整threshold的值,我们能看到焦点的变化情况,但总体来说,该算法的鲁棒性不够好,下面我们看一下和Harris算法有点相似的shi-Tomoshi角点检测。
Shi-Tomasi角点检测
由于Harris算法的稳定性和k值有关,Shi-Tomasi发现,角点的稳定性和矩阵M的较小特征值有关,改进的Harris算法即直接计算出矩阵M的特征值,用较小的特征值与阈值比较,大于阈值的即为强特征点。
opencv中对其实现算法在goodFeaturesToTrack()函数中:
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat src, gray_src;
char *output_image = "Tomoshi Result"; //输出窗口
int num_corners = 23; //角点个数
const int max_corners = 150; //定义最大的角点个数
void Tomoshi_demo(int, void*) {
if (num_corners < 5)
num_corners = 5; //避免角点太少
vector<Point2f> corners;
double qualityLevel = 0.01; //最大,最小特征值的乘法因子。定义可接受图像角点的最小质量因子。
double min_distance = 10.0; //两个角点之间的最小欧式距离
int block_size = 3; //领域窗口大小
bool Harris_use = false; //是否适用harris
double k = 0.04; //经验系数(0.04-0.06)
Mat result_image = src.clone();
goodFeaturesToTrack(gray_src, corners, max_corners, qualityLevel, min_distance, Mat(), block_size, Harris_use, k);
for (int i = 0; i < corners.size(); i++)
circle(result_image, corners[i], 2, (0, 0, 255), 1, 8, 0);
imshow(output_image, result_image);
}
int main() {
src = imread("D:/opencv/yuner.jpg");
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
namedWindow(output_image, CV_WINDOW_AUTOSIZE);
cvtColor(src, gray_src, CV_BGR2GRAY);
createTrackbar("corner number", output_image, &num_corners, max_corners, Tomoshi_demo);
Tomoshi_demo(0, 0);
waitKey(0);
return 0;
}
继续看看上述程序运行的结果。
可以看到,检测出来的角点更符合实际情况,Shi-Tomasi算法的鲁棒性更好。
亚像素级别角点检测
理论和现实不可能完全保持一致,上面所说的Harris和Shi-Tomasi角点检测中,可能我们检测得到的角点为(100,5),但实际上可能是一个浮点数(100.234,5.789),为了提高检测精度,特别是在跟踪定位,相机矫正,三维重建等领域里面对精度要求很高,我们引入亚像素角点检测。
亚像素定位大致具有三种方法:
- 插值方法
- 基于图像矩计算
- 曲线拟合方法(高斯曲面,椭圆曲面,多项式)
opencv为我们提供了现成的亚像素角点检测函数cornerSubPix(),通过迭代角点或者径向鞍点精确的亚像素位置。
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat src, gray_src;
char *output_image = "SubPix Result"; //输出窗口
int num_corners = 20; //角点个数
const int max_corners = 50; //定义最大的角点个数
void SubPix_demo(int, void*) {
if (num_corners < 5)
num_corners = 5; //避免角点太少
vector<Point2f> corners;
double qualityLevel = 0.01; //最大,最小特征值的乘法因子。定义可接受图像角点的最小质量因子。
double min_distance = 10.0; //两个角点之间的最小欧式距离
int block_size = 3; //领域窗口大小
bool Harris_use = false; //是否适用harris
double k = 0.04; //经验系数(0.04-0.06)
Mat result_image = src.clone();
goodFeaturesToTrack(gray_src, corners, max_corners, qualityLevel, min_distance, Mat(), block_size, Harris_use, k);
Size winSize = Size(5, 5); //搜索窗口边长的一半,(5,5)所对应的的搜索窗口5∗2+1×5∗2+1=11×11大小的搜索窗口
Size zeroZone = Size(-1, -1); //搜索区域内死区大小的一半,(-1,-1)表示没有这样的大小
TermCriteria tc = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001); //终止角点优化迭代的条件
cornerSubPix(gray_src, corners, winSize, zeroZone, tc);
for (int i = 0; i < corners.size(); i++) {
cout << (i + 1) << " Point(x,y)= " << corners[i].x << ',' << corners[i].y << endl;
circle(result_image, corners[i], 2, (0, 0, 255), 1, 8, 0);
}
return;
}
int main() {
src = imread("D:/opencv/yuner.jpg");
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
namedWindow(output_image, CV_WINDOW_AUTOSIZE);
cvtColor(src, gray_src, CV_BGR2GRAY);
createTrackbar("corners", output_image, &num_corners, max_corners, SubPix_demo);
SubPix_demo(0, 0);
waitKey(0);
return 0;
}
运行程序打印输出结果如下所示:
可以看到所有的角点坐标已经精确到浮点数。
总结
角点检测差不多就学到这里了,其实我们可以自定义实现Harris以及shi-Tomasi角点检测算法,自定义Harris角点检测器用到的函数为cornerEigenValsAndVecs(),用来计算特征值和特征向量,自定义Shi-Tomasi角点检测用到的函数为cornerMinEigenVal(),由于我太懒就没有实现了。有感兴趣的可以自己去尝试下。