目录
特征点分类
1 ORB
①特征点检测
②计算特征描述
2 SIFT
1 SIFT特征检测的步骤
①.在DOG尺度空间中获取极值点,即关键点。
②.特征点方向估计
③生成特征描述
④.代码实现
3.SURF
①.SURF的介绍
②.SURF算法步骤
③. SIFT与SURF效果比较
④代码实现
4 FAST角点检测且阈值可调节
补充
图像金字塔
灰度质心法
实现思路:
图像本身是由亮度和色彩组成的矩阵,VO核心问题是如何根据图像估计相机运动。数字图像在计算机中是以灰度值矩阵的方式存储的,灰度值不稳定,所以采用灰度值+特征点;图像中的角点、边缘和区块都是具有代表性的地方,可以作为图像特征;
特征点由关键点和描述子组成,计算SIFT特征点指的是:“提取SIFT关键点,并计算SIFT描述子”两件事。
关键点:特征点在图像里的位置,大小,方向信息; 描述子:描述了该关键点周围的像素信息的向量。
ORB特征点关键点为Oriented FAST计算了特征点的主方向、描述子为BRIER利用关键点方向信息描述子增加了旋转不变性。
FAST角点:局部像素灰度变化明显的地方,速递快。计算像素间亮度差异。ORB通过构建图像金字塔,并在图像金字塔的每一层上来检测角点实现尺度不变性;ORB特征旋转由灰度质心法实现。
BRIEF描述子是二进制描述,需要用到汉明距离,ORB描述子为旋转之后的BRIEF。
ORB特征点采用FAST(features from accelerated segment test)算法来检测特征点。FAST核心思想是拿一个点跟它周围的点比较,如果它和其中大部分的点都不一样就可以认为它是一个特征点。比如我们可以那一个点与它周围半径是3的圆上的所有像素点进行比较,如下图所示:
有了计算的方案和策略,接下来我们来看计算的具体过程。
FAST具体计算过程:
①从图片中选取一个像素点P,下面我们将判断它是否是一个特征点。我们首先把它的密度(即灰度值)设为Ip。
②设定一个合适的阙值t :当2个点的灰度值之差的绝对值大于t时,我们认为这2个点不相同。
③考虑该像素点周围的16个像素。(见上图)
④现在如果这16个点中有连续的n个点都和选取像素点不同,那么它就是一个角点。 这里n设定为12。
⑤我们现在提出一个高效的测试,来快速排除一大部分非特征点的点。该测试仅仅检查在位置1、9、5和13四个位置的像素。如果是一个角点,那么上述四个像素点中至少有3个应该和点相同。如果都不满足,那么不可能是一个角点。
通过上述过程,我们的图片像多了很多特征点,我们用红色标出。
得到特征点后我们需要以某种方式 F 描述这些特征点的属性。这些属性的输出我们称之为该特征点的描述子。ORB采用BRIEF算法来计算一个特征点的描述子。BRIEF算法的核心思想是在关键点P的周围以一定模式选取N个点对,把这N个点对的比较结果组合起来作为描述子。
接下来看一下具体操作:
以关键点P为圆心,以d为半径做圆O。
在圆O内某一模式选取N个点对。这里为方便说明,N=4,实际应用中N可以取512.
假设当前选取的4个点对如上图所示分别标记为:
定义操作T
分别对已选取的点对进行T操作,将得到的结果进行组合。
假如:
则最终的描述子为:1011
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
Mat src;
int main(int argc, char** argv)
{
src = imread("./data2/101.png", IMREAD_GRAYSCALE); //加载灰度图像
//src = imread("./data2/101.png"); //加载图像
if (!src.data)
{
cout << "图片加载失败" << endl;
return -1;
}
namedWindow("加载的灰度图像", CV_WINDOW_NORMAL); //可任意改变窗口大小
imshow("加载的灰度图像", src);
int numfeature = 400; //特征点数目
Ptrdetector = ORB::create(numfeature);
//auto detector = ORB::create(); //自动生成特征点的个数
vectorkeypoints;
detector->detect(src, keypoints, Mat());
printf("所有的特征点个数:%d", keypoints.size());
Mat resultImg;
drawKeypoints(src, keypoints, resultImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT); //特征点颜色随机
imshow("特征点提取", resultImg);
imwrite("./效果图/特征点提取.jpg", resultImg);
waitKey(0);
return 0;
}
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('./data/box.png',0) # queryImage
img2 = cv.imread('./data/box_in_scene.png',0) # trainImage
# Initiate ORB detector
orb = cv.ORB_create()
# find the keypoints and descriptors with ORB
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
# create BFMatcher object
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
# Match descriptors.
matches = bf.match(des1,des2)
# Sort them in the order of their distance.
matches = sorted(matches, key = lambda x:x.distance)
# Draw first 10 matches.
img3 = cv.drawMatches(img1,kp1,img2,kp2,matches[:20],None, flags=2)
plt.imshow(img3),plt.show()
尺度不变特征,SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。对视角变化、仿射变换、噪声也保持一定程度的稳定性。
关于LOG(高斯拉普拉斯金字塔) DOG(高斯差分金字塔)参考 https://blog.csdn.net/dcrmg/article/details/52561656
确定极值点:寻找DoG极值点时,每一个像素点和它所有的相邻点比较,当其大于(或小于)它的图像域和尺度域的所有相邻点时,即为极值点。如下图所示,比较的范围是个3×3的立方体:中间的检测点和它同尺度的8个相邻点,以及和上下相邻尺度对应的9×2个点——共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。
计算以特征点为中心、以3×1.5σ为半径的区域图像的幅角和幅值,每个点L(x,y)的梯度的模m(x,y)以及方向θ(x,y)可通过下面公式求得
计算得到梯度方向后,就要使用直方图统计特征点邻域内像素对应的梯度方向和幅值。梯度方向的直方图的横轴是梯度方向的角度(梯度方向的范围是0到360度,直方图每36度一个柱共10个柱,或者没45度一个柱共8个柱),纵轴是梯度方向对应梯度幅值的累加,在直方图的峰值就是特征点的主方向。在梯度直方图中,当存在一个相当于主峰值80%能量的柱值时,则可以将这个方向认为是该特征点辅助方向。所以,一个特征点可能检测到多个方向(也可以理解为,一个特征点可能产生多个坐标、尺度相同,但是方向不同的特征点)。
为了保证特征矢量的旋转不变性,要以特征点为中心,在附近邻域内将坐标轴旋转θ(特征点的主方向)角度,即将坐标轴旋转为特征点的主方向。
将旋转后区域划分为d×d(d为2或者4,通常取4)个子区域(每个区域间隔为mσ像元),在子区域内计算8个方向的梯度直方图,绘制每个方向梯度方向的累加值,形成一个种子点。
与求主方向不同的是,此时,每个子区域梯度方向直方图将0°~360°划分为8个方向区间,每个区间为45°。即每个种子点有8个方向区间的梯度强度信息。由于存在d×d,即4×4个子区域,所以最终共有4×4×8=128个数据,形成128维SIFT特征矢量。
参考blog:图像特征点提取(SIFT,SURF,ORB)_阿飞大魔王的博客-CSDN博客_图像特征点提取
import numpy as np
import cv2 as cv
img = cv.imread('./data/home.jpg')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)
sift = cv.xfeatures2d.SIFT_create()
kp = sift.detect(gray,None)
img=cv.drawKeypoints(gray,kp,img)
cv.imshow("SIFT", img)
cv.imwrite('sift_keypoints.jpg',img)
cv.waitKey(0)
cv.destroyAllWindows()
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
Mat src;
int main(int argc, char** argv)
{
src = imread("./data2/101.png", IMREAD_GRAYSCALE); //加载灰度图像
//src = imread("./data2/101.png"); //加载图像
if (!src.data)
{
cout << "图片加载失败" << endl;
return -1;
}
//namedWindow("加载的灰度图像", CV_WINDOW_NORMAL); //可任意改变窗口大小
imshow("加载的灰度图像", src);
int numfeature = 400; //特征点数目
Ptrdetector = SIFT::create(numfeature);
//auto detector = SIFT::create(); //自动生成特征点的个数
vectorkeypoints;
detector->detect(src, keypoints, Mat());
printf("所有的特征点个数:%d", keypoints.size());
Mat resultImg;
drawKeypoints(src, keypoints, resultImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT); //特征点颜色随机
imshow("SIFT特征点提取", resultImg);
imwrite("./效果图/SIFT特征点提取.jpg", resultImg);
waitKey(0);
return 0;
}
图像特征点匹配
img1_gray = cv2.imread("./data/000029.jpg")
img2_gray = cv2.imread("./data/000087.jpg")
# sift = cv2.xfeatures2d.SIFT_create()
sift = cv2.xfeatures2d.SURF_create()
kp1, des1 = sift.detectAndCompute(img1_gray, None)
kp2, des2 = sift.detectAndCompute(img2_gray, None)
# BFmatcher with default parms
bf = cv2.BFMatcher(cv2.NORM_L2)
matches = bf.knnMatch(des1, des2, k=2)
goodMatch = []
for m, n in matches:
if m.distance < 0.50 * n.distance:
goodMatch.append(m)
goodMatch = np.expand_dims(goodMatch, 1)
# print(goodMatch[:20])
# drawMatchesKnn_cv2(img1_gray, kp1, img2_gray, kp2, goodMatch[:20])
res = cv2.drawMatchesKnn(img1_gray, kp1, img2_gray, kp2, goodMatch[:100], None, flags=2)
cv2.namedWindow('res', cv2.WINDOW_NORMAL)
cv2.resizeWindow('res', 1080, 720)
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
提取效果
结果:(显示了前100个匹配点)
SURF(Speeded Up Robust Features)改进了特征的提取和描述方式,用一种更为高效的方式完成特征的提取和描述。Sift算法的优点是特征稳定,对旋转、尺度变换、亮度保持不变性,对视角变换、噪声也有一定程度的稳定性;缺点是实时性不高,并且对于边缘光滑目标的特征点提取能力较弱。 SURF是对SIFT的改进,可将速度提高3倍。SURF主要是把SIFT中的某些运算做了简化。
尺度空间的极值检测:搜索所有尺度空间上的图像,通过Hessian来识别潜在的对尺度和选择不变的兴趣点。
特征点过滤并进行精确定位。
特征方向赋值:统计特征点圆形邻域内的Harr小波特征。即在60度扇形内,每次将60度扇形区域旋转0.2弧度进行统计,将值最大的那个扇形的方向作为该特征点的主方向。
特征点描述:沿着特征点主方向周围的邻域内,取4×4个矩形小区域,统计每个小区域的Haar特征,然后每个区域得到一个4维的特征向量。一个特征点共有64维的特征向量作为SURF特征的描述子。
具体可以参考,写得很好 https://www.cnblogs.com/zyly/p/9531907.html
import numpy as np
import cv2 as cv
img = cv.imread('./data/000029.jpg',0)
surf = cv.xfeatures2d.SURF_create(2000)
kp, des = surf.detectAndCompute(img,None)
img1 = cv.drawKeypoints(img,kp,None,(0,0,255),4)
cv.imshow('surf', img1)
cv.waitKey(0)
cv.destroyAllWindows()
surf.setHessianThreshold(20000)
kp, des = surf.detectAndCompute(img,None)
img2 = cv.drawKeypoints(img,kp,None,(0,0,255),4)
cv.imshow('surf',img2)
cv.waitKey(0)
cv.destroyAllWindows()
#include
#include
#include
#include
#include
#include
//FAST角点检测
using namespace std;
using namespace cv;
int thre = 40;
Mat src;
void trackBar(int, void*);
int main(int argc, char** argv)
{
//src = imread("./data2/88.jpg");
src = imread("./data2/88.jpg", IMREAD_GRAYSCALE); //加载灰度图像
if (src.empty())
{
printf("无图像加载 \n");
return -1;
}
namedWindow("input", WINDOW_NORMAL);
imshow("input", src);
namedWindow("output", WINDOW_NORMAL);
createTrackbar("threshould", "output", &thre, 255, trackBar);
waitKey(0);
return 0;
}
void trackBar(int, void*)
{
std::vector keypoints;
Mat dst = src.clone();
Ptr detector = FastFeatureDetector::create(thre);
printf("阈值:%d", thre); //打印检测到的特征点个数,随阈值变化
detector->detect(src, keypoints);
printf("检测到的所有的特征点个数:%d", keypoints.size()); //打印检测到的特征点个数,随阈值变化
drawKeypoints(dst, keypoints, dst, Scalar::all(-1), DrawMatchesFlags::DRAW_OVER_OUTIMG); //随机颜色画出
imshow("角点检测图", dst);
imwrite("./效果图/角点检测图.jpg", dst);
}
ORB特征添加了尺度和旋转描述,
尺度不变性由构建图像金字塔,并在金字塔每一层上检测角点实现。
特征旋转不变性由灰度质心法实现。
金字塔是CV中常用到的一种图像处理方法,金字塔底层是原始图像;
每往上一层就对图像进行固定倍率的缩放,得到了不同分辨率的图像;
较小的图像可以看成是远处看过来的场景;
在特征匹配算法中,我们可以匹配不同层上的图像,从而实现尺度不变性。
例如,如果相机在后退,我们就能在上一个图像金字塔的上一层或者下一个图像金字塔的下层中找到匹配。
如果我们通过下采样生成一个金字塔,最简单的做法就是:不断地删除图像的偶数行和偶数列,重复这个过程,就得到一个金字塔。
如果我们通过上采样生成一个金字塔,最简单的就是:在每列像素点的右边插入值为0的列,在每行像素点下面插入值为0的行,不断重复,就生成一个金字塔了。
小结:
1、下采样是图像不断变小的过程,上采样是图像不断变大的过程。
2、一个图像下采样一次,在执行一次上采样,虽然尺寸恢复到原图像的尺寸,但像素值已经改变!!!也就是这两种操作是不可逆的。
cv2.pyrDown(img [, dstsize, borderType])
上采样:
cv2.pyrUp(img [, dstsize, borderType])
默认的尺寸都是一半一半的减小,或者一倍一倍的增加。
默认的滤波器都是高斯滤波器
为了减轻图像信息的丢失,滤波后的图像就是原始图像的近似图像
为什么要用滤波器?
我们下采样生成金字塔图像时,是直接删除偶数行偶数列的操作,但这种操作意味着直接丢弃图像中的信息!为了减轻图像信息的丢失,我们在下采样操作之前先用滤波器对原始图像滤波操作一遍,这样滤波后的图像就是原始图像的近似图像,此时我们在删偶数行偶数列,就没有直接的信息损失了。而对原始图像进行滤波操作有很多方法,比如我们可以用邻域滤波器进行操作,这样生成的图像就是平均金字塔。如果我们用高斯滤波器处理,我们就生成的是高斯金字塔 。
同理,当我们上采样生成图像金字塔时,我们直接右插入列下插入行操作,这种操作会生成大量的0值像素点,这些0值像素点毫无意义,我们就需要对0值像素点进行赋值。而赋值就是插值处理。插值处理也有很多方法,比如用区域均值补充,那生成的就是平均金字塔,如果用高斯核填充就是高斯金字塔
拉普拉斯金字塔是在高斯金字塔的基础上生成的。
为啥要发明拉普拉斯金字塔?还是因为高斯金字塔,虽然它用高斯核过滤了一遍,但或多或少还是有信息丢失,而这些丢失的信息就是拉普拉斯金字塔 。所以拉普拉斯金字塔的作用就在于能够恢复图像的细节,就是我们从高层的尺寸小的特征图中提取特征后,我们还能通过拉普拉斯金字塔数据找回高层像素点对应的底层清晰度更高的图像,就是返回来找到更多图像的细节。
Li = Gi - PyrUp( PyrDown(Gi) )
其中,Gi:原始图像 ; Li:拉普拉斯金字塔图像
ORB特征旋转方面,计算图像特征点附近的图像灰度质心。质心是以图像块灰度值作为权重的中心
目的:已知形心P,求解质心Q
1、为了保证旋转不变性,所以要在圆内计算;
2、为了在圆内计算,故要知道圆的边界,才能去索引;
3、为了知道圆的边界,在已知纵坐标的情况下,还需知道横坐标umax;
4、最终利用IC_Angle函数进行并行求解像素和
①在一个小的图像块B中,定义图像块矩阵为:
②通过矩可以找到图像块的质心:
③连接图像块的几何中心 O 与质心 C,得到一个方向向量 ,于是特征点的方向可以定义为:
通过以上方法,FAST 角点便具有了尺度与旋转的描述,大大提升了它们在不同图像之间 表述的鲁棒性。所以在 ORB 中,把这种改进后的 FAST 称为 Oriented FAST。
参考文献
图像特征点提取(SIFT,SURF,ORB)_阿飞大魔王的博客-CSDN博客_图像特征点提取
图像特征点提取及匹配的几种方法总结——基于C++和OPENCV实现SIFT、SURF、ORB、FAST_Fighting_XH的博客-CSDN博客_vector