角点通常被定义为两条边的交点,或者说,角点的局部邻域应该具有两个不同区域的不同方向的边界。角点检测(Corner Detection)是计算机视觉系统中获取图像特征的一种方法,广泛应用于运动检测、图像匹配、视频跟踪、三维重建和目标识别等,也可称为特征点检测。
目前,角点检测算法还不是十分完善,许多算法需要依赖大量的训练集和冗余数据来防止和减少错误的特征的出现。对于角点检测算法的重要评价标准是:其对多幅图像中相同或者相似特征的检测能力,并且能够应对光照变化、或者图像旋转等影响。
关于角点的具体描述可以有几种:
1.一阶导数(即灰度的梯度)的局部最大所对应的像素点;
2.两条及两条以上边缘的交点;
3.图像中梯度值和梯度方向的变化速率都很高的点;
4.角点处的一阶导数最大,二阶导数为零,指示物体边缘变化不连续的方向。
三类角点检测算法:
1.基于二值图像的角点检测;
2.基于轮廓曲线的角点检测;
3.基于灰度图像的角点检测:基于梯度、基于模板和基于模板和梯度组合三类方法;常见的基于模板的角点检测算法有:Kitchen-Rosenfeld角点检测算法,Harris角点检测算法,KLT角点检测算法及SUSAN角点检测算法。基于模板的方法主要是考虑像素领域点灰度的变化,即亮度的变化。
Harris角点检测的思想是通过图像的局部小窗口观察图像,角点的特征是窗口沿任意方向移动都会导致图像灰度的明显变化
将上述思想转化为数学表达式,即将局部窗口向各个方向移动(u,v)并计算所有灰度差异的总和,表达式
如下:
E(u,v)=∑w(x,y)[I(x+u,y+v)-I(x,y)]²
其中I(x,y)是局部窗口的灰度图,I(x+u,y+v)是平移后的灰度图像,w(x,y)是窗口函数,其可以是矩形
窗口,也可以是对每一个像素赋予不同权重的高斯窗口
角点检测中使E(u,v)的值最大.利用一阶泰勒展开有:
I(x+u,y+v)=I(x,y)+Ixu+Iyv
其中:Ix和Iy是沿x和y方向的导数,可用sobel算子计算.
E(u,v)=∑w(x,y)[I(x+u,y+v)-I(x,y)]²
=∑w(x,y)[Ix²u²+2Ix*Iy*u*v+Iy²*v²]
|Ix² Ix*Iy|
=∑w(x,y)[u,v]|Ix*Iy Iy²|
=[u v]M|u|
|v|
M矩阵决定了E(u,v)的取值,M是Ix和Iy的二次函数,可以表示成椭圆的形状,椭圆的长短半轴由M的特征
值a1和a2决定,方向由特征矢量决定
共可分为三种情况:
.图像中的直线.一个特征值大,另一个特征值小,a1>>a2或者a2>>a1.椭圆函数值在某一个方向上大,
在其他方向小
.图像中的平面.两个特征值都小,且近似相等,椭圆函数值再各个方向都有
.图像中的角点.两个特征值都大,且近似相等,椭圆函数在所有方向都增大.
Harris给出的角点计算方法并不需要计算具体的特征值,而是计算一个角点响应值R来判断角点.R的计算公式
为:
R=detM-α(traceM)²
式中,detM为矩阵M的行列式,traceM为矩阵M的迹,α为常数取值范围为0.04~0.06.事实上,特征是隐含
在detM和traceM中,因为:
detM=a1a2
traceM=a1+a2
那么认为:
.当R为大数时的正数是角点
.当R为大数时的负数为边界
.当R为小数认为是平坦区域
API:
void cv::cornerHarris ( InputArray src,
OutputArray dst,
int blockSize,
int ksize,
double k,
int borderType = BORDER_DEFAULT
)
参数:
src:单通道图像,即灰度图
dst:输出
blockSize:计算角点时,覆盖像素点的窗口大小,即上文中的u、v值
ksize:Sobel算子应用的卷积核大小
k:角点检测中的自由参数取值范围为[0.04,0.06]
int main()
{
std::string img_path = "C:\\Users\\Administrator\\Downloads\\1.jpeg";
cv::Mat img = cv::imread(img_path);
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
cv::Mat dst;
cv::cornerHarris(gray, dst, 2, 3, 0.04);
//归一化
Mat dst_norm = Mat::zeros(dst.size(), dst.type());
normalize(dst, dst_norm, 0, 255, NORM_MINMAX, -1, Mat());
//绝对值化
convertScaleAbs(dst_norm, dst_norm);
//绘制角点
RNG rng(12345);
for (int row = 0; row < img.rows; ++row) {
for (int col = 0; col < img.cols; ++col) {
int rsp = dst_norm.at<uchar>(row, col);//取出对应像素坐标的检测值
//限制
if (rsp > 100) {
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
circle(img, Point(col, row), 3, Scalar(b, g, r), 1, 8);
}
}
}
namedWindow("result", WINDOW_FREERATIO);
imshow("result", img);
while (true) {
int key = cv::waitKey(0);
if (key == 27)
{
break;
}
}
destroyWindow("result");
return 0;
}
shi-Tomas算法是对Harris角点检测算法的改进,一般会比Harris算法得到更好的角点.
Harris算法的角点响应函数是将矩阵M的行列式值与M的迹相减,利用差值判断是否为角点,
后来shi和Tomas提出改进的方法是,若矩阵M的两个特征值中较小的一个大于阈值,则认为
他是角点,即:
R=min(a1,a2)
API:
void cv::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为输入的单通道图像;
_corners为输出提取的角点坐标;
maxCorners为设置的最大角点个数,程序中会按照角点强度降序排序,超过maxCorners的角点将被舍弃;
qualityLevel为角点强度的阈值系数,如果检测出所有角点中的最大强度为max,则强度值<max*qualityLevel得角点会被舍弃;
minDistance为角点与其邻域强角点之间的欧式距离,与邻域强角点距离小于minDistance的角点将被舍弃;
_mask为设定的感兴趣区域,通常可不设置;
blockSize是协方差矩阵滤波的窗口大小;
gradientSize为sobel算子求微分的窗口的大小;
useHarrisDetector为是否使用Harris角点检测;harrisK为Harris角点检测特征表达式中的常数k值
int main()
{
std::string img_path = "C:\\Users\\Administrator\\Downloads\\1.jpeg";
cv::Mat img = cv::imread(img_path);
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
vector<Point2f> corners;
goodFeaturesToTrack(gray, corners, 200, 0.01, 3, Mat(), 3, false);
//绘制角点
RNG rng(12345);
for (int i = 0; i < corners.size(); ++i) {
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
circle(img, corners[i], 3, Scalar(b, g, r), 1, 8);
}
cv::imwrite("C:\\Users\\Administrator\\Downloads\\2.jpg", img);
namedWindow("result", WINDOW_FREERATIO);
imshow("result", img);
while (true) {
int key = cv::waitKey(0);
if (key == 27)
{
break;
}
}
destroyWindow("result");
return 0;
}
Harris和shi-Tomas角点检测算法,这两种算法具有旋转不变性,但不具有尺度不变性,在原本能检测到角点的位置,当图像放大后,就检测不到角点了.尺度不变特征转换即SIFT(Scale-invariant feature transform).它用来侦测与描述影响中局部性特征,它在空间尺度中寻找极值点,并提取出其位置,尺度,旋转不变量,此算法由David Lowe在1999年所发表,2004年完善总结.应用范围包含物体识别,机器人地图感知与导航,影像缝合,3D模型建立,手势识别,影像追踪和动作对比等领域.
SIFT算法实质是在不同的尺度空间上寻找关键点(特征点),并计算出关键点的方向.SIFT所查找到的关键点是一些十分突出,不会因为光照,仿射变换和噪音等因素影响而变化的点,如角点,边缘点,暗区的亮点及亮区的暗点.
static Ptr<SIFT> create(
int nfeatures =0,//需要的特征点的数量,是对特征点的质量进行排名,返回最好的前几个;
int n0ctaveLayers =3,//金字塔的层数;
double contrastThrehold =0.04,//过滤特征点的阈值;
double edgeThreshold =10;//过滤边缘效应的阈值;
double sigma=1.6;//用于预处理光滑的;
)
void detectAndCompute(
InputArray image, //图像
InputArray mask, //掩模
CV_OUT std::vector& keypoints,//输出关键点的集合
OutputArray descriptors,//计算描述符(descriptors[i]是为keypoints[i]的计算描述符)
bool useProvidedKeypoints=false //使用提供的关键点
);
void drawKeypoints(
InputArray image, //源图像
const std::vector<KeyPoint>& keypoints, //来自源图像的关键点
InputOutputArray outImage,//输出图像
const Scalar& color=Scalar::all(-1), //关键点的颜色
int flags=DrawMatchesFlags::DEFAULT //设置绘图功能的标志
);
#include
#include "opencv2\xfeatures2d.hpp"
#include
#include
#include
using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;
int main()
{
int numfeature = 400;
Ptr<SIFT>detector = SIFT::create(numfeature);//与SURF一样,剩余的取默认值
vector<KeyPoint>keypoints;
detector->detect(gray, keypoints, Mat());
Mat resultImg;
drawKeypoints(img, keypoints, resultImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("SIFT keypoint", resultImg);
cv::imwrite("C:\\Users\\Administrator\\Downloads\\2.jpg", resultImg);
namedWindow("result", WINDOW_FREERATIO);
cv::waitKey(0);
destroyWindow("result");
return 0;
}
SURF(加速版的具有鲁棒性的特征,SpeededUp Robust Features),SURF是尺度不变特征变换算法(SIFT算法)的加速版。SURF最大的特征在于采用了harr特征以及积分图像的概念。
SURF原理:
(1)构建Hessian矩阵构造高斯金字塔尺度空间
SIFT采用的是DoG图像,而SURF采用的是Hessian矩阵(SURF算法核心)行列式近似值图像。在数学中,Hessian矩阵是一个自变量为向量的实值函数的二阶偏导数组成的方块矩阵,即每一个像素点都可以求出一个2x2的Hessian矩阵,可计算出其行列式detH,可以利用行列式取值正负来判别该点是或不是极值点来将所有点分类。在SURF算法中,选用二阶标准高斯函数作为滤波器,通过特定核间的卷积计算二阶偏导数,从而计算出Hessian矩阵,但是由于特征点需要具备尺度无关性,所以在进行Hessian矩阵构造前,需要对其进行高斯滤波,即与以方差为自变量的高斯函数的二阶导数进行卷积。通过这种方法可以为图像中每个像素计算出其H的行列式的决定值,并用这个值来判别特征点。
上面说这么多,只是得到了一张近似hessian的行列式图,类似SIFT中的DoG图。但是在金字塔图像中分为很多层, 每一层叫做一个octave,每一个octave中又有几张尺度不同的图片。在SIFT算法中,同一个octave层中的图片尺寸(大小)相同,但是尺度不同(模糊程度)不同,而不同的octave层中的图片尺寸也不相同,因为它是由上一层图片将采样得到的。在进行高斯模糊时,SIFT的高斯模板大小是始终不变的,只是在不同的octave之间改变图片的大小。而在SURF中,图片的大小是一直不变的,不同octave层的待检测图片是改变高斯模糊尺寸大小得到的,当然,同一个octave中不同图片用到的高斯模板尺寸也不同。算法允许尺度空间多层图像同时被处理,不需要对图像进行二次抽样,从而提高算法性能。
(2)利用非极大值抑制初步确定特征点
此步骤和SIFT类似,将经过hessian矩阵处理过的每个像素点与其三维邻域的26个点进行大小比较,如果它是这26个点中的最大值或者最小值,则保留下来,当作初步的特征点。检测过程中使用与该尺度层图像解析度相对应大小的滤波器进行检测。
(3)精确定位极值点
这里也和SIFT算法中类似,采用三维线性插值法得到亚像素级的特征点,同时也去掉那些值小于一定阈值的点,增加极值使检测到的特征点数量减少,最终只有几个特征最强点会被检测出来。
(4)选取特征点的主方向
这一步与SIFT也大有不同,SIFT选取特征点主方向是采用在特征点邻域统计其梯度直方图,取直方图bin值最大的以及超过bin值80%的那些方向作为特征点的主方向。
而在SURF中,不统计其梯度直方图,而是统计特征点邻域内的harr小波特征。即在特征点的邻域(比如说,半径为6s的圆内,s为该点所在的尺度)内,统计60度扇形内所有点的水平haar小波特征和垂直haar小波特征总和,haar小波的尺寸变长为4s,这样一个扇形得到了一个值,然后60度扇形以一定间隔进行旋转,最后将最大值那个扇形的方向作为该特征点的主方向。
(5)构造surf特征点描述算子
在SIFT中,是在特征点周围取16x16的邻域,并把该邻域化为4x4个的小区域,每个小区统计8个方向的梯度,最后得到4x4x8=128维的向量,该向量作为该点SIFT描述子。
在SURF中,也是在特征点周围取一个正方形框,框的边长为20s(s是所检测到该特征点所在的尺度)。该框带方向,方向当然就是第(4)步检测出来的主方向了。然后把该框分为16个子区域,每个子区域统计25个像素的水平方向和垂直方向的haar小博特征,这里的水平和垂直方向都是相对主方向而言的。该haar小波特征为水平方向值之和,水平方向绝对值之和,垂直方向之和,垂直方向绝对值之和。这样每个区域就有4个值,所以每个特征点就是16x4=64维向量,相比于SIFT而言,少了一半,这在特征匹配过程中会大大加快匹配速度。
SURF采用Hessian矩阵获取图像局部最值十分稳定,但是在求主方向阶段太过于依赖局部区域像素的梯度方向,有可能使找到的主方向不准确。后面的特征向量提取以及匹配都严重依赖于主方向,即使不大偏差角度也可以造成后面特征匹配的放大误差,从而使匹配不成功。另外图像金字塔的层取得不够紧密也会使得尺度有误差,后面的特征向量提取同样依赖响应的尺度,发明者在这个问题上的折中解决办法是取适量的层然后进行插值。
SURF::SURF(
double hessianThreshold, --阈值检测器使用Hessian的关键点,默认值在300-500之间
int nOctaves=4, -- 4表示在四个尺度空间
int nOctaveLayers=2, -- 表示每个尺度的层数
bool extended=false,
bool upright=false --表示计算选择不变性,不计算的速度更快
)
#include
#include "opencv2\xfeatures2d.hpp"
#include
#include
#include
using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;
int main()
{
int numfeature = 400;
Ptr<SURF>detector = SURF::create(numfeature);//与SURF一样,剩余的取默认值
vector<KeyPoint>keypoints;
detector->detect(gray, keypoints, Mat());
Mat resultImg;
drawKeypoints(img, keypoints, resultImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("SIFT keypoint", resultImg);
cv::imwrite("C:\\Users\\Administrator\\Downloads\\2.jpg", resultImg);
namedWindow("result", WINDOW_FREERATIO);
cv::waitKey(0);
destroyWindow("result");
return 0;
}
Fast(全称Features from accelerated segment test)是一种用于角点检测的算法,
该算法的原理是取图像中检测点,以该点为圆心的周围邻域内像素点判断检测点是否为角点,
通俗的讲就是若一个像素周围有一定数量的像素与该点像素不同,则认为其为角点.
基本流程:
1.在图像中选取一个像素点p,来判断它是不是关键点.Ip等于点p的灰度值.
2.以r为半径,覆盖p点周围M个像素,通常情况下,设置r=3,则M=16
3.设置一个阈值t,如果在这16个像素点中存在n个连续像素点的灰度值都高于Ip+t,或者低于
Ip-t,那么像素点p就被认为是一个角点,n一般取值为12
4.由于检测特征点时是需要对所有的像素点进行监测,然而图像中绝大多数点都不是特征点,如
果对每个像素点都进行上述的检测过程,那显然浪费许多时间,因此采用一种进行非特征点判 别的方法:首先对候选点的周围每个90度的点:1,9,5,13进行测试(先测1和9,如果他们符合阈值
要求,再测5和13).如果p是角点,那么这是个点中至少有3个要符合阈值要求,否则直接剔除,
对保留下来的点再继续进行测试
虽然这个检测器的效率很高,但它有一下几个缺点:
.获得的候选点比较多
.特征点的选取不是最优的,因为它的效果取决于与要解决的问题的角点的分布情况
.进行非特征点判别时大量的点被丢弃
.检测到的很多特征点都是相邻的
前3个问题可以通过机器学习的方法解决,最后一个问题可以使用非最大值抑制的方法解决
1.选择一组训练图片(最好是跟最后应用相关的图片)
2.使用Fast算法找出每幅图像的特征点,对图像中每一个特征点,将其周围的16个像素存储构成
一个向量p
3.每一个特征点的16个像素点都属于下列三类中的一种:
| d Ip->x≤Ip-t (darker)
Sp->x=| s Ip-t≤Ip->x≤Ip+t (similar)
| b Ip->x≥Ip+t (brighter)
4.根据这些像素点的分类,特征向量P也被分为3个子集:Pd,Ps,Pb
5.定义一个新的布尔变量Kp,如果p是角点就设置为True,否则为False
6.利用特征向量p,目标值Kp,训练ID3树
7.将构建好的决策树运用于其他图像的快速检测
在筛选出来的候选点中有很多是紧挨在一起的,需要通过非极大值抑制来消除这种影响.
为所有的候选点后确定一个打分函数V,V的值可以这样计算:先分别计算Ip与圆上16个
点的像素值差值,取绝对值,再将这16个绝对值相加,就得到了V值
V=∑|Ip-Ii| (i=1,2…16)
最后比较毗邻候选点的V值,把V值较小的候选点pass掉
Fast算法的思想与我们对角点的直观认识非常接近,化繁为简.Fast算法比其他角点的检测
算法快,但是在噪声比较高时不够稳定,这需要设置合适的阈值.
‘’’
1.实例化fast
static Ptr<FastFeatureDetector> cv::FastFeatureDetector::create ( int threshold = 10,
bool nonmaxSuppression = true,
int type = FastFeatureDetector::TYPE_9_16
)
参数:
threshold:阈值t,默认值为10(特征点与周围像素值差值的阈值)
nonmaxSuppression:是否进行非极大值抑制,默认为True
返回:
fast:创建的FastFeatureDetector对象
2.利用detect检测关键点
CV_WRAP virtual void detect( InputArray image,
CV_OUT std::vector<KeyPoint>& keypoints,
InputArray mask=noArray() );
参数:
image:输入的灰度图片(彩色图像也可以)
keypoints:检测到的关键点
mask:掩码
3.将关键点监测结果绘制在图像上,与sift中的一样
参见sift
int main()
{
std::string img_path = "C:\\Users\\Administrator\\Downloads\\1.jpeg";
cv::Mat img = cv::imread(img_path);
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
std::vector<KeyPoint> keyPoints;
// construction of the fast feature detector object
Ptr<FastFeatureDetector> detector = FastFeatureDetector::create(100); // 检测的阈值为40
// feature point detection
detector->detect(img, keyPoints, Mat());
Mat resultImg;
drawKeypoints(img, keyPoints, resultImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("FAST feature", resultImg);
cv::imwrite("C:\\Users\\Administrator\\Downloads\\2.jpg", resultImg);
cv::waitKey(0);
return 0;
}
ORB特征是将FAST特征点的检测方法与BRIEF特征描述子结合起来,并在它们原来的基础上做了改进与优化。ORB算法的速度大约是SIFT的100倍,是SURF的10倍。
1.实例化ORB
static Ptr<ORB>cv::ORB::create(int nfeatures=500,
float scaleFactor=1.2f,
int nlevels=8,
int edgeThreshold=31,
int firstLevel=0,
int WTA_K=2,
ORB::ScoreType scoreType=ORB::HARRIS_SCORE,
int patchSize=31,
int fastThreshold =20)
参数:
nfeatures:检测ORB特征点的数目。
scaleFactor:"金字塔"尺寸缩小的比例,如果是2,那么表示“金字塔”的下层图像尺寸是上层图像的2倍。
nlevels:"金字塔"层数。
edgeThreshold:边缘阈值。
firstLevel:将原图像放入“金字塔”中的等级,例如放入第0层。
WTA_K:生成每位描述子时需要的像素点数目。
scoreType:检测关键点时关键点的评价方法。
patchSize:生成描述子时关键点周围邻域的尺寸。
fastThreshold:计算FAST角点时像素值差值的阈值。
2.利用detect检测关键点
CV_WRAP virtual void detect( InputArray image,
CV_OUT std::vector<KeyPoint>& keypoints,
InputArray mask=noArray() );
参数:
image:输入的灰度图片(彩色图像也可以)
keypoints:检测到的关键点
mask:掩码
3.将关键点绘制在图像上
参见SIFT
int main()
{
std::string img_path = "C:\\Users\\Administrator\\Downloads\\1.jpeg";
cv::Mat img = cv::imread(img_path);
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
Ptr<ORB>orb = ORB::create(500, 1.2f, 8, 31, 0, 2, ORB::HARRIS_SCORE, 31, 20);
vector<KeyPoint>Keypoints;
orb->detect(img, Keypoints);
Mat descriptions;
orb->compute(img, Keypoints, descriptions);
Mat imgAngel;
img.copyTo(imgAngel);
Mat resultImg;
drawKeypoints(img, Keypoints, resultImg, Scalar::all(-1));
drawKeypoints(img, Keypoints, imgAngel, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
imshow("不含角度和大小的结果", resultImg);
imshow("含有角度和大小的结果", imgAngel);
cv::imwrite("C:\\Users\\Administrator\\Downloads\\2.jpg", resultImg);
//namedWindow("result", WINDOW_FREERATIO);
cv::waitKey(0);
//destroyWindow("result");
return 0;
}
RIEF算法计算出来的是一个二进制串的特征描述符。它是在一个特征点的邻域内,选择n对像素点pi、qi(i=1,2,…,n)。然后比较每个点对的灰度值的大小。如果I(pi)> I(qi),则生成二进制串中的1,否则为0。所有的点对都进行比较,则生成长度为n的二进制串。一般n取128、256或512,opencv默认为256。另外,值得注意的是为了增加特征描述符的抗噪性,算法首先需要对图像进行高斯平滑处理。
在旋转不是非常厉害的图像里,用BRIEF生成的描述子的匹配质量非常高,作者测试的大多数情况中都超越了SURF。但在旋转大于30°后,BRIEF的匹配率快速降到0左右。BRIEF的耗时非常短,在相同情形下计算512个特征点的描述子时,SURF耗时335ms,BRIEF仅8.18ms;匹配SURF描述子需28.3ms,BRIEF仅需2.19ms。在要求不太高的情形下,BRIEF描述子更容易做到实时。
(1)steered BRIEF(旋转不变性改进)
在使用oFast算法计算出的特征点中包括了特征点的方向角度。假设原始的BRIEF算法在特征点SxS(一般S取31)邻域内选取n对点集。经过旋转角度θ旋转,得到新的点对,在新的点集位置上比较点对的大小形成二进制串的描述符。这里需要注意的是,在使用oFast算法是在不同的尺度上提取的特征点。因此,在使用BRIEF特征描述时,要将图像转换到相应的尺度图像上,然后在尺度图像上的特征点处取SxS邻域,然后选择点对并旋转,得到二进制串描述符。
(2)rBRIEF-改进特征点描述子的相关性
使用steeredBRIEF方法得到的特征描述子具有旋转不变性,但是却在另外一个性质上不如原始的BRIEF算法。是什么性质呢,是描述符的可区分性,或者说是相关性。这个性质对特征匹配的好坏影响非常大。描述子是特征点性质的描述。描述子表达了特征点不同于其他特征点的区别。我们计算的描述子要尽量的表达特征点的独特性。如果不同特征点的描述子的可区分性比较差,匹配时不容易找到对应的匹配点,引起误匹配。
为了解决描述子的可区分性和相关性的问题,ORB使用统计学习的方法来重新选择点对集合。
首先建立300k个特征点测试集。对于测试集中的每个点,考虑其31x31邻域。这里不同于原始BRIEF算法的地方是,这里在对图像进行高斯平滑之后,使用邻域中的某个点的5x5邻域灰度平均值来代替某个点对的值,进而比较点对的大小。这样特征值更加具备抗噪性。另外可以使用积分图像加快求取5x5邻域灰度平均值的速度。
从上面可知,在31x31的邻域内共有(31-5+1)x(31-5+1)=729个这样的子窗口,那么取点对的方法共有M=265356种,我们就要在这M种方法中选取256种取法,选择的原则是这256种取法之间的相关性最小。怎么选取呢?
在300k特征点的每个31x31邻域内按M种方法取点对,比较点对大小,形成一个300kxM的二进制矩阵Q。矩阵的每一列代表300k个点按某种取法得到的二进制数。
对Q矩阵的每一列求取平均值,按照平均值到0.5的距离大小重新对Q矩阵的列向量排序,形成矩阵T。
将T的第一列向量放到R中。
取T的下一列向量和R中的所有列向量计算相关性,如果相关系数小于设定的阈值,则将T中的该列向量移至R中。
按照上一步的方式不断进行操作,直到R中的向量数量为256。
基于Harris与Shi-Tomasi角点检测
首先通过计算矩阵M得到lamda1和lamda2两个特征值根据他们得到角点响应值
然后自己设置阈值实现计算出阈值得到有效响应值的角点设置
函数简介:
C++: void cornerEigenValsAndVecs(
InputArray src, --单通道输入8位或浮点图像
OutputArray dst, --输出图像,同源图像或CV_32FC(6)
int blockSize, --邻域大小值
int apertureSize, --Sobel算子的参数
int borderType=BORDER_DEFAULT --像素外插方法
)//对应于Harris
C++: void cornerMinEigenVal(
InputArray src, --单通道输入8位或浮点图像
OutputArray dst, --图像存储的最小特征值。类型为CV_32FC1
int blockSize, --邻域大小值
int apertureSize=3, --Sobel算子的参数
int borderType=BORDER_DEFAULT --像素外插方法
)//对应Shi-Tomasi
1 #include <opencv2/opencv.hpp>
2 #include <iostream>
3
4 using namespace cv;
5 using namespace std;
6
7
8 Mat src,src_gray;
9 Mat HarrisRsImage;
10
11 double harris_min_rsp,harris_max_rsp;
12 int qualityLevel = 40;
13 int max_count = 100;
14
15 const char* harris_window = "Harris";
16
17 void CustomHarris_demo(int, void*);
18
19 int main(int argc, char** argv)
20 {
21 src = imread("数字.jpg");
22 if (src.empty()) {
23 printf("Can not load Image...");
24 return -1;
25 }
26 imshow("input Image",src);
27
28 cvtColor(src, src_gray, COLOR_BGR2GRAY);
29
30 //计算特征值lambda1和lambda2
31 int blockSize = 3;
32 int ksize = 3;
33 Mat Harris_dst=Mat::zeros(src.size(),CV_32FC(6));//CV_32FC(6),有6个值要存储
34 cornerEigenValsAndVecs(src_gray, Harris_dst, blockSize, ksize,4);
35
36 //计算响应
37 HarrisRsImage = Mat::zeros(src.size(), CV_32FC1);
38 double k = 0.04;
39 for (int row = 0; row < Harris_dst.rows; row++) {
40 for (int col = 0; col < Harris_dst.cols; col++) {
41 double lambda1=Harris_dst.at<Vec6f>(row, col)[0];
42 double lambda2= Harris_dst.at<Vec6f>(row, col)[1];
43 HarrisRsImage.at<float>(row, col) = lambda1 * lambda2 - k * pow((lambda1 + lambda2), 2);
44 }
45 }
46
47 minMaxLoc(HarrisRsImage,&harris_min_rsp,&harris_max_rsp,0,0,Mat());
48 namedWindow(harris_window,CV_WINDOW_AUTOSIZE);
49 createTrackbar("Quality", harris_window,&qualityLevel, max_count,CustomHarris_demo);
50 CustomHarris_demo(0,0);
51
52 waitKey(0);
53 return 0;
54 }
55
56 void CustomHarris_demo(int, void*)
57 {
58 if (qualityLevel < 10) qualityLevel = 10;
59 Mat resultImage = src.clone();
60
61 float thresh = harris_min_rsp + (((double)qualityLevel) / max_count)*(harris_max_rsp - harris_min_rsp);//阈值
62 for (int row = 0; row < src.rows; row++) {
63 for (int col = 0; col < src.cols; col++) {
64 float value = HarrisRsImage.at<float>(row, col);
65 if (value > thresh) {
66 circle(resultImage, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
67 }
68
69 }
70 }
71
72 imshow(harris_window, resultImage);
73 }
1 //Shi-Tomasi和Harris自定义角点检测
2
3 #include <opencv2/opencv.hpp>
4 #include <iostream>
5
6 #include <math.h>
7 using namespace cv;
8 using namespace std;
9 const char* harris_win = "Custom Harris Corners Detector";
10 const char* shitomasi_win = "Custom Shi-Tomasi Corners Detector";
11 Mat src, gray_src;
12 // harris corner response
13 Mat harris_dst, harrisRspImg;
14 double harris_min_rsp;
15 double harris_max_rsp;
16 // shi-tomasi corner response
17 Mat shiTomasiRsp;
18 double shitomasi_max_rsp;
19 double shitomasi_min_rsp;
20 int sm_qualitylevel = 30;
21 // quality level
22 int qualityLevel = 30;
23 int max_count = 100;
24 void CustomHarris_Demo(int, void*);
25 void CustomShiTomasi_Demo(int, void*);
26 int main(int argc, char** argv) {
27 src = imread("D:/vcprojects/images/home.jpg");
28 if (src.empty()) {
29 printf("could not load image...\n");
30 return -1;
31 }
32 namedWindow("input image", CV_WINDOW_AUTOSIZE);
33 imshow("input image", src);
34 cvtColor(src, gray_src, COLOR_BGR2GRAY);
35 // 计算特征值
36 int blockSize = 3;
37 int ksize = 3;
38 double k = 0.04;
39 harris_dst = Mat::zeros(src.size(), CV_32FC(6));
40 harrisRspImg = Mat::zeros(src.size(), CV_32FC1);
41 cornerEigenValsAndVecs(gray_src, harris_dst, blockSize, ksize, 4);
42 // 计算响应
43 for (int row = 0; row < harris_dst.rows; row++) {
44 for (int col = 0; col < harris_dst.cols; col++) {
45 double lambda1 =harris_dst.at<Vec6f>(row, col)[0];
46 double lambda2 = harris_dst.at<Vec6f>(row, col)[1];
47 harrisRspImg.at<float>(row, col) = lambda1*lambda2 - k*pow((lambda1 + lambda2), 2);
48 }
49 }
50 minMaxLoc(harrisRspImg, &harris_min_rsp, &harris_max_rsp, 0, 0, Mat());
51 namedWindow(harris_win, CV_WINDOW_AUTOSIZE);
52 createTrackbar("Quality Value:", harris_win, &qualityLevel, max_count, CustomHarris_Demo);
53 CustomHarris_Demo(0, 0);
54
55 // 计算最小特征值
56 shiTomasiRsp = Mat::zeros(src.size(), CV_32FC1);
57 cornerMinEigenVal(gray_src, shiTomasiRsp, blockSize, ksize, 4);
58 minMaxLoc(shiTomasiRsp, &shitomasi_min_rsp, &shitomasi_max_rsp, 0, 0, Mat());
59 namedWindow(shitomasi_win, CV_WINDOW_AUTOSIZE);
60 createTrackbar("Quality:", shitomasi_win, &sm_qualitylevel, max_count, CustomShiTomasi_Demo);
61 CustomShiTomasi_Demo(0, 0);
62
63 waitKey(0);
64 return 0;
65 }
66
67 void CustomHarris_Demo(int, void*) {
68 if (qualityLevel < 10) {
69 qualityLevel = 10;
70 }
71 Mat resultImg = src.clone();
72 float t = harris_min_rsp + (((double)qualityLevel) / max_count)*(harris_max_rsp - harris_min_rsp);
73 for (int row = 0; row < src.rows; row++) {
74 for (int col = 0; col < src.cols; col++) {
75 float v = harrisRspImg.at<float>(row, col);
76 if (v > t) {
77 circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
78 }
79 }
80 }
81
82 imshow(harris_win, resultImg);
83 }
84
85 void CustomShiTomasi_Demo(int, void*) {
86 if (sm_qualitylevel < 20) {
87 sm_qualitylevel = 20;
88 }
89
90 Mat resultImg = src.clone();
91 float t = shitomasi_min_rsp + (((double)sm_qualitylevel) / max_count)*(shitomasi_max_rsp - shitomasi_min_rsp);
92 for (int row = 0; row < src.rows; row++) {
93 for (int col = 0; col < src.cols; col++) {
94 float v = shiTomasiRsp.at<float>(row, col);
95 if (v > t) {
96 circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
97 }
98 }
99 }
100 imshow(shitomasi_win, resultImg);
101 }
几何测量需要更高的精度,而goodFuturesToTrack只能提供简单的整数坐标值,而几何测量需要更高精度的实数坐标值。
connerSubPix用于寻找亚像素级角点值
void cv::cornerSubPix( InputArray _image, InputOutputArray _corners,
Size win, Size zeroZone, TermCriteria criteria )
参数:
_image为输入的单通道图像;
_corners为提取的初始整数角点(比如用goodFeatureToTrack提取的强角点);
win为求取亚像素角点的窗口大小,比如设置Size(11,11),需要注意的是11为半径,则窗口大小为23x23=(112+1)x(112+1)
zeroZone是设置的“零区域”,在搜索窗口内,设置的“零区域”内的值不会被累加,权重值为0。如果设置为Size(-1,-1),则表示没有这样的区域;
critteria是条件阈值,包括迭代次数阈值和误差精度阈值,一旦其中一项条件满足设置的阈值,则停止迭代,获得亚像素角点。
1 #include <opencv2/opencv.hpp>
2 #include <iostream>
3
4 using namespace cv;
5 using namespace std;
6
7 Mat src,src_gray;
8
9 int max_corners = 20;
10 int max_count = 50;
11
12 const char* output_title = "SubPix Result";
13
14 void SubPixel_demo(int,void*);
15
16 int main(int argc, char** argv)
17 {
18 src = imread("数字.jpg");
19 if (src.empty()) {
20 printf("Can not load Image...");
21 return -1;
22 }
23 imshow("input Image",src);
24
25 cvtColor(src, src_gray, COLOR_BGR2GRAY);
26
27 namedWindow(output_title,CV_WINDOW_AUTOSIZE);
28 createTrackbar("Corners:", output_title,&max_corners, max_count, SubPixel_demo);
29 SubPixel_demo(0,0);
30
31 waitKey(0);
32 return 0;
33 }
34
35 void SubPixel_demo(int, void*)
36 {
37 if (max_corners < 5) max_corners = 5;
38
39 //先进行Shi-Tomasi角点检测
40 vector<Point2f> corners;
41 double qualityLevel = 0.01;
42 double minDistance = 10;
43 int blockSize = 3;
44 goodFeaturesToTrack(src_gray, corners,max_corners,qualityLevel,minDistance,Mat());
45 cout << "number of corners:" << corners.size() << endl;
46
47 //画出角点
48 Mat resultImage = src.clone();
49 for (size_t t = 0; t < corners.size(); t++)
50 {
51 circle(resultImage, corners[t],2,Scalar(0,0,255),2,8,0);
52 }
53
54 imshow(output_title, resultImage);
55
56 Size winSize = Size(5,5);
57 Size zerozone = Size(-1, -1);
58 TermCriteria tc = TermCriteria(TermCriteria::EPS+ TermCriteria::MAX_ITER,40,0.001);//最大值迭代次数40,精度半径0.001
59 cornerSubPix(src_gray, corners, winSize, zerozone, tc);//corners需要输入初始坐标,然后输出精确坐标(因此前面才会先做Shi—Tomasi)
60 for (size_t t = 0; t < corners.size(); t++)
61 {
62 cout << (t + 1) << "Point(x,y):" << corners[t].x << "," << corners[t].y << endl;
63 }
64 }
参考链接:
https://blog.csdn.net/m0_61897853/article/details/123242081
https://blog.csdn.net/max_LLL/article/details/119728338
https://blog.csdn.net/qq_31531635/article/details/73798398
https://blog.51cto.com/u_14439393/5978188
https://blog.51cto.com/u_13984132/5622619
https://blog.51cto.com/u_15060515/4027412