使用 C++ 和 Eigen 库理解 IMU 数据处理与可视化

使用 C++ 和 Eigen 库理解 IMU 数据处理与可视化

        在本文中,我们将探讨如何使用 C++ 和 Eigen 库处理和可视化惯性测量单元(IMU)数据。IMU 数据在各种应用中至关重要,包括机器人技术、导航系统和虚拟现实。我们将探讨如何读取 IMU 数据,处理数据以估计姿态,并使用 Pangolin 可视化轨迹。

读取 IMU 数据

        我们首先从文件中读取 IMU 数据。数据包括时间戳、角速度和加速度。我们使用一个简单的函数 readIMUData 解析数据,并将其存储在 ImuFrame 结构的向量中。

IMU的数据格式

1403636579758555392 -0.099134701513277898 0.14730578886832138 0.02722713633111154 8.1476917083333333 -0.37592158333333331 -2.4026292499999999

估计姿态

        接下来,我们实现一个 IMU 跟踪器类(IMUTracker)来估计随时间变化的姿态(位置和方向)。我们用第一个 IMU 帧初始化跟踪器,并顺序处理后续帧来更新姿态估计。姿态估计的核心是将角速度积分以更新方向,将加速度积分以更新位置。

可视化

        一旦我们估计了姿态,我们就使用 Pangolin 在三维空间中可视化轨迹。我们创建一个窗口,并在其中渲染轨迹。每个姿态表示为一个点,我们连接连续的姿态以可视化轨迹。

代码展示

IMUTracker.h

