兴趣点(也叫做关键点或特征点)主要是指某些特殊的点,经过对它们执行局部分析,如果能够检测到足够多的这种点,同时它们区分度很高,并且可以精确定位稳定的特征。这类点被大量用于解决物体识别,图像匹配,视觉跟踪,三维重建等问题。
一、检测Harris角点
1.概念及原理
(1)角点:最直观的印象就是在水平、竖直两个方向上变化均较大的点。Harris观察一个假定的特征点周围小窗口内的方向性强度平均变化。如果我们考虑偏移量(u,v),它的平均强度变换为
即Harris测试的过程为:我们首先获取平均强度变化最大值对应的方向,接着检查位于它垂直方向的变化是否也很强烈,同时满足条件便是一个角点。用数学方式测试该条件的展开公式在此就不介绍了,有兴趣的查找相关资料。
2.实验
(1)检测并显示一幅图像的角点
cv::cornerHarris使用简单输出结果为浮点数类型的图像,其中每一项为对应位置的角点强度,之后可使用阈值化得到一组检测到的角点,得到结果为二值图像,加以反转
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main(){
Mat image = cvLoadImage("C:\\Users\\Administrator\\Desktop\\12.jpg");
Mat grayImage;
cvtColor(image, grayImage, CV_BGR2GRAY);//转换为二值图
//检测Harris角点
Mat cornerStrength;
cornerHarris(//Harris角点检测,判断出某一点是不是图像的角点
grayImage,//输入图像
cornerStrength,//用于存放Harris角点检测的输出结果,和输入图片有一样的尺寸和类型;
3, //相邻像素的尺寸
3, //滤波器的孔径大小
0.01); //Harris参数
//角点强度的阈值
Mat harrisCorners;
double threshold = 0.0001;
cv::threshold(//threshold函数去掉噪,例如过滤很小或很大像素值的图像点。
cornerStrength,//输入的图像
harrisCorners, //输出图像,且和输入图像有同等大小和类型
threshold, //设定阈值的具体值,即当前阈值
255, //最大阈值,一般为255。即当第五个参数类型为THRESH_BINARY和THRESH_BINARY_INV是的最大值
THRESH_BINARY_INV//确定生成阈值图像的方法,THRESH_BINARY使角点为白色,THRESH_BINARY_INV使角点为黑色
);
imshow("harrisCorners", harrisCorners);
imwrite("C:\\Users\\Administrator\\Desktop\\harrisCorners.jpg", harrisCorners);
waitKey(0);
return 0;
}
输入图像;
输出图像:
(2)观察到特征点检测器需要多个参数,难以调优。此外,获取的角点图形包含许多角点群,这与我们需要精准确定位点的事实矛盾。我将通过封装自定义类来改进角点检测的方法。
1、创建Harris检测器的对象
2、计算每个像素处的Harris值,然后进行局部极大值检测
3、基于特定阈值获取特征点。该类通过增加非极大值抑制步骤改进Harris角点检测的效果,
4、将检测到的点通过circle函数绘制处来
#include
#include
#include
#include
using namespace std;
using namespace cv;
class HarrisDetector{
private:
//表示角点强度的32位浮点图像
Mat cornerStrength;
//表示阈值化后角度的32位浮点图像
Mat cornerTh;
//局部极大值图像(内部)
Mat localMax;
//导数平滑的相邻像素的尺寸
int neighbourhood;
//梯度计算的孔径大小
int aperture;
//Harris参数
double k;
//阈值计算的最大强度
double maxStrength;
//计算得到的阈值(内部)
double thresholdValue;
//非极大值抑制的相邻像素的尺寸
int nonMaxSize;
//非极大值抑制的核
Mat kernel;
public:
HarrisDetector() :neighbourhood(3), aperture(3), k(0.01), maxStrength(0.0), thresholdValue(0.01), nonMaxSize(3){};
//创建非极大值抑制的核
void setLocalMaxWindowsize(int nonMaxSize){
this->nonMaxSize = nonMaxSize;
}
//计算Harris角点
void detect(const Mat&image){
//Harris计算
cornerHarris(//Harris角点检测,判断出某一点是不是图像的角点
image,//输入图像
cornerStrength,//用于存放Harris角点检测的输出结果,和输入图片有一样的尺寸和类型;
neighbourhood, //相邻像素的尺寸
aperture, //滤波器的孔径大小
k); //Harris参数
//内部阈值计算
double minStrength; //未使用
minMaxLoc(//计算图像Mat中灰度最大值、最小值、返回最大最小的索引
cornerStrength,//输入图像
&minStrength, //最小值
&maxStrength//最大值
);
//局部极大值检测
Mat dilated; //临时图像
dilate(//对Harris得分图进行膨胀,移除彼此相邻的Harris角点.因为膨胀运算只会保留相邻范围内的最大值
cornerStrength, //输入图像
dilated, //即目标图像,需要和输入图片有一样的尺寸和类型
Mat()//膨胀操作的核。若为 NULL 时,表示的是使用参考点位于中心 3x3 的核。
);
compare(//compare(InputArray src1, InputArray src2, OutputArray dst, int cmpop):比较输入的src1和src2中的元素,输出结果到dst中
cornerStrength, //输入图像1
dilated, //输入图像2
localMax, //大小和输入图像1和输入图像2中最大的那个一样,比较结果为真的地方值为255,否则为0;
CMP_EQ//比较结果相等
);
}
//基于特定的阈值获取特征点
//由Harris值获取角点图
Mat getCornerMap(double qualityLevel){
Mat cornerMap;
//对角点图像进行阈值化
thresholdValue = qualityLevel*maxStrength;
threshold(//threshold函数去掉噪,例如过滤很小或很大像素值的图像点。
cornerStrength, //输入的图像
cornerTh, //输出图像,且和输入图像有同等大小和类型
thresholdValue, //设定阈值的具体值,即当前阈值
255, //最大阈值,一般为255。即当第五个参数类型为THRESH_BINARY和THRESH_BINARY_INV是的最大值
THRESH_BINARY//确定生成阈值图像的方法,THRESH_BINARY使角点为白色,THRESH_BINARY_INV使角点为黑色
);
//转换为8位图像
cornerTh.convertTo(cornerMap, CV_8U);
//非极大值抑制
bitwise_and(cornerMap, localMax, cornerMap);
return cornerMap;
}
//由Harris值得到特征点
void getCorners(vector<Point>&points, double qualityLevel){
//得到角点图
Mat cornerMap = getCornerMap(qualityLevel);
//得到角点
getCorners(points,cornerMap);
}
//由角点图获取特征点
void getCorners(vector<Point>&points, const Mat&cornerMap){
//遍历像素得到所有特征
for (int y = 0; y < cornerMap.rows; y++)
{
const uchar*rowPtr = cornerMap.ptr<uchar>(y);
for (int x = 0; x < cornerMap.cols; x++)
{
//如果是特征点
if (rowPtr[x])
{
points.push_back(Point(x, y));
}
}
}
}
void drawOnImage(Mat&image, const vector<Point>&points, Scalar color = Scalar(255, 255, 255), int radius = 3, int thickness = 2){
vector<Point>::const_iterator it = points.begin();
//对于所有角点
while (it!=points.end())
{
//绘制一个圆
circle(image, *it, radius, color, thickness);
++it;
}
}
};
//上面把用到的函数定义,下面直接使用
int main(){
Mat image = cvLoadImage("C:\\Users\\Administrator\\Desktop\\12.jpg");
Mat grayImage;
cvtColor(image, grayImage, CV_BGR2GRAY);//转换为二值图
//创建Harris检测器的对象
HarrisDetector harris;
//计算Harris值
harris.detect(grayImage);
//检测Harris角点
vector<Point>pts;
harris.getCorners(pts, 0.01);
//绘制Harris角点
harris.drawOnImage(grayImage, pts);
namedWindow("harris");
imshow("harris", grayImage);
imwrite("C:\\Users\\Administrator\\Desktop\\harris.jpg", grayImage);
waitKey(0);
return 0;
}
从经典的Harris角点检测方法不难看出,该算法的稳定性和k有关,而k是个经验值,不好把握,浮动也有可能较大。鉴于此,改进的Harris()方法直接计算出两个特征值,通过比较两个特征值直接分类,这样就不用计算Harris响应函数了。另一方面,我们不再用非极大值抑制了,而选取容忍距离:容忍距离内只有一个特征点。
该算法首先选取一个具有最大 最小特征值的点(即:max(min(e1,e2)),e1,e2是harris矩阵的特征值)作为角点,然后依次按照最大最小特征值顺序寻找余下的角点,当然和前一角点距离在容忍距离内的新角点被忽略。这便是cv::goodFeaturesToTrack实现的方法,它检测到的特征能用于视觉跟踪应用中的优质特征集合。
#include
#include
#include
#include
using namespace std;
using namespace cv;
class HarrisDetector{
private:
//表示角点强度的32位浮点图像
Mat cornerStrength;
//表示阈值化后角度的32位浮点图像
Mat cornerTh;
//局部极大值图像(内部)
Mat localMax;
//导数平滑的相邻像素的尺寸
int neighbourhood;
//梯度计算的孔径大小
int aperture;
//Harris参数
double k;
//阈值计算的最大强度
double maxStrength;
//计算得到的阈值(内部)
double thresholdValue;
//非极大值抑制的相邻像素的尺寸
int nonMaxSize;
//非极大值抑制的核
Mat kernel;
public:
HarrisDetector() :neighbourhood(3), aperture(3), k(0.01), maxStrength(0.0), thresholdValue(0.01), nonMaxSize(3){};
//创建非极大值抑制的核
void setLocalMaxWindowsize(int nonMaxSize){
this->nonMaxSize = nonMaxSize;
}
void drawOnImage(Mat&image, const vector<Point>&points, Scalar color = Scalar(255, 255, 255), int radius = 3, int thickness = 2){
vector<Point>::const_iterator it = points.begin();
//对于所有角点
while (it!=points.end())
{
//绘制一个圆
circle(image, *it, radius, color, thickness);
++it;
}
}
};
int main(){
Mat image = cvLoadImage("C:\\Users\\Administrator\\Desktop\\12.jpg");
Mat grayImage;
cvtColor(image, grayImage, CV_BGR2GRAY);
//创建Harris检测器的对象
HarrisDetector harris;
// 改进的harris角点检测方法 ,计算适合跟踪的优质特征
std::vector<cv::Point> corners;
cv::goodFeaturesToTrack(
grayImage, //输入图像
corners,//保存检测出的角点
500, //角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点
0.01, // 质量等级,这里是0.01*max(min(e1,e2)),e1,e2是harris矩阵的特征值
10// 两个角点之间的距离容忍度
);
harris.drawOnImage(grayImage, corners); //绘制角点
namedWindow("improveharris");
imshow("improveharris", grayImage);
imwrite("C:\\Users\\Administrator\\Desktop\\improveharris.jpg", grayImage);
waitKey(0);
return 0;
}
输入图像:
输出图像:
二、检测FAST特征
1.概念及原理
Harris算子是基于两个正交方向上的强度变化率提出了角点。而FAST特征算法对角点的定义有所不同,它定义基于假定特征点周围的图像强度,通过检查候选像素周围一圈像素来决定是否接受一个特征点。与中心差异较大的像素如果组成连续的圆弧,并且弧长大于圆周长度的3/4,那么我们认为找到一个特征点。和Harris特征相同的是可以在找到的角点上执行非极大值抑制,因此需要指定角点强度的测量方法。该算法可以获得非常快速的特征点检测,在需要考虑运行速度时可以选用,如高帧率的视频序列中进行视觉跟踪。
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main(){
Mat image = cvLoadImage("C:\\Users\\Administrator\\Desktop\\12.jpg");
//特征点的向量
vector<KeyPoint> keypoints;
//构造FAST特征检测器
FastFeatureDetector fast(40);//检测阈值
//进行检测
fast.detect(image, keypoints);
//绘制特征点
drawKeypoints(
image,//原始图像
keypoints,//特征点向量
image,//生成图像
Scalar(255, 255, 255),//特征点颜色
DrawMatchesFlags::DRAW_RICH_KEYPOINTS);//绘图功能的标识设置,DRAW_RICH_KEYPOINTS对每一个特征点绘制带大小和方向的关键点图形
namedWindow("FAST");
imshow("FAST", image);
imwrite("C:\\Users\\Administrator\\Desktop\\FAST.jpg", image);
waitKey(0);
return 0;
}
输入图像:
输出图像:
三、检测尺度不变的SURF特征
1.概念及原理
(1)通常由于尺度变化我们很难在不同图像之间匹配特征,如果我们尝试使用固定尺寸的相邻尺寸来匹配不同图像中的相同特征,由于尺度变化,强度模板并不会匹配。因此我们引入了尺寸不变特征即每个检测到的特征点都伴随着对应的尺寸因子,也就是SURF特征,加速鲁棒性特征。
(2)尺度不变特征应该即是空间域(图像中)上又是尺度域(在求到滤波器应用于不同尺度时获得)上的局部极大值。SURF的实现如下,首先对每个像素计算Hessian矩阵以得到特征,该矩阵测量一个函数的局部曲率,定义如下:
2.实验
(1)SURF算法检测特征
#include
#include
#include
#include
#include "opencv2/nonfree/nonfree.hpp"
#include
using namespace std;
using namespace cv;
int main(){
Mat image = cvLoadImage("C:\\Users\\Administrator\\Desktop\\12.jpg");
//特征点的向量
vector<KeyPoint> keypoints;
//构造SURF特征检测器
SurfFeatureDetector surf(2500.);//阈值
//检测SURF特征
surf.detect(image, keypoints);
//绘制特征点,加上尺度与方向信息
drawKeypoints(
image, //原始图像
keypoints, //特征点向量
image, //输出图像
Scalar(255, 255, 255), //特征点颜色
DrawMatchesFlags::DRAW_RICH_KEYPOINTS); //绘制标记 使用DRAW_RICH_KEYPOINTS之后关键点上圆圈的尺寸与特征的尺寸成正比
namedWindow("SURF");
imshow("SURF", image);
imwrite("C:\\Users\\Administrator\\Desktop\\SURF.jpg", image);
waitKey(0);
return 0;
}
SURF算法是SIFT的高效变种,SIFT也检测空间域及尺度域上的局部极大值作为特征,但是使用Laplacian滤波器相应而不是Hessian行列式,不同尺度的Laplacian通过Gaussian滤波器的差值进行计算。SIFT在空间和尺度上更加精确,但也更加耗时。
#include
#include
#include
#include
#include "opencv2/nonfree/nonfree.hpp"
#include
using namespace std;
using namespace cv;
int main(){
Mat image = cvLoadImage("C:\\Users\\Administrator\\Desktop\\12.jpg");
//特征点的向量
vector<KeyPoint> keypoints;
//构造SIFT特征检测器
SiftFeatureDetector sift(
0.03, //特征的阈值
10.); //用于降低直线敏感度的阈值
//检测SIFT特征
sift.detect(image, keypoints);
//绘制特征点,加上尺度与方向信息
drawKeypoints(
image, //原始图像
keypoints, //特征点向量
image, //输出图像
Scalar(255, 255, 255), //特征点颜色
DrawMatchesFlags::DRAW_RICH_KEYPOINTS); //绘制标记 使用DRAW_RICH_KEYPOINTS之后关键点上圆圈的尺寸与特征的尺寸成正比
namedWindow("SIFT");
imshow("SIFT", image);
imwrite("C:\\Users\\Administrator\\Desktop\\SIFT.jpg", image);
waitKey(0);
return 0;
}
输入图像:
输出图像:
四、描述SURF特征
1.概念及原理
(1)描述子在图像匹配中尤其有用,如我们想匹配同一个场景中的两幅图像,首先我们检查每幅图像中的特征,然后提取它们的描述子。第一幅图像中的描述子向量都会与第二幅图像中的描述子进行比较,得分最高的一对描述子将视为那个特征的最佳匹配,该过程对于第一幅图像中的所有特征进行重复,这便是cv::BruteForceMatcher的基本策略。
(2)优质的特征描述子必须在光照和视角发生轻微变化,并且受到图形噪点影响下具有不变性。它们通常基于局部强度差值,SURF描述子将在特征点周围较大范围内应用下图所示的简单核。
第一个核测量水平方向的局部强度差值(记为dx),第二个核测量垂直方向的差值(记为dy)。用于提取描述子向量的相邻区域的尺寸定义为特征尺寸因子的20倍(即20σ)。方形区域被分为4x4大小的较小子区域,对于每个子区域我们计算5x5整齐摆放的dx相应和dy响应(核的尺寸为2σ)。对所有这些相应求和,对每个子区域提取4个描述子的值。
2.实验
提取不同的特征点描述子
#include
#include
#include
#include
#include "opencv2/nonfree/nonfree.hpp"
#include
#include
using namespace std;
using namespace cv;
int main(){
Mat image1 = cvLoadImage("C:\\Users\\Administrator\\Desktop\\1_A.jpg");
Mat image2 = cvLoadImage("C:\\Users\\Administrator\\Desktop\\1_B.jpg");
//特征点的向量
vector<KeyPoint> keypoints1, keypoints2;
//构造SURF特征检测器
SurfFeatureDetector surf(2500.); //阈值
//检测SURF特征
surf.detect(image1, keypoints1);
surf.detect(image2, keypoints2);
//构造SURF描述子提取器
SurfDescriptorExtractor surfDesc;
//提取SURF描述子
Mat descriptors1, descriptors2;
surfDesc.compute(image1, keypoints1, descriptors1);
surfDesc.compute(image2, keypoints2, descriptors2);
//构造匹配器
BruteForceMatcher<L2<float>> matcher;
//匹配两幅图像的描述子
vector<DMatch>matches;
matcher.match(descriptors1,descriptors2,matches);
//nth_element(first, nth, last, compar),求[first, last]这个区间中第n大小的元素,如果参数加入了compare函数,就按compare函数的方式比较。只提取
//只提取前25条匹配的描述子
nth_element(matches.begin(), //初始位置
matches.begin() + 24, //排序元素的位置
matches.end()); //终止位置
//移除第25位之后所有的元素
matches.erase(matches.begin() + 25, matches.end());
Mat imageMatches;
drawMatches(
image1, keypoints1, //第一幅图像及其特征点
image2, keypoints2, //第二幅图像及其特征点
matches, //匹配结果
imageMatches, //生成的图像
Scalar(255, 255, 255)); //直线的颜色
namedWindow("SUFTDescription");
imshow("SUFTDescription", imageMatches);
imwrite("C:\\Users\\Administrator\\Desktop\\SUFTDescription.jpg",imageMatches);
waitKey(0);
return 0;
}