OV2SLAM代码解析之feature_tracker.cpp

#include "feature_tracker.hpp"

#include <iostream>
#include <unordered_map>
#include <opencv2/video/tracking.hpp>

#include "multi_view_geometry.hpp"//用于多视角几何计算

/*
函数FeatureTracker使用Lucas-Kanade光流算法进行特征跟踪,并返回跟踪结果。

它的输入参数是两个图像金字塔vprevpyr和vcurpyr,它们都是OpenCV中的cv::Mat类型的向量。nwinsize是每个特征点周围用于跟踪的像素窗口大小。
nbpyrlvl是金字塔的层数。ferr是允许的光流估计误差阈值,fmax_fbklt_dist是反向跟踪的最大距离阈值。

vkps是一个cv::Point2f类型的向量,其中存储了需要跟踪的特征点的初始位置。vpriorkps是特征点的先前位置,vkpstatus是特征点的跟踪状态,
如果跟踪成功,则为true,否则为false。

该函数的输出是vkps、vpriorkps和vkpstatus,这些向量将包含成功跟踪的特征点的位置、先前位置和跟踪状态。
*/
void FeatureTracker::fbKltTracking(const std::vector<cv::Mat> &vprevpyr, const std::vector<cv::Mat> &vcurpyr, 
        int nwinsize, int nbpyrlvl, float ferr, float fmax_fbklt_dist, std::vector<cv::Point2f> &vkps, 
        std::vector<cv::Point2f> &vpriorkps, std::vector<bool> &vkpstatus) const
{
    // std::cout << "\n \t >>> Forward-Backward kltTracking with Pyr of Images and Motion Prior! \n";

    //检查先前和当前金字塔图像的大小是否相等,如果不相等,则返回。
    assert(vprevpyr.size() == vcurpyr.size());

    //如果vkps为空,则函数也返回。
    if( vkps.empty() ) {
        // std::cout << "\n \t >>> No kps were provided to kltTracking()!\n";
        return;
    }

    //根据提供的参数设置KLT算法使用的窗口大小、金字塔的级数,并准备了一些用于存储光流跟踪结果的向量。
    cv::Size klt_win_size(nwinsize, nwinsize);

    if( (int)vprevpyr.size() < 2*(nbpyrlvl+1) ) {  
        nbpyrlvl = vprevpyr.size() / 2 - 1;
    }

    // Objects for OpenCV KLT
    size_t nbkps = vkps.size();
    vkpstatus.reserve(nbkps);     //vkpstatus向量存储每个感兴趣点的状态(跟踪成功或失败)

    std::vector<uchar> vstatus;
    std::vector<float> verr;
    std::vector<int> vkpsidx;
    vstatus.reserve(nbkps);       //vstatus向量存储每个感兴趣点的状态(跟踪成功或失败)
    verr.reserve(nbkps);          //verr向量存储每个感兴趣点的跟踪误差
    vkpsidx.reserve(nbkps);       //vkpsidx向量存储vkps中每个感兴趣点的索引。

    // Tracking Forward
    /*
    函数使用OpenCV的calcOpticalFlowPyrLK函数执行光流跟踪。该函数接收先前和当前的金字塔图像(vprevpyr和vcurpyr)、感兴趣点向量(vkps)、
    先前帧感兴趣点向量(vpriorkps)、感兴趣点状态向量(vstatus)、感兴趣点跟踪误差向量(verr)、KLT算法使用的窗口大小(klt_win_size)、
    金字塔级数(nbpyrlvl)、KLT算法的收敛标准(klt_convg_crit_)和一些标志位(cv::OPTFLOW_USE_INITIAL_FLOW + cv::OPTFLOW_LK_GET_MIN_EIGENVALS)。
    其中,cv::OPTFLOW_USE_INITIAL_FLOW标志告诉算法使用vpriorkps中的先前帧感兴趣点作为初始估计,
    cv::OPTFLOW_LK_GET_MIN_EIGENVALS标志告诉算法计算每个感兴趣点的最小特征值,以便过滤出不稳定的跟踪点。
    */
    cv::calcOpticalFlowPyrLK(vprevpyr, vcurpyr, vkps, vpriorkps, 
                vstatus, verr, klt_win_size,  nbpyrlvl, klt_convg_crit_, 
                (cv::OPTFLOW_USE_INITIAL_FLOW + cv::OPTFLOW_LK_GET_MIN_EIGENVALS) 
                );

    std::vector<cv::Point2f> vnewkps;    //返回一个新的感兴趣点向量(vnewkps),存储跟踪后的感兴趣点
    std::vector<cv::Point2f> vbackkps;   //一个回溯的感兴趣点向量(vbackkps),存储它们在先前帧中的位置,这两个向量的长度与vkps的长度相同,因为它们对应着每个输入的感兴趣点
    vnewkps.reserve(nbkps);
    vbackkps.reserve(nbkps);

    size_t nbgood = 0;

    // Init outliers vector & update tracked kps初始化非关键点 (outlier) 列表和已跟踪的关键点列表
    //用于估计关键点位的算法的实现
    for( size_t i = 0 ; i < nbkps ; i++ ) //循环遍历所有的关键点
    {
        if( !vstatus.at(i) ) {
            vkpstatus.push_back(false);//对于每个关键点,检查其状态是否为未标记,如果未标记则将其添加到非关键点列表中。
            continue;
        }

        if( verr.at(i) > ferr ) {
            vkpstatus.push_back(false);//如果该关键点的误差大于设定的最大误差 (ferr),则将其添加到非关键点列表中
            continue;
        }

        if( !inBorder(vpriorkps.at(i), vcurpyr.at(0)) ) {
            vkpstatus.push_back(false);//如果该关键点与上一个关键点的位置关系不在预定义的边界内 ,则将其添加到非关键点列表中
            continue;
        }

        vnewkps.push_back(vpriorkps.at(i));//将当前关键点添加到新关键点列表中,并将其上一个关键点添加到旧关键点列表中
        vbackkps.push_back(vkps.at(i));//将当前关键点的状态标记为已标记 (tagged),并将其索引添加到关键点状态列表中
        vkpstatus.push_back(true);//将关键点列表中的所有关键点更新为当前关键点的位置信息
        vkpsidx.push_back(i);
        nbgood++;//nbgood 表示当前循环遍历到的关键点中状态为已标记 (tagged) 的关键点的数量,用于更新已跟踪的关键点列表
    }  

    if( vnewkps.empty() ) {//检查一个名为vnewkps的向量是否为空。
        return;
    }
    
    vstatus.clear();//如果vnewkps不为空,则清空另外两个名为vstatus和verr的向量
    verr.clear();

    // std::cout << "\n \t >>> Forward kltTracking : #" << nbgood << " out of #" << nbkps << " \n";

    // Tracking Backward
    /*
    OpenCV库中的calcOpticalFlowPyrLK函数对两幅图像之间的光流进行计算,并且将结果存储在vnewkps, vbackkps, vstatus, 
    和 verr四个向量中。其中,vcurpyr和vprevpyr是待计算光流的两幅图像,klt_win_size是KLT算法中的窗口大小,klt_convg_crit_是KLT算法中的迭代收敛标准。
    */
    cv::calcOpticalFlowPyrLK(vcurpyr, vprevpyr, vnewkps, vbackkps, 
                vstatus, verr, klt_win_size,  0, klt_convg_crit_,
                (cv::OPTFLOW_USE_INITIAL_FLOW + cv::OPTFLOW_LK_GET_MIN_EIGENVALS) 
                );
    
    nbgood = 0;
    for( int i = 0, iend=vnewkps.size() ; i < iend ; i++ )//遍历vnewkps向量中的所有特征点
    {
        int idx = vkpsidx.at(i);

        //检查其对应的光流跟踪结果是否有效,如果vstatus向量中对应的元素值为false,则说明该特征点的光流跟踪失败,将其在vkpstatus向量中的对应元素值也设置为false。
        if( !vstatus.at(i) ) {
            vkpstatus.at(idx) = false;
            continue;
        }

        //如果该特征点与其在之前图像中的位置的距离超过了fmax_fbklt_dist,则同样认为其光流跟踪失败,将其在vkpstatus向量中的对应元素值也设置为false。
        if( cv::norm(vkps.at(idx) - vbackkps.at(i)) > fmax_fbklt_dist ) {
            vkpstatus.at(idx) = false;
            continue;
        }

        nbgood++;//将所有光流跟踪成功的特征点的数量记录在nbgood变量中
    }

    // std::cout << "\n \t >>> Backward kltTracking : #" << nbgood << " out of #" << vkpsidx.size() << " \n";
}


