LIO-SAM过滤GPS多路径效应数据

最近在不同环境下进行lio-sam建图时,遇到一个问题:在通过狭窄道路时,两侧高耸的建筑反射卫星信号,导致出现RTK解算status=1/2 RTK增强定位时(1 SBAS_FIX星基增强系统修复,2 GBAS_FIX地基增强系统修复),仍然出现大幅度的位置漂移,从而导致GPS因子介入制图的位置出现错位。

一、概述

这种现象称为多路径效应(Multi-Path Effect),有时也简称多径效应。它出现的原因是在GPS接收机接收卫星信号的过程中,除了接收到通过直线路径到达接收机的信号以外,还可能收到经接收机周围物体的反射再传播过来的卫星信号,这些反射的信号改变了传播方向、振幅、极化以及相位等,与直线信号产生叠加,从而使观测值偏离其真值而产生误差。多径效应会在接收天线处与反射信号干扰导致原始信号失真。这可能导致超过10米位置误差

二、解决方案

遇到这个问题后,反复播报看了下跳动幅度,相当的大,100ms内位置可能就会漂移超过10米,既然漂移这么激烈,完全可以通过一个简单的位置跳动过滤来滤掉这类数据,于是仔细研读了下LIO-SAM的源码,涉及的几个关键位置如下:

1. GPS介入频率(gpsFrequence)

config/params.yaml中,存在几个与GPS相关的配置。gpsFrequence是其中的一个参数,默认为10(1.0 / gpsFrequence,相当于10hz,间隔100ms),在mapOptmizationGps.cpp的syncGPS函数调用时传入:

...
void updateInitialGuess() {  # 使用GPS初始化pose定位
if (syncGPS(gpsQueue, alignedGPS, timeLaserInfoCur, 1.0 / gpsFrequence)) {
...
void addGPSFactor() {  # 建图过程中添加GPS因子
if (syncGPS(gpsQueue, thisGPS, timeLaserInfoCur, 1.0 / gpsFrequence)) {
...

当前帧激光雷达数据时间+-100ms,过早的数据丢弃,过晚的数据暂不使用,只使用激光雷达时间戳前后100ms的数据:

bool syncGPS(std::deque<nav_msgs::Odometry> &gpsBuf,
             nav_msgs::Odometry &aligedGps, double timestamp,
             double eps_cam) {
    bool hasGPS = false;
    while (!gpsQueue.empty()) {
        mtxGpsInfo.lock();
        if (gpsQueue.front().header.stamp.toSec() < timestamp - eps_cam) {
            // message too old,丢弃
            gpsQueue.pop_front();
            mtxGpsInfo.unlock();
        } else if (gpsQueue.front().header.stamp.toSec() > timestamp + eps_cam) {
            // message too new,暂不使用
            mtxGpsInfo.unlock();
            break;
        } else {
            hasGPS = true;
            aligedGps = gpsQueue.front();
            gpsQueue.pop_front();
            mtxGpsInfo.unlock();
        }
    }
    return hasGPS;
}

gpsQueue是通过sub gps_odom topic获取:

...
subGPS = nh.subscribe<nav_msgs::Odometry>( "gps_odom", 200, &mapOptimization::gpsHandler, this, ros::TransportHints().tcpNoDelay());
...
std::deque<nav_msgs::Odometry> gpsQueue;
void gpsHandler(const nav_msgs::Odometry::ConstPtr &gpsMsg) {
    if (useGPS) {
        mtxGpsInfo.lock();
        gpsQueue.push_back(*gpsMsg);
        mtxGpsInfo.unlock();
    }
}

而gps_odom是由simpleGpsOdom.cpp订阅/gps/fix并转换后发布的,所以这里可以先增加一个gps增强信号质量过滤:

...
# gpsTopic 在params.yaml中配置
gpsSub = nh.subscribe(gpsTopic, 1000, &GNSSOdom::GNSSCB, this, ros::TransportHints().tcpNoDelay());
left_odom_pub = nh.advertise<nav_msgs::Odometry>("/gps_odom", 100, false);
...
void GNSSCB(const sensor_msgs::NavSatFixConstPtr &msg) {
        ...
        /** if you have some satellite info or rtk status info, put it here*/
        int status = -1;
        int satell_num = -1;
        double x, y, z;
        // LLA->ENU, better accuacy than gpsTools especially for z value
        geo_converter.Forward(lla[0], lla[1], lla[2], x, y, z);
        Eigen::Vector3d enu(x, y, z);
        if (abs(enu.x()) > 10000 || abs(enu.x()) > 10000 || abs(enu.x()) > 10000) {
            /** check your lla coordinate */
            ROS_INFO("Error ogigin : %f, %f, %f", enu(0), enu(1), enu(2));
            return;
        }

        # 过滤低质量数据  status:当状态 >= 0 STATUS_FIX时,此定位才有效;-1 NO_FIX,0 FIX(标准GPS定位),1 SBAS_FIX(星基增强系统修复),2 GBAS_FIX(地基增强系统修复)
        if (msg->status.status != 1) {
            ROS_WARN("GPS poor signal! gps status: %d", msg->status.status);
            return;
        }

        bool orientationReady = false;
        double yaw = 0.0;
        double distance =
                sqrt(pow(enu(1) - prev_pose_left(1), 2) + pow(enu(0) - prev_pose_left(0), 2));
        if (distance > 0.1) {
            // 返回值是此点与远点连线与x轴正方向的夹角
            yaw = atan2(enu(1) - prev_pose_left(1), enu(0) - prev_pose_left(0));
            yaw_quat_left = tf::createQuaternionMsgFromYaw(yaw);
            prev_pose_left = enu;
            orientationReady = true;
        }

        /** pub gps odometry*/
        nav_msgs::Odometry odom_msg;
        odom_msg.header.stamp = msg->header.stamp;
        odom_msg.header.frame_id = odometryFrame;
        odom_msg.child_frame_id = "gps";

        odom_msg.pose.pose.position.x = enu(0);
        odom_msg.pose.pose.position.y = enu(1);
        odom_msg.pose.pose.position.z = enu(2);
        odom_msg.pose.covariance[0] = msg->position_covariance[0];
        odom_msg.pose.covariance[7] = msg->position_covariance[4];
        odom_msg.pose.covariance[14] = msg->position_covariance[8];
        odom_msg.pose.covariance[1] = lla[0];
        odom_msg.pose.covariance[2] = lla[1];
        odom_msg.pose.covariance[3] = lla[2];
        odom_msg.pose.covariance[4] = status;
        odom_msg.pose.covariance[5] = satell_num;
        odom_msg.pose.covariance[6] = orientationReady;
        odom_msg.pose.pose.orientation = yaw_quat_left;
        left_odom_pub.publish(odom_msg);
...

2. GPS协方差噪声阈值(gpsCovThreshold)

这个参数也在config/params.yaml中,默认为8。它主要通过协方差噪声进行过滤,这里可以把值打印出来,观察下在多路径漂移和正常时的噪声,然后配置一个合适的值进行过滤。

...
void addGPSFactor() {
...
    if (syncGPS(gpsQueue, thisGPS, timeLaserInfoCur, 1.0 / gpsFrequence)) {
        // GPS too noisy, skip
        float noise_x = thisGPS.pose.covariance[0];
        float noise_y = thisGPS.pose.covariance[7];
        float noise_z = thisGPS.pose.covariance[14];

        // make sure the gps data is stable encough
        ROS_INFO("curr gps noise: %f, %f , %f", noise_x, noise_y, noise_z);
        if (abs(noise_x) > gpsCovThreshold || abs(noise_y) > gpsCovThreshold) {
            ROS_WARN("GPS noise too large: %d,%d > %d", abs(noise_x), abs(noise_y), gpsCovThreshold);
            return;
        }
...

3. 距离跳动过滤(GPSDISTANCE

这个参数也在config/params.yaml中,对应参数名为gpsDistance,默认为0.5,单位为米。当最新有效gps位置与上一帧有效gps位置之间的距离小于0.5米时,会丢弃数据。这里设计本意应该是为了降低频繁的lidar-rtk位置对齐,可以借用改造下,增加gps跳动过大数据的过滤。这里的过滤需要考虑时间间隔和车速因素,可以以“rtk跳动距离 > 最大车速*时间可能行驶的最远距离”为过滤条件,车辆不可能在那么短时间跑那么远,就意味着gps数据已经失真了。

...
void addGPSFactor() {
      static float lastGPSTime = 0;
      if (syncGPS(gpsQueue, thisGPS, timeLaserInfoCur, 1.0 / gpsFrequence)) {
            ...
            // Add GPS every a few meters
            PointType curGPSPoint;
            curGPSPoint.x = gps_x;
            curGPSPoint.y = gps_y;
            curGPSPoint.z = gps_z;
            float point_dist = pointDistance(curGPSPoint, lastGPSPoint);
            float time_diff = thisGPS.header.stamp.toSec() - lastGPSTime; // yan 25.8.28 最新有效gps位置与上一帧有效gps位置之间的时间差(秒) 
            float vehicleMaxSecSpeed = 1.5; // yan 25.8.28 车辆最大行驶速度(米/每秒)
            time_diff = thisGPS.header.stamp.toSec() - lastGPSTime;
            if (point_dist < GPSDISTANCE) {
                ROS_WARN("Skip addGPSFactor, Point Distance < gpsDistance: %f < %f", point_dist, GPSDISTANCE);
                return;
            } else if (point_dist > time_diff * vehicleMaxSecSpeed) { // yan 25.8.28 增加gps跳动过大的过滤,需要考虑时间间隔和车速因素
                ROS_WARN("Skip addGPSFactor, Point Distance > time_diff * vehicleMaxSecSpeed: %f > %f * %f", point_dist, time_diff, vehicleMaxSecSpeed);
                return;
            } else {
                lastGPSPoint = curGPSPoint;
                lastGPSTime = thisGPS.header.stamp.toSec();
            }
...

先这样,我先验证下改动效果后再更新。

yan 25.8.28

发表评论

您的电子邮箱地址不会被公开。