opencv 二维图像 特征检测 特征描述 特征匹配 平面物体识别跟踪

github代码

一、Harris角点 cornerHarris() R = det(M) - k*(trace(M))^2 

算法基本思想是使用一个固定窗口在图像上进行任意方向上的滑动,

 比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,

 如果存在任意方向上的滑动,都有着较大灰度变化,

 那么我们可以认为该窗口中存在角点。

    图像特征类型:
        边缘 (Edges    物体边缘)
        角点 (Corners  感兴趣关键点( interest points)    边缘交叉点 )
        斑点(Blobs     感兴趣区域( regions of interest ) 交叉点形成的区域 )


    为什么角点是特殊的?
        因为角点是两个边缘的连接点(交点)它代表了两个边缘变化的方向上的点。
        图像梯度有很高的变化。这种变化是可以用来帮助检测角点的。


    G = SUM( W(x,y) * [I(x+u, y+v) -I(x,y)]^2 )

     [u,v] 是窗口的偏移量
     (x,y) 是窗口内所对应的像素坐标位置,窗口有多大,就有多少个位置
     w(x,y)是窗口函数,最简单情形就是窗口内的所有像素所对应的w权重系数均为1。
                 设定为以窗口中心为原点的二元正态分布

    泰勒展开(I(x+u, y+v) 相当于 导数)
    G = SUM( W(x,y) * [I(x,y) + u*Ix + v*Iy - I(x,y)]^2)
      = SUM( W(x,y) * (u*u*Ix*Ix + v*v*Iy*Iy))
      = SUM(W(x,y) * [u v] * [Ix^2   Ix*Iy] * [u 
                              Ix*Iy  Iy^2]     v] )
      = [u v]  * SUM(W(x,y) * [Ix^2   Ix*Iy] ) * [u  应为 [u v]为常数 可以拿到求和外面
                               Ix*Iy  Iy^2]       v]    
      = [u v] * M * [u
                     v]
    则计算 det(M)   矩阵M的行列式的值  取值为一个标量,写作det(A)或 | A |  
    矩阵表示的空间的单位面积/体积/..
     trace(M) 矩阵M的迹 矩阵M的对角线元素求和,
    用字母T来表示这种算子,他的学名叫矩阵的迹

    M的两个特征值为 lamd1  lamd2

    det(M)    = lamd1 * lamd2
    trace(M)  = lamd1 + lamd2

    R = det(M)  -  k*(trace(M))^2 
    其中k是常量,一般取值为0.04~0.06,
    R大于一个阈值的话就认为这个点是 角点

    因此可以得出下列结论:
    >特征值都比较大时,即窗口中含有角点
    >特征值一个较大,一个较小,窗口中含有边缘
    >特征值都比较小,窗口处在平坦区域

    https://blog.csdn.net/woxincd/article/details/60754658

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include 
#include 
#include 

using namespace cv;
using namespace std;

//全局变量
Mat src, src_gray;
int thresh = 200;//阈值  R大于一个阈值的话就认为这个点是 角点
int max_thresh = 255;

char* source_window = "Source image";
char* corners_window = "Corners detected";

//函数声明  滑动条回调函数
void cornerHarris_demo( int, void* );

