视觉里程计法:提取、匹配图像特征点,然后估计两帧之间的相机运动,给后端提供较好的初始值。
角点的局限:例如:从远处上看上去是角点的地方,当相机走近后,可能就是角点了。或者,当旋转相机的时候,角点的外观会发生变换。
进而,我们提出了SIFT,SURF,ORB特征。
特征点由提取关键点和计算描述子两部分组成。
关键点:该特征点在图像的位置,具有朝向、大小的信息。
描述子:描述关键点周围像素信息的向量。
SIFT(尺度不变特征变换—Scale-Invariant Feature Transform):充分考虑图像变换过程中光照、尺度、旋转等变换,但计算量大。
FAST:只有关键点没有描述子,降低精度和健壮性,提升计算速度。
ORB:(Oriented FAST and Rotated BRIEF):改进了FAST 不具有方向性的问题,添加了尺度和旋转的特性。采用速度极快的二进制描述子BRIEF,
主要检测局部像素灰度变化明显的地方。思想:如果一个像素与邻域像素差别较大(过亮或者过暗),只需比较像素亮度大小。
1、在图像中选取像素p,假设他的亮度是IP
2、设置一个阈值T,例如T=IP*20%
3、以像素p为中心,选取半径为3的圆上的16个像素点, 这里的3应该是三个像素框
4、如果在选取的圆上面,有连续N个点的亮度值大于IP+T(1.2*IP))或者小于IP-T(0.8*IP),那么像素p通常可以被认为是特征点(N通常取12,FAST-12,通常n取9和11,的时候,称为FAST-9和FAST-11)
添加预测试操作,于每个像素,直接检测在邻域圆上的第1,5,9,13个像素的亮度,只有当这四个像素当中有三个同时大于IP+T或者小于IP-T的时候,当前像素才有可能是是角点。
问题1:FAST特征点的数量很多,并且不是确定,而大多数情况下,我们希望能够固定特征点的数量。
解决方法:在ORB当中,我们可以指定要提取的特征点数量。对原始的FAST角点分别计算Harris的响应值,然后选取前N个点具有最大相应值的角点,作为最终角点的集合。
问题2:FAST角点不具有方向信息和尺度问题。
解决方法:尺度不变性构建的图像的金字塔,并且从每一层上面来检测角点。旋转性是由灰度质心法实现。
灰度质心法:质心是指以图像块灰度值作为权重的中心。(目标是为找找到方向)
BRIEF是二进制描述子,它的描述向量是由许多个0和1组成,这里的0和1编码了关键点附近的两个像素p和q的大小关系,如果p>q,则取1 ;否则取0;p q 的挑选为随机选点。
通过对图像与地图之间的描述子进行准确匹配,为后续的姿态估计优化等操作减轻负担。
暴力匹配:对两帧图像中每一个特征点x(t)与所有的特征点x(t+1)测量描述子的距离,然后排序,取最近的一个作为匹配点。
描述子距离表明了两个特征之间的相似距离。
二进制描述子,使用汉明距离,指的是不同位数的个数。
快速近似最邻近算法(FLANN):适合匹配点数量多情况。
#include
#include
using namespace std;
using namespace cv;
int main()
{
//0读取图像
Mat img1 = imread("1.png");
Mat img2 = imread("2.png");
// 1 初始化特征点和描述子,ORB
std::vector
Mat descriptors1, descriptors2;
Ptr
// 2 提取 Oriented FAST 特征点
orb->detect(img1, keypoints1);
orb->detect(img1, keypoints1);
// 3 根据角点位置计算 BRIEF 描述子
orb->compute(img1, keypoints1, descriptors1);
orb->compute(img2, keypoints2, descriptors2);
// 4 对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector
BFMatcher bfmatcher(NORM_HAMMING);
bfmatcher.match(descriptors1, descriptors2, matches);
// 5 绘制匹配结果
Mat img_match;
drawMatches(img1, keypoints1, img2, keypoints2, matches, img_match);
imshow("所有匹配点对", img_match);
waitKey(0);
return 0;
}
得到了描述子后,可调用匹配算法进行特征点的匹配。上面代码中,使用了opencv中封装后的暴力匹配算法BFMatcher,该算法在向量空间中,将特征点的描述子一一比较,选择距离(上面代码中使用的是Hamming距离)较小的一对作为匹配点。
class KeyPoint
{
Point2f pt; //特征点坐标
float size; //特征点邻域直径
float angle; //特征点的方向,值为0~360,负值表示不使用
float response; //特征点的响应强度,代表了该点是特征点的程度,可以用于后续处理中特征点排序
int octave; //特征点所在的图像金字塔的组
int class_id; //用于聚类的id
}
struct DMatch
{
int queryIdx; //此匹配对应的查询图像的特征描述子索引
int trainIdx; //此匹配对应的训练(模板)图像的特征描述子索引
int imgIdx; //训练图像的索引(若有多个)
float distance; //两个特征向量之间的欧氏距离,越小表明匹配度越高。
bool operator < (const DMatch &m) const;
};
匹配函数match (matches中保存着描述子之间的匹配关系)
vector
BFMatcher bfmatcher(NORM_HAMMING);
bfmatcher.match(descriptors1, descriptors2, matches);
画有特征点的图像drawKeypoints
drawKeypoints(img1,keypoints1,outimg1,Scalar::all(-1),DrawMatchesFlags::DEFAULT);
画匹配图像drawMatches
drawMatches(img1, keypoints1, img2, keypoints2, matches, img_match);
img_match为目标图像。
// 匹配对筛选
double min_dist = 1000, max_dist = 0;
// 找出所有匹配之间的最大值和最小值
for (int i = 0; i < descriptors1.rows; i++)
{
double dist = matches[i].distance;//汉明距离在matches中
if (dist < min_dist) min_dist = dist;
if (dist > max_dist) max_dist = dist;
}
// 当描述子之间的匹配大于2倍的最小距离时,即认为该匹配是一个错误的匹配。
// 但有时描述子之间的最小距离非常小,可以设置一个经验值作为下限
vector
for (int i = 0; i < descriptors1.rows; i++)
{
if (matches[i].distance <= max(2 * min_dist, 30.0))
good_matches.push_back(matches[i]);
}