图像处理之ORB特征提取

171 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天

1 ORB算法概述

ORB为是ORiented Brief的简称,是brief算法的改进版。ORB于2011年在《ORB: an efficient alternative to SIFT or SURF》这篇文章中被提出。此文章的摘要中说,ORB 算法比sift算法效率高两个数量级,而在计算速度上,ORB是sift的100倍,是surf的10倍。而江湖上流传的说法是,ORB算法综合性能在各种测评里相较于其他特征提取算法是最好的。

若想要引出ORB(ORiented Brief)算法,要由Brief描述子入手,下面就来先介绍Brief描述子。

2 相关概念认知

2.1 关于Brief描述子

Brief 是 Binary Robust Independent Elementary Features的缩写。这个特征描述 子是由EPFL的Calonder在ECCV2010上提出的,主要思路就是在特征点附近随机选取若干点对,将这些点对的灰度值的大小,组合成一个二进制串,并将这个二进制串作为该特征点的特征描述子。

BRIEF的优点在于速度,而缺点也相当明显。

  • 不具备旋转不变性。
  • 对噪声敏感
  • 不具备尺度不变性。

而ORB算法就是试图解决上述缺点中的1和2提出的一种新概念。

2.2关于尺度不变性

ORB没有试图解决尺度不变性(因为FAST 本身就不具有尺度不变性),但是这样只求速度的特征描述子,一般都是应用在实时的视频处理中的,这样的话就可以通过跟踪还有一些启发式的策略来解决尺度不变性的问题。

2.3关于计算速度

经过统计,ORB算法的执行速度是SIFT的100倍,是SURF的10倍。

3 ORB类相关源码简单分析

同样是在路径···opencv\build\include\opencv2\features2d\features2d.hpp 下,我们会发现和之前的SURF类源码分析时惊人相似的两句代码:

typedef ORB OrbFeatureDetector;

typedef ORB OrbDescriptorExtractor;

这就是说,ORB,OrbFeatureDetector,OrbDescriptorExtractor 这三个类,也是完全等价的。然后我们继续溯源,发现ORB类同样继承自 eature2D类:

class CV_EXPORTS_W ORB : public Feature2D

{

//......

};

接下来的分析思路,和我们之前讲解SURF类的源码时一样。

4 示例程序:ORB算法描述与匹配

在与时俱进的OpenCV中,ORB算法已被实现出来,而我们将其代码修改和注释,便得到了本期的示例程序。 此程序演示了ORB的关键点和描述符的提取,采用摄像头获取待检测图像,使用FLANN—LSH进行匹配。经过详细注释的代码如下。

源码:


//---------------------------------【头文件、命名空间包含部分】----------------------------
//		描述:包含程序所使用的头文件和命名空间
//---------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <opencv2/features2d/features2d.hpp>
using namespace cv;
using namespace std;


//--------------------------------------【main( )函数】-----------------------------------------
//          描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//--------------------------------------------------------------------------------------------
int main(	) 
{
	//【0】改变console字体颜色
	system("color 2F"); 

	//【0】载入源图,显示并转化为灰度图
	Mat srcImage = imread("1.jpg");
	imshow("原始图",srcImage);
	Mat grayImage;
	cvtColor(srcImage, grayImage, CV_BGR2GRAY);

	//------------------检测SIFT特征点并在图像中提取物体的描述符----------------------

	//【1】参数定义
	OrbFeatureDetector featureDetector;
	vector<KeyPoint> keyPoints;
	Mat descriptors;

	//【2】调用detect函数检测出特征关键点,保存在vector容器中
	featureDetector.detect(grayImage, keyPoints);

	//【3】计算描述符(特征向量)
	OrbDescriptorExtractor featureExtractor;
	featureExtractor.compute(grayImage, keyPoints, descriptors);

	//【4】基于FLANN的描述符对象匹配
	flann::Index flannIndex(descriptors, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);

	//【5】初始化视频采集对象
	VideoCapture cap(0);

	unsigned int frameCount = 0;//帧数

	//【6】轮询,直到按下ESC键退出循环
	while(1)
	{
		double time0 = static_cast<double>(getTickCount( ));//记录起始时间
		Mat  captureImage, captureImage_gray;//定义两个Mat变量,用于视频采集
		cap >>  captureImage;//采集视频帧
		if( captureImage.empty())//采集为空的处理
			continue;

		//转化图像到灰度
		cvtColor( captureImage, captureImage_gray, CV_BGR2GRAY);//采集的视频帧转化为灰度图

		//【7】检测SIFT关键点并提取测试图像中的描述符
		vector<KeyPoint> captureKeyPoints;
		Mat captureDescription;

		//【8】调用detect函数检测出特征关键点,保存在vector容器中
		featureDetector.detect(captureImage_gray, captureKeyPoints);

		//【9】计算描述符
		featureExtractor.compute(captureImage_gray, captureKeyPoints, captureDescription);

		//【10】匹配和测试描述符,获取两个最邻近的描述符
		Mat matchIndex(captureDescription.rows, 2, CV_32SC1), matchDistance(captureDescription.rows, 2, CV_32FC1);
		flannIndex.knnSearch(captureDescription, matchIndex, matchDistance, 2, flann::SearchParams());//调用K邻近算法

		//【11】根据劳氏算法(Lowe's algorithm)选出优秀的匹配
		vector<DMatch> goodMatches;
		for(int i = 0; i < matchDistance.rows; i++) 
		{
			if(matchDistance.at<float>(i, 0) < 0.6 * matchDistance.at<float>(i, 1)) 
			{
				DMatch dmatches(i, matchIndex.at<int>(i, 0), matchDistance.at<float>(i, 0));
				goodMatches.push_back(dmatches);
			}
		}

		//【12】绘制并显示匹配窗口
		Mat resultImage;
		drawMatches( captureImage, captureKeyPoints, srcImage, keyPoints, goodMatches, resultImage);
		imshow("匹配窗口", resultImage);

		//【13】显示帧率
		cout << ">帧率= " << getTickFrequency() / (getTickCount() - time0) << endl;

		//按下ESC键,则程序退出
		if(char(waitKey(1)) == 27) break;
	}

	return 0;
}



开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天