// 包含必要的头文件
#pragma once#include <eigen3/Eigen/Dense> // Eigen 线性代数库的头文件
#include <fstream>            // 文件输入输出流
#include <iomanip>            // 格式控制头文件,用于输出格式控制
#include <iostream>           // 标准输入输出流
#include <pangolin/pangolin.h> // Pangolin 可视化库头文件
#include <unistd.h>             // 提供对 POSIX 操作系统 API 的访问
#include <vector>               // 向量容器头文件using namespace std;
using namespace Eigen; // Eigen 命名空间// 姿态结构体
struct Pose
{uint64_t timestamp;       // 时间戳Vector3d position;        // 位置Quaterniond orientation;  // 方向Vector3d linear_vel;      // 线性速度Vector3d ang_vel;         // 角速度
};// 点结构体
struct Point
{Vector3d pos;       // 位置Matrix3d orien;     // 方向Vector3d ang_vel;   // 角速度Vector3d linear_vel; // 线性速度
};// IMU 数据帧结构体
struct ImuFrame
{uint64_t timestamp; // 时间戳Vector3d ang_vel;   // 角速度Vector3d acc_vel;   // 加速度
};// 读取 IMU 数据的函数
bool readIMUData(const string &path, vector<ImuFrame> &imu_msg_buffer)
{ifstream fin; // 文件输入流对象fin.open(path, ios::in); // 打开文件if (!fin.is_open()){cout << "打开文件失败。" << endl; // 输出错误信息return false;}// 读取文件中的数据,并存储到 imu_msg_buffer 向量中for (int i = 0; i < 36820; i++){double data[7]; // 存储数据的数组for (int j = 0; j < 7; j++){fin >> data[j]; // 读取数据}ImuFrame IMUData; // 创建 ImuFrame 对象IMUData.timestamp = data[0]; // 设置时间戳IMUData.ang_vel = Vector3d(data[1], data[2], data[3]); // 设置角速度IMUData.acc_vel = Vector3d(data[4], data[5], data[6]); // 设置加速度imu_msg_buffer.push_back(IMUData); // 将数据添加到 imu_msg_buffer 中}return true; // 返回 true,表示读取成功
}// 保存轨迹数据到 TUM 格式文件的函数
void saveTrajectoryTum(const string &filename, vector<Pose> &vPose)
{ofstream f; // 文件输出流对象f.open(filename.c_str()); // 打开文件f << fixed; // 设置输出格式// 将姿态数据写入文件for (auto iter = vPose.begin(); iter != vPose.end(); iter++){Quaterniond q = (*iter).orientation;Vector3d t = (*iter).position;f << setprecision(6) << (*iter).timestamp << setprecision(7) << " " << t(0) << " " << t(1) << " " << t(2) << " "<< q.w() << " " << q.x() << " " << q.y() << " " << q.z() << endl;}f.close(); // 关闭文件
}// 显示轨迹的函数
void showTrack(vector<Isometry3d, aligned_allocator<Isometry3d>> &poses)
{// 创建窗口并设置 OpenGL 渲染状态pangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 768);glEnable(GL_DEPTH_TEST);glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);// 设置 OpenGL 视图状态pangolin::OpenGlRenderState s_cam(pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000),pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0));// 创建 Pangolin 显示器并设置其边界和处理程序pangolin::View &d_cam = pangolin::CreateDisplay().SetBounds(0.0, 1.0, 0.0, 1.0, -1024.0f / 768.0f).SetHandler(new pangolin::Handler3D(s_cam));while (pangolin::ShouldQuit() == false) // 循环直到用户关闭窗口{glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除颜色缓冲区和深度缓冲区d_cam.Activate(s_cam); // 激活相机视图// 渲染轨迹for (auto &pose : poses){glPointSize(1.0);glBegin(GL_POINTS);glColor3f(0.0, 1.0, 0.0);glVertex3d(pose(0, 2), pose(1, 2), pose(2, 2)); // 绘制点glEnd();}pangolin::FinishFrame(); // 完成当前帧的绘制usleep(5000); // 休眠 5 毫秒}
}// IMU 跟踪器类
class IMUTracker
{public:IMUTracker() // 构造函数{Vector3d zero{0.0, 0.0, 0.0}; // 零向量point_.pos = zero; // 设置位置point_.orien = Matrix3d::Identity(); // 设置方向为单位矩阵point_.linear_vel = zero; // 设置线性速度point_.ang_vel = zero; // 设置角速度firstFrame_ = true; // 第一帧标志// 读取 IMU 数据到缓冲区if (!readIMUData("/Downloads/pythonRead/imu_data_0329.txt", imu_msg_buffer_)){cout << "读取 IMU 数据失败。" << endl;}// 创建初始姿态Pose p0;p0.timestamp = imu_msg_buffer_[0].timestamp;p0.position = Vector3d(0., 0., 0.);p0.orientation = Quaterniond(1., 0., 0., 0.);p0.linear_vel = Vector3d(0., 0., 0.);p0.ang_vel = Vector3d(0., 0., 0.);vp_.push_back(p0); // 将初始姿态添加到向量中}~IMUTracker() // 析构函数{}void track() // 跟踪函数{for (auto &msg : imu_msg_buffer_){if (firstFrame_) // 第一帧处理{prev_time_ = msg.timestamp;deltaT_ = 0;setGravity(msg.acc_vel); // 设置重力firstFrame_ = false;}else{deltaT_ = (msg.timestamp - prev_time_) * 1e-9;prev_time_ = msg.timestamp;calOrien(msg.ang_vel); // 计算方向calPos(msg.acc_vel); // 计算位置updatePos(point_); // 更新姿态}}}/*** @brief set the first imu frame's acc_vel as gravity** @param msg*/void setGravity(Vector3d &msg) // 设置重力函数{gravity_ = Vector3d(msg); // 设置重力向量}/*** @brief** @param msg 加速度*/void calPos(Vector3d &msg) // 计算位置函数{Vector3d acc_i(msg);Vector3d acc_w = point_.orien * acc_i;point_.linear_vel += deltaT_ * (acc_w - gravity_);point_.pos += deltaT_ * point_.linear_vel;}/*** @brief** @param msg 角速度*/void calOrien(Vector3d &msg) // 计算方向函数{point_.ang_vel = msg;Matrix3d B; // 角速度 * 时间 = 角度(表示为反对称矩阵)B << 0, -msg.z() * deltaT_, msg.y() * deltaT_, msg.z() * deltaT_, 0, -msg.x() * deltaT_, -msg.y() * deltaT_,msg.x() * deltaT_, 0;double sigma = sqrt(pow(msg.x(), 2) + pow(msg.y(), 2) + pow(msg.z(), 2)) * deltaT_;point_.orien *= (Matrix3d::Identity() + (sin(sigma) / sigma) * B - ((1 - cos(sigma)) / pow(sigma, 2)) * B * B);}/*** @brief current pose** @param point*/void updatePos(Point &point) // 更新姿态函数{Pose p;p.timestamp = prev_time_; // 设置时间戳p.position = Vector3d(point.pos); // 设置位置p.linear_vel = Vector3d(point.linear_vel); // 设置线性速度p.ang_vel = Vector3d(point.ang_vel); // 设置角速度// 从旋转矩阵中计算四元数p.orientation.x() = (point.orien(2, 1) - point.orien(1, 2)) / 4;p.orientation.y() = (point.orien(0, 2) - point.orien(2, 0)) / 4;p.orientation.z() = (point.orien(1, 0) - point.orien(0, 1)) / 4;p.orientation.w() = sqrt(1 + point.orien(0, 0) + point.orien(1, 1) + point.orien(2, 2)) / 2;vp_.push_back(p); // 将姿态添加到向量中}public:Point point_; // 点对象bool firstFrame_; // 是否是第一帧vector<ImuFrame> imu_msg_buffer_; // IMU 数据缓冲区Vector3d gravity_; // 重力向量double deltaT_; // 时间间隔uint64_t prev_time_; // 上一帧时间戳vector<Pose> vp_; // 姿态向量
};

test.cpp

#include "IMUTracker.h"
#include <iostream>
#include <vector>using namespace std;
using namespace Eigen;int main(int argc, char **argv)
{IMUTracker imu_tracker;imu_tracker.track();// saveTrajectoryTum("../imu_traj.tum", imu_tracker.vp_);// vector<Pose> -> vector<Isometry3d>// Isometry3d: [R t]//             [0 1]vector<Isometry3d, aligned_allocator<Isometry3d>> poses;for (auto &p : imu_tracker.vp_){Isometry3d T(Quaterniond(p.orientation.w(), p.orientation.x(), p.orientation.y(), p.orientation.z()));T.pretranslate(Vector3d(p.position));poses.push_back(T);}showTrack(poses);return 0;
}

