基于RGB-D相机的视觉里程计实现(一)(图像坐标转换及图像畸变)

本文介绍了一种基于RGB-DSLAM的视觉里程计实现方法,详细阐述了特征点匹配、ICP算法应用及相机内外参数的计算过程,并提供了图像畸变校正的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此博客记录我的slam学习之旅,近几天目标为实现基于RGB-D SLAM的视觉里程计主要参考资料为高博的《视觉SLAM十四讲:从理论到实践》及高博的博客https://www.cnblogs.com/gaoxiang12/

首先实现一个不考虑任何实际问题的视觉里程计,根据RGB图像及对应的深度图像恢复出相邻两帧图像间的R,T(也就是运动相机的位置姿态),帧与帧之间的运动关系最后构成相机的运动轨迹。大致思路分为如下几个步骤:

(1)在图像上选取特征点,进行图像间的特征点匹配。

(2)根据特征匹配的结果(3D-3D),利用ICP算法恢复出相机的位置姿态(以第一帧的相机坐标系为参考坐标系)

(3)两两帧间的运动估计完成,再进行帧与帧之间的连接,最后连接成整体的轨迹了。

(ps:把视觉里程计写的这么简单也是没谁了。。。。。实际的情况比这复杂很多)

补充一点基本数学知识:(这里只考虑小孔成像模型,实际还有几种其他摄像机模型)

(本人使用opencv 3.1,ubuntu 16.04(在虚拟机上运行))

相机与图像的坐标系

(1)相机坐标系

(2)图像坐标系

(3)像素坐标系

 

由图可知,设空间点P在相机坐标系的坐标为P[x,y,z],通过相机光心投影到图像坐标系中P/(x/,y/),根据相似三角形原理(如上图所示)可知,

                                                               (ps:由于第一次写博客,不会使用公式编辑插件,所以在草稿本上写放的照片)

 

  为了简化模型,将成像平面放到相机前方,可以得到对应公式,不过在相机图像中,我们得到的是像素,我们设物理成像平面坐标系上固定着一个像素坐标系,p/对应像素坐标p(u,v)

图像成像坐标系与像素坐标系之间相差了一个缩放和平移,设在u轴上被缩放了a倍,按原点平移了cx,在v轴缩放了B倍,按原点平移了cy,即可得上述公式

为了易于表达,将左侧公式写成齐次坐标形式,其中K为相机内参矩阵,一般工厂会标定好相机内参,但很多时候需要我们进行标定,标定工具主要有基于Opencv和matlab的,目前已经很成熟了

 

有相机内参自然有相机外参,上面假定的空间点p的坐标是在相机坐标系下的,那么如何根据世界坐标系下的p点计算得到像素坐标下对应的点?

 

 

                                                                                                                                                                                                             因为都是齐次坐标,所以可以写成上述公式。         

下面贴一段代码表达像素坐标与相机坐标之间的关系:

以下是2d-3d的转换代码:

添加CMakeLists.txt

#设定最小版本

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

#设定工程名

PROJECT(2D)

# 添加c++ 11标准支持

set( CMAKE_CXX_FLAGS "-std=c++11" )

 

#找到opencv库

FIND_PACKAGE(OpenCV REQUIRED)

 

# 添加头文件

include_directories( ${OpenCV_INCLUDE_DIRS} )

 

ADD_EXECUTABLE(2D 2d.cpp)

# 链接OpenCV库

