在windows下vs c++运行g2o的BA优化程序示例

news2024/11/27 12:36:36

目录

  • 1、前言
  • 2、准备工作
    • 安装git
    • 安装vcpkg
      • (1)下载
      • (2)安装
      • (3)集成至vs
    • 安装cmake
  • 3、安装g2o
  • 4、安装opencv
    • (1)下载
    • (2)双击安装
    • (3)环境变量和system文件夹设置
  • 使用g2o进行BA优化
  • 5、总结

1、前言

本篇博客主要介绍如何在Windows下安装g2o,并利用g2o和OpenCV库实现一个两帧之间的ORB特征点检测和BA优化的C++程序。ORB是一种既能检测特征点,又能描述特征点的算法,BA是一种优化算法,可以优化相机位姿和三维点云。本文将详细介绍如何在Windows下安装g2o,并利用g2o和OpenCV库实现一个两帧之间的ORB特征点检测和BA优化的C++程序。首先,我们需要在Windows下安装g2o。g2o是一个用于图优化的C++库,可以用于SLAM、机器人、计算机视觉等领域。其次,我们需要使用OpenCV库来进行ORB特征点检测。最后,我们将使用g2o库来进行BA优化。在本文中,我们将详细介绍如何在Windows下安装g2o,并利用g2o和OpenCV库实现一个两帧之间的ORB特征点检测和BA优化的C++程序。

2、准备工作

安装git

需要安装git工具,可以上官网去下载安装软件。直接选择默认选项安装到底就可以。
在这里插入图片描述

安装vcpkg

经过多种方法安装失败后,本人认为,通过vcpkg安装g2o库是最为方便的,vcpkg可以自动安装g2o所需要的依赖库。vcpkg本身的安装也非常方便。
关于安装vcpkg详细步骤如下:

(1)下载

在D盘为 vcpkg 的克隆实例创建目录。
打开cmd,进入创建的目录,从 GitHub 克隆 vcpkg 存储库:https://github.com/Microsoft/vcpkg。

git clone https://github.com/microsoft/vcpkg

或者直接上github去打包下载。

(2)安装

下载完后,解压到安装目录。然后cmd进入vcpkg目录内,里面有个.bat文件,在 vcpkg 根目录下,cmd 下运行 vcpkg 引导程序命令:

bootstrap-vcpkg.bat

就算完成了。

(3)集成至vs

cmd进到vcpkg目录下,只要允许这个命令就可以

 .\vcpkg integrate install

如果后期不想集成可以通过指令去除

 .\vcpkg integrate remove

安装cmake

这个也简单,直接去官网下载安装包,双击安装完事。
先打开下载链接点击下载,下后双击正常安装。
在这里插入图片描述

3、安装g2o

非常简单,cmd进入vcpkg目录,输入指令安装:

vcpkg install g2o:x64-windows

耐心等待安装结束,网络要有保障。

4、安装opencv

opencv也可以通过指令安装,也可以去官网下载安装包,我是直接官网下的,弄完需要设置一些东西:

(1)下载

直接从官网下载安装包就行:
在这里插入图片描述

(2)双击安装

这个是二进制文件,直接按照到相应的目录下。
在这里插入图片描述
在这里插入图片描述

(3)环境变量和system文件夹设置

将opencv的\build\x64\vc16\bin文件夹路径写入系统环境变量(path)里面:
在这里插入图片描述
同时,需要把bin下面的所有dll文件都放到system32里面:
在这里插入图片描述
在这里插入图片描述

使用g2o进行BA优化

创建一个vs C++项目,然后直接复制如下代码,就可以编译运行了,对代码进行了一些注释,供参考:

// g2otest.cpp: 定义应用程序的入口点。
//
#pragma once
// for opencv
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
// for g2o
#include <g2o/core/sparse_optimizer.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/core/robust_kernel_impl.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/solvers/cholmod/linear_solver_cholmod.h>
#include <g2o/types/slam3d/se3quat.h>
#include <g2o/types/sba/types_six_dof_expmap.h>
//for eigen
#include <Eigen/Core>
#include <Eigen/Geometry>

/**
 * BA Example
 * Author: Xiang Gao
 * Date: 2016.3
 * Email: gaoxiang12@mails.tsinghua.edu.cn
 *
 * 在这个程序中,我们读取两张图像,进行特征匹配。然后根据匹配得到的特征,计算相机运动以及特征点的位置。这是一个典型的Bundle Adjustment,我们用g2o进行优化。
 */
using namespace std;

