OpenCV特征提取与检测(一)
1 Harris角点检测
1.1 绪论
角点通常被定义为两条边的交点,或者说,角点的局部邻域应该具有两个不同区域的不同方向的边界。角点检测(Corner Detection)是计算机视觉系统中获取图像特征的一种方法,广泛应用于运动检测、图像匹配、视频跟踪、三维重建和目标识别等,也可称为特征点检测。
它们在图像中可以轻易定位,同时它们在人造物体中场景中,如门窗,桌子等中随处可见。因为角点位于两条边缘的交点处,代表了两个边缘变化的方向上的点,所以它们是可以精确定位的二维特征,甚至可以达到亚像素的精度。且其图像梯度有很高的变化,这种变化是可以用来帮助检测角点的。需要注意的是,角点与位于相同强度区域上的点不同,与物体轮廓上的点也不同,因为轮廓点难以在相同的其他物体上精确定位。
1.2 角点的定义
当某一点在任何方向上产生轻微扰动都会导致亮度发生显著变化时,则称该点为角点。
1.3 Harris角点检测原理
该检测方法在任意方向上的平移(u, v)都会产生显著的变化。
如上图所示的一个局部较小的区域,在图像区域中平移灰度值无明显变化,则该窗口内无角点。
如果在一个特定的方向上发生较大的灰度值变化而另一侧无明显变化,则说明该区域位于目标对象的边缘部分。

基于上述算法理论基础, 可用来建立数学模型. 当图像窗口发生平移[u,v]变化时, 亮度变化E(u,v)表现为:

其意义在于,在图像I(x,y)中,在点(x,y)处进行(u,v)的平移变换后所得图像将保持与原始图像类似的特性特征。其中w(x,y)是一个加权函数,并非固定不变而是可以取常数值或高斯权重形式等不同取值范围内的值以调节权重分配情况。如图2所示

根据泰勒展开和一些数学步骤后可得到如下结果:

M的两个特征值λ₁和λ₂决定了一个图像元素属于角点、边缘或平坦区域。
此时对应的图像元素被认为是平坦区域。
当R为负数时(即|R|较小),对应的图像元素被认为是边缘。
而较大时(即R绝对值较大),对应的图像元素被认为是角点。
它们之间的关系可以通过下图表示:

