ORB

来源:互联网 发布:艺术类推荐信 知乎 编辑:程序博客网 时间:2024/06/14 07:44

一、介绍

假如有两张人物图片,我们的目标是要确认这两张图片中的人物是否是同一个人。如果人来判断,这太简单了。但是让计算机来完成这个功能就困难重重。一种可行的方法是:

  1. 分别找出两张图片中的特征点
  2. 描述这些特征点的属性,
  3. 比较这两张图片的特征点的属性。如果有足够多的特征点具有相同的属性,那么就可以认为两张图片中的人物就是同一个人。

ORB(Oriented FAST and Rotated BRIEF)就是一种特征提取并描述的方法。ORB是由Ethan Rublee, Vincent Rabaud, Kurt Konolige以及Gary R.Bradski在2011年提出,论文名称为"ORB:An Efficient Alternative to SIFTor SURF",(http://www.willowgarage.com/sites/default/files/orb_final.pdf)

ORB分两部分,即特征点提取和特征点描述。特征提取是由FAST(Features from Accelerated Segment Test)算法发展来的,特征点描述是根据BRIEF(Binary Robust Independent Elementary Features)特征描述算法改进的。ORB特征是将FAST特征点的检测方法与BRIEF特征描述子结合起来,并在它们原来的基础上做了改进与优化。据说ORB算法的速度是sift的100倍,是surf的10倍。

二、Oriented FAST(oFast)特征点的提取

oFast就是在使用FAST提取特征点之后,给其定义一个该特征点的放向,并以此来实现该特征点的旋转不变形

2.1、粗提取

图像的特征点可以简单的理解为图像中比较显著显著的点,如轮廓点,较暗区域中的亮点,较亮区域中的暗点等。

FAST的核心思想是找出那些卓尔不群的点。即拿一个点跟它周围的点比较,如果它和其中大部分的点都不一样,就人物它是一个特征点。


如上图,假设图像中的一点P,及其一个邻域。右半拉是放大的图,每个小方格代表一个像素,方格内的颜色只是为了便于区分,不代表该像素点的颜色。判断该点是不是特征点的方法是,以P为圆心画一个半径为3pixel的圆(周长为16pixel)。圆周上如果有连续n个像素点的灰度值比P点的灰度值大或者小(需事先设定一个阈值T),则认为P为特征点。一般n设置为12

为了加快特征点的提取,快速排除非特征点,首先检测1、9、5、13位置上的灰度值,如果P是特征点,那么这四个位置上有3个或3个以上的的像素值都大于或者小于P点的灰度值。如果不满足,则直接排除此点。

2.2、使用ID3决策树,将特征点圆周上的16个像素输入决策树中,以此来筛选出最优的FAST特征点。

2.3、使用非极大值抑制算法去除临近位置多个特征点的。具体:为每一个特征点计算出其响应大小(特征点P和其周围16个特征点偏差的绝对值和)。在比较临近的特征点中,保留响应值较大的特征点,删除其余的特征点。

2.4、特征点的尺度不变性

建立金字塔,来实现特征点的多尺度不变性。设置一个比例因子scaleFactor(opencv默认为1.2)和金字塔的层数nlevels(pencv默认为8)。将原图像按比例因子缩小成nlevels幅图像。缩放后的图像为:I’= I/scaleFactork(k=1,2,…, nlevels)。nlevels幅不同比例的图像提取特征点总和作为这幅图像的oFAST特征点。

2.5、特征点的旋转不变形

oFast用矩(moment)法来确定FAST特征点的方向。即计算特征点以r为半径范围内的质心,特征点坐标到质心形成一个向量作为该特征点的方向。矩定义如下:


三、Rotated BRIEF(rBRIEF)特征点的描述

3.1、BRIEF算法

BRIEF算法计算出来的是一个二进制串的特征描述符。它是在一个特征点的邻域内,选择n对像素点pi、qi(i=1,2,…,n)。然后比较每个点对的灰度值的大小。如果I(pi)> I(qi),则生成二进制串中的1,否则为0。所有的点对都进行比较,则生成长度为n的二进制串。一般n取128、256或512(opencv默认为256)。

另外,为了增加特征描述符的抗噪性,算法需要先对图像进行高斯平滑处理。在ORB算法中,在这个地方进行了改进,在使用高斯函数进行平滑后又用了其他操作,使其更加的具有抗噪性。具体方法下面将会描述。

在特征点SxS的区域内选取点对的方法,BRIEF论文中测试了5种方法:

  • 在图像块内平均采样;
  • p和q都符合(0,S2/25)的高斯分布;
  • p符合(0,S2/25)的高斯分布,而q符合(0,S2/100)的高斯分布;
  • 在空间量化极坐标下的离散位置随机采样;
  • 把p固定为(0,0),q在周围平均采样。


3.2、rBRIEF算法

3.2.1、steered BRIEF(旋转不变性改进):

在使用oFast算法计算出的特征点中包括了特征点的方向角度。假设原始的BRIEF算法在特征点SxS(一般S取31)邻域内选取n对点集。


经过旋转角度θ旋转,得到新的点对:


在新的点集位置上比较点对的大小形成二进制串的描述符。这里需要注意的是,在使用oFast算法是在不同的尺度上提取的特征点。因此,在使用BRIEF特征描述时,要将图像转换到相应的尺度图像上,然后在尺度图像上的特征点处取SxS邻域,然后选择点对并旋转,得到二进制串描述符。

3.2.2、rBRIEF-改进特征点描述子的相关性

使用steeredBRIEF方法得到的特征描述子具有旋转不变性,但是却在另外一个性质上不如原始的BRIEF算法,即描述符的可区分性(相关性)。为了解决描述子的可区分性和相关性的问题,ORB论文中没有使用原始BRIEF算法中选取点对时的5种方法中的任意一种,而是使用统计学习的方法来重新选择点对集合。

对每个特征点选取31x31领域,每个领域选择5x5的平均灰度值代替原来单个像素值进行比对,因此可以得到N=(31-5+1)x(31-5+1) = 729个可以比对的子窗口(patch),可以使用积分图像加快求取5x5邻域灰度平均值的速度。一共有M = 1+2+3+...+N = 265356种点对组合,也就是一个长度为M的01字符串。显然M远大于256,我们得筛选。

筛选方法如下:

  • 重组所有点以及对应的初始二值串得到矩阵O,行数为提取得到的点数,每行是每个点对应的初始二值描述子
  • 对重组后的矩阵​O,按照每列均值与0.5的绝对差从小到大排序,得到矩阵T
  • 贪心选择:把T中第一列放进矩阵R(一开始为空)中,并从T中移除依次选择T的每列,与R中所有的列进行比较,如果相似度超过一定阈值,忽略,进行下一列,否则放进R中,并从T中移除重复以上过程直到选择​256个列,这样每个特征点就有256个0,1组成的描述子。如果不足256个,则降低阈值直到满足256就可,R即为最终特征描述矩阵。

三、特征点匹配

这部分是另外一个话题了。ORB算法最大的特点就是计算速度快 。这得益于使用FAST检测特征点,FAST的检测速度正如它的名字一样是出了名的快。再就是是使用BRIEF算法计算描述子,该描述子特有的2进制串的表现形式不仅节约了存储空间,而且大大缩短了匹配的时间。
例如特征点A、B的描述子如下。
A:10101011
B:10101010

设定一个阈值,比如80%。当A和B的描述子的相似度大于90%时,我们判断A,B是相同的特征点,即这2个点匹配成功。在这个例子中A,B只有最后一位不同,相似度为87.5%,大于80%。则A和B是匹配的。
将A和B进行异或操作就可以轻松计算出A和B的相似度。而异或操作可以借助硬件完成,具有很高的效率,加快了匹配的速度。

四、OpenCV实验(OpenCV3.0以上版本,包含contrib模块)

#include <iostream>#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <string>#include <dirent.h>#include <unistd.h>#include <vector>#include <sstream>#include <fstream>#include <sys/io.h>#include <sys/times.h>#include <iomanip>#include <tuple>#include <cstdlib>using namespace std;#include "opencv2/imgproc.hpp"#include "opencv2/imgcodecs.hpp"#include "opencv2/highgui.hpp"#include "opencv2/stitching.hpp"#include "opencv2/xfeatures2d/nonfree.hpp"using namespace cv;#define ENABLE_LOGbool PreapreImg(vector<Mat> &imgs);bool Match(vector<cv::detail::MatchesInfo> &pairwise_matches,            const vector<cv::detail::ImageFeatures> &features,           const cv::String matcher_type = "homography",            const int range_width = -1,           const bool try_cuda = false,            const double match_conf = 0.3f);void demo();int main(int argc, char** argv){    cout << "# STA ##############################" << endl;    cout << "\n" << endl;    int64 app_start_time = getTickCount();        demo();        cout << "\n" << endl;    cout << "# END ############################## Time: "          << ((getTickCount() - app_start_time) / getTickFrequency())          << " sec" << endl;    return 0;}void demo(){    vector<Mat> imgs;     PreapreImg(imgs);        // define feature finder    Ptr<cv::detail::FeaturesFinder> finder =     cv::makePtr<cv::detail::OrbFeaturesFinder>();        // detect features    int num_images = static_cast<int>(imgs.size());    vector<cv::detail::ImageFeatures> features(num_images);    for (int i = 0; i < num_images; i++) {        (*finder)(imgs[i], features[i]);        features[i].img_idx = i;#ifdef ENABLE_LOG        cout << ">> features number: " << setw(4) << features[i].img_idx             << setw(5) << static_cast<int>(features[i].keypoints.size())             << endl;        Mat tmp;        cv::drawKeypoints(imgs[i], features[i].keypoints, tmp);        stringstream ss;        ss << i;        cv::imwrite(("./img" + string(ss.str()) + "_keypoints.jpg").c_str(), tmp);#endif    }    // Frees unused memory allocated before if there is any    finder->collectGarbage();        // Pairwise matching     vector<cv::detail::MatchesInfo> pairwise_matches;    Match(pairwise_matches, features);#ifdef ENABLE_LOG        cout << ">> pairwise matches: "              << setw(5) << static_cast<int>(pairwise_matches.size())             << endl;        cout << ">> Saving matches graph..." << endl;        ofstream f("./matchGraph.txt");        vector<cv::String> img_names;        for (int i = 0; i < num_images; i++) {            stringstream ss; ss << i;            img_names.push_back(ss.str());        }        f << matchesGraphAsString(img_names, pairwise_matches, 1.0f);        cout << ">> Saving matches graph OK. Position: ./matchGraph.txt" << endl;        Mat tmp;        cv::drawMatches(imgs[0], features[0].keypoints,                         imgs[1], features[1].keypoints,                        pairwise_matches[1].matches,                        tmp);        cv::imwrite("./matches0_1.jpg", tmp);#endif}bool PreapreImg(vector<Mat> &imgs){    Mat image0 = imread("./0.jpg", IMREAD_GRAYSCALE);    Mat image1 = imread("./1.jpg", IMREAD_GRAYSCALE);    imgs.push_back(image0);    imgs.push_back(image1);        // Check if have enough images    int num_images = static_cast<int>(imgs.size());    if (num_images < 2)    {        cout << ">> error. num_images < 2" << endl;        return false;    }    #ifdef ENABLE_LOG    for (int i = 0; i < num_images; i++) {        cout << ">> image " << setw(2) << i << ": "             << setw(5) << imgs[i].rows             << setw(5) << imgs[i].cols             << setw(5) << imgs[i].channels()             << endl;    }#endif    return true;}/************************************************* There are 3 kinds of feature matchers offered by "matchers.hpp"*/bool Match(vector<cv::detail::MatchesInfo> &pairwise_matches,            const vector<cv::detail::ImageFeatures> &features,           const cv::String matcher_type = "homography",            const int range_width = -1,           const bool try_cuda = false,            const double match_conf = 0.3f){    Ptr<cv::detail::FeaturesMatcher> matcher;    if (matcher_type == "affine")     {        bool full_affine = false;        int num_matches_thresh1 = 6;        matcher = makePtr<cv::detail::AffineBestOf2NearestMatcher>(        full_affine, try_cuda, match_conf, num_matches_thresh1);    }    else if (matcher_type == "homography")     {        int num_matches_thresh1 = 6;        int num_matches_thresh2 = 6;        if (range_width == -1)            matcher = makePtr<cv::detail::BestOf2NearestMatcher>(            try_cuda, match_conf, num_matches_thresh1, num_matches_thresh2);        else            matcher = makePtr<cv::detail::BestOf2NearestRangeMatcher>(            range_width, try_cuda, match_conf, num_matches_thresh1, num_matches_thresh2);    }        (*matcher)(features, pairwise_matches);    matcher->collectGarbage();        return true;}

实验代码:https://code.csdn.net/guoyunfei20/orb.git

实验结果:

输入图像1:


输入图像2:


图像1的ORB特征点位置:


图像2的ORB特征点位置:


利用cv::detail::BestOf2NearestMatcher匹配算法得到的能匹配上的特征点(图像0 -> 图像1):







原创粉丝点击