CMakelists.txt

 

cmake_minimum_required(VERSION 2.8)
project(imu_tracker)find_package("/usr/include/eigen3")
find_package(Pangolin REQUIRED)
include_directories(${Pangolin_INCLUDE_DIRS})add_executable(imu_tracker test.cpp)
target_link_libraries(imu_tracker ${Pangolin_LIBRARIES})

结果展示

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

千视携 NDI 6 轻量化媒体方案亮相北京CCBN展会

展会简介 第30届中国国际广播电视网络技术展览会&#xff08;CCBN&#xff09;将于4月24至26日在北京首钢会展中心举行。此次展会将汇集全球各大数字媒体、广播电视单位以及IT、通信技术厂商。展会重点关注数字化转型、智能媒体、融媒体等主题&#xff0c;并展示最新的5G、4K/8…

基于starganvc2的变声器论文原理解读

数据与代码见文末 论文地址&#xff1a;https://arxiv.org/pdf/1907.12279.pdf 1.概述 什么是变声器&#xff0c;变声器就是将语音特征进行转换&#xff0c;而语音内容不改变 那么我们如何构建一个变声器呢&#xff1f; 首先&#xff0c;我们肯定不能为转换的每一种风格的声…

vue项目入门——index.html和App.vue

vue项目中的index.html文件 在Vue项目中&#xff0c;index.html文件通常作为项目的入口文件&#xff0c;它包含了Vue应用程序的基础结构和配置。 该文件的主要作用是引入Vue框架和其他必要的库&#xff0c;以及定义Vue应用程序的启动配置。 import Vue from vue import App …

HBase详解(2)

HBase 结构 HRegion 概述 在HBase中&#xff0c;会从行键方向上对表来进行切分&#xff0c;切分出来的每一个结构称之为是一个HRegion 切分之后&#xff0c;每一个HRegion会交给某一个HRegionServer来进行管理。HRegionServer是HBase的从节点&#xff0c;每一个HRegionServ…

谷歌浏览器插件开发速成指南:弹窗

诸神缄默不语-个人CSDN博文目录 本文介绍谷歌浏览器插件开发的入门教程&#xff0c;阅读完本文后应该就能开发一个简单的“hello world”插件&#xff0c;效果是出现写有“Hello Extensions”的弹窗。 作为系列文章的第一篇&#xff0c;本文还希望读者阅读后能够简要了解在此基…

由 LDO 稳压器 CAT6219-330TDGT3提供快速响应时间,快速启动 实现高效率解决方案

CAT6219-330TDGT3是一款 500 mA CMOS 低漏稳压器&#xff0c;在负载电流和线路电压变化期间提供快速响应时间。 快速启动特性允许使用外部旁通电容器&#xff0c;可降低总体输出噪声&#xff0c;而不会影响仅为 150 s 的导通时间。 零关断电流和 55 A 的低静止电流典型值使其适…

【随笔】Git 高级篇 -- 整理提交记录(下)rebase(十六)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

JavaScript(二)-Web APIS

文章目录 Web API 基本认知作用和分类什么是DOMDOM树DOM对象获取DOM对象操作元素内容操作元素属性操作元素常用属性操作元素样式属性自定义属性 定时器-间歇函数定时器函数的理解定时器函数使用间歇函数 事件监听与绑定事件监听事件监听版本事件类型事件对象什么是事件对象获取…

搭建Zookeeper集群:三台服务器,一场分布式之舞

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 搭建Zookeeper集群&#xff1a;三台服务器&#xff0c;一场分布式之舞 前言前置设置主机名对应关系要有java环境 步骤1. 下载和解压 ZooKeeper&#xff1a;2. 配置 ZooKeeper&#xff1a;3. 配置集群节…

健康元 穿越周期看底色

中国创新药正在迈进2.0时代。 进入2024年之后&#xff0c;越来越多的国内创新药企开始主动调整研发管线&#xff0c;缩减研发开支&#xff0c;甚至是直接被“溢出”了市场。 在“风向标”的融资端&#xff0c;过去的2023年也是中国创新药融资市场连续第二年出现一二级市场融资…

LabVIEW数控磨床振动分析及监控系统

LabVIEW数控磨床振动分析及监控系统 在现代精密加工中&#xff0c;数控磨床作为关键设备之一&#xff0c;其加工质量直接影响到产品的精度与性能。然而&#xff0c;磨削过程中的振动是影响加工质量的主要因素之一&#xff0c;不仅会导致工件表面质量下降&#xff0c;还可能缩短…

达梦备份与恢复

达梦备份与恢复 基础环境 操作系统&#xff1a;Red Hat Enterprise Linux Server release 7.9 (Maipo) 数据库版本&#xff1a;DM Database Server 64 V8 架构&#xff1a;单实例1 设置bak_path路径 --创建备份文件存放目录 su - dmdba mkdir -p /dm8/backup--修改dm.ini 文件…