1.4 相应的API
Harris角点检测是一种用于图像处理的算法:
- 该算法的算子对于光照变化和对比度调整具有良好的鲁棒性。
- 该算法的算子在旋转变换下保持不变性。
- 该算法的算子不具备尺度归一化特性。
基于OpenCV库实现的Harris角点检测函数为cornerHarris()。
void cv::cornerHarris(InputArray src, OutputArray dst,int blockSize, int ksize, double k, int borderType)
参数说明:
src为一个8位、单通道、灰度Mat矩阵类型;
dst用于存储Harris角点检测结果,并采用32位单通道格式与src尺寸一致;
blockSize表示滑动窗口的尺寸设置;
ksize指定Sobel边缘检测滤波器的空间扩展程度;
k为Harris角点检测中的中间调节因子,默认取值范围在0.04到0.06之间;
borderType=BORDER_DEFAULT指定图像边缘填充模式,默认填充策略。
综合示例:
基于输入图像计算其梯度并提取特征点;
通过设定合适的参数组合实现目标函数优化;
利用Hessian矩阵计算特征响应并筛选候选角点;
根据阈值确定最终稳定角点集合。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat src, gray_src;
int thresh = 130;
int max_count = 255;
const char* output_title = "HarrisCornerDetection Result";
void Harris_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/home.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
cvtColor(src, gray_src, COLOR_BGR2GRAY);
createTrackbar("Threshold:", output_title, &thresh, max_count, Harris_Demo);
Harris_Demo(0, 0);
waitKey(0);
return 0;
}
void Harris_Demo(int, void*) {
Mat dst, norm_dst, normScaleDst;
dst = Mat::zeros(gray_src.size(), CV_32FC1);
int blockSize = 2;
int ksize = 3;
double k = 0.04;
cornerHarris(gray_src, dst, blockSize, ksize, k, BORDER_DEFAULT);
normalize(dst, norm_dst, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
convertScaleAbs(norm_dst, normScaleDst);
Mat resultImg = src.clone();
for (int row = 0; row < resultImg.rows; row++) {
uchar* currentRow = normScaleDst.ptr(row);
for (int col = 0; col < resultImg.cols; col++) {
int value = (int)*currentRow;
if (value > thresh) {
circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
}
currentRow++;
}
}
imshow(output_title, resultImg);
}
2 Shi-Tomasi角点检测
2.1 绪论
基于Harris算法的改进方案是Shi-Tomasi算法。两者的原理基本相同主要区别在于利用矩阵特征值λ₁和λ₂来计算角度响应。按照原始定义的方式将矩阵M的行列式与M的迹进行比较然后将其差额与预先设定好的阈值进行对比。相比之下Shi和Tomasi提出了一种改进方法当两个特征值中的较小者超过预设最小阈值时则会得到强角点如图所示

Shi和Tomasi提出的方法极为全面,并且在多数情况下能够提供优于Harris算法的结果
2.2 相应的API
void goodFeaturesToTrack( InputArray image, OutputArray corners,int maxCorners, double qualityLevel, double minDistance,
InputArray mask=noArray(), int blockSize=3,bool useHarrisDetector=false, double k=0.04 )
图像参数:
第一个参数image为单通道灰度图像,默认采用8位或32位分辨率;
第二个参数corners表示各角点的坐标集合;
第三个参数maxCorners设定最多可提取的角点数量,在超过该数值时将只返回指定的最大数目;
第四个参数qualityLevel决定了检测到的角点质量标准:若计算出的质量值低于 qualityLevel与最大质量值的乘积,则该质素点将被排除;
第五个参数minDistance规定了相邻角点之间的最小距离(以像素计),以确保检测结果具有足够的分离度;
第六个parameter mask定义了搜索区域,在未指定时该区域将覆盖整个图像平面;
第七个parameter blockSize用于协方差矩阵计算时所采用的窗口尺寸;
第八个parameter useHarrisDetector控制使用哪种角点检测算法:当设置为false时,默认启用Shi-Tomasi算法;
第九个parameter k在Harris算法中作为中间变量起作用,默认取值范围在0.04~0.06之间(仅在不使用Harrs探测器时生效)。
综合示例:
//代码里面有三种程序
#include "stdafx.h"
#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
int main(int argv, char** argc)
{
Mat srcImage = imread("D:/Users/zhj/Desktop/image/template.bmp");
if (srcImage.empty())
{
printf("could not load image..\n");
return false;
}
Mat srcgray;
cvtColor(srcImage, srcgray, CV_BGR2GRAY);
//Shi-Tomasi算法:确定图像强角点
vector<Point2f> corners;//提供初始角点的坐标位置和精确的坐标的位置
int maxcorners = 200;
double qualityLevel = 0.01; //角点检测可接受的最小特征值
double minDistance = 10; //角点之间最小距离
int blockSize = 3;//计算导数自相关矩阵时指定的领域范围
double k = 0.04; //权重系数
goodFeaturesToTrack(srcgray, corners, maxcorners, qualityLevel, minDistance, Mat(), blockSize, false, k);
//Mat():表示感兴趣区域;false:表示不用Harris角点检测
//输出角点信息
cout << "角点信息为:" << corners.size() << endl;
//绘制角点
RNG rng(12345);
for (unsigned i = 0; i < corners.size(); i++)
{
circle(srcImage, corners[i], 2, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), -1, 8, 0);
cout << "角点坐标:" << corners[i] << endl;
}
waitKey(0);
return(0);
}
3 亚像素级别角点检测
3.1 绪论
亚像素的精度: 亚像素精度是指相邻 pixels 之间的细分情况。输入值常见于二分之一、三分之一或四分之一。这意味着每个 pixel 会被划分为更小的单元,并在此基础上实施插值算法。例如,在选择四分之一时,则相当于将每个 pixel 横向和纵向划分为四个子 pixel 来计算处理。因此,在一张 5×5 pixels 的图像中选择四分之一精度后,则会生成一个 20×20 的离散点阵(因为每个 pixel 被划分为四个子 pixel),随后对该点阵进行插值处理以恢复高分辨率图像数据点集矩阵的尺寸关系信息和几何坐标信息矩阵的关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵关系信息矩阵
3.2 相应的API
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria );
图像, 即作为输入提供的源图像;
角点坐标, 包括初始估计值以及经过精确计算后的最终坐标;
搜索窗口大小, 其大小由Size类型的参数winSize指定;
当winSize设为如Size(5,5)时, 实际采用的是中心位置周围(5//2+1)x(5//2+1)=11x11大小的区域进行计算;
零死区尺寸, 表示被排除在自相关矩阵求和运算之外的最大禁用区域尺寸;其值若设定为(-1,-1)则表示不设置死区以避免潜在奇异情况;
终止条件, 用于终止角点迭代搜索的过程条件。
综合示例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int max_corners = 20;
int max_count = 50;
Mat src, gray_src;
const char* output_title = "SubPixel Result";
void SubPixel_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/home.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
cvtColor(src, gray_src, COLOR_BGR2GRAY);
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
createTrackbar("Corners:", output_title, &max_corners, max_count, SubPixel_Demo);
SubPixel_Demo(0, 0);
waitKey(0);
return 0;
}
void SubPixel_Demo(int, void*) {
if (max_corners < 5) {
max_corners = 5;
}
vector<Point2f> corners;
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3;
double k = 0.04;
goodFeaturesToTrack(gray_src, corners, max_corners, qualityLevel, minDistance, Mat(), blockSize, false, k);
cout << "number of corners: " << corners.size() << endl;
Mat resultImg = src.clone();
for (size_t t = 0; t < corners.size(); t++) {
circle(resultImg, corners[t], 2, Scalar(0, 0, 255), 2, 8, 0);
}
imshow(output_title, resultImg);
Size winSize = Size(5, 5);
Size zerozone = Size(-1, -1);
TermCriteria tc = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
cornerSubPix(gray_src, corners, winSize, zerozone, tc);
for (size_t t = 0; t < corners.size(); t++) {
cout << (t + 1) << " .point[x, y] = " << corners[t].x << " , " << corners[t].y << endl;
}
return;
}
