作者lowkeyway

时间为友,记录点滴。

费尽千辛万苦,总算是把SIFT看得懵懂了,还好OpenCV给我们的API已经完全封装了所有的步骤。只是这个专利费让9012年的我们即便学习也要折腾折腾。

最重要的是,有大神们也对SIFT不满意,持续不断优化它。直到Ethan Rublee, Vincent Rabaud, Kurt Konolige以及Gary R.Bradski在2011年一篇名为“ORB:An Efficient Alternative to SIFTor SURF”的文章,开启了一个新篇章。


为什么引入ORB

嗯,跟Harris一样,如果你运行过SIFT,也一定有所感悟。即便它集尺度不变、旋转不变、光变不敏感等优点于一身,但是它运行实在是慢了点。

那么改善SIFT,从SURF开始,大家的重点都是集中在速度优化上。据论文中提供,ORB要比SIFT快两个数量级!

这么厉害,我们先了解一下啥是ORB。

什么是ORB

ORB(Oriented FAST and Rotated BRIEF)是Oriented FAST + Rotated BRIEF的缩写(感觉应该叫OFRB)。是目前最快速稳定的特征点检测和提取算法,许多图像拼接和目标追踪技术利用ORB特征进行实现。

当然,没看懂不要紧,先记住我们的初衷:

  • 首先要实现目标检测的功能;
  • 其次在不牺牲性能的代价下提高速度;
  • 最后,开源无专利;

大神们在ORB上都做到了!

怎么实现ORB

要了解ORB,如果直接去看他们的论文,感觉多半会蒙圈。那是写给学术圈有一定基础的人看的。我等工程狗要了解ORB用简单的公式就可以:

ORB = Oriented FAST(特征点) + Rotated BRIEF(特征描述)

弗洛伊德说:一个问题搞不定,就拆成两个小问题,这样我们就有了两个搞不定的问题。

我们再来分别了解一下什么是FAST,为什么叫 Oriented FAST;什么是什么是BRIEF,为什么叫Rotated BRIEF。


FAST

FAST,正如其名。它的出现就是为了解决SIFT在建立特征点时速度慢的问题。我们程序员都知道,要解Bug首先要做的是定位问题。

那么,SIFT为什么慢呢?

SIFT进行特征点检测时需要建立尺度空间,基于局部图像的梯度直方图来计算描述子,整个算法的计算和数据存储复杂度比较高,不适用于处理实时性很强的图像。

好了,FAST的解决方案是什么呢?

若某像素与其周围领域内足够多的像素点相差较大,则该像素可能是特征点。

如何用计算机实现呢?

两步走,海选 + 筛选。

Step1: 确定候选角点(Segment Test)

特征点检测-ORB_特征点看图说话

  1. 选择某个像素 特征点检测-ORB_特征点_02 , 其像素值为 特征点检测-ORB_邻域_03 。以 特征点检测-ORB_邻域_04 为圆心,半径为3, 确立一个圆,圆上有16个像素,分别为 特征点检测-ORB_特征点_05
  2. 确定一个阈值: 特征点检测-ORB_特征点_06 (比如 Ip 的 20%)。
  3. 让圆上的像素的像素值分别与 特征点检测-ORB_像素点_07 的像素值做差,如果存在连续n个点满足 特征点检测-ORB_特征点_08 或 特征点检测-ORB_特征点_09 (其中 特征点检测-ORB_特征点_10 代表此圆上16个像素中的一个点),那么就把该点作为一个候选点。根据经验,一般令n=12(n 通常取 12,即为 FAST-12。其它常用的 N 取值为 9 和 11, 他们分别被称为 FAST-9,FAST-11).


补充:
由于在检测特征点时是需要对图像中所有的像素点进行检测,然而图像中的绝大多数点都不是特征点,如果对每个像素点都进行上述的检测过程,那显然会浪费许多时间,因此FAST采用了一种进行非特征点判别的方法。如上图中,对于每个点都检测第1、5、9、13号(即上下左右)像素点,如果这4个点中至少有3个满足都比 特征点检测-ORB_邻域_11 大或者都比 特征点检测-ORB_特征点_12 小,则继续对该点进行16个邻域像素点都检测的方法,否则则判定该点是非特征点(也不可能是角点,如果是一个角点,那么上述四个像素点中至少有3个应该和点相同),直接剔除即可。


Step2:非极大值抑制

