单目视觉里程记代码

2023-11-11

在Github上发现了一个简单的单目vo,有接近500星,链接如下:https://github.com/avisingh599/mono-vo 。
这个单目里程计主要依靠opencv实现,提取fast角点并进行光流跟踪,然后求取本质矩阵并恢复两帧位姿。整个里程计都是按照上述流程进行,并没有构建地图,因此也没有使用pnp算法。
我对一些难懂的地方做了注释,分享给大家。
以下是头文件:

#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/calib3d/calib3d.hpp"

#include <iostream>
#include <ctype.h>
#include <algorithm> // for copy
#include <iterator> // for ostream_iterator
#include <vector>
#include <ctime>
#include <sstream>
#include <fstream>
#include <string>

using namespace cv;
using namespace std;

// 特征提取
void featureTracking(Mat img_1, Mat img_2, vector<Point2f>& points1, vector<Point2f>& points2, vector<uchar>& status)
{
    //this function automatically gets rid of points for which tracking fails
    vector<float> err;
    Size winSize=Size(21,21);
    TermCriteria termcrit=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01);

    // LK光流跟踪
    calcOpticalFlowPyrLK(img_1, img_2, points1, points2, status, err, winSize, 3, termcrit, 0, 0.001);

    //getting rid of points for which the KLT tracking failed or those who have gone outside the frame
    int indexCorrection = 0;
    // 若跟踪失败,状态status为0(假)
    for( int i=0; i<status.size(); i++)
    {
        Point2f pt = points2.at(i- indexCorrection);
        if((status.at(i) == 0)||(pt.x<0)||(pt.y<0))
        {
            if((pt.x<0)||(pt.y<0))
            {
                status.at(i) = 0;
            }
            points1.erase (points1.begin() + (i - indexCorrection));
            points2.erase (points2.begin() + (i - indexCorrection));
            indexCorrection++;
        }

    }
}

// FAST角点检测
void featureDetection(Mat img_1, vector<Point2f>& points1)
{
    //uses FAST as of now, modify parameters as necessary
    vector<KeyPoint> keypoints_1;
    int fast_threshold = 20;
    bool nonmaxSuppression = true;
    FAST(img_1, keypoints_1, fast_threshold, nonmaxSuppression);
    KeyPoint::convert(keypoints_1, points1, vector<int>());
}

以下为cpp文件

#include "vo_features.h"
using namespace cv;
using namespace std;

#define MAX_FRAME 1000
#define MIN_NUM_FEAT 2000

/// 关于尺度 参考:  https://blog.csdn.net/ouyangandy/article/details/89332114

double getAbsoluteScale(int frame_id, int sequence_id, double z_cal)
{
    string line;
    int i = 0;
    //读取位姿真实值pose.txt  ground_truth
    ifstream myfile ("/home/wang/catkin_jg/src/ORB_SLAM2/Data/KITTI/00/00.txt");
    double x =0, y=0, z = 0;
    double x_prev, y_prev, z_prev;
    if (myfile.is_open())
    {
        // 一直循环到i>frame_id时结束while循环,此时x y z 保存的是i=frame_id时读取的数据
        while (( getline (myfile,line) ) && (i<=frame_id))
        {
            z_prev = z;
            x_prev = x;
            y_prev = y;
            std::istringstream in(line);
            //cout << line << '\n';
            //其排列方式为一个增广矩阵:[R|t]  每一行的最后一列数据为平移的t的数据。
            //把位移t提取出来,即第4,8,12列的数据,从0开始,则是3,7,11
            for (int j=0; j<12; j++)
            {
                in >> z;
                if (j==7) y=z;
                if (j==3) x=z;
            }
            i++;
        }
        myfile.close();
    }
    else
    {
        cout << "Unable to open file";
        return 0;
    }

    return sqrt((x-x_prev)*(x-x_prev) + (y-y_prev)*(y-y_prev) + (z-z_prev)*(z-z_prev)) ;

    /**
     从以上代码来看,数据集KITTI-00的真实值pose.txt,里面每行是12列数据,12列数据很容易想到是3个平移量
     和一个3x3的旋转矩阵,这样想没错,但是其排列方式却不是这样的,而是一个3*4的矩阵,
     其排列方式为一个增广矩阵:[R|t]
     也就是说,每一行的最后一列数据为平移的t的数据。
     那么是如何得到尺度因子的呢?众所周知,单目无法得到真实尺度的信息,不具有单位的概念,因此上面的基于单目的
     视觉里程计其尺度信息是来自于groundtruth的,也就是事先知道的真实尺度,
     如何计算:获取当前帧真实位置与前一帧的真实位置的距离作为尺度值。
     也就是最后return的值,其实就是当前帧的(x,y,z)减去上一帧的(x,y,z)这个真实距离作为真实尺度。
     */

}