//主函数
int main( int argc, char** argv )
{
  string imageName("../../common/data/building.jpg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }
  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  // 转换成灰度图
  cvtColor( src, src_gray, CV_BGR2GRAY );

  //创建一个窗口 和 滑动条
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  createTrackbar( "Threshold阈值: ", source_window, &thresh, max_thresh, cornerHarris_demo );
  imshow( source_window, src );//显示图像

  cornerHarris_demo( 0, 0 );//初始化 滑动条回调函数

  waitKey(0);
  return(0);
}

// 滑动条回调函数
void cornerHarris_demo( int, void* )
{

  Mat dst, dst_norm, dst_norm_scaled;
  dst = Mat::zeros( src.size(), CV_32FC1 );

  /// 检测参数
  int blockSize = 2;// 滑动窗口大小
  int apertureSize = 3;// Sobel算子的大小(默认值为3) 用来计算 梯度 Ix^2   Ix*Iy Iy^2 
  double k = 0.04;// R = det(M)  -  k*(trace(M))^2   一般取值为0.04~0.06

  /// 检测角点
  cornerHarris( src_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT );

  /// 归一化  得到 R值 的 图像大小矩阵
  normalize( dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
  convertScaleAbs( dst_norm, dst_norm_scaled );

  /// 在检测到的角点处  画圆
  for( int j = 0; j < dst_norm.rows ; j++ )
     { for( int i = 0; i < dst_norm.cols; i++ )
          {
            if( (int) dst_norm.at(j,i) > thresh )// R大于一个阈值的话就认为这个点是 角点
              {
               circle( dst_norm_scaled, Point( i, j ), 5,  Scalar(0), 2, 8, 0 );
              }
          }
     }
  /// 显示结果
  namedWindow( corners_window, CV_WINDOW_AUTOSIZE );
  imshow( corners_window, dst_norm_scaled );
}

二、Shi-Tomasi 算法 goodFeaturesToTrack()

    是Harris 算法的改进。
    Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减,
    再将差值同预先给定的阈值进行比较。

    后来Shi 和Tomasi 提出改进的方法,
    若两个特征值中较小的一个大于最小阈值,则会得到强角点。
    M 对角化>>> M的两个特征值为 lamd1  lamd2

    R = mini(lamd1,lamd2) > 阈值 认为是角点
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include 
#include 
#include 
using namespace cv;
using namespace std;

//全局变量
Mat src, src_gray;
int maxCorners = 23;//角点数量
int maxTrackbar = 100;
RNG rng(12345);//随机数
char* source_window = "Image";

//函数声明  滑动条回调函数
void goodFeaturesToTrack_Demo( int, void* );

//主函数
int main( int argc, char** argv )
{

  string imageName("../../common/data/building.jpg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  // 转换成灰度图
  cvtColor( src, src_gray, COLOR_BGR2GRAY );
  //创建一个窗口 和 滑动条
  namedWindow( source_window, WINDOW_AUTOSIZE );
  createTrackbar( "Max  corners:", source_window, &maxCorners, maxTrackbar, goodFeaturesToTrack_Demo );

  imshow( source_window, src );//显示图像
  goodFeaturesToTrack_Demo( 0, 0 );//初始化 滑动条回调函数

  waitKey(0);
  return(0);
}

// 滑动条回调函数
void goodFeaturesToTrack_Demo( int, void* )
{
  if( maxCorners < 1 ) { maxCorners = 1; }
  vector corners;//角点容器
  double qualityLevel = 0.01;
  double minDistance = 10;
  int blockSize = 3;//滑窗大小
  bool useHarrisDetector = false;
  double k = 0.04;// harris 角点阈值
  Mat copy;
  copy = src.clone();
  goodFeaturesToTrack( src_gray,
               corners,
               maxCorners,
               qualityLevel,
               minDistance,
               Mat(),
               blockSize,
               useHarrisDetector,
               k );
  cout<<"** Number of corners detected: "<

三、FAST角点检测算法 ORB特征检测中使用的就是这种角点检测算法

   FAST角点检测算法  ORB特征检测中使用的就是这种角点检测算法
    周围区域灰度值 都较大 或 较小

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

    该算法检测的角点定义为在像素点的周围邻域内有足够多的像素点与该点处于不同的区域。
    应用到灰度图像中,即有足够多的像素点的灰度值大于该点的灰度值或者小于该点的灰度值。

    p点附近半径为3的圆环上的16个点,
    一个思路是若其中有连续的12( (FAST-9,当然FAST-10、FAST-11、FAST-12、FAST-12)
        )个点的灰度值与p点的灰度值差别超过某一阈值,
    则可以认为p点为角点。

        之后可进行非极大值抑制

    这一思路可以使用机器学习的方法进行加速。
    对同一类图像,例如同一场景的图像,可以在16个方向上进行训练,
    得到一棵决策树,从而在判定某一像素点是否为角点时,
    不再需要对所有方向进行检测,

    而只需要按照决策树指定的方向进行2-3次判定即可确定该点是否为角点。 



#include "opencv2/highgui/highgui.hpp"
#include   
#include "opencv2/imgproc/imgproc.hpp"
#include 
#include 
#include 

using namespace cv;
using namespace std;

//全局变量
Mat src, src_gray;
int thresh = 50;//阈值  R大于一个阈值的话就认为这个点是 角点
int max_thresh = 200;

char* source_window = "Source image";
char* corners_window = "Corners detected";

//函数声明  滑动条回调函数
void cornerFast_demo( int, void* );

//主函数
int main( int argc, char** argv )
{
  string imageName("../../common/data/building.jpg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }
  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  // 转换成灰度图
  cvtColor( src, src_gray, CV_BGR2GRAY );

  //创建一个窗口 和 滑动条
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  createTrackbar( "Threshold阈值: ", source_window, &thresh, max_thresh, cornerFast_demo );
  imshow( source_window, src );//显示图像

  cornerFast_demo( 0, 0 );//初始化 滑动条回调函数

  waitKey(0);
  return(0);
}

// 滑动条回调函数
void cornerFast_demo( int, void* )
{
  Mat dst = src.clone();
  //cv::FastFeatureDetector fast(50);   // 检测的阈值为50  
  std::vector keyPoints; 
  //fast.detect(src_gray, keyPoints);  // 检测角点
  FAST(src_gray, keyPoints,thresh);
  // 画角点
  drawKeypoints( dst, keyPoints, dst, Scalar(0,0,255), DrawMatchesFlags::DRAW_OVER_OUTIMG); 
  // 显示结果
  namedWindow( corners_window, CV_WINDOW_AUTOSIZE );
  imshow( corners_window,  dst );
}


四、通过自定义 R的计算方法和自适应阈值 来定制化检测角点

计算 M矩阵
计算判断矩阵 R

设置自适应阈值

阈值大小为 判断矩阵 最小值和最大值之间 百分比
阈值为 最小值 + (最大值-最小值)× 百分比
百分比 = myHarris_qualityLevel/max_qualityLevel


#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include 
#include 
#include 
using namespace cv;
using namespace std;

// 全局变量
Mat src, src_gray;
Mat myHarris_dst; Mat myHarris_copy; Mat Mc;// Harris角点角点相关 判断矩阵R
Mat myShiTomasi_dst; Mat myShiTomasi_copy;  // Shi-Tomasi 角点检测算法 

int myShiTomasi_qualityLevel = 50;// Shi-Tomasi 角点检测算法 阈值
int myHarris_qualityLevel = 50;   // Harris角点角点检测算法 阈值
int max_qualityLevel = 100;       // 最大阈值  百分比  myHarris_qualityLevel/max_qualityLevel
// 阈值为 最小值 + (最大值-最小值)× 百分比

double myHarris_minVal; double myHarris_maxVal;       // Harris角点 判断矩阵的最小最大值
double myShiTomasi_minVal; double myShiTomasi_maxVal;// Shi-Tomasi 角点 判断矩阵的最小最大值

RNG rng(12345);//随机数  产生 随机颜色

const char* myHarris_window = "My Harris corner detector";
const char* myShiTomasi_window = "My Shi Tomasi corner detector";

//函数声明
void myShiTomasi_function( int, void* );
void myHarris_function( int, void* );


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

  string imageName("../../common/data/building.jpg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }
  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  // 转换成灰度图
  cvtColor( src, src_gray, COLOR_BGR2GRAY );

  int blockSize = 3; // 滑动窗口大小
  int apertureSize = 3;// Sobel算子的大小(默认值为3) 用来计算 梯度 Ix^2   Ix*Iy Iy^2 
//============== Harris角点==========================
  myHarris_dst = Mat::zeros( src_gray.size(), CV_32FC(6) );//得到的 矩阵M的特征值
  Mc = Mat::zeros( src_gray.size(), CV_32FC1 );//定制化的 判断矩阵 R
  // 计算 矩阵M的特征值
  cornerEigenValsAndVecs( src_gray, myHarris_dst, blockSize, apertureSize, BORDER_DEFAULT );
  // 计算判断矩阵 R
  for( int j = 0; j < src_gray.rows; j++ )
     { for( int i = 0; i < src_gray.cols; i++ )
          {
            float lambda_1 = myHarris_dst.at(j, i)[0];
            float lambda_2 = myHarris_dst.at(j, i)[1];
            Mc.at(j,i) = lambda_1*lambda_2 - 0.04f*pow( ( lambda_1 + lambda_2 ), 2 );
          }
     } // 判断矩阵 R 的最低值和最高值
  minMaxLoc( Mc, &myHarris_minVal, &myHarris_maxVal, 0, 0, Mat() );
  //创建一个窗口 和 滑动条
  namedWindow( myHarris_window, WINDOW_AUTOSIZE );
  createTrackbar( " Quality Level:", myHarris_window, &myHarris_qualityLevel, max_qualityLevel, myHarris_function );
  myHarris_function( 0, 0 );

//===================Shi-Tomasi 角点检测算法===============================
  myShiTomasi_dst = Mat::zeros( src_gray.size(), CV_32FC1 );// Harris角点角点检测算法 判断矩阵  min(lambda_1 , lambda_2)
  cornerMinEigenVal( src_gray, myShiTomasi_dst, blockSize, apertureSize, BORDER_DEFAULT );
  minMaxLoc( myShiTomasi_dst, &myShiTomasi_minVal, &myShiTomasi_maxVal, 0, 0, Mat() );
  //创建一个窗口 和 滑动条
  namedWindow( myShiTomasi_window, WINDOW_AUTOSIZE );
  createTrackbar( " Quality Level:", myShiTomasi_window, &myShiTomasi_qualityLevel, max_qualityLevel, myShiTomasi_function );
  myShiTomasi_function( 0, 0 );
  waitKey(0);
  return(0);
}
// Shi-Tomasi 角点检测算法 滑动条回调函数
void myShiTomasi_function( int, void* )
{

  if( myShiTomasi_qualityLevel < 1 ) 
	{ 
	  myShiTomasi_qualityLevel = 1; 
	}
  // 自适应阈值 
  // 阈值为 最小值 + (最大值-最小值)× 百分比
  // 百分比 = yShiTomasi_qualityLevel/max_qualityLevel
  float thresh = myShiTomasi_minVal + ( myShiTomasi_maxVal - myShiTomasi_minVal ) * myShiTomasi_qualityLevel/max_qualityLevel;
  myShiTomasi_copy = src.clone();
  for( int j = 0; j < src_gray.rows; j++ )
     { 
	for( int i = 0; i < src_gray.cols; i++ )
          {
            if( myShiTomasi_dst.at(j,i) > thresh)
            { // 在角点处画圆圈
	    circle( myShiTomasi_copy, Point(i,j), 4, Scalar( rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255) ), -1, 8, 0 ); 
            }
         }
     }
  imshow( myShiTomasi_window, myShiTomasi_copy );
}
 

// Harris角点 滑动条回调函数
void myHarris_function( int, void* )
{

  if( myHarris_qualityLevel < 1 ) 
	{ 
	myHarris_qualityLevel = 1; 
	}

  // 自适应阈值 
  // 阈值为 最小值 + (最大值-最小值)× 百分比
  // 百分比 = myHarris_qualityLevel/max_qualityLevel
  float thresh = myHarris_minVal + ( myHarris_maxVal - myHarris_minVal ) * myHarris_qualityLevel/ max_qualityLevel;
  myHarris_copy = src.clone();
  for( int j = 0; j < src_gray.rows; j++ )
     { for( int i = 0; i < src_gray.cols; i++ )
          {
            if( Mc.at(j,i) >  thresh )
             { // 在角点处画圆圈
	     circle( myHarris_copy, Point(i,j), 4, Scalar( rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255) ), -1, 8, 0 );
 	     }
          }
     }
  imshow( myHarris_window, myHarris_copy );
}

五、亚像素级的角点检测  补偿

使用OpenCV函数 cornerSubPix
寻找更精确的角点位置 (不是整数类型的位置,而是更精确的浮点类型位置).

除了利用Harris进行角点检测和利用Shi-Tomasi方法进行角点检测外,
还可以使用cornerEigenValsAndVecs()函数和cornerMinEigenVal()函数自定义角点检测函数。
如果对角点的精度有更高的要求,可以用cornerSubPix()函数将角点定位到子像素,从而取得亚像素级别的角点检测效果。

使用cornerSubPix()函数在goodFeaturesToTrack()的角点检测基础上将角点位置精确到亚像素级别

常见的亚像素级别精准定位方法有三类:
    1. 基于插值方法
    2. 基于几何矩寻找方法
    3. 拟合方法 - 比较常用

拟合方法中根据使用的公式不同可以分为
    1. 高斯曲面拟合与
    2. 多项式拟合等等。

以高斯拟合为例:

    窗口内的数据符合二维高斯分布
    Z = n / (2 * pi * 西格玛^2) * exp(-P^2/(2*西格玛^2))
    P = sqrt( (x-x0)^2 + (y-y0)^2)

    x,y   原来 整数点坐标
    x0,y0 亚像素补偿后的 坐标 需要求取

    ln(Z) = n0 + x0/(西格玛^2)*x +  y0/(西格玛^2)*y - 1/(2*西格玛^2) * x^2 - 1/(2*西格玛^2) * y^2
              n0 +            n1*x + n2*y +             n3*x^2 +              n3 * y^2
       
    对窗口内的像素点 使用最小二乘拟合 得到上述 n0 n1 n2 n3
      则 x0 = - n1/(2*n3)
          y0 = - n2/(2*n3)

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include 
#include 
#include 

using namespace cv;
using namespace std;

/// 全局变量
Mat src, src_gray;

int maxCorners = 10;
int maxTrackbar = 50;//需要检测的角点数量

RNG rng(12345);//随机数 产生随机颜色 
char* source_window = "Image";
char* refineWindow = "refinement";  

/// 滑动条 回调函数 声明  亚像素级的角点检测
void goodFeaturesToTrack_Demo( int, void* );

//主函数
int main( int argc, char** argv )
{

  string imageName("../../common/data/building.jpg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  // 转换成灰度图
  cvtColor( src, src_gray, CV_BGR2GRAY );

  //创建一个窗口 和 滑动条
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  createTrackbar( "Max  corners:", source_window, &maxCorners, maxTrackbar, goodFeaturesToTrack_Demo);

  imshow( source_window, src );

  goodFeaturesToTrack_Demo( 0, 0 );

  waitKey(0);
  return(0);
}

// 亚像素级的角点检测
void goodFeaturesToTrack_Demo( int, void* )
{
  if( maxCorners < 1 ) { maxCorners = 1; }

  /// Parameters for Shi-Tomasi algorithm
  vector corners;//角点容器
  double qualityLevel = 0.01;
  double minDistance = 10;
  int blockSize = 3;//滑窗大小
  bool useHarrisDetector = false;
  double k = 0.04;// harris 角点阈值

  /// 复制源图像
  Mat copy, refineSrcCopy;
  copy = src.clone();
  refineSrcCopy = src.clone();
  /// 检测 Shi-Tomasi 角点
  goodFeaturesToTrack( src_gray,
                       corners,
                       maxCorners,//最多角点数量
                       qualityLevel,
                       minDistance,
                       Mat(),
                       blockSize,
                       useHarrisDetector,
                       k );


  /// 显示 角点
  cout<<"** Number of corners detected: "<

六、斑点检测原理 SIFT SURF

    SIFT定位算法关键步骤的说明 
    http://www.cnblogs.com/ronny/p/4028776.html
    SIFT原理与源码分析 https://blog.csdn.net/xiaowei_cqu/article/details/8069548

    该算法大概可以归纳为三步:
        1)高斯差分金字塔的构建;
        2)特征点的搜索;
        3)特征描述。

    DoG尺度空间构造(Scale-space extrema detection)
    关键点搜索与定位(Keypoint localization)
    方向赋值(Orientation assignment)
    关键点描述(Keypoint descriptor)
    OpenCV实现:特征检测器FeatureDetector
    SIFT中LoG和DoG的比较


    SURF算法与源码分析、上  加速鲁棒特征(SURF)
    www.cnblogs.com/ronny/p/4045979.html
    https://blog.csdn.net/abcjennifer/article/details/7639681

通过在不同的尺度上利用积分图像可以有效地计算出近似Harr小波值,
简化了二阶微分模板的构建,搞高了尺度空间的特征检测的效率。
在以关键点为中心的3×3×3像素邻域内进行非极大值抑制,
最后通过对斑点特征进行插值运算,完成了SURF特征点的精确定位。

而SURF特征点的描述,则也是充分利用了积分图,用两个方向上的Harr小波模板来计算梯度,
然后用一个扇形对邻域内点的梯度方向进行统计,求得特征点的主方向。
  
  
// SURF放在另外一个包的xfeatures2d里边了,在github.com/Itseez/opencv_contrib 这个仓库里。
// 按说明把这个仓库编译进3.0.0就可以用了。
opencv2中SurfFeatureDetector、SurfDescriptorExtractor、BruteForceMatcher在opencv3中发生了改变。
具体如何完成特征点匹配呢?示例如下:

//寻找关键点
int minHessian = 700;
Ptrdetector = SURF::create(minHessian);
detector->detect( srcImage1, keyPoint1 );
detector->detect( srcImage2, keyPoints2 );

//绘制特征关键点
Mat img_keypoints_1; Mat img_keypoints_2;
drawKeypoints( srcImage1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
drawKeypoints( srcImage2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT );

//显示效果图
imshow("特征点检测效果图1", img_keypoints_1 );
imshow("特征点检测效果图2", img_keypoints_2 );

//计算特征向量
Ptrextractor = SURF::create();
Mat descriptors1, descriptors2;
extractor->compute( srcImage1, keyPoint1, descriptors1 );
extractor->compute( srcImage2, keyPoints2, descriptors2 );

//使用BruteForce进行匹配
Ptr matcher = DescriptorMatcher::create("BruteForce");
std::vector< DMatch > matches;
matcher->match( descriptors1, descriptors2, matches );

//绘制从两个图像中匹配出的关键点
Mat imgMatches;
drawMatches( srcImage1, keyPoint1, srcImage2, keyPoints2, matches, imgMatches );//进行绘制
//显示
imshow("匹配图", imgMatches );


3.x的特征检测:

    算法:SURF,SIFT,BRIEF,FREAK 
    类:cv::xfeatures2d::SURF

    cv::xfeatures2d::SIFT
    cv::xfeatures::BriefDescriptorExtractor
    cv::xfeatures2d::FREAK
    cv::xfeatures2d::StarDetector

    需要进行以下几步

    加入opencv_contrib
    包含opencv2/xfeatures2d.hpp
    using namepsace cv::xfeatures2d
    使用create(),detect(),compute(),detectAndCompute()

七、二进制字符串特征描述子

注意到在两种角点检测算法里,我们并没有像SIFT或SURF那样提到特征点的描述问题。
事实上,特征点一旦检测出来,无论是斑点还是角点描述方法都是一样的,
可以选用你认为最有效的特征描述子。

比较有代表性的就是 浮点型特征描述子(sift、surf 欧氏距离匹配)和
二进制字符串特征描述子 (字符串汉明距离 匹配 )。

1 浮点型特征描述子(sift、surf 欧氏距离匹配):
	像SIFT与SURF算法里的,用梯度统计直方图来描述的描述子都属于浮点型特征描述子。
	但它们计算起来,算法复杂,效率较低.

	SIFT特征采用了128维的特征描述子,由于描述子用的浮点数,所以它将会占用512 bytes的空间。
	类似地,对于SURF特征,常见的是64维的描述子,它也将占用256bytes的空间。
	如果一幅图像中有1000个特征点(不要惊讶,这是很正常的事),
	那么SIFT或SURF特征描述子将占用大量的内存空间,对于那些资源紧张的应用,
	尤其是嵌入式的应用,这样的特征描述子显然是不可行的。而且,越占有越大的空间,
	意味着越长的匹配时间。

	我们可以用PCA、LDA等特征降维的方法来压缩特征描述子的维度。
	还有一些算法,例如LSH,将SIFT的特征描述子转换为一个二值的码串,
	然后这个码串用汉明距离进行特征点之间的匹配。这种方法将大大提高特征之间的匹配,
	因为汉明距离的计算可以用异或操作然后计算二进制位数来实现,在现代计算机结构中很方便。

2 二进制字符串特征描述子 (字符串汉明距离 匹配 ):
	如BRIEF。后来很多二进制串描述子ORB,BRISK,FREAK等都是在它上面的基础上的改进。

【A】 BRIEF:  Binary Robust Independent Elementary Features
  http://www.cnblogs.com/ronny/p/4081362.html

	它需要先平滑图像,然后在特征点周围选择一个Patch,在这个Patch内通过一种选定的方法来挑选出来nd个点对。
	然后对于每一个点对(p,q),我们来比较这两个点的亮度值,
	如果I(p)>I(q)则这个点对生成了二值串中一个的值为1,
	如果I(p)

/*
使用 orb 特征检测 匹配 
使用二维特征点(Features2D)和单映射(Homography)寻找已知物体

【1】 创建新的控制台(console)项目。读入两个输入图像。
【2】 检测两个图像的关键点(尺度旋转都不发生变化的关键点)
【3】 计算每个关键点的描述向量(Descriptor)
【4】 计算两幅图像中的关键点对应的描述向量距离,寻找两图像中距离最近的描述向量对应的关键点,即为两图像中匹配上的关键点:
【5】 寻找两个点集合中的单映射变换(homography transformation):
【6】 创建内匹配点集合同时绘制出匹配上的点。用perspectiveTransform函数来通过单映射来映射点:
【7】 用 drawMatches 来绘制内匹配点.

*/
#include 
#include 
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/calib3d/calib3d.hpp"

using namespace cv;
using namespace std;
void readme();

// 主函数
int main( int argc, char** argv )
{
  /// 加载源图像
    string imageName1("../../common/data/box.png"); // 图片文件名路径(默认值)
    string imageName2("../../common/data/box_in_scene.png"); // 图片文件名路径(默认值)
    if( argc > 2)
    {
        imageName1 = argv[1];//如果传递了文件 就更新
	imageName2 = argv[2];//如果传递了文件 就更新
    }
    Mat img_object = imread( imageName1, CV_LOAD_IMAGE_GRAYSCALE );
    Mat img_scene  = imread( imageName2, CV_LOAD_IMAGE_GRAYSCALE );
    if( img_object.empty() || img_scene.empty() )
    { 
        cout <<  "can't load image " << endl;
        readme();
	return -1;  
    }

//======【1】检测关键点  ==================
  std::vector keypoints_object, keypoints_scene;

  //int minHessian = 400; // SURF关键点
  //SurfFeatureDetector detector( minHessian );
  //detector.detect( img_object, keypoints_object );
  //detector.detect( img_scene, keypoints_scene );

  Ptr orb = ORB::create();//orb 检测器
  orb->detect(img_object, keypoints_object);
  orb->detect(img_scene,  keypoints_scene);

  cout<< "keypoints_object size() " << keypoints_object.size() << endl;
  cout<< "keypoints_scene size() "  << keypoints_scene.size() << endl;

//======【2】计算描述子========
  Mat descriptors_object, descriptors_scene;

  //SurfDescriptorExtractor extractor;
  //extractor.compute( img_object, keypoints_object, descriptors_object );
  //extractor.compute( img_scene, keypoints_scene, descriptors_scene );

  orb->compute(img_object, keypoints_object, descriptors_object);
  orb->compute(img_scene,  keypoints_scene,  descriptors_scene);

///*
//=====【3】对描述子进行匹配 使用FLANN 匹配
// 鲁棒匹配器设置 描述子匹配器
  Ptr indexParams = makePtr(6, 12, 1); //  LSH index parameters
  Ptr searchParams = makePtr(50);       //   flann search parameters
  // instantiate FlannBased matcher
  Ptr matcher = makePtr(indexParams, searchParams);

  //FlannBasedMatcher matcher;
  //std::vector< DMatch > matches;
  //matcher.match( descriptors_object, descriptors_scene, matches );

 std::vector > matches12, matches21;// 最匹配 和 次匹配
 //std::vector matches12;
 matcher->knnMatch(descriptors_object, descriptors_scene, matches12, 1); // 1->2
 // matcher->knnMatch(descriptors_scene, descriptors_object, matches21, 1);
 cout<< "match12 size() " << matches12.size() << endl;

///*
   double max_dist = 0; double min_dist = 100;
  //  找到最小的最大距离
  for( int i = 0; i < descriptors_object.rows; i++ )
  { 
    double dist = matches12[i][0].distance;//1匹配上2中点对 距离
    if( dist < min_dist ) min_dist = dist;//最小距离
    if( dist > max_dist ) max_dist = dist;//最大距离
  }
  printf("-- Max dist : %f \n", max_dist );
  printf("-- Min dist : %f \n", min_dist );
///*
//====筛选 最优的匹配点对  距离小于 2.3 *最小距离 为好的匹配点对============
// 其次 可以在考虑 相互匹配 的 才为 好的匹配点
  std::vector< DMatch > good_matches;
  for( int i = 0; i < descriptors_object.rows; i++ )
  { if( matches12[i][0].distance < 2.3 * min_dist )
     { good_matches.push_back( matches12[i][0]); }
  }

 cout<< "good_matches size() " << good_matches.size() << endl;
///*
//=====显示匹配点======================
  Mat img_matches;
  drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,
               good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
               vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );

  //-- 得到2d-2d匹配点坐标
  std::vector obj;
  std::vector scene;
  for( int i = 0; i < good_matches.size(); i++ )
  {
    //-- Get the keypoints from the good matches
    obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );
    scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );
  }

  // 求解单映射矩阵 H  p1 = H * p2   平面变换
  Mat H = findHomography( obj, scene, CV_RANSAC );

  //-- 得到需要检测的物体的四个顶点  
  std::vector obj_corners(4);
  obj_corners[0] = cvPoint(0,0); 
  obj_corners[1] = cvPoint( img_object.cols, 0 );
  obj_corners[2] = cvPoint( img_object.cols, img_object.rows ); 
  obj_corners[3] = cvPoint( 0, img_object.rows );

  std::vector scene_corners(4);// 场景中的 该物体的顶点

  // 投影过去
  perspectiveTransform( obj_corners, scene_corners, H);

  //--在场景中显示检测到的物体 (the mapped object in the scene - image_2 )
  line( img_matches, scene_corners[0] + Point2f( img_object.cols, 0), scene_corners[1] + Point2f( img_object.cols, 0), Scalar(0, 255, 0), 4 );
  line( img_matches, scene_corners[1] + Point2f( img_object.cols, 0), scene_corners[2] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );
  line( img_matches, scene_corners[2] + Point2f( img_object.cols, 0), scene_corners[3] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );
  line( img_matches, scene_corners[3] + Point2f( img_object.cols, 0), scene_corners[0] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );

  //-- 显示检测结果
  imshow( "Good Matches & Object detection", img_matches );

  waitKey(0);
  return 0;
}

