VINS-Fusion源码逐行解析:inputImage函数与trackImage函数及trackImage函数中涉及的函数

之前在rosNodeTest.cpp主程序中的setParameter函数开启了新线程processMeasurements,然后开启之前介绍过的sync_process()线程,在该线程中通过inputImage函数将同步的图像输入到估计器中,让我们来看一下inputImage函数

// 输入图像数据的函数

void Estimator::inputImage(double t, const cv::Mat &_img, const cv::Mat &_img1)
{
    inputImageCnt++;// 增加输入图像计数
    map<int, vector<pair<int, Eigen::Matrix<double, 7, 1>>>> featureFrame;// 定义特征帧数据结构
    TicToc featureTrackerTime;// 定义计时器用于跟踪特征提取时间

    if(_img1.empty())// 如果只有一个图像(单目相机)
        featureFrame = featureTracker.trackImage(t, _img);// 进行单目特征跟踪
    else// 如果有两个图像(双目相机)
        featureFrame = featureTracker.trackImage(t, _img, _img1);// 进行双目特征跟踪
    //printf("featureTracker time: %f\n", featureTrackerTime.toc());

    if (SHOW_TRACK)// 如果需要显示跟踪结果
    {
        cv::Mat imgTrack = featureTracker.getTrackImage();// 获取跟踪结果图像
        pubTrackImage(imgTrack, t);// 发布跟踪结果图像
    }
    
    if(MULTIPLE_THREAD)  // 如果使用多线程
    {     
        if(inputImageCnt % 2 == 0)// 每隔两帧图像处理一次
        {
            mBuf.lock();// 加锁
            featureBuf.push(make_pair(t, featureFrame)); // 将特征帧推入队列
            mBuf.unlock(); // 解锁
        }
    }
    else// 如果不使用多线程
    {
        mBuf.lock();// 加锁
        featureBuf.push(make_pair(t, featureFrame));// 将特征帧推入队列
        mBuf.unlock();// 解锁
        TicToc processTime;// 定义计时器用于测量处理时间
        processMeasurements();// 调用processMeasurements()函数来处理测量数据
        printf("process time: %f\n", processTime.toc());// 打印处理时间
    }
    
}

该函数首先通过调用trackImage函数对输入的图像进行处理存进featureFrame,最后放进featureBuf然后调用processMeasurements()函数来处理数据。

我们接着看下trackImage函数吧

