绘制关键点的drawKeypoints()函数:

void drawKeypoints(
		    const Mat &image,//输入图像
		    const vector<KeyPoint> &keypoints,//根据源图像得到的特征点,它是一个输出参数
		    Mat &outImage,//输出图像,内容取决于第五个参数
		    const Scalar &color=Scalar::all(-1),//关键点的颜色
		    int flag=DrawMatchesFlags::DEFAULT)//绘制关键点的特征标识符
``
struct DrawMatchesFlags
{
	enum
	{
        	DEFAULT=0,//创建输出图形矩阵;使用现存的输出图像绘制匹配对和特征点;对每个关键点,只绘制中间点
        	DRAW_OVER_OUTIMG=1,//不创建输出图像矩阵,而是在输出图像上绘制匹配对
        	NOT_DRAW_SINGLE_POINTS=2,//单点特征点不被绘制
        	DRAW_RICH_KEYPOINTS=4//所有关键点,绘制带大小和方向的关键点圆圈
        }
};

KeyPoint类:

class KeyPoint
{
	Point2f pt;//坐标
	float size;//特征点邻域直径
	float angle;//特征点的方向,取值[0,360),负值表示不使用
	float response;//
	int octave;//特征点所在金字塔的组
	int class_id;//用于聚类的id
}

绘制两个相匹配图像关键点的drawMatches()函数【有两个版本】:

void drawMatches(
		  const Mat &img1,//第一幅源图像
		  const vector<KeyPoint> &keypoints1,//根据第一幅源图像得到的特征点
		  const Mat &img2,//第二幅源图像
		  const vector<KeyPoint> &keypoints2,//根据第二幅源图像得到的特征点
	       *  const vector<DMatch> &matches1to2,//第一幅图像到第二幅图像的匹配点
		  Mat &outImg,//输出图像,其内容取决于第十个参数,标识符flags
		  const Scalar &matchColor=Scalar::all(-1),//匹配的线和关键点的颜色,默认为随机生成
		  const Scalar &singlePointColor=Scalar::all(-1),//单一特征点的颜色,默认为随机生成
		* const vector<char> &matchesMask=vector<char>(),//掩膜,确定那些匹配可以绘制出来,为空则绘制所有匹配
		  int flag=DrawMatchesFlags::DEFAULT)//特征绘制标识符
void drawMatches(
		  const Mat &img1,//第一幅源图像
		  const vector<KeyPoint> &keypoints1,//根据第一幅源图像得到的特征点
		  const Mat &img2,//第二幅源图像
		  const vector<KeyPoint> &keypoints2,//根据第二幅源图像得到的特征点
	       *  const vector<vector<DMatch>> &matches1to2,//第一幅图像到第二幅图像的匹配点
		  Mat &outImg,//输出图像,其内容取决于第十个参数,标识符flags
		  const Scalar &matchColor=Scalar::all(-1),//匹配的线和关键点的颜色,默认为随机生成
		* const Scalar &singlePointColor=Scalar::all(-1),//单一特征点的颜色,默认为随机生成
		  const vector<vector<char>> &matchesMask=vector<vector<char>>(),//掩膜,确定那些匹配可以绘制出来,为空则绘制所有匹配
		  int flag=DrawMatchesFlags::DEFAULT)//特征绘制标识符

匹配关键点描述子所用到的DMatch类:

struct DMatch
{         //三个构造函数
          DMatch():queryIdx(-1),trainIdx(-1),imgIdx(-1),distance(std::numeric_limits<float>::max()) {}
          DMatch(int  _queryIdx, int  _trainIdx, float  _distance ):queryIdx( _queryIdx),trainIdx( _trainIdx), imgIdx(-1),distance( _distance) {}
          DMatch(int  _queryIdx, int  _trainIdx, int  _imgIdx, float  _distance ):queryIdx(_queryIdx), trainIdx( _trainIdx), imgIdx( _imgIdx),distance( _distance) {}
          int queryIdx;  //此匹配对应的查询图像的特征描述子索引
          int trainIdx;   //此匹配对应的训练(模板)图像的特征描述子索引
          int imgIdx;    //训练图像的索引(若有多个)
          float distance;  //两个特征向量之间的欧氏距离,越小表明匹配度越高。
          bool operator < (const DMatch &m) const;
};

图像匹配时,相关类的继承关系:

opencv drawContours 内部填充 opencv drawkeypoints_opencv


cv::BFMatcher和cv::FlannBasedMatcher主要使用来自DescriptorMatcher类的match方法进行匹配

寻找最佳匹配的DescriptorMatcher::match()函数【两个版本】:

void DescriptorMatcher::match(
				const Mat &queryDescriptors,//查询描述符集
				const Mat &trainDescriptors,//训练描述符集
				vector<DMatch> &matches,//得到的匹配;在查询描述集中,描述符被掩膜标记则去除
				const Mat &mask=Mat())//指定输入查询和训练描述符允许匹配的掩膜
void DescriptorMatcher::match(
				const Mat &queryDescriptors,//查询描述符集
				vector<DMatch> &matches,//得到的匹配;在查询描述集中,描述符被掩膜标记则去除
				const vector<Mat> &masks=vector<Mat>())//一组掩膜,每个masks[i]从第i个图像trainDescCollection[i]指定输入查询和训练描述符允许匹配的掩膜