经过Step 1的海选后,还是会有很多个特征点。好在他们有个缺点:很可能大部分检测出来的点彼此之间相邻,我们要去除一部分这样的点。为了解决这一问题,可以采用非最大值抑制的算法:

  • 假设P,Q两个点相邻,分别计算两个点与其周围的16个像素点之间的差分和为V。
  • 去除V值较小的点,即把非最大的角点抑制掉。


经过上述两步后FAST特征值筛选的结果就结束了。


Oriented FAST

是不是对FAST感觉意犹未尽?

FAST有什么缺点呢?

嗯,FAST快是快,但是无法体现出一个优良特征点的尺度不变性和旋转不变性。

Fast角点本不具有方向,由于特征点匹配需要,ORB对Fast角点进行了改进,改进后的 FAST 被称为 Oriented FAST,具有旋转和尺度的描述。

那么,Oriented FAST是怎么解决这个问题呢?

从SIFT过来的我们对这个问题不陌生。

  • 尺度不变性:可以用金字塔解决;
  • 旋转不变性:可以用质心标定方向解决;


尺度不变性:

  1. 对图像做不同尺度的高斯模糊
  2. 对图像做降采样(隔点采样)
  3. 对每层金字塔做FAST特征点检测
  4. n幅不同比例的图像提取特征点总和作为这幅图像的oFAST特征点。


旋转不变性:

1、在一个小的图像块 B 中,定义图像块的矩。

特征点检测-ORB_像素点_13

2、通过矩可以找到图像块的质心

特征点检测-ORB_特征点_14

3、连接图像块的几何中心 O 与质心 C,得到一个方向向量 特征点检测-ORB_邻域_15 ,这就是特征点的方向

特征点检测-ORB_邻域_16

特征点检测-ORB_邻域_17旋转测量。灰度质心在人工旋转噪声影响下,与直方图算法和MAX算法相比,具有最好的恢复主方向的性能。


BRIEF

BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度直方图描述特征点的传统方法,采用二级制、位异或运算,大大的加快了特征描述符建立的速度,同时也极大的降低了特征匹配的时间,是一种非常快速,很有潜力的算法。

如果说FAST用来解决寻找特征点的速度问题,那么BRIEF就用来解决描述子的空间占用冗余问题。


特征点怎么来?

好了,我们知道了BRIEF的实质就是特征点的描述子。那么前提就是描述对象特征点从何而来?实际上,Harris/FAST/SIFT/SURF等算法提供的特征点都可以。


描述子怎么加?

先定格调:在关键点周围做像素值比较,得到的结果是二进制串。

那下面就看一看它是如何给这些特征点加上描述子的:

1、为减少噪声干扰,先对图像进行高斯滤波(方差为2,高斯窗口为9x9)

2、以特征点为中心,取SxS的邻域窗口。在窗口内随机选取一对(两个)点,比较二者像素的大小,进行如下二进制赋值。

如下二进制赋值。

特征点检测-ORB_特征点_18

其中,p(x),p(y)分别是随机点x=(u1,v1),y=(u2,v2)的像素值。

3、在窗口中随机选取N对随机点,重复步骤2的二进制赋值,形成一个二进制编码,这个编码就是对特征点的描述,即特征描述子。(一般N=256)

这个特征可以由n位二进制测试向量表示,BRIEF描述子:

特征点检测-ORB_特征点_19


这里面,最关键的地方其实是随机特征对的选取,论文中就给出了5种方法(其中第二种比较好),分别为:

  1. 特征点检测-ORB_邻域_20 都呈均匀分布 特征点检测-ORB_像素点_21 ;
  2. 特征点检测-ORB_像素点_22 都呈高斯分布特征点检测-ORB_邻域_23 ,准则采样服从各向同性的同一高斯分布;
  3. 特征点检测-ORB_邻域_24 服从高斯分布 特征点检测-ORB_像素点_25 , 特征点检测-ORB_像素点_26 服从高斯分布 特征点检测-ORB_邻域_27 ,采样分两步进行:首先在原点处为 特征点检测-ORB_像素点_28 进行高斯采样,然后在中心为 特征点检测-ORB_特征点_29 处为 特征点检测-ORB_邻域_30 进行高斯采样;
  4. 特征点检测-ORB_特征点_31 在空间量化极坐标下的离散位置处进行随机采样;
  5. 特征点检测-ORB_邻域_32 在空间量化极坐标下的离散位置处进行随机采样;

这5种方法生成的256对(OpenCV中用32个字节存储这256对)随机点如下(一条线段的两个端点是一对):