map<int, vector<pair<int, Eigen::Matrix<double, 7, 1>>>> FeatureTracker::trackImage(double _cur_time, const cv::Mat &_img, const cv::Mat &_img1)
{
    TicToc t_r;// 计时器,用于记录整个跟踪过程的时间
    cur_time = _cur_time;// 更新当前时间
    cur_img = _img;// 更新当前图像
    row = cur_img.rows;// 获取图像的行数
    col = cur_img.cols;// 获取图像的列数
    cv::Mat rightImg = _img1;// 如果是双目相机,则获取右图像
    /*
    {
        cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(3.0, cv::Size(8, 8));
        clahe->apply(cur_img, cur_img);
        if(!rightImg.empty())
            clahe->apply(rightImg, rightImg);
    }
    */
    cur_pts.clear();// 清空当前帧的特征点

    // 如果有前一帧的特征点,则进行光流跟踪
    if (prev_pts.size() > 0)
    {
        TicToc t_o;// 计时器,用于记录光流跟踪的时间
        vector<uchar> status;// 用于存储光流跟踪状态
        vector<float> err;// 用于存储光流跟踪误差
        if(hasPrediction)// 如果有预测值
        {
            cur_pts = predict_pts;// 使用预测值作为当前帧的初始特征点
            // 进行光流跟踪
            cv::calcOpticalFlowPyrLK(prev_img, cur_img, prev_pts, cur_pts, status, err, cv::Size(21, 21), 1, 
            cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS, 30, 0.01), cv::OPTFLOW_USE_INITIAL_FLOW);
            
            int succ_num = 0;// 成功跟踪的特征点数量
            for (size_t i = 0; i < status.size(); i++)
            {
                if (status[i])
                    succ_num++;
            }
            // 如果成功跟踪的特征点少于10个,则重新进行光流跟踪
            if (succ_num < 10)
               cv::calcOpticalFlowPyrLK(prev_img, cur_img, prev_pts, cur_pts, status, err, cv::Size(21, 21), 3);
        }
        // 进行光流跟踪
        else
            cv::calcOpticalFlowPyrLK(prev_img, cur_img, prev_pts, cur_pts, status, err, cv::Size(21, 21), 3);
        
        // reverse check
        // 反向检查光流跟踪结果,以验证光流跟踪的可靠性。
        if(FLOW_BACK)
        {
            vector<uchar> reverse_status;// 定义用于存储反向跟踪状态的向量
            vector<cv::Point2f> reverse_pts = prev_pts;// 复制前一帧的特征点到 reverse_pts 中
            // 进行反向光流跟踪:从当前帧跟踪到前一帧
            cv::calcOpticalFlowPyrLK(cur_img, prev_img, cur_pts, reverse_pts, reverse_status, err, cv::Size(21, 21), 1, 
            cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS, 30, 0.01), cv::OPTFLOW_USE_INITIAL_FLOW);
            //cv::calcOpticalFlowPyrLK(cur_img, prev_img, cur_pts, reverse_pts, reverse_status, err, cv::Size(21, 21), 3); 
            // 遍历所有跟踪点,检查正向和反向跟踪的匹配情况
            for(size_t i = 0; i < status.size(); i++)
            {
                // 如果正向和反向跟踪都成功,并且正向和反向的点距离小于等于0.5
                if(status[i] && reverse_status[i] && distance(prev_pts[i], reverse_pts[i]) <= 0.5)
                {
                    // 保留这个点
                    status[i] = 1;
                }
                else
                    // 否则,丢弃这个点
                    status[i] = 0;
            }
        }
        
        // 过滤掉不在图像边界内的特征点
        for (int i = 0; i < int(cur_pts.size()); i++)
            if (status[i] && !inBorder(cur_pts[i]))
                status[i] = 0;
        // 根据状态向量过滤特征点、ID和跟踪计数
        reduceVector(prev_pts, status);
        reduceVector(cur_pts, status);
        reduceVector(ids, status);
        reduceVector(track_cnt, status);
        // 输出光流跟踪的时间
        ROS_DEBUG("temporal optical flow costs: %fms", t_o.toc());
        //printf("track cnt %d\n", (int)ids.size());
    }

    // 增加每个特征点的跟踪计数
    for (auto &n : track_cnt)
        n++;

    // 如果需要重新检测特征点
    if (1)
    {
        //rejectWithF();
        ROS_DEBUG("set mask begins");
        TicToc t_m;// 计时器,用于记录设置掩码的时间
        setMask();// 设置掩码
        ROS_DEBUG("set mask costs %fms", t_m.toc());

        ROS_DEBUG("detect feature begins");
        TicToc t_t;// 计时器,用于记录特征检测的时间
        int n_max_cnt = MAX_CNT - static_cast<int>(cur_pts.size());// 计算需要检测的新特征点数量
        if (n_max_cnt > 0)
        {
            if(mask.empty())
                cout << "mask is empty " << endl;
            if (mask.type() != CV_8UC1)
                cout << "mask type wrong " << endl;
            // 检测新特征点
            cv::goodFeaturesToTrack(cur_img, n_pts, MAX_CNT - cur_pts.size(), 0.01, MIN_DIST, mask);
        }
        else
            n_pts.clear();
        ROS_DEBUG("detect feature costs: %f ms", t_t.toc());

        // 将新检测到的特征点加入当前特征点列表
        for (auto &p : n_pts)
        {
            cur_pts.push_back(p);
            ids.push_back(n_id++);
            track_cnt.push_back(1);
        }
        //printf("feature cnt after add %d\n", (int)ids.size());
    }

    // 将当前特征点去畸变
    cur_un_pts = undistortedPts(cur_pts, m_camera[0]);
    pts_velocity = ptsVelocity(ids, cur_un_pts, cur_un_pts_map, prev_un_pts_map);

    // 如果是双目相机,进行右图像的特征点跟踪
    if(!_img1.empty() && stereo_cam)
    {
        ids_right.clear();
        cur_right_pts.clear();
        cur_un_right_pts.clear();
        right_pts_velocity.clear();
        cur_un_right_pts_map.clear();
        if(!cur_pts.empty())
        {
            //printf("stereo image; track feature on right image\n");
            vector<cv::Point2f> reverseLeftPts;
            vector<uchar> status, statusRightLeft;
            vector<float> err;
            // cur left ---- cur right
            cv::calcOpticalFlowPyrLK(cur_img, rightImg, cur_pts, cur_right_pts, status, err, cv::Size(21, 21), 3);
            // reverse check cur right ---- cur left
            // 反向检查右图到左图光流跟踪
            if(FLOW_BACK)
            {
                cv::calcOpticalFlowPyrLK(rightImg, cur_img, cur_right_pts, reverseLeftPts, statusRightLeft, err, cv::Size(21, 21), 3);
                for(size_t i = 0; i < status.size(); i++)
                {
                    if(status[i] && statusRightLeft[i] && inBorder(cur_right_pts[i]) && distance(cur_pts[i], reverseLeftPts[i]) <= 0.5)
                        status[i] = 1;
                    else
                        status[i] = 0;
                }
            }

            ids_right = ids;
            reduceVector(cur_right_pts, status);
            reduceVector(ids_right, status);
            // only keep left-right pts
            /*
            reduceVector(cur_pts, status);
            reduceVector(ids, status);
            reduceVector(track_cnt, status);
            reduceVector(cur_un_pts, status);
            reduceVector(pts_velocity, status);
            */
            cur_un_right_pts = undistortedPts(cur_right_pts, m_camera[1]);
            right_pts_velocity = ptsVelocity(ids_right, cur_un_right_pts, cur_un_right_pts_map, prev_un_right_pts_map);
        }
        prev_un_right_pts_map = cur_un_right_pts_map;
    }
    // 如果需要显示跟踪结果
    if(SHOW_TRACK)
        drawTrack(cur_img, rightImg, ids, cur_pts, cur_right_pts, prevLeftPtsMap);

    // 更新前一帧的图像和特征点数据
    prev_img = cur_img;
    prev_pts = cur_pts;
    prev_un_pts = cur_un_pts;
    prev_un_pts_map = cur_un_pts_map;
    prev_time = cur_time;
    hasPrediction = false;

    // 更新前一帧的特征点映射
    prevLeftPtsMap.clear();
    for(size_t i = 0; i < cur_pts.size(); i++)
        prevLeftPtsMap[ids[i]] = cur_pts[i];

    // 构建特征帧数据结构
    map<int, vector<pair<int, Eigen::Matrix<double, 7, 1>>>> featureFrame;
    for (size_t i = 0; i < ids.size(); i++)
    {
        int feature_id = ids[i];
        //去畸变坐标
        double x, y ,z;
        x = cur_un_pts[i].x;
        y = cur_un_pts[i].y;
        z = 1;
        //像素坐标
        double p_u, p_v;
        p_u = cur_pts[i].x;
        p_v = cur_pts[i].y;
        //相机id
        int camera_id = 0;
        //速度
        double velocity_x, velocity_y;
        velocity_x = pts_velocity[i].x;
        velocity_y = pts_velocity[i].y;

        Eigen::Matrix<double, 7, 1> xyz_uv_velocity;
        xyz_uv_velocity << x, y, z, p_u, p_v, velocity_x, velocity_y;
        featureFrame[feature_id].emplace_back(camera_id,  xyz_uv_velocity);
    }

    // 如果是双目相机,处理右图像的特征帧数据
    if (!_img1.empty() && stereo_cam)
    {
        for (size_t i = 0; i < ids_right.size(); i++)
        {
            int feature_id = ids_right[i];
            double x, y ,z;
            x = cur_un_right_pts[i].x;
            y = cur_un_right_pts[i].y;
            z = 1;
            double p_u, p_v;
            p_u = cur_right_pts[i].x;
            p_v = cur_right_pts[i].y;
            int camera_id = 1;
            double velocity_x, velocity_y;
            velocity_x = right_pts_velocity[i].x;
            velocity_y = right_pts_velocity[i].y;

            Eigen::Matrix<double, 7, 1> xyz_uv_velocity;
            xyz_uv_velocity << x, y, z, p_u, p_v, velocity_x, velocity_y;
            featureFrame[feature_id].emplace_back(camera_id,  xyz_uv_velocity);
        }
    }

    //printf("feature track whole time %f\n", t_r.toc());
    // 返回构建的特征帧数据
    return featureFrame;
}