// 寻找两个图像中的对应点,像素坐标系
// 输入:img1, img2 两张图像
// 输出:points1, points2, 两组对应的2D点
int     findCorrespondingPoints(const cv::Mat& img1, const cv::Mat& img2, vector<cv::Point2f>& points1, vector<cv::Point2f>& points2);

// 相机内参,自己标定设定
double cx = 256;
double cy = 256;
double fx = 520;
double fy = 520;

int main(int argc, char** argv)
{
    // 调用格式:命令 [第一个图] [第二个图]
    cv::Mat img1;
    cv::Mat img2;
    if (argc < 3)
    {
        cout << "无输入图像路径"<< endl;
        // 读取图像
        img1 = cv::imread("F:/c++test/g2otest/img/image1.jpg", cv::IMREAD_GRAYSCALE);
        img2 = cv::imread("F:/c++test/g2otest/img/image2.jpg", cv::IMREAD_GRAYSCALE);
    }
    else {
        cout << "输入图像路径" << argv[1]<<"和" << argv[2] << endl;
        img1 = cv::imread(argv[1]);
        img2 = cv::imread(argv[2]);

    }



    // 找到对应点
    vector<cv::Point2f> pts1, pts2;
    if (findCorrespondingPoints(img1, img2, pts1, pts2) == false)
    {
        cout << "匹配点不够!" << endl;
        return 0;
    }
    cout << "找到了" << pts1.size() << "组对应特征点。" << endl;
    // 构造g2o中的图
    /*
    */
    // 先构造求解器
    g2o::SparseOptimizer    optimizer;
    // 6*3 的参数
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 3>> Block; 

    // 使用Cholmod中的线性方程求解器
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 3>> BlockSolverType;
    typedef g2o::LinearSolverCholmod<BlockSolverType::PoseMatrixType> LinearSolverType;

    auto solver = new g2o::OptimizationAlgorithmLevenberg(g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));

    optimizer.setAlgorithm(solver);
    optimizer.setVerbose(false);
    
    // 添加节点
    // 两个位姿节点
    for (int i = 0; i < 2; i++)
    {
        g2o::VertexSE3Expmap* v = new g2o::VertexSE3Expmap();
        v->setId(i);
        if (i == 0)
            v->setFixed(true); // 第一个点固定为零
        // 预设值为单位Pose,因为我们不知道任何信息
        v->setEstimate(g2o::SE3Quat());
        optimizer.addVertex(v);
    }
    // 很多个特征点的节点观测值
    // 以第一帧为准
    for (size_t i = 0; i < pts1.size(); i++)
    {
        g2o::VertexSBAPointXYZ* v = new g2o::VertexSBAPointXYZ();
        v->setId(2 + i);
        // 由于深度不知道,只能把深度设置为1了
        double z = 1;
        double x = (pts1[i].x - cx) * z / fx;
        double y = (pts1[i].y - cy) * z / fy;
        /*
        * v->setMarginalized函数的作用是将节点标记为边缘化节点。
        边缘化节点是指在优化过程中,将该节点的估计值从优化变量中剔除,
        只保留其对应的误差项。这样做的好处是可以减少优化变量的数量,
        从而降低计算复杂度。同时,边缘化节点还可以提高优化的精度和鲁棒性,
        因为它可以将一些不确定的变量边缘化掉,从而减少误差的传递。需要注意
        的是,只有在节点的所有边都被边缘化后,该节点才能被边缘化。因此,在
        使用v->setMarginalized函数时,需要保证该节点的所有边都已经被正确地边缘化了。
        */
        v->setMarginalized(true);
        v->setEstimate(Eigen::Vector3d(x, y, z));
        optimizer.addVertex(v);
    }

    // 准备相机参数
    g2o::CameraParameters* camera = new g2o::CameraParameters(fx, Eigen::Vector2d(cx, cy), 0);
    camera->setId(0);
    optimizer.addParameter(camera);
    /*
    * 使用EdgeProjectXYZ2UV类型,设置边的两个顶点分别为特征点节点和位姿节点,测量值为(pts1[i].x, pts1[i].y)
    或(pts2[i].x, pts2[i].y),
    信息矩阵为单位矩阵,参数id为0,核函数为Huber核函数,并将其添加到优化器中。
    */
    // 准备边
    // 第一帧
    vector<g2o::EdgeProjectXYZ2UV*> edges;
    for (size_t i = 0; i < pts1.size(); i++)
    {//创建一个新的边对象,类型为g2o::EdgeProjectXYZ2UV
        g2o::EdgeProjectXYZ2UV* edge = new g2o::EdgeProjectXYZ2UV();
        //设置边的第一个顶点,即3D点的顶点。这里的i+2是因为在优化器中,前两个顶点是相机位姿的顶点,
        //所以3D点的顶点编号从2开始。dynamic_cast是将基类指针转换为派生类指针的方法。
        edge->setVertex(0, dynamic_cast<g2o::VertexSBAPointXYZ*>   (optimizer.vertex(i + 2)));
        //设置边的第二个顶点,即相机位姿的顶点。这里的相机位姿是指将相机从世界坐标系变换到相机坐标系的变换矩阵。
        edge->setVertex(1, dynamic_cast<g2o::VertexSE3Expmap*>     (optimizer.vertex(0)));
        //设置边的观测值,即特征点在图像上的坐标。
        edge->setMeasurement(Eigen::Vector2d(pts1[i].x, pts1[i].y));
        //设置边的信息矩阵,这里是单位矩阵。
        edge->setInformation(Eigen::Matrix2d::Identity());
        //设置边的参数,这里是指定边的参数块为0号参数块。
        edge->setParameterId(0, 0);
        // 核函数,设置边的核函数,这里是Huber核函数,用于鲁棒优化。
        edge->setRobustKernel(new g2o::RobustKernelHuber());
        //将边添加到优化器中。
        optimizer.addEdge(edge);
        //将边对象指针添加到一个vector中,方便后续的操作。
        edges.push_back(edge);
    }
    // 第二帧
    for (size_t i = 0; i < pts2.size(); i++)
    {
        g2o::EdgeProjectXYZ2UV* edge = new g2o::EdgeProjectXYZ2UV();
        edge->setVertex(0, dynamic_cast<g2o::VertexSBAPointXYZ*>   (optimizer.vertex(i + 2)));
        edge->setVertex(1, dynamic_cast<g2o::VertexSE3Expmap*>     (optimizer.vertex(1)));
        edge->setMeasurement(Eigen::Vector2d(pts2[i].x, pts2[i].y));
        edge->setInformation(Eigen::Matrix2d::Identity());
        edge->setParameterId(0, 0);
        // 核函数
        edge->setRobustKernel(new g2o::RobustKernelHuber());
        optimizer.addEdge(edge);
        edges.push_back(edge);
    }

    cout << "开始优化" << endl;
    optimizer.setVerbose(true);
    optimizer.initializeOptimization();
    optimizer.optimize(10);
    cout << "优化完毕" << endl;

    //我们比较关心两帧之间的变换矩阵
    /*
    * 从g2o优化器中获取id为1的VertexSE3Expmap类型的顶点v,并获取其位姿估计值pose,最后输出位姿矩阵。
    其中,g2o是一个用于非线性优化的C++库,VertexSE3Expmap是g2o中的一个顶点类型,表示一个带有平移和
    旋转的位姿,estimate()函数返回该顶点的位姿估计值,matrix()函数返回该位姿的变换矩阵。
    dynamic_cast是C++中的一种类型转换方式,用于将基类指针或引用转换为派生类指针或引用,
    这里将optimizer.vertex(1)返回的基类指针转换为VertexSE3Expmap类型的指针。
    */
    g2o::VertexSE3Expmap* v = dynamic_cast<g2o::VertexSE3Expmap*>(optimizer.vertex(1));
    Eigen::Isometry3d pose = v->estimate();
    cout << "Pose=" << endl << pose.matrix() << endl;

    // 以及所有特征点的位置
    for (size_t i = 0; i < pts1.size(); i++)
    {
        g2o::VertexSBAPointXYZ* v = dynamic_cast<g2o::VertexSBAPointXYZ*> (optimizer.vertex(i + 2));
        cout << "vertex id " << i + 2 << ", pos = ";
        Eigen::Vector3d pos = v->estimate();
        cout << pos(0) << "," << pos(1) << "," << pos(2) << endl;
    }

    // 估计inlier的个数
    int inliers = 0;
    for (auto e : edges)
    {
        e->computeError();
        // chi2 就是 error*\Omega*error, 如果这个数很大,说明此边的值与其他边很不相符
        if (e->chi2() > 1)
        {
            cout << "error = " << e->chi2() << endl;
        }
        else
        {
            inliers++;
        }
    }

    cout << "inliers in total points: " << inliers << "/" << pts1.size() + pts2.size() << endl;
    optimizer.save("ba.g2o");
    return 0;
}