target_link_libraries( 2D ${OpenCV_LIBS}

 

 

以下是2d转换为3d坐标的代码

//

// Created by xl on 18-5-4.

//此程序为将图像像素坐标转为相机坐标系,原图像为一张RGB图像和一张对应的深度图

//opencv库

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

//c++库

#include <iostream>

using namespace std;

using namespace cv;

 

int main(int argc,char **argv)

{

    Mat rgb=imread("rgb.png",0);

    Mat depth=imread("depth.png",-1);//-1表示对原数据不做任何修改

    double scale=1000;//scale表示深度图的缩放因子,一般为1000

    /*Mat K;

    K=(Mat_<double>(3,3)<<518.0,0,325.5,0,518.0,253.5,0,0,1);*///定义相机内参K,也可以采用这种方式

    // 相机内参

    const double camera_factor = 1000;

    const double camera_cx = 325.5;

    const double camera_cy = 253.5;

    const double camera_fx = 518.0;

    const double camera_fy = 519.0;

    vector<Point3f> pts_3d;//定义三维点

     for(int m=0;m<depth.rows;m++)

    {

        for(int n=0;n<depth.cols;n++){

            ushort d=depth.ptr<unsigned short>(m)[n];

            if(d==0)

                continue;//如果d=0,则被认为是坏点

            double dd=d/scale;

            pts_3d.push_back(Point3f((m-camera_cx)*dd/camera_fx,(n-camera_cy)*dd/camera_fy,dd));

        }

          

    }

}

 

既然讲到图像了,那就顺便提一下图像畸变吧。

为了呈现更好的成像效果,我们会在相机前方加上透镜,透镜的加入会对成像过程中光线的传播产生新的影响。

畸变的形式一般为两种:

(1)径向畸变:由透镜形状引起的畸变,径向畸变又可以分为枕形畸变和桶形畸变。

(2)切向畸变:在组装过程中,透镜和成像平面不能严格平行产生切向畸变

 

对于切向畸变,无论哪个形式,都可以用一个多项式函数来描述畸变前后的变化,可以用与距中心的距离有关的二次及高次多项式函数进行纠正。

对于切向畸变,也可以使用两个参数来纠正。(具体公式见下图)

 

                                                                                                                         注意:x,y为归一化平面的坐标,r也是距归一化中心点的距离。

所以给出一个空间点位置P(X,Y,Z),我们可以求出其在带有畸变的相机上像素坐标。

 

                                                                                     (ps:对了,之前没有讲到归一化坐标,这里的归一化坐标其实就是相机坐标系下的坐标值(x,y,z)转换到z=1的平面上,原来的坐标转为(x/z,y/z,1))

接下来到了我们的实践环节了,给你一张带有畸变的图像,如何把它纠正转为去畸变的照片??

问题描述:

input:畸变后的图像(因为相机原因,可能拍出来的照片里面的物体是斜的),上面提到的畸变参数

output:畸变前的图像。

 

(ps:最开始我接触这个问题的时候,当时也不明白该怎么求,后来看了网上的博客,才弄懂了。)

 

其实这个问题的核心是畸变后的坐标与畸变前的坐标,它们两处坐标的灰度值是相同的,这里要求出畸变前的图像,其实就是求出畸变前图像的一个点坐标p,对应的畸变图像的坐标是多少,找到p点对应到畸变图像上的坐标值,再把畸变图像的坐标值的灰度值赋给畸变前的坐标p,所有的图像是一个个像素组成的,遍历所有图像这就可以了。。。所以这个问题的求解可以分为以下步骤

(注意:这里还有一个隐藏问题,像素点(u,v)经过畸变后在畸变图像的坐标值不一定为整数,但是在像素坐标里一定要是整数的啊,比如在原始图像(1,1)畸变后就可能变成了(1.2,1.3),这样显然是不行的,这就需要对畸变坐标作插值处理,我们这里先不讨论插值的问题,对畸变坐标取整进行(ps:这样真的是太暴力了啊 。。。。))

(还有个问题:我们这里讨论的是灰度图像,当然也可以考虑RGB图像,其实原理是一样的,只要每个坐标计算三个RGB值就行了,这里以灰度图为例)

 

下面就是代码示例了:

#include <iostream>

#include <opencv2/core/core.hpp>

#include <opencv2/features2d/features2d.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/calib3d/calib3d.hpp>

 

usingnamespace std;

usingnamespace cv;

int main(int argc, char **argv) {

 

    // 畸变参数,这里只考虑k1,k2,这里需要你知道参数。

    double k1,k2,p1,p2;
   //相机内参矩阵
  double fx,fy,cx,cy;
   Mat K;
  K=(Mat_double<3,3><<fx,0,cx,0,fy,cy,0,0,1);


 

    Mat image = imread(“test.png”,0);   // 这里填写你的路径,0表示将图像转换为灰度图

 

    Mat image1 = Mat(image.rows, image.cols, CV_8UC1);   // 设置去畸变以后的图

 

    // u,v为畸变前图像的像素坐标,v为列,u为行

    for (int v= 0; v < image.rows; v++)

        for (int u = 0; u < image.cols; u++) {

 

            double x=(u-cx)/fx;

            double y=(v-cy)/fy;//这里的x,y为归一化后的坐标

            double r=pow(x,2)+pow(y,2);//r为距中心点的距离的平方,之所以是平方,是因为方便,没有调用开根号,注意也是归一化平面上的

            double x1=x*(1+k1*r+k2*pow(r,2))+2*p1*x*y+p2*(r+2*x*x);

            double y1=y*(1+k1*r+k2*pow(r,2))+p1*(r+2*y*y)+2*p2*x*y;//x1,y1为畸变图像上的归一化坐标

           double u1=fx*x1+cx;

            double v1=fy*y1+cy;//u1,v1为畸变图像上的像素坐标

          

            // 最后一步灰度值相等,而且需要对畸变图像的像素坐标取整,这里的方法很粗暴,直接int

            if (u1= 0 && v1>=0 && u1< image.cols && v1<image.rows) {

                image1.at<uchar>(v, u) = image.at<uchar>((int) v1, (int) u1);

            } else {

                image1.at<uchar>(v, u) = 255;//对于超出坐标范围,赋灰度值为255

            }

        }

 

    // 画图去畸变后图像

    imshow("image1", image1);

    imshow("image ", image);

    cv::waitKey();

 

    return0;

}

 

 

                                                                                                                     (ps:代码写得可能不够规范,请原谅啊,哈哈,因为中英文字符问题调了半天)

 

好了,这次主要介绍相机图像的问题了,下一节将记录特征点检测匹配的问题了。。

 

 

 

 

 

 

如果有人看到我的博客,欢迎大家一起交流啊,,,,(写的这么烂估计没有人看吧。。。。。)

我的邮箱:1439741774@qq.com18:21:24

 

 

 

 

转载于:https://www.cnblogs.com/xialiang12/p/8992094.html

在RTAB-Map中,视觉RGB-D相机)和激光雷达(LiDAR)的数据输入并非直接未经处理地传递给算法,而是通过系列预处理、同步和融合步骤,确保数据的致性和有效性。以下是详细的流程解析: --- ### **1. 数据输入与预处理** #### **(1) 视觉数据(RGB-D相机)** - **输入形式**: - **RGB图像**:提供纹理信息(如`/camera/rgb/image_raw`)。 - **深度图像**:提供像素级3D信息(如`/camera/depth/image_raw`)。 - **相机内参**:焦距(`fx, fy`)、主点(`cx, cy`)和畸变参数(`distortion_coefficients`)。 - **预处理步骤**: - **特征提取**:使用ORB/SIFT等算法提取关键点和描述子,用于帧间匹配和回环检测。 - **点云生成**:将深度图像转换为3D点云(通过`depth_image_proc/convert_metric`节点)。 - **去噪**:移除深度图像中的无效值(如`NaN`)或应用双边滤波平滑数据。 #### **(2) 激光雷达数据(LiDAR)** - **输入形式**: - **2D激光扫描**(如`/scan`,类型为`sensor_msgs/LaserScan`)。 - **3D点云**(如`/velodyne_points`,类型为`sensor_msgs/PointCloud2`)。 - **预处理步骤**: - **降采样**:使用体素网格滤波(VoxelGrid)减少点云密度。 - **地面分割**:移除地面点(如基于RANSAC的平面拟合)。 - **去噪**:统计离群点移除(StatisticalOutlierRemoval)。 --- ### **2. 数据同步与传感器融合** #### **(1) 时间同步** - **硬件同步**:部分设备(如Kinect V2+LiDAR)支持硬件触发同步。 - **软件同步**:在ROS中通过`message_filters`实现近似时间同步: ```python sync = ApproximateTimeSynchronizer( [rgb_sub, depth_sub, scan_sub], queue_size=10, slop=0.1 # 允许的时间偏差(秒) ) sync.registerCallback(callback) ``` #### **(2) 坐标系对齐** - **外参标定**:通过`tf2`发布相机与LiDAR的静态变换(如`/camera_link`到`/lidar_link`的`geometry_msgs/TransformStamped`)。 - **点云对齐**:将LiDAR点云转换到相机坐标系(通过`pcl_ros.transformPointCloud`)。 --- ### **3. 里程计生成与关键帧选择** #### **(1) 视觉里程计RGB-D Odometry)** - **特征匹配**:对连续RGB帧提取ORB特征,通过RANSAC计算本质矩阵(Essential Matrix)。 - **ICP优化**:对齐连续深度点云,优化位姿(`rtabmap::ICP()`)。 #### **(2) 激光里程计(LiDAR Odometry)** - **扫描匹配**:使用ICP或NDT算法匹配相邻LiDAR扫描(`pcl::GeneralizedIterativeClosestPoint`)。 - **运动估计**:输出为`nav_msgs/Odometry`消息(如`/odom_lidar`)。 #### **(3) 融合策略** - **松耦合**:通过EKF融合视觉和激光里程计(如`robot_localization`包)。 - **紧耦合**:在RTAB-Map的因子图中联合优化视觉和LiDAR约束(需设置`Reg/Strategy=1`启用RGB-D+LiDAR模式)。 --- ### **4. RTAB-Map的数据输入接口** #### **(1) ROS话题订阅** - **单传感器模式**: ```xml <param name="subscribe_depth" type="bool" value="true"/> <param name="subscribe_scan" type="bool" value="false"/> ``` - **多传感器模式**: ```xml <param name="subscribe_rgbd" type="bool" value="true"/> <param name="subscribe_scan_cloud" type="bool" value="true"/> ``` #### **(2) 关键帧策略** - **基于运动量**:当位移超过`RGBD/LinearUpdate=0.1m`或旋转超过`RGBD/AngularUpdate=0.2rad`时创建关键帧。 - **基于信息量**:使用熵值判断场景变化(`Mem/STMSize=30`限制短期记忆中的关键帧数)。 --- ### **5. 实际配置示例** #### **(1) 启动文件片段(ROS)** ```xml <node pkg="rtabmap_ros" type="rtabmap" name="rtabmap"> <param name="frame_id" value="base_link"/> <param name="subscribe_rgbd" value="true"/> <param name="subscribe_scan_cloud" value="true"/> <param name="Reg/Strategy" value="1"/> <!-- 0=Visual, 1=ICP, 2=Visual+ICP --> <param name="Icp/CorrespondenceRatio" value="0.3"/> <remap from="rgbd_image" to="/camera/rgbd_image"/> <remap from="scan_cloud" to="/velodyne_points"/> </node> ``` #### **(2) 外参配置(TF树)** ```bash rosrun tf static_transform_publisher 0.1 0 0 0 0 0 /camera_link /lidar_link 100 ``` --- ### **6. 数据是否“直接输入”?** - **否**:原始数据需经过以下处理链: ``` RGB/Depth → 特征提取 → 点云生成 → 时间同步 → 坐标系对齐 → 里程计计算 → 关键帧选择 → 图优化 LiDAR → 降采样 → 去噪 → 配准 → 时间同步 → 坐标系对齐 → 里程计计算 → 关键帧选择 → 图优化 ``` - **是**:若使用已同步且校准的传感器(如Intel RealSense L515+LiDAR模块),可直接通过`rtabmap_ros/rtabmap`订阅`/rgbd_image`和`/scan_cloud`话题。 --- ### **7. 性能优化技巧** 1. **降采样**:将深度图像和LiDAR点云分辨率降低至QVGA(320x240)或`VoxelSize=0.05m`。 2. **并行计算**:启用`RGBD/OptimizeFromGraphEnd=true`在后台优化位姿图。 3. **硬件加速**:使用OpenCV的CUDA模块加速特征提取(如`ORB::setUseCuda(true)`)。 --- ### **总结** RTAB-Map通过严格的预处理流程(时间同步、坐标变换、特征提取/配准)将视觉和LiDAR数据转化为统的位姿约束和地图点云。多传感器数据并非“直接输入”,而是经过融合后参与图优化,最终输出高精度三维地图与轨迹。实际应用中需重点关注**传感器标定质量**和**关键帧策略**以平衡精度与实时性。 将以上进行总结,只使用汉语文字
最新发布
05-31
<think>好的,我现在需要总结RTAB-Map中视觉和激光雷达数据的融合流程及处理方法。用户已经提供了之前的对话和相关引用,我需要仔细分析这些信息,并结合自己的知识来构建个详细的流程总结。首先,回顾用户之前的提问,他们希望了解RTAB-Map的视觉激光融合算法在仿真中的具体方法,现在进步询问详细的流程,包括预处理、同步、融合和优化技巧。之前的回答已经分步骤说明了仿真环境搭建、标定、数据融合、回环检测等,但需要更聚焦于流程本身,而不是仿真实验的步骤。引用[1]中提到RTAB-MAP支持相机和激光雷达,构建稠密地图,并且包含各个SLAM环节。这说明融合流程需要涵盖传感器输入、数据处理、融合策略、优化等部分。引用[3]中的预处理方法,如体素滤波和离群点去除,可能适用于激光雷达数据的预处理。接下来,我需要将流程分为数据预处理、时间同步、空间对齐、特征融合、联合优化、地图融合等步骤。每个步骤需要详细说明RTAB-Map的处理方法,比如预处理中的视觉特征提取使用ORB,激光点云可能用体素滤波。时间同步方面,用户之前提到使用ROS的message_filters,这里需要确认是否在RTAB-Map中默认处理还是需要配置。 空间对齐涉及标定,引用[1]提到传感器坐标系标定,可能需要外参标定,如相机到激光雷达的变换矩阵。特征融合部分,RTAB-Map可能将视觉特征与激光几何信息结合,比如将视觉特征点投影到点云中,或利用激光数据增强深度估计。联合优化可能使用因子图优化,结合视觉重投影误差和激光匹配误差。地图融合部分,如何将视觉点云和激光点云合并,可能需要体素滤波和表面重建,如引用[3]中的体素滤波方法。 还需要考虑回环检测,引用[1]提到RTAB-Map有回环检测模块,可能结合视觉词袋和激光ScanContext等方法。优化技巧方面,可能包括核函数的使用、动态权重调整,如用户之前的问题中提到的根据场景调整权重。需要确保数学公式正确使用LaTeX格式,如因子图优化的目标函数,体素滤波的公式等。同时,引用需要正确标注,例如预处理部分引用[3],标定部分引用[1]等。可能遗漏的细节包括具体参数设置,如体素尺寸、特征匹配阈值,或者RTAB-Map中的具体配置参数,如是否使用IMU辅助等。需要参考用户提供的之前回答中的参数示例,如ICP参数、特征类型等。最后,生成相关问题时要围绕流程中的关键点,比如预处理方法、标定误差处理、动态权重调整等。</think>### RTAB-Map视觉与激光雷达融合算法流程总结 RTAB-Map通过多模态数据融合实现高精度建图与定位,其核心流程涵盖数据预处理、时空对齐、特征融合和全局优化四个关键阶段。以下为详细流程总结: --- #### **1. 数据预处理** - **视觉数据**: - **特征提取**:采用ORB特征提取算法,从RGB图像中获取关键点$p_i = (u_i, v_i)$及其描述子$\mathbf{des}_i$,提取阈值设为$H_{\text{response}} > 20$[^1]。 - **深度关联**:将RGB-D相机的深度图$\mathbf{D}_t$与特征点绑定,生成三维坐标$P_i = (x_i, y_i, z_i)$,过滤无效深度值(如$z_i > 5$m)[^1]。 - **激光数据**: - **降采样**:对原始点云$\mathcal{P}_l$进行体素滤波,体素尺寸设为$0.05$m,减少计算量: $$ \mathcal{P}_{\text{filtered}} = \text{VoxelFilter}(\mathcal{P}_l, \delta) $$ 其中$\delta$为体素边长[^3]。 - **离群点剔除**:使用统计滤波移除噪声点,设置邻域点数$k=50$,标准差阈值$\sigma_{\text{th}} = 1.0$[^3]。 --- #### **2. 时间同步与空间对齐** - **时间同步**: - 通过ROS的`message_filters`模块实现跨传感器同步,采用**近似时间策略**(Approximate Time Policy),允许最大时间偏差$\Delta t \leq 0.1$秒[^1]。 - 若存在硬件延迟,可通过插值法补偿时序差异。 - **空间标定**: - 预先标定激光雷达与相机的外参矩阵$T_{\text{lidar}\leftarrow\text{camera}} \in SE(3)$,确保数据在统坐标系下表达。 - 在线优化标定误差:利用特征匹配对$(p_i, q_j)$($p_i$为视觉特征点,$q_j$为对应激光点),通过ICP算法迭代优化外参: $$ T_{\text{opt}} = \arg\min_T \sum \| T \cdot p_i - q_j \|^2 $$ 收敛条件为残差变化率$< 0.01\%$[^3]。 --- #### **3. 多模态特征融合** - **几何-纹理关联**: -视觉特征点$P_i$投影至激光点云坐标系,建立特征点与局部点云的关联关系。 - 通过KD-Tree搜索激光点云的最近邻点,构建联合观测约束[^1]。 - **误差建模**: - **视觉重投影误差**:定义特征点在相邻帧间的投影误差: $$ e_{\text{visual}} = \sum \| \pi(T_{\text{cam}} \cdot P_i) - p_i' \|^2 $$ 其中$\pi(\cdot)$为相机投影函数,$p_i'$为匹配特征点。 - **激光匹配误差**:基于ICP或GICP算法计算点云配准误差: $$ e_{\text{laser}} = \sum \| T_{\text{base}} \cdot q_j - q_j' \|^2 $$ --- #### **4. 联合优化与地图融合** - **因子图优化**: - 构建包含视觉约束、激光约束和运动模型的因子图,目标函数为: $$ \min_{T} \left( \lambda_v \cdot e_{\text{visual}} + \lambda_l \cdot e_{\text{laser}} \right) $$ 其中$\lambda_v$和$\lambda_l$为权重系数(默认$\lambda_v : \lambda_l = 1 : 2$)[^1]。 - 使用GTSAM或g2o求解器进行优化,采用Levenberg-Marquardt算法,最大迭代次数$100$次。 - **地图融合**: - **三维稠密地图**:融合视觉点云(含RGB纹理)与激光点云(高精度几何),通过移动立方体算法(Marching Cubes)生成表面网格: $$ \mathcal{M}_{\text{fused}} = \text{SurfaceReconstruct}(\mathcal{P}_{\text{visual}} \cup \mathcal{P}_{\text{laser}}) $$ - **动态物体剔除**:基于点云时序致性检测移动物体,使用RANSAC拟合静态背景模型[^3]。 --- #### **5. 回环检测与全局优化** - **多模态回环候选**: - **视觉回环**:基于词袋模型(Bag-of-Words)计算图像相似度,相似度阈值$s_{\text{visual}} > 0.15$。 - **激光回环**:利用Scan Context描述子匹配点云环状特征,相似度阈值$s_{\text{laser}} > 0.7$[^1]。 - **全局优化触发**: -视觉与激光回环检测结果空间致时,触发位姿图全局优化,更新关键帧位姿$T_k$及地图节点。 --- #### **6. 关键参数配置示例** ```xml <!-- RTAB-Map参数配置片段 --> <param name="Reg/Strategy" value="2"/> <!-- 0:纯视觉, 1:纯激光, 2:融合 --> <param name="Vis/FeatureType" value="ORB"/> <!-- 视觉特征类型 --> <param name="Icp/CorrespondenceRatio" value="0.1"/> <!-- 激光匹配有效对应比例 --> <param name="RGBD/LinearUpdate" value="0.5"/> <!-- 平移更新阈值() --> <param name="Mem/STMSize" value="30"/> <!-- 短期记忆节点数 --> ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值