看详细注释的代码即可,简单来说就是通过opencv中的calcOpticalFlowPyrLK函数来实现光流跟踪,并进行反向检验,将跟踪到的特征点进行去畸变,最终将跟踪到的特征点的信息放进featureFrame中返回,如是双目,则右目同理。

其中,undistortedPts函数的作用是对特征点进行去畸变

//对一组二维图像点进行去畸变操作的函数
vector<cv::Point2f> FeatureTracker::undistortedPts(vector<cv::Point2f> &pts, camodocal::CameraPtr cam)
{
    // 创建一个空的向量来存储去畸变后的点
    vector<cv::Point2f> un_pts;
    // 对于每个输入的二维图像点,执行以下操作
    for (unsigned int i = 0; i < pts.size(); i++)
    {
        // 将二维图像点转换为 Eigen::Vector2d
        Eigen::Vector2d a(pts[i].x, pts[i].y);
        // 创建一个空的三维向量来存储去畸变后的点
        Eigen::Vector3d b;
        // 使用相机模型对二维点进行反投影和去畸变操作
        cam->liftProjective(a, b);
        // 将去畸变后的三维点转换回二维图像点,并将其存储在 un_pts 中
        un_pts.push_back(cv::Point2f(b.x() / b.z(), b.y() / b.z()));
    }
    // 返回去畸变后的二维图像点向量
    return un_pts;
}

 ptsVelocity函数是计算当前帧相对于前一帧 特征点沿x,y方向的像素移动速度