int     findCorrespondingPoints(const cv::Mat& img1, const cv::Mat& img2, vector<cv::Point2f>& points1, vector<cv::Point2f>& points2)
{

    cv::Ptr<cv::FeatureDetector> orb = cv::ORB::create();
    //cv::ORB orb;
    vector<cv::KeyPoint> kp1, kp2;
    cv::Mat desp1, desp2;
    orb->detectAndCompute(img1, cv::Mat(), kp1, desp1);
    orb-> detectAndCompute(img2, cv::Mat(), kp2, desp2);

    cout << "分别找到了" << kp1.size() << "和" << kp2.size() << "个特征点" << endl;

    cv::Ptr<cv::DescriptorMatcher>  matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");

    double knn_match_ratio = 0.8;
    vector< vector<cv::DMatch> > matches_knn;
    matcher->knnMatch(desp1, desp2, matches_knn, 2);
    // 输出匹配点
    cv::Mat img_matches;
    cv::drawMatches(img1, kp1, img2, kp2, matches_knn, img_matches);
    cv::imshow("Matches", img_matches);
    cv::waitKey(0);

    vector< cv::DMatch > matches;
    for (size_t i = 0; i < matches_knn.size(); i++)
    {
        if (matches_knn[i][0].distance < knn_match_ratio * matches_knn[i][1].distance)
            matches.push_back(matches_knn[i][0]);
    }


    if (matches.size() <= 20) //匹配点太少
        return false;

    for (auto m : matches)
    {
        points1.push_back(kp1[m.queryIdx].pt);
        points2.push_back(kp2[m.trainIdx].pt);
    }

    return true;
}