int main( int argc, char** argv )
{
    Mat img_1, img_2;
    Mat R_f, t_f; //the final rotation and tranlation vectors

    ofstream myfile;
    myfile.open ("results1_1.txt");

    double scale = 1.00;
    char filename1[200];
    char filename2[200];
    // %06d 打印6个字符,不足的用0填充(在前面补)
    // sptintf用法见:https://www.runoob.com/cprogramming/c-function-sprintf.html
    /*
     *  int main()
        {
           char str[80];
           sprintf(str, "Pi 的值 = %f", M_PI);
           puts(str);     return(0);
        }
        让我们编译并运行上面的程序,这将产生以下结果:
        Pi 的值 = 3.141593
     */
    sprintf(filename1, "/home/wang/catkin_jg/src/ORB_SLAM2/Data/KITTI/00/image_0/%06d.png", 0);
    sprintf(filename2, "/home/wang/catkin_jg/src/ORB_SLAM2/Data/KITTI/00/image_0/%06d.png", 1);

    // 用于轨迹pose GUI界面显示的参数
    char text[100];
    int fontFace = FONT_HERSHEY_PLAIN;
    double fontScale = 1;
    int thickness = 1;
    cv::Point textOrg(10, 50);

    // read the first two frames from the dataset
    Mat img_1_c = imread(filename1);
    Mat img_2_c = imread(filename2);

    if ( !img_1_c.data || !img_2_c.data )
    {
        std::cout<< " --(!) Error reading images " << std::endl;
        return -1;
    }

    // we work with grayscale images
    cvtColor(img_1_c, img_1, COLOR_BGR2GRAY);
    cvtColor(img_2_c, img_2, COLOR_BGR2GRAY);

    // feature detection, tracking
    vector<Point2f> points1, points2;        //vectors to store the coordinates of the feature points
    featureDetection(img_1, points1);     //detect features in img_1
    vector<uchar> status;
    featureTracking(img_1,img_2,points1,points2, status); //track those features to img_2

    //TODO: add a fucntion to load these values directly from KITTI's calib files
    // WARNING: different sequences in the KITTI VO dataset have different intrinsic/extrinsic parameters
    double focal = 718.8560;
    cv::Point2d pp(607.1928, 185.2157);
    //recovering the pose and the essential matrix
    Mat E, R, t, mask;
    E = findEssentialMat(points2, points1, focal, pp, RANSAC, 0.999, 1.0, mask);
    recoverPose(E, points2, points1, R, t, focal, pp, mask);

    Mat prevImage = img_2;
    Mat currImage;
    vector<Point2f> prevFeatures = points2;
    vector<Point2f> currFeatures;

    char filename[100];

    R_f = R.clone();
    t_f = t.clone();

    clock_t begin = clock();

    namedWindow( "Road facing camera", WINDOW_AUTOSIZE );// Create a window for display.
    namedWindow( "Trajectory", WINDOW_AUTOSIZE );// Create a window for display.

    Mat traj = Mat::zeros(600, 600, CV_8UC3);

    for(int numFrame=2; numFrame < MAX_FRAME; numFrame++)
    {
        sprintf(filename, "/home/wang/catkin_jg/src/ORB_SLAM2/Data/KITTI/00/image_0/%06d.png", numFrame);
        //cout << numFrame << endl;
        Mat currImage_c = imread(filename);
        cvtColor(currImage_c, currImage, COLOR_BGR2GRAY);
        vector<uchar> status;
        featureTracking(prevImage, currImage, prevFeatures, currFeatures, status);

        E = findEssentialMat(currFeatures, prevFeatures, focal, pp, RANSAC, 0.999, 1.0, mask);
        recoverPose(E, currFeatures, prevFeatures, R, t, focal, pp, mask);

        Mat prevPts(2,prevFeatures.size(), CV_64F), currPts(2,currFeatures.size(), CV_64F);

        for(int i=0;i<prevFeatures.size();i++)
        {
            //this (x,y) combination makes sense as observed from the source code of triangulatePoints on GitHub
            prevPts.at<double>(0,i) = prevFeatures.at(i).x;
            prevPts.at<double>(1,i) = prevFeatures.at(i).y;

            currPts.at<double>(0,i) = currFeatures.at(i).x;
            currPts.at<double>(1,i) = currFeatures.at(i).y;
        }

        scale = getAbsoluteScale(numFrame, 0, t.at<double>(2));

        //cout << "Scale is " << scale << endl;


        if ((scale>0.1)&&(t.at<double>(2) > t.at<double>(0)) && (t.at<double>(2) > t.at<double>(1)))
        {
            t_f = t_f + scale*(R_f*t);
            R_f = R*R_f;
        }
        /// 错误示范
        /**if (scale > 0.1)
        {
            cur_t_ = cur_t_ + scale*t;
            cur_R_ = R*cur_R_;
        }
        之所以错误的原因是,忽略了平移的方向性,因此左乘旋转矩阵,就规定了它朝哪个方向旋转,
        这也符合真实的平移情况
        我的理解是:先旋转后平移再加上原来的平移量才是真实的平移,如上注释。*/

        else {
         //cout << "scale below 0.1, or incorrect translation" << endl;
        }

        // lines for printing results
        // myfile << t_f.at<double>(0) << " " << t_f.at<double>(1) << " " << t_f.at<double>(2) << endl;

        // a redetection is triggered in case the number of feautres being trakced go below a particular threshold
        if (prevFeatures.size() < MIN_NUM_FEAT)
        {
            //cout << "Number of tracked features reduced to " << prevFeatures.size() << endl;
            //cout << "trigerring redection" << endl;
            featureDetection(prevImage, prevFeatures);
            featureTracking(prevImage,currImage,prevFeatures,currFeatures, status);
        }

        prevImage = currImage.clone();
        prevFeatures = currFeatures;

        int x = int(t_f.at<double>(0)) + 300;
        int y = int(t_f.at<double>(2)) + 100;
        // 半径为1,线宽为2,画出来就成了实心的圆。按照traj轨迹一直画圆,就画出了轨迹。
        circle(traj, Point(x, y) ,1, CV_RGB(255,0,0), 2);
        // 画出显示的矩形
        rectangle( traj, Point(10, 30), Point(550, 50), CV_RGB(0,0,0), CV_FILLED);
        sprintf(text, "Coordinates: x = %02fm y = %02fm z = %02fm", t_f.at<double>(0), t_f.at<double>(1), t_f.at<double>(2));
        putText(traj, text, textOrg, fontFace, fontScale, Scalar::all(255), thickness, 8);

        imshow( "Road facing camera", currImage_c );
        imshow( "Trajectory", traj );

        waitKey(1);

    }

    clock_t end = clock();
    double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
    cout << "Total time taken: " << elapsed_secs << "s" << endl;
    return 0;
}