//计算了特征点的速度
vector<cv::Point2f> FeatureTracker::ptsVelocity(vector<int> &ids, vector<cv::Point2f> &pts, 
                                            map<int, cv::Point2f> &cur_id_pts, map<int, cv::Point2f> &prev_id_pts)
{
    // 存储特征点速度的向量
    vector<cv::Point2f> pts_velocity;
    // 清空当前ID-点映射
    cur_id_pts.clear();
    for (unsigned int i = 0; i < ids.size(); i++)
    {
        // 将特征点ID和对应的点插入到映射中
        cur_id_pts.insert(make_pair(ids[i], pts[i]));
    }

    // caculate points velocity
    // 计算特征点的速度
    if (!prev_id_pts.empty())
    {
        // 计算当前帧和上一帧之间的时间差
        double dt = cur_time - prev_time;
        // 遍历所有特征点
        for (unsigned int i = 0; i < pts.size(); i++)
        {
            // 根据特征点ID在上一帧中查找对应的点
            std::map<int, cv::Point2f>::iterator it;
            it = prev_id_pts.find(ids[i]);
            // 如果找到了对应的点,计算速度
            if (it != prev_id_pts.end())
            {
                // 计算速度分量
                double v_x = (pts[i].x - it->second.x) / dt;
                double v_y = (pts[i].y - it->second.y) / dt;

                // 将速度添加到向量中
                pts_velocity.push_back(cv::Point2f(v_x, v_y));
            }
            else
                // 如果没有找到对应的点,将速度设为零向量
                pts_velocity.push_back(cv::Point2f(0, 0));

        }
    }
    else
    {
        // 如果上一帧中的ID-点映射为空,则特征点的速度设为零向量
        for (unsigned int i = 0; i < cur_pts.size(); i++)
        {
            pts_velocity.push_back(cv::Point2f(0, 0));
        }
    }
    // 返回特征点的速度向量
    return pts_velocity;
}

drawTrack函数用来绘制特征点在图像上的轨迹