// 用法
  void readme()
  { std::cout << " Usage: ./SURF_descriptor  " << std::endl; }

八、KAZE非线性尺度空间 特征

#include 
#include 
#include "opencv2/highgui/highgui.hpp"
#include 
#include 
#include 

using namespace std;
using namespace cv;

// 全局变量 
const float inlier_threshold = 2.5f; // 单应变换后 和匹配点差值阈值
const float nn_match_ratio = 0.8f;   // 最近距离/次近距离 < 阈值 

int main(int argc, char** argv)
{
/// 加载源图像
    string imageName1("../../common/data/graf1.png"); // 图片文件名路径(默认值)
    string imageName2("../../common/data/graf3.png"); // 图片文件名路径(默认值)
    if( argc > 2)
    {
        imageName1 = argv[1];//如果传递了文件 就更新
	imageName2 = argv[2];//如果传递了文件 就更新
    }
    Mat img1 = imread( imageName1, CV_LOAD_IMAGE_GRAYSCALE );
    Mat img2  = imread( imageName2, CV_LOAD_IMAGE_GRAYSCALE );
    if( img1.empty() || img2.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
    // 单应变换矩阵
    Mat homography;
    FileStorage fs("../../common/data/H1to3p.xml", FileStorage::READ);
    fs.getFirstTopLevelNode() >> homography;


    vector kpts1, kpts2;//关键点
    Mat desc1, desc2;//描述子
    // 非线性尺度空间 AKAZE特征检测
    Ptr akaze = AKAZE::create();
    // 检测 + 描述
    akaze->detectAndCompute(img1, noArray(), kpts1, desc1);
    akaze->detectAndCompute(img2, noArray(), kpts2, desc2);
    // 汉明距离匹配器
    BFMatcher matcher(NORM_HAMMING);
    // 匹配点对 二维数组 一个点检测多个匹配点(按距离远近)
    vector< vector > nn_matches;
    // 进行描述子匹配
    matcher.knnMatch(desc1, desc2, nn_matches, 2);

    vector matched1, matched2, inliers1, inliers2;
     // matched1, matched2, 最近距离的匹配/次近距离的匹配的匹配点
     // inliers1, inliers2 一个匹配点 单应变换后 和 其匹配点的 差平方和 开根号 小于 阈值的 更好的匹配点
    vector good_matches;//较好的匹配

   //====最近距离的匹配/次近距离的匹配 小于一个阈值 才认为是 匹配点对 ================
    for(size_t i = 0; i < nn_matches.size(); i++) {
        DMatch first = nn_matches[i][0];
        float dist1 = nn_matches[i][0].distance;//最近距离的匹配
        float dist2 = nn_matches[i][1].distance;//次近距离的匹配 
        if(dist1 < nn_match_ratio * dist2) {
            matched1.push_back(kpts1[first.queryIdx]);// 对应图1的关键点坐标
            matched2.push_back(kpts2[first.trainIdx]);// 对应图2的关键点坐标
        }
    }

   //==========一个匹配点 单应变换后 和 其匹配点的 差平方和 开根号 < 阈值 为最终的匹配点对
    for(unsigned i = 0; i < matched1.size(); i++) {
        Mat col = Mat::ones(3, 1, CV_64F);//图1 点 的 其次表达方式 (u,v,1)
        col.at(0) = matched1[i].pt.x;
        col.at(1) = matched1[i].pt.y;
        col = homography * col;// 单应变换 后
        col /= col.at(2);// 将第三维归一化(x,y,z)-> (x/z,y/z,z/z)->(u',v',1)
        // 计算 一个匹配点 单应变换后 和 其匹配点的 差平方和 开根号
        double dist = sqrt( pow(col.at(0) - matched2[i].pt.x, 2) +
                            pow(col.at(1) - matched2[i].pt.y, 2));

        if(dist < inlier_threshold) {
            int new_i = static_cast(inliers1.size());
            inliers1.push_back(matched1[i]);// 内点 符合 单应变换的   点
            inliers2.push_back(matched2[i]);// 是按 匹配点对方式存入的 
            good_matches.push_back(DMatch(new_i, new_i, 0));//所以匹配点对 1-1 2-2 3-3 4-4 5-5
        }
    }

// ======= 显示 ===============
    Mat res;//最后的图像
    drawMatches(img1, inliers1, img2, inliers2, good_matches, res, Scalar::all(-1), Scalar::all(-1),
               vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    imshow("res.png", res);//显示匹配后的图像
    
    double inlier_ratio = inliers1.size() * 1.0 / matched1.size();
    cout << "A-KAZE Matching Results" << endl;
    cout << "*******************************" << endl;
    cout << "# Keypoints 1:                        \t" << kpts1.size() << endl;
    cout << "# Keypoints 2:                        \t" << kpts2.size() << endl;
    cout << "# Matches:                            \t" << matched1.size() << endl;
    cout << "# Inliers:                            \t" << inliers1.size() << endl;
    cout << "# Inliers Ratio:                      \t" << inlier_ratio << endl;
    cout << endl;
    return 0;
}

基于非线性尺度空间的KAZE特征提取方法以及它的改进AKATE
https://blog.csdn.net/chenyusiyuan/article/details/8710462

KAZE是日语‘风’的谐音,寓意是就像风的形成是空气在空间中非线性的流动过程一样,
KAZE特征检测是在图像域中进行非线性扩散处理的过程。

传统的SIFT、SURF等特征检测算法都是基于 线性的高斯金字塔 进行多尺度分解来消除噪声和提取显著特征点。
但高斯分解是牺牲了局部精度为代价的,容易造成边界模糊和细节丢失。

非线性的尺度分解有望解决这种问题,但传统方法基于正向欧拉法(forward Euler scheme)
求解非线性扩散(Non-linear diffusion)方程时迭代收敛的步长太短,耗时长、计算复杂度高。

由此,KAZE算法的作者提出采用加性算子分裂算法(Additive Operator Splitting, AOS)
来进行非线性扩散滤波,可以采用任意步长来构造稳定的非线性尺度空间。


非线性扩散滤波
	Perona-Malik扩散方程:
		具体地,非线性扩散滤波方法是将图像亮度(L)在不同尺度上的变化视为某种形式的
		流动函数(flow function)的散度(divergence),可以通过非线性偏微分方程来描述:
	AOS算法:
		由于非线性偏微分方程并没有解析解,一般通过数值分析的方法进行迭代求解。
		传统上采用显式差分格式的求解方法只能采用小步长,收敛缓慢。

KAZE特征检测与描述

KAZE特征的检测步骤大致如下:
1) 首先通过AOS算法和可变传导扩散(Variable  Conductance  Diffusion)([4,5])方法来构造非线性尺度空间。
2) 检测感兴趣特征点,这些特征点在非线性尺度空间上经过尺度归一化后的Hessian矩阵行列式是局部极大值(3×3邻域)。
3) 计算特征点的主方向,并且基于一阶微分图像提取具有尺度和旋转不变性的描述向量。