特征点检测-ORB_邻域_33这个图有助于帮我们理解随机对描述子


经过上面三个步骤,我们就可以为每个特征点表示为一个256bit的二进制编码。


Rotated BRIEF

在介绍ORB的改善之前,我们先思考一个问题。描述子是用来描述一个特征点的属性的,除了标记特征点之外,它最重要的一个功能就是要实现特征点匹配。BRIEF是如何实现特征点匹配的呢?

答案是:Hamming距离!

汉明距离是使用在数据传输差错控制编码里面的,汉明距离是一个概念,它表示两个(相同长度)字对应位不同的数量,我们以d(x,y)表示两个字x,y之间的汉明距离。对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。

  1. 两个特征编码对应bit位上相同元素的个数小于128的,一定不是配对的。
  2. 一幅图上特征点与另一幅图上特征编码对应bit位上相同元素的个数最多的特征点配成一对。

其实就是按位求异或的过程。(相同为0,不同为1)

特征点检测-ORB_特征点_34

所以,对于BRIEF来说,描述子里不包含旋转属性,所以一旦匹配图片有稍微大点的旋转角度,按照Hamming算法,匹配度将会大幅下降。


ORB如何优化?

首先,做一些前期优化:

  1. ORB算法进一步增强描述子的抗噪能力,采用积分图像来进行平滑;
  2. 在特征点的31x31邻域内,产生随机点对,并以随机点为中心,取5x5的子窗口。
  3. 比较两个随机点的子窗口内25个像素的大小进行编码(而不仅仅是两个随机点了)

其次,为BRIEF增加旋转不变性(Steered BRIEF):

ORB算法采用关键点的主方向来旋转BEIEF描述子。

1、对于任意特征点,在31x31邻域内位置为 特征点检测-ORB_邻域_35 的n对点集,可以用2 x n的矩阵来表示:

特征点检测-ORB_像素点_36

2、利用FAST求出的特征点的主方向 特征点检测-ORB_特征点_37 和对应的旋转矩阵 特征点检测-ORB_特征点_38 ,算出旋转的 特征点检测-ORB_特征点_39 来代表 特征点检测-ORB_邻域_40 :

特征点检测-ORB_特征点_41

特征点检测-ORB_特征点_42

特征点检测-ORB_邻域_43

3、计算旋转描述子(steered BRIEF):

特征点检测-ORB_特征点_44

其中 特征点检测-ORB_像素点_45 为BRIEF的描述子。


最后,rBRIEF-改进特征点描述子的相关性

使用steeredBRIEF方法得到的特征描述子具有旋转不变性,但是却在另外一个性质上不如原始的BRIEF算法。是什么性质呢,是描述符的可区分性,或者说是相关性。这个性质对特征匹配的好坏影响非常大。描述子是特征点性质的描述。描述子表达了特征点不同于其他特征点的区别。我们计算的描述子要尽量的表达特征点的独特性。如果不同特征点的描述子的可区分性比较差,匹配时不容易找到对应的匹配点,引起误匹配。ORB论文中,作者用不同的方法对100k个特征点计算二进制描述符,对这些描述符进行统计,如下表所示:

特征点检测-ORB_像素点_46特征描述子的均值分布。X轴代表距离均值0.5的距离;y轴是相应均值下的特征点数量统计

我们先不看rBRIEF的分布。对BRIEF和steeredBRIEF两种算法的比较可知,BRIEF算法落在0上的特征点数较多,因此BRIEF算法计算的描述符的均值在0.5左右,每个描述符的方差较大,可区分性较强。而steeredBRIEF失去了这个特性。

至于为什么均值在0.5左右,方差较大,可区分性较强的原因,这里大概分析一下。这里的描述子是二进制串,里面的数值不是0就是1,如果二进制串的均值在0.5左右的话,那么这个串有大约相同数目的0和1,那么方差就较大了。用统计的观点来分析二进制串的区分性,如果两个二进制串的均值都比0.5大很多,那么说明这两个二进制串中都有较多的1时,在这两个串的相同位置同时出现1的概率就会很高。那么这两个特征点的描述子就有很大的相似性。这就增大了描述符之间的相关性,减小之案件的可区分性。

下面我们介绍解决上面这个问题的方法:rBRIEF。

原始的BRIEF算法有5种去点对的方法,原文作者使用了方法2。为了解决描述子的可区分性和相关性的问题,ORB论文中没有使用5种方法中的任意一种,而是使用统计学习的方法来重新选择点对集合。