//用于在图像上绘制跟踪特征点的轨迹
void FeatureTracker::drawTrack(const cv::Mat &imLeft, const cv::Mat &imRight, 
                               vector<int> &curLeftIds,
                               vector<cv::Point2f> &curLeftPts, 
                               vector<cv::Point2f> &curRightPts,
                               map<int, cv::Point2f> &prevLeftPtsMap)
{
    //int rows = imLeft.rows;
    // 获取左图像的列数
    int cols = imLeft.cols;
    // 如果右图像不为空且使用的是立体相机,将左右图像水平拼接
    if (!imRight.empty() && stereo_cam)
        cv::hconcat(imLeft, imRight, imTrack);
    else
        // 否则克隆左图像
        imTrack = imLeft.clone();
    // 将图像转换为彩色图像(灰度到RGB)
    cv::cvtColor(imTrack, imTrack, CV_GRAY2RGB);

    // 在图像上绘制左图像中的特征点
    for (size_t j = 0; j < curLeftPts.size(); j++)
    {
        // 根据跟踪计数决定颜色
        double len = std::min(1.0, 1.0 * track_cnt[j] / 20);
        // 绘制特征点
        cv::circle(imTrack, curLeftPts[j], 2, cv::Scalar(255 * (1 - len), 0, 255 * len), 2);
    }
    // 如果右图像不为空且使用的是立体相机,在图像上绘制右图像中的特征点
    if (!imRight.empty() && stereo_cam)
    {
        for (size_t i = 0; i < curRightPts.size(); i++)
        {
            // 将右图像中的特征点坐标的x值加上左图像的列数
            cv::Point2f rightPt = curRightPts[i];
            rightPt.x += cols;
            // 绘制特征点
            cv::circle(imTrack, rightPt, 2, cv::Scalar(0, 255, 0), 2);
            //cv::Point2f leftPt = curLeftPtsTrackRight[i];
            //cv::line(imTrack, leftPt, rightPt, cv::Scalar(0, 255, 0), 1, 8, 0);
        }
    }
    
    // 绘制左图像中特征点的轨迹
    map<int, cv::Point2f>::iterator mapIt;
    for (size_t i = 0; i < curLeftIds.size(); i++)
    {
        int id = curLeftIds[i];
        // 查找特征点在前一帧中的位置
        mapIt = prevLeftPtsMap.find(id);
        if(mapIt != prevLeftPtsMap.end())
        {
            // 绘制从当前帧特征点到前一帧特征点的箭头
            cv::arrowedLine(imTrack, curLeftPts[i], mapIt->second, cv::Scalar(0, 255, 0), 1, 8, 0, 0.2);
        }
    }

    //draw prediction
    /*
    for(size_t i = 0; i < predict_pts_debug.size(); i++)
    {
        cv::circle(imTrack, predict_pts_debug[i], 2, cv::Scalar(0, 170, 255), 2);
    }
    */
    //printf("predict pts size %d \n", (int)predict_pts_debug.size());

    //cv::Mat imCur2Compress;
    //cv::resize(imCur2, imCur2Compress, cv::Size(cols, rows / 2));
}

 setMask函数绘制特征点掩膜

个人理解是可以是特征点分布更加均匀,设置一个mask,用于控制特征点的分布,以避免特征点过于密集。它优先保留跟踪时间较长的特征点,同时在遮罩中标记特征点的位置,确保新特征点不会在这些区域内再次出现

//设置特征点的掩膜
void FeatureTracker::setMask()
{
    // 创建一个与图像大小相同的遮罩,初始值为255(白色)
    mask = cv::Mat(row, col, CV_8UC1, cv::Scalar(255));

    // prefer to keep features that are tracked for long time
    // 创建一个向量,用于存储特征点的跟踪计数、点坐标和ID
    vector<pair<int, pair<cv::Point2f, int>>> cnt_pts_id;

    // 遍历当前帧的特征点,将跟踪计数、点坐标和ID存储到向量中
    for (unsigned int i = 0; i < cur_pts.size(); i++)
        cnt_pts_id.push_back(make_pair(track_cnt[i], make_pair(cur_pts[i], ids[i])));

    // 按照特征点的跟踪计数排序,优先保留跟踪时间长的特征点
    sort(cnt_pts_id.begin(), cnt_pts_id.end(), [](const pair<int, pair<cv::Point2f, int>> &a, const pair<int, pair<cv::Point2f, int>> &b)
         {
            return a.first > b.first;
         });

    // 清空当前帧的特征点、ID和跟踪计数
    cur_pts.clear();
    ids.clear();
    track_cnt.clear();

    // 遍历排序后的特征点
    for (auto &it : cnt_pts_id)
    {
        // 如果掩膜中对应位置为白色,表示该特征点没有被遮挡
        if (mask.at<uchar>(it.second.first) == 255)
        {
            // 将特征点、ID和跟踪计数添加到当前帧的特征点、ID和跟踪计数中
            cur_pts.push_back(it.second.first);
            ids.push_back(it.second.second);
            track_cnt.push_back(it.first);
            // 将特征点周围一定半径范围内的遮罩像素值设为黑色,表示该区域已经被遮挡
            cv::circle(mask, it.second.first, MIN_DIST, 0, -1);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值