/*
这是一个特征跟踪器(FeatureTracker)类中的函数,用于计算两幅图像(iml和imr)中某个点(pt)处,
一条水平线上(bgoleft为false)或者一条与水平线倾斜的线上(bgoleft为true),在一个指定大小(nwinsize)的窗口内最小的绝对差异(L1误差),
并返回该误差和所在位置的横坐标(xprior)。
*/
void FeatureTracker::getLineMinSAD(const cv::Mat &iml, const cv::Mat &imr, 
    const cv::Point2f &pt,  const int nwinsize, float &xprior, 
    float &l1err, bool bgoleft) const
{
    xprior = -1;

    //检查指定的窗口大小是否为奇数,如果不是,会输出错误信息并返回。
    if( nwinsize % 2 == 0 ) {
        std::cerr << "\ngetLineMinSAD requires an odd window size\n";
        return;
    }

    //获取目标点(pt)在imr图像中所在的矩形窗口,矩形大小由指定的窗口大小nwinsize来确定。
    const float x = pt.x;
    const float y = pt.y;
    int halfwin = nwinsize / 2;

    //根据目标点所在位置(pt)和指定的窗口大小(nwinsize),计算矩形窗口在imr图像中的位置和大小。
    //如果矩形窗口的某个边界超出了imr图像的边界,函数会重新计算窗口大小以保证其在图像内部,并返回。
    if( x - halfwin < 0 ) 
        halfwin += (x-halfwin);
    if( x + halfwin >= imr.cols )
        halfwin += (x+halfwin - imr.cols - 1);
    if( y - halfwin < 0 )
        halfwin += (y-halfwin);
    if( y + halfwin >= imr.rows )
        halfwin += (y+halfwin - imr.rows - 1);
    
    //如果窗口大小小于等于0,则直接返回
    if( halfwin <= 0 ) {
        return;
    }


    /*
    计算两幅图像之间的均方误差(Mean Squared Error,MSE)和绝对差值误差(L1误差)。以下代码的主要目的是找到两幅图像中相似区域的匹配位置。
    */

    cv::Size winsize(2 * halfwin + 1, 2 * halfwin + 1);//定义一个cv::Size类型的变量winsize,用于表示搜索窗口的大小。其中halfwin是一个整数变量,表示搜索窗口的半径。

    int nbwinpx = (winsize.width * winsize.height);//计算搜索窗口中像素的数量,用于后面的归一化处理。

    float minsad = 255.;//初始化最小的L1误差为255
    // int minxpx = -1;

    cv::Mat patch, target; //定义两个cv::Mat类型的变量patch和target,分别表示搜索窗口和目标图像

    cv::getRectSubPix(iml, winsize, pt, patch);//从左图像iml中提取以pt为中心,大小为winsize的矩形区域,并将其存储在patch中

    //根据标志bgoleft的值,选择从右图像的左侧还是右侧开始搜索。这个标志表示当前搜索的方向
    if( bgoleft ) {
        for( float c = x ; c >= halfwin ; c-=1. )
        {
            cv::getRectSubPix(imr, winsize, cv::Point2f(c, y), target);//从右图像imr中提取以(c, y)为中心,大小为winsize的矩形区域,并将其存储在target中
            l1err = cv::norm(patch, target, cv::NORM_L1);//计算patch和target之间的L1误差
            l1err /= nbwinpx;//对L1误差进行归一化,使其除以搜索窗口中像素的数量

            //如果L1误差小于最小值,则更新最小值和匹配位置的x坐标xprior
            if( l1err < minsad ) {
                minsad = l1err;
                xprior = c;
            }
        }
    } else {
        for( float c = x ; c < imr.cols - halfwin ; c+=1. )
        {
            cv::getRectSubPix(imr, winsize, cv::Point2f(c, y), target);
            l1err = cv::norm(patch, target, cv::NORM_L1);
            l1err /= nbwinpx;

            if( l1err < minsad ) {
                minsad = l1err;
                xprior = c;
            }
        }
    }

    l1err = minsad;//最后,将最小L1误差赋值给l1err变量
}

/**
 * \brief Perform a forward-backward calcOpticalFlowPyrLK() tracking with OpenCV.
 *
 * \param[in] pt  Opencv 2D point.
 * \return True if pt is within image borders, False otherwise
 */
//判断一个二维坐标点是否在图像边缘内部
bool FeatureTracker::inBorder(const cv::Point2f &pt, const cv::Mat &im) const
{
    const float BORDER_SIZE = 1.;//定义了一个常量BORDER_SIZE,用于表示图像边缘的大小

    /*
    检查点pt的x坐标是否大于等于BORDER_SIZE且小于图像im的宽度减去BORDER_SIZE,同时检查点pt的y坐标是否大于等于BORDER_SIZE且小于图像im的高度减去BORDER_SIZE。
    如果同时满足这两个条件,函数将返回true,表示该点在图像边界内;否则,函数将返回false,表示该点在图像边界外。
    */
    return BORDER_SIZE <= pt.x && pt.x < im.cols - BORDER_SIZE && BORDER_SIZE <= pt.y && pt.y < im.rows - BORDER_SIZE;
}
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值