首先,建立300k个特征点测试集。

备注:
对于测试集中的每个点,预处理参考第一个步骤。考虑其31x31邻域。这里不同于原始BRIEF算法的地方是,这里在对图像进行高斯平滑之后,使用邻域中的某个点的5x5邻域灰度平均值来代替某个点对的值,进而比较点对的大小。这样特征值更加具备抗噪性。另外可以使用积分图像加快求取5x5邻域灰度平均值的速度。

其次,特征点选取

从上面可知,在31 x 31的邻域内共有(31-5+1)x(31-5+1)=729个这样的子窗口,那么取点对的方法共有M=205590种,我们就要在这M种方法中选取256种取法,选择的原则是这256种取法之间的相关性最小。怎么选取呢?

  • 在300k特征点的每个31x31邻域内按M种方法取点对,比较点对大小,形成一个300k x M的二进制矩阵Q。矩阵的每一列代表300k个点按某种取法得到的二进制数。
  • 对Q矩阵的每一列求取平均值,按照平均值到0.5的距离大小重新对Q矩阵的列向量排序,形成矩阵T。
  • 将T的第一列向量放到R中
  • 取T的下一列向量和R中的所有列向量计算相关性,如果相关系数小于设定的阈值,则将T中的该列向量移至R中。
  • 按照第四步的方式不断进行操作,直到R中的向量数量为256。

通过这种方法就选取了这256种取点对的方法。这个算法是对均值靠近0.5的不相关测试进行贪婪搜索,结果称为rBRIEF。rBRIEF在方差和相关性上与旋转BRIEF相比有明显进步(如图4)。PCA的特征值较高,它们的下降速度要快得多。

特征点检测-ORB_邻域_47三个特征向量超过100k个关键点的PCA分解的特征值分布:BRIEF,旋转BRIEF,和rBRIEF。



至此,ORB的优化就结束了。我们尝试总结一下:

  • FAST是用来寻找特征点的。ORB在FAST基础上通过金字塔、质心标定解决了尺度不变和旋转不变。即oFAST。
  • BRIEF是用来构造描述子的。ORB在BRIEF基础上通过引入oFAST的旋转角度和机器学习解决了旋转特性和特征点难以区分的问题。即rBRIEF.

现在,有了特征点寻找和描述子,ORB就成了!


跟以往一样,OpenCV给我们提供了便捷的接口和使用方法。外部看上跟SIFT简直不要太一样。


C++

#include <iostream>#include <string>#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int *argv, int **argc)
{

Mat imgCat = imread("cat.png");
Mat imgSmallCat = imread("smallCat.png");

auto orbDetector = ORB::create();
vector<KeyPoint> kpCat, kpSmallCat;
Mat descriptorCat, descriptorSmallCat;

orbDetector->detectAndCompute(imgCat, Mat(), kpCat, descriptorCat);
orbDetector->detectAndCompute(imgSmallCat, Mat(), kpSmallCat, descriptorSmallCat);

Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create(DescriptorMatcher::BRUTEFORCE);
std::vector<DMatch> matchers;
matcher->match(descriptorCat, descriptorSmallCat, matchers);

Mat imgMatches;
drawMatches(imgCat, kpCat, imgSmallCat, kpSmallCat, matchers, imgMatches);

imshow("Cat", imgCat);
imshow("SmallCat", imgSmallCat);
imshow("Match", imgMatches);

waitKey(0);

return true;
}

特征点检测-ORB_邻域_48运行结果


Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:lowkeyway time:11/13/2019

import sys
import cv2 as cv
import numpy as np


def main_func(argv):
imgCat = cv.imread("cat.png")
imgSmallCat = cv.imread("smallCat.png")

orb = cv.ORB_create()

kpCat, desCat = orb.detectAndCompute(imgCat,None)
kpSmallCat, desSmallCat = orb.detectAndCompute(imgSmallCat, None)

bf = cv.BFMatcher_create(cv.NORM_HAMMING, crossCheck=True)

matches = bf.match(desCat, desSmallCat)
matchImg = cv.drawMatches(imgCat, kpCat, imgSmallCat, kpSmallCat, matches, None)


cv.imshow("Cat", imgCat)
cv.imshow("SmallCat", imgSmallCat)
cv.imshow('match', matchImg)

cv.waitKey(0)


if __name__ == '__main__':
main_func(sys.argv)


特征点检测-ORB_像素点_49

运行结果

特征点检测-ORB_像素点_50

▲长按关注我们