BFMatcher类:

opencv drawContours 内部填充 opencv drawkeypoints_描述符_02

normType: One of NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2. L1 and L2 norms are preferable choices for SIFT and SURF descriptors, NORM_HAMMING should be used with ORB, BRISK and BRIEF, NORM_HAMMING2 should be used with ORB when WTA_K==3 or 4 (see ORB::ORB constructor description).

crossCheck:If it is false, this is will be default BFMatcher behaviour when it finds the k nearest neighbors for each query descriptor. If crossCheck==true, then the knnMatch() method with k=1 will only return pairs (i,j) such that for i-th query descriptor the j-th descriptor in the matcher’s collection is the nearest and vice versa, i.e. the BFMatcher will only return consistent pairs. Such technique usually produces best results with minimal number of outliers when there are enough matches. This is alternative to the ratio test, used by D. Lowe in SIFT paper.

opencv drawContours 内部填充 opencv drawkeypoints_Scala_03

FlannBasedMatcher类:

opencv drawContours 内部填充 opencv drawkeypoints_Scala_04

orb特征检测的相关继承关系:

opencv drawContours 内部填充 opencv drawkeypoints_opencv_05

orb类:

opencv drawContours 内部填充 opencv drawkeypoints_描述符_06

create()成员函数详解:

static Ptr<ORB> cv::ORB::create (
				  int  	nfeatures = 500,//能够保留的特征的最大数量
				  float scaleFactor = 1.2f,//金字塔采样比率,1~2之间,越靠近1,需要的金字塔层数越多,精度越高,速度越慢
				  int  	nlevels = 8,//金字塔层数
				  int  	edgeThreshold = 31,//特征不能被检测到的边缘的尺寸
			  	  int  	firstLevel = 0,//在目前的方案中只能为0
				  int  	WTA_K = 2,//orb描述子的每个元素产生的点的数量;可以是2、3、4
				  int  	scoreType = ORB::HARRIS_SCORE,//默认使用harris算法排列特征;也可以使用FAST_SCORE,稳定性下降,但计算速度变快
		  		  int  	patchSize = 31,//orb描述子中所使用的patch的尺寸;金字塔的层数越少,特征覆盖的区域越大
				  int  	fastThreshold = 20 )//

使用ORB进行特征检测所涉及的函数和类大致就这么多。
一个实际的例子,帮助理解:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

int main ( int argc, char** argv )
{
    if ( argc != 3 )
    {
        cout<<"usage: feature_extraction img1 img2"<<endl;
        return 1;
    }
    //-- 读取图像
    Mat img_1 = imread ( argv[1], 1 );
    Mat img_2 = imread ( argv[2], 1);

    //-- 初始化
    vector<KeyPoint> keypoints_1, keypoints_2;
    Mat descriptors_1, descriptors_2;
    Ptr<FeatureDetector> detector = ORB::create();
    Ptr<DescriptorExtractor> descriptor = ORB::create();
    // Ptr<FeatureDetector> detector = FeatureDetector::create(detector_name);
    // Ptr<DescriptorExtractor> descriptor = DescriptorExtractor::create(descriptor_name);
    Ptr<DescriptorMatcher> matcher  = DescriptorMatcher::create ( "BruteForce-Hamming" );

    //-- 第一步:检测 Oriented FAST 角点位置
    detector->detect ( img_1,keypoints_1 );
    detector->detect ( img_2,keypoints_2 );

    //-- 第二步:根据角点位置计算 BRIEF 描述子
    descriptor->compute ( img_1, keypoints_1, descriptors_1 );
    descriptor->compute ( img_2, keypoints_2, descriptors_2 );

    Mat outimg1;
    drawKeypoints( img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
    imshow("ORB特征点",outimg1);

    //-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
    vector<DMatch> matches;
    //BFMatcher matcher ( NORM_HAMMING );
    matcher->match ( descriptors_1, descriptors_2, matches );

    //-- 第四步:匹配点对筛选
    double min_dist=10000, max_dist=0;

    //找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
    for ( int i = 0; i < descriptors_1.rows; i++ )
    {
        double dist = matches[i].distance;
        if ( dist < min_dist ) min_dist = dist;
        if ( dist > max_dist ) max_dist = dist;
    }
    
    // 仅供娱乐的写法
    min_dist = min_element( matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;
    max_dist = max_element( matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;

    printf ( "-- Max dist : %f \n", max_dist );
    printf ( "-- Min dist : %f \n", min_dist );

    //当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
    std::vector< DMatch > good_matches;
    for ( int i = 0; i < descriptors_1.rows; i++ )
    {
        if ( matches[i].distance <= max ( 2*min_dist, 30.0 ) )
        {
            good_matches.push_back ( matches[i] );
        }
    }

    //-- 第五步:绘制匹配结果
    Mat img_match;
    Mat img_goodmatch;
    drawMatches ( img_1, keypoints_1, img_2, keypoints_2, matches, img_match );
    drawMatches ( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch );
    imshow ( "所有匹配点对", img_match );
    imshow ( "优化后匹配点对", img_goodmatch );
    waitKey(0);

    return 0;
}

这个程序利用了这样一个事实:

opencv drawContours 内部填充 opencv drawkeypoints_opencv_07