5、总结

由于python的性能及机器人导航算法开源工具主流均采用C++,后续要做的工作就是进行c++和python的联合开发,将c++的优秀工具集成进python,这样就可以优势互补,形成一个可行的工程应用解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1216577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis应用场景及常见的数据类型

目录 一、Redis应用场景 1.1 Redis作为缓存 1.2 Redis作为消息队列 1.3 实现计数器和排行榜 1.4 实现分布式锁及分布式会话管理 二、Redis常见的数据类型 2.1 String&#xff08;字符串&#xff09;类型 2.2 list类型 2.3 Hash类型 2.4 Set类型 2.5 Sorted Set 一、Redis应用场…

前端开发学习 (一) 搭建Vue基础环境

一、环境搭建 1、安装nodejs #下载地址 https://nodejs.org/dist/v20.9.0/node-v20.9.0-x64.msi 2、配置环境变量 上面下载完安装包后自行安装&#xff0c;安装完成后安装下图操作添加环境变量 #查看版本 node --version v20.9.0# npm --version 10.1.03、配置npm加速源 np…

机器学习的逻辑回归

Sigmoid函数 逻辑回归的预测函数 梯度下降法-逻辑回归 import matplotlib.pyplot as plt import numpy as np # 生成一个关于分类器性能的详细报告。 # 这个报告包含了每个类别的精度、召回率、F1分数&#xff0c;以及所有类别的平均精度、召回率和F1分数 from sklearn.metri…

leetcode:367. 有效的完全平方数(python3解法)

难度&#xff1a;简单 给你一个正整数 num 。如果 num 是一个完全平方数&#xff0c;则返回 true &#xff0c;否则返回 false 。 完全平方数 是一个可以写成某个整数的平方的整数。换句话说&#xff0c;它可以写成某个整数和自身的乘积。 不能使用任何内置的库函数&#xff0c…

科技云报道:Citrix正式退出中国市场!国产们谁能真正顶上?

科技云报道原创。 2023年12月3日&#xff0c; Citrix&#xff08;思杰&#xff09;发布的公告将全面生效&#xff0c;中国市场&#xff08;包括香港地区和澳门地区&#xff09;也会停止所有新的交易。 这个消息&#xff0c;无疑是引起了业界的热议&#xff0c;毕竟Citrix可以…

11.11作业题

1.不死兔子 def fib(n):if n < 4:return 1else:return fib(n-1) fib(n-2) print("一年后共繁殖{}对兔子".format(fib(12))) 2.输入字符串&#xff0c;判断该字符串是否是回文字符串 s input("请输入一个字符串&#xff1a;") if not s:print("…

el-select组件绑定change怎么获取label和value值

组件中change回调只能获取到value,但是有时候需求是要传两个参数&#xff08;elementui 封装的change只能获取到value,我们可以通过原生事件去获取option值&#xff09;。 如果要在element组件上触发原生事件&#xff0c;一律都得加.native修饰符&#xff0c;否则无法触发事件。…

6.1 集合概述