特征点检测
KAZE的特征点检测与SURF类似,是通过寻找不同尺度归一化后的Hessian局部极大值点来实现的。
非线性尺度空间的KAZE特征提取
【1】非线性尺度空间 AKAZE特征检测
【2】检测 + 描述
【3】汉明距离匹配器
【4】进行描述子匹配
【5】最近距离的匹配/次近距离的匹配 小于一个阈值 才认为是 初级匹配点对
【6】一个匹配点 单应变换后 和 其匹配点的 差平方和 开根号 < 阈值  为最终的匹配点对

【7】显示匹配

#include 
#include 
#include "opencv2/highgui/highgui.hpp"
#include 
#include 
#include 

using namespace std;
using namespace cv;

// 全局变量 
const float inlier_threshold = 2.5f; // 单应变换后 和匹配点差值阈值
const float nn_match_ratio = 0.8f;   // 最近距离/次近距离 < 阈值 

int main(int argc, char** argv)
{
/// 加载源图像
    string imageName1("../../common/data/graf1.png"); // 图片文件名路径(默认值)
    string imageName2("../../common/data/graf3.png"); // 图片文件名路径(默认值)
    if( argc > 2)
    {
        imageName1 = argv[1];//如果传递了文件 就更新
	imageName2 = argv[2];//如果传递了文件 就更新
    }
    Mat img1 = imread( imageName1, CV_LOAD_IMAGE_GRAYSCALE );
    Mat img2  = imread( imageName2, CV_LOAD_IMAGE_GRAYSCALE );
    if( img1.empty() || img2.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
    // 单应变换矩阵
    Mat homography;
    FileStorage fs("../../common/data/H1to3p.xml", FileStorage::READ);
    fs.getFirstTopLevelNode() >> homography;


    vector kpts1, kpts2;//关键点
    Mat desc1, desc2;//描述子
    // 非线性尺度空间 AKAZE特征检测
    Ptr akaze = AKAZE::create();
    // 检测 + 描述
    akaze->detectAndCompute(img1, noArray(), kpts1, desc1);
    akaze->detectAndCompute(img2, noArray(), kpts2, desc2);
    // 汉明距离匹配器
    BFMatcher matcher(NORM_HAMMING);
    // 匹配点对 二维数组 一个点检测多个匹配点(按距离远近)
    vector< vector > nn_matches;
    // 进行描述子匹配
    matcher.knnMatch(desc1, desc2, nn_matches, 2);

    vector matched1, matched2, inliers1, inliers2;
     // matched1, matched2, 最近距离的匹配/次近距离的匹配的匹配点
     // inliers1, inliers2 一个匹配点 单应变换后 和 其匹配点的 差平方和 开根号 小于 阈值的 更好的匹配点
    vector good_matches;//较好的匹配

   //====最近距离的匹配/次近距离的匹配 小于一个阈值 才认为是 匹配点对 ================
    for(size_t i = 0; i < nn_matches.size(); i++) {
        DMatch first = nn_matches[i][0];
        float dist1 = nn_matches[i][0].distance;//最近距离的匹配
        float dist2 = nn_matches[i][1].distance;//次近距离的匹配 
        if(dist1 < nn_match_ratio * dist2) {
            matched1.push_back(kpts1[first.queryIdx]);// 对应图1的关键点坐标
            matched2.push_back(kpts2[first.trainIdx]);// 对应图2的关键点坐标
        }
    }

   //==========一个匹配点 单应变换后 和 其匹配点的 差平方和 开根号 < 阈值 为最终的匹配点对
    for(unsigned i = 0; i < matched1.size(); i++) {
        Mat col = Mat::ones(3, 1, CV_64F);//图1 点 的 其次表达方式 (u,v,1)
        col.at(0) = matched1[i].pt.x;
        col.at(1) = matched1[i].pt.y;
        col = homography * col;// 单应变换 后
        col /= col.at(2);// 将第三维归一化(x,y,z)-> (x/z,y/z,z/z)->(u',v',1)
        // 计算 一个匹配点 单应变换后 和 其匹配点的 差平方和 开根号
        double dist = sqrt( pow(col.at(0) - matched2[i].pt.x, 2) +
                            pow(col.at(1) - matched2[i].pt.y, 2));

        if(dist < inlier_threshold) {
            int new_i = static_cast(inliers1.size());
            inliers1.push_back(matched1[i]);// 内点 符合 单应变换的   点
            inliers2.push_back(matched2[i]);// 是按 匹配点对方式存入的 
            good_matches.push_back(DMatch(new_i, new_i, 0));//所以匹配点对 1-1 2-2 3-3 4-4 5-5
        }
    }

// ======= 显示 ===============
    Mat res;//最后的图像
    drawMatches(img1, inliers1, img2, inliers2, good_matches, res, Scalar::all(-1), Scalar::all(-1),
               vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    imshow("res.png", res);//显示匹配后的图像
    
    double inlier_ratio = inliers1.size() * 1.0 / matched1.size();
    cout << "A-KAZE Matching Results" << endl;
    cout << "*******************************" << endl;
    cout << "# Keypoints 1:                        \t" << kpts1.size() << endl;
    cout << "# Keypoints 2:                        \t" << kpts2.size() << endl;
    cout << "# Matches:                            \t" << matched1.size() << endl;
    cout << "# Inliers:                            \t" << inliers1.size() << endl;
    cout << "# Inliers Ratio:                      \t" << inlier_ratio << endl;
    cout << endl;
    return 0;
}

基于特征点的 物体跟踪
非线性尺度空间的KAZE特征提取
orb 特征检测 匹配

 ./planar_tracking blais.mp4 result.avi blais_bb.xml.gz

#include 
#include 
#include 
#include 
#include 
#include 
#include "stats.h" // Stats structure definition
#include "utils.h" // Drawing and printing functions
using namespace std;
using namespace cv;
const double akaze_thresh = 3e-4;  // AKAZE 检测阈值  1000个点
const double ransac_thresh = 2.5f;  // 随机序列采样 内点阈值 
const double nn_match_ratio = 0.8f; // 近邻匹配点阈值 
const int bb_min_inliers = 100;     // 最小数量的内点数 来画 包围框
const int stats_update_period = 10; // 更新频率

// 定义 跟踪类
class Tracker
{
public:
// 默认构造函数
    Tracker(Ptr _detector, Ptr _matcher) :
        detector(_detector),//特征点检测器
        matcher(_matcher)//描述子匹配器
    {}
// 第一帧图像
    void setFirstFrame(const Mat frame, vector bb, string title, Stats& stats);
// 以后每一帧进行处理 跟踪
    Mat process(const Mat frame, Stats& stats);
// 返回检测器 
    Ptr getDetector() {
        return detector;
    }
protected:
    Ptr detector;//特征点检测器
    Ptr matcher;//描述子匹配器
    Mat first_frame, first_desc;//
    vector first_kp;//关键点
    vector object_bb;//物体的 包围框
};

// 第一帧图像
void Tracker::setFirstFrame(const Mat frame, vector bb, string title, Stats& stats)
{
    first_frame = frame.clone();//复制图像
    detector->detectAndCompute(first_frame, noArray(), first_kp, first_desc);// 检测 + 描述
    stats.keypoints = (int)first_kp.size();// 关键点数量
    drawBoundingBox(first_frame, bb);// 显示 包围框
    putText(first_frame, title, Point(0, 60), FONT_HERSHEY_PLAIN, 5, Scalar::all(0), 4);
    object_bb = bb;// 包围框
}

// 处理
Mat Tracker::process(const Mat frame, Stats& stats)
{
    vector kp;//关键点
    Mat desc;//描述子
    detector->detectAndCompute(frame, noArray(), kp, desc);//检测新一帧图像的 关键点和描述子
    stats.keypoints = (int)kp.size();//关键点数量
    vector< vector > matches;//匹配点 索引  二维数组
    vector matched1, matched2;// 和上一帧对应 匹配的 关键点坐标
    matcher->knnMatch(first_desc, desc, matches, 2);// 最近领匹配进行匹配 2个匹配
    for(unsigned i = 0; i < matches.size(); i++) {
	// 符合近邻匹配 阈值的 才可能是 关键点
        if(matches[i][0].distance < nn_match_ratio * matches[i][1].distance) {
            matched1.push_back(first_kp[matches[i][0].queryIdx]);//上一帧中对应的关键点
            matched2.push_back(      kp[matches[i][0].trainIdx]);//当前帧中对应的关键点
        }
    }

// 求出初级匹配点对符合 的 单应变换矩阵
    stats.matches = (int)matched1.size();//匹配点的数量
    Mat inlier_mask, homography;
    vector inliers1, inliers2;// 符合找到的单应矩阵 的 匹配点
    vector inlier_matches;// 匹配点对是否符合 该单应矩阵
    if(matched1.size() >= 4) {// 求解单应矩阵 需要4个匹配点对以上
        homography = findHomography(Points(matched1), Points(matched2),
                                    RANSAC, ransac_thresh, inlier_mask);
    }
    if(matched1.size() < 4 || homography.empty()) {
        Mat res;// 匹配效果不好  未能求解到 单应矩阵
        hconcat(first_frame, frame, res);
        stats.inliers = 0;
        stats.ratio = 0;
        return res;
    }
// 保留 符合求出的 单应变换矩阵 的 匹配点对 
    for(unsigned i = 0; i < matched1.size(); i++) {
        if(inlier_mask.at(i)) {//该匹配点对 符合求出的 单应矩阵
            int new_i = static_cast(inliers1.size());
            inliers1.push_back(matched1[i]);
            inliers2.push_back(matched2[i]);
            inlier_matches.push_back(DMatch(new_i, new_i, 0));
        }
    }
    stats.inliers = (int)inliers1.size();//内点数量
    stats.ratio = stats.inliers * 1.0 / stats.matches;//内点比率

    vector new_bb;//新的物体包围框
    perspectiveTransform(object_bb, new_bb, homography);// 利用 单应变换矩阵 重映射 之前的包围框

    Mat frame_with_bb = frame.clone();// 带有物体包围框的 图像
    if(stats.inliers >= bb_min_inliers) {//内点数量超过阈值 显示 包围框
        drawBoundingBox(frame_with_bb, new_bb);
    }
    // 显示匹配点对
    Mat res;
    drawMatches(first_frame, inliers1, frame_with_bb, inliers2,
                inlier_matches, res,
                Scalar(255, 0, 0), Scalar(255, 0, 0));
    return res;// 返回显示了 包围框 和 匹配点对 的图像
}

// 主函数
int main(int argc, char **argv)
{
    if(argc < 4) {
        cerr << "Usage: " << endl <<
                "akaze_track input_path output_path bounding_box" << endl;
        return 1;
    }

    VideoCapture video_in(argv[1]);//输入视频
    //输出视频
    VideoWriter  video_out(argv[2],
                           (int)video_in.get(CAP_PROP_FOURCC),
                           (int)video_in.get(CAP_PROP_FPS),//帧率
                           Size(2 * (int)video_in.get(CAP_PROP_FRAME_WIDTH),//尺寸
                                2 * (int)video_in.get(CAP_PROP_FRAME_HEIGHT)));
    if(!video_in.isOpened()) {
        cerr << "Couldn't open " << argv[1] << endl;
        return 1;
    }
    if(!video_out.isOpened()) {
        cerr << "Couldn't open " << argv[2] << endl;
        return 1;
    }

    vector bb;// 第一帧所需要跟踪物体 的 包围框
    // 这里可以使用鼠标指定
    FileStorage fs(argv[3], FileStorage::READ);
    if(fs["bounding_box"].empty()) {
        cerr << "Couldn't read bounding_box from " << argv[3] << endl;
        return 1;
    }
    // 第一帧所需要跟踪物体 的 包围框
    fs["bounding_box"] >> bb;
    // 状态
    Stats stats, akaze_stats, orb_stats;
    // AKAZE 特征检测
    Ptr akaze = AKAZE::create();
    akaze->setThreshold(akaze_thresh);
    // ORB 特征检测 
    Ptr orb = ORB::create();
    orb->setMaxFeatures(stats.keypoints);//最多关键点数量

    // 描述子匹配器  汉明字符串距离匹配
    Ptr matcher = DescriptorMatcher::create("BruteForce-Hamming");

    // 构造跟踪器 对象
    Tracker akaze_tracker(akaze, matcher);
    Tracker orb_tracker(orb, matcher);

    // 第一帧图像
    Mat frame;
    video_in >> frame;
    akaze_tracker.setFirstFrame(frame, bb, "AKAZE", stats);
    orb_tracker.setFirstFrame(frame, bb, "ORB", stats);

    Stats akaze_draw_stats, orb_draw_stats;
    // 总帧数
    int frame_count = (int)video_in.get(CAP_PROP_FRAME_COUNT);
    // 跟踪后的帧 
    Mat akaze_res, orb_res, res_frame;

    for(int i = 1; i < frame_count; i++) {//处理每一帧
        bool update_stats = (i % stats_update_period == 0);
        video_in >> frame;//捕获一帧

//=======AKAZE 跟踪 处理一帧======
        akaze_res = akaze_tracker.process(frame, stats);// AKAZE 跟踪 处理一帧
        akaze_stats += stats;
        if(update_stats) {
            akaze_draw_stats = stats;//更新状态
        }
//=======ORB 跟踪 处理一帧======
        orb->setMaxFeatures(stats.keypoints);//最多关键点数量 根据上一次 检测数量
        orb_res = orb_tracker.process(frame, stats);// ORB 跟踪 处理一帧
        orb_stats += stats;

        if(update_stats) {
            orb_draw_stats = stats;//更新状态
        }

        drawStatistics(akaze_res, akaze_draw_stats);

        drawStatistics(orb_res, orb_draw_stats);

        vconcat(akaze_res, orb_res, res_frame);

        video_out << res_frame;
        cout << i << "/" << frame_count - 1 << endl;
    }

    akaze_stats /= frame_count - 1;

    orb_stats /= frame_count - 1;

    printStatistics("AKAZE", akaze_stats);

    printStatistics("ORB", orb_stats);

    return 0;
}




你可能感兴趣的:(机器视觉)