以下是运行KITTI数据集00序列时的运行截图
在这里插入图片描述
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单目视觉里程记代码 的相关文章

  • 如何将本地不同的 Git 分支推送到 Heroku/master

    Heroku 的政策是忽略除 master 之外的所有分支 虽然我确信 Heroku 的设计者对这个政策有很好的理由 我猜测是为了存储和性能优化 但对我作为开发人员来说 结果是无论我正在研究什么本地主题分支 我都想要一种简单的方法将 Her
  • Git - 远程:致命:你在一个尚未诞生的分支上

    我正在尝试设置一个钩子来从我的桌面推送到我的服务器 这在过去已经工作了无数次 但现在我在设置新网站时遇到错误 remote fatal You are on a branch yet to be born 我一如既往地完成了与命令相同的系列
  • 如何将index.html链接到github页面的css文件

    我正在托管一个 github 页面 并希望将 index html 链接到 main css 该文件位于根目录中名为 stylesheets 的文件夹中 我的文件结构 index html stylesheets main css 现在我有
  • 如何更改 Github/Markdown 中图像的大小?

    我正在 Github 存储库中编辑 Readme md 文件 并插入了一张图片 请参阅https github com khpeek FMCW 雷达 https github com khpeek FMCW radar 图片占据了整个宽度
  • git clone 永远挂在 github 上

    当我按照 github 中的第 5 点 测试所有内容 时guide http help github com linux set up git ssh 命令也永远挂起 根据该指南 我应该看到一条消息 Github 不提供 shell 访问
  • 在 Google Cloud Functions 中安装私有 GitHub npm 包不起作用

    我正在尝试将微服务部署到 GCF 它依赖于私有 GitHub 托管的包 为了访问该包 我向该函数添加了一个 npmrc 文件 如下所述here https cloud google com functions docs writing sp
  • Github Pages 网站图标未显示

    我正在使用 Github Pages 托管一个网站 由于 SSL 我将 Cloudflare 连接到该网站 当我最后添加时favicon ico到我的网站和以下代码使图标显示出来 它仍然不显示 我能做些什么 英语不是我的母语 Edit 似乎
  • 如何在 github 提交中设置用户名别名?

    我刚刚在大学读完一个学期 决定将我的所有项目从 bitbucket 我的课程所需 导入到 github 我所有其他项目都在其中 我成功导入了它们 不幸的是 当我从事这些项目时 我在三台不同的计算机之间切换 因此 提交历史记录中有许多我自己所
  • 创建 git 分支并将其合并到史诗分支

    我正在开发一个项目 例如 SO bubbleSort 我需要创建一个名为 feature version 1 的史诗分支 因此从这个史诗分支中很少有开发人员进行开发 因此为此 他们需要创建一个将分支与史诗分支分开 我的问题是我们如何合并这些
  • GitHub:本地使用 wiki:首次推送到 GitHub wiki 时出错

    这个问题我搜索了很多次 还是无法解决 假设我从 user1 分叉了一个存储库 我们将其命名为 repo1 现在我也想分叉该存储库的 Wiki 部分 当您分叉存储库时 GitHub 不会为您做任何事情 https stackoverflow
  • Github 操作/缓存工作的限制在什么范围内?

    我不太明白Github到了什么程度动作 缓存有效 我的意思是 如果我发出拉取请求 然后在同一个拉取请求中添加 1 个提交 则缓存工作正常 但如果我在同一分支中创建新的拉取请求 缓存将重置并再次启动 但为什么呢 有没有办法将文件缓存扩展到所有
  • 没有工作树就无法使用 Git-Windows-git-pull

    我在 Windows 上遇到与 Git 相关的问题 无法从 git 上的存储库中提取更改 我能够添加 提交和推送我的更改 但不能拉取 它给了我一个错误 致命 C Git libexec git core git pull 不能在没有 工作树
  • `env.BRANCH_NAME` 变为 `PR-1`

    我们使用 Jenkins 管道和 Github Multibranch 我在一个名为的功能分支上工作feature my1stfeature Jenkins 作业返回正确的分支名称 println env BRANCH NAME 回feat
  • Azure Pipelines 状态徽章未显示在 Markdown 中

    我已经为我的 github 存储库之一设置了 azure 管道 除了构建状态徽章之外 一切都工作正常 它没有正确显示 似乎无法加载图像 目前正在显示 Edit markdown 文件中使用的代码由 azure devops 自动生成 Bui
  • 将 jQuery 与托管在 Github 页面上的 Jekyll 站点结合使用

    我有一个简单的 Jekyll 博客托管在 github 页面上 我已经包含了 jQuery 和 tablesorter 插件 但遇到了错误 据我所知 这个错误是由于没有以正确的顺序加载 jQuery 引起的 我的下图显示代码的顺序是正确的
  • 更改用户名 Github “您必须验证您的电子邮件地址” git Push 上出现 403 错误

    我最近在 github 上更改了我的用户名 以便所有内容都重定向到我用户名上的新 URL 我通过命令行收到此错误git push 您必须验证您的电子邮件地址 致命 请求 URL 返回错误 403 但是 github 不允许我在帐户 gt 设
  • 如何修复 GitHub 拉取请求中被 git rebase 破坏的提交顺序?

    当我编写代码时 我会将其分解为小的逻辑更改 以便轻松快速地进行审查 为此 我使用git rebase i 交互式 压缩 删除和更改提交的顺序 我注意到这有时会导致 GitHub 拉取请求的提交顺序不同 尽管该顺序保留在远程分支上 例如 co
  • 如何在命令行上创建要点

    我正在尝试从 bash 创建一个要点 并且我尝试了许多可以获得的版本脚本 但没有一个有效 这似乎是正确的 但它也不起作用 curl X POST d public true files test txt content String fil
  • 对 git Push 运行单元测试,对 Pull 请求运行集成测试

    在构建 R 包时 我们使用 testthat 来编写测试 我们有 2 个文件 特定包的测试文件 特异性R 我们用它来确保所有包继续一起工作并且总体结果良好 整体R 当前 当我们推送到 github 或通过 Travis 创建 PR 时 都会
  • 图表贡献者为空

    我在 github 上有几个项目 但其中一些项目的贡献者图是空的 即使我的 gitconfig 设置了名称和电子邮件 https github com jlengrand batchWaterMarking graphs contribut