1. 集合概述 1.1. 引入 在前面的章节中我们学习了数组&#xff0c;数组可以存储多个对象&#xff0c;但是数组只能存储相同类型的对象&#xff0c;如果要存储一批不同类型的对象&#xff0c;数组便无法满足需求了。为此&#xff0c;Java提供了集合&#xff0c;集合可以存储不…

《011.SpringBoot之餐厅点餐系统》

《011.SpringBoot之餐厅点餐系统》【界面简洁功能简单】 项目简介 需要源码及数据库的私信… [1]本系统涉及到的技术主要如下&#xff1a; 推荐环境配置&#xff1a;DEA jdk1.8 Maven MySQL 前后端分离; 后台&#xff1a;SpringBootMybatisPlus; 前台&#xff1a;Layuivue; …

现在做跨境电商还需要全球代理IP吗?全球代理IP哪家靠谱?

随着全球互联网的发展&#xff0c;电商平台的发展和跨境贸易的便利化&#xff0c;跨境电商在过去几年中也一直呈现增长趋势&#xff0c;吸引了越来越多的企业和个体创业者入行。 然而&#xff0c;行业竞争也在不断加剧&#xff0c;跨境电商面临更多的越来越多的挑战&#xff0…

3C品牌国际市场攻略:海外网红营销如何推动电子经济

随着全球信息技术的快速发展&#xff0c;3C电子产品市场变得愈发竞争激烈&#xff0c;各品牌需要不断寻求新的市场推广方法来吸引更多消费者。其中&#xff0c;海外网红营销成为了一个备受关注的趋势&#xff0c;融合了互联网、社交媒体和消费品牌的力量&#xff0c;为3C品牌在…

comfyui指北-1

https://colab.research.google.com/github/tieai/SDXL-ComfyUI-Colab/blob/main/SDXL_OneClick_ComfyUI.ipynb#scrollToSaAJk33ppFw1https://colab.research.google.com/github/tieai/SDXL-ComfyUI-Colab/blob/main/SDXL_OneClick_ComfyUI.ipynb#scrollToSaAJk33ppFw1 可以用上…

解决IP查询结果偏差的几个方法

解决IP查询结果偏差的方法可以包括以下几个方面&#xff1a; 选择权威的IP查询工具&#xff1a;使用来自可信来源的IP查询工具&#xff0c;例如官方或专业的IP地址数据库&#xff0c;以确保查询结果的准确性和可靠性。 考虑使用代理服务器或VPN&#xff1a;如果需要更准确的IP…

基于单片机的温度控制器系统设计

**单片机设计介绍&#xff0c; 基于单片机的温度控制器系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的温度控制器系统是一种利用单片机来检测环境温度并控制温度的系统。它通常由以下几个部分组成&#xff…

释放机器人潜力,INDEMIND深耕底层技术

市场转暖&#xff0c;但攘外需要同时安内。 市场降温之后&#xff0c;正迎来拐点 疫情之后&#xff0c;经济逐渐下行&#xff0c;服务机器人的“好日子”也随之结束&#xff0c;整个行业都在动荡中经历渡劫。根据TE智库报告显示&#xff0c;从2022年开始&#xff0c;我国服务…

LeetCode(21)反转字符串中的单词【数组/字符串】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 151. 反转字符串中的单词 1.题目 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单…

单例模式(常用)

单例模式&#xff08;单例设计模式) 在有些系统中&#xff0c;为了节省内存资源、保证数据内容的一致性&#xff0c;对某些类要求只能创建一个实例&#xff0c;这就是所谓的单例模式。 单例模式的定义与特点 单例&#xff08;Singleton&#xff09;模式的定义&#xff1a;指…

35、Flink 的 Formats 之CSV 和 JSON Format

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

CCF-C类 | 仅1个月Accept!中科院2区SCI,Elsevier出版社,审稿快易录用!

【SciencePub学术】本期&#xff0c;小编给大家推荐的是一本CCF-C类、审稿快易录用&#xff0c;且对国人相当友好的SCI期刊&#xff0c;其详情及如下&#xff1a; 期刊简介 IMAGE AND VISION COMPUTING ISSN&#xff1a;0262-8856 E-ISSN&#xff1a;1872-8138 IF&#x…

营收净利双降、市值蒸发50亿,莱克电气苦战双11

文 | 不二研究 若楠 熊生 新增长难寻&#xff0c;新故事难讲。莱克电气股份有限公司(下称“莱克电气”&#xff0c;603355.SH)承压的困局&#xff0c;都写在最新的三季报里。 今年双11前夕&#xff0c;全国吸尘器ODM龙头莱克电气公布了2023年三季报&#xff0c;其前三季度营收…