随机推荐

  • “export ‘default‘ (imported as ‘VueRouter‘) was not found in ‘vue-router‘

    vue router 4使用时 报上面的错 代码是这么写的 import VueRouter from vue router const Test template h1 测试 h1 const routes name Test path
  • uni-app编写轮播图

    使用usw就会显示swiper的快捷代码
  • ReduceTask工作机制图解

    1 Copy阶段 ReduceTask从各个MapTask上远程拷贝一片数据 并针对某一片数据 如果其大小超过一定阈值 则写到磁盘上 否则直接放到内存中 2 Merge阶段 在远程拷贝数据的同时 ReduceTask启动了两个后台线程对内存
  • ARM体系结构简介 —— 迅为

    目录 单片机和ARM处理器 内存管理单元 MMU 高速缓冲存储器 CACHE 指令集 ARM的指令系统 ARM处理器工作模式 ARM处理器的内部寄存器 ARM处理器的异常 ARM中断向量 ARM架构的发展 单片机和ARM处理器 内存管理单元
  • 10. adb截图命令

    adb截图命令 adb shell screencap 输入以下命令进行截屏 adb shell screencap sdcard screen png 将截图上传到PC的F盘 已创建目录F screenshot adb pull sdca
  • docker 笔记1

    目录 1 为什么有docker 2 Docker 的核心概念 3 容器与虚拟机比较 3 1传统的虚拟化技术 3 2容器技术 3 3Docker容器的有什么作用 3 4应用案例 4 docker 安装下载 4 1CentOS Docker 安
  • 排序(7)归并排序

    6 归并排序 将两个有序表合并为一个有序表
  • (万字,细细阅读)竞赛算法入门必经算法模型(附带题目链接和模板)

    文章前言 一个普通的ACM算法竞赛选手 以前只知道写题 却没有自己弄一个算法流程 思考许久 决定整理一下算法 先从入门算法入手 如有不足 望指出 持续更新 直到完善 现在已经破万了 最后字数粗略估计将会达到6万字 写完有时间的话会写进阶版的
  • Angularjs的http请求

    1 使用 http发起请求 scope exportWord function item var config headers Content Type application json http post commonFileUrl pa
  • html怎么调用数据库数据类型,引用数据类型是什么?

    引用数据类型是指由类型的实际值引用 类似于指针 表示的数据类型 如果为某个变量分配一个引用类型 则该变量将引用 或 指向 原始值 不创建任何副本 引用类型包括类 接口 委托和装箱值类型 引用数据类型的概念 引用 reference 是c 的
  • 2020美赛D题解题思路方法:团队合作策略

    随着社会之间的联系越来越紧密 它们面临的一系列挑战也越来越复杂 我们依靠具有不同专业知识和不同观点的跨学科团队来解决许多最具挑战性的问题 在过去50多年里 我们对团队成功的概念性理解有了显著的进步 使得更好的科学 创新或物理团队能够解决这些
  • MobaXterm_Personal_12.2软件连接开发板

    MobaXterm Personal 12 2软件连接开发板会出现乱码 有以下几个原因 1 波特率没设置对 2 编码格式不对 要选GBK 我这边板子对应波特率是115200 流控也要关掉
  • OpenMMLab AI实战营第二天笔记

    图像分类与基础视觉模型 卷积神经网络 AlexNet 2012 第一个成功实现大规模图像的模型 在 ImagNet 数据集上达到 85 的 top 5 的准确率 5 个卷积层 3 个全连接层 共有 60M 个可学习参数 使用 ReLU 激活
  • linux环境部署jmeter并执行测试

    下载jmeter和jdk jmeter官网和java jdk官网下载压缩包文件 jmeter下载地址 点此下载 jmeter Apache JMeter Download Apache JMeter java jdk下载地址 点此下载 jd
  • C# HTML转图片

    C HTML转图片 一 WebBrowser 二 实例 1 HTML文件 2 CS代码 三 总结 一 WebBrowser WebBrowser常用来做应用内嵌的WebUI 使用时需要进入System Windows Forms程序集 二
  • Gitee Config服务读取加载远程信息

    Config服务读取加载远程信息 课程回顾 1 微服务调用常见现象 客户端会多次请求不同的微服务 增加了客户端的复杂性 存在跨域请求 在一定场景下处理相对复杂 认证复杂 每个服务都需要独立认证 难以重构 随着项目的迭代 可能需要重新划分微服
  • 【SpringMVC】JSON数据传输与异常处理的使用

    文章目录 一 Jackson 1 1 Jackson是什么 1 2 常用注解 1 3 实例 1 3 1导入依赖 1 3 2 配置spring mvc xml 1 3 3 JsonController java 二 Spring MVC异常处
  • 程序猿专属的国庆中秋放假通知!

    无bug 不用加班 系统上线一切正常 批准放假 程序猿祝大家 国庆中秋快乐
  • ibatis.binding.BindingException: Invalid bound statement (not found): com.suntang.storage.mapper.Per

    今天新来的小弟弟调试代码时发现控制台报错了 自己调试了半天也没找到原因 该排查的方案也都排查了 就是解决不了 无奈找到了我 我也照着网上的方案排查了一遍 确实不行 然后就自己分析了一下 这个问题已经两个人问过我了 还是在此记录一下吧 控制台
  • 单目视觉里程记代码

    在Github上发现了一个简单的单目vo 有接近500星 链接如下 https github com avisingh599 mono vo 这个单目里程计主要依靠opencv实现 提取fast角点并进行光流跟踪 然后求取本质矩阵并恢复两帧