多傳感器融合定位2(封裝KF和EKF)

有了上一節分別對激光雷達做KF、對毫米波雷達做EKF兩種流程之後,我們合併KF和EKF的跟蹤算法的代碼,將他們封裝在一個名爲KalmanFilter的類中,方便後續調用。下面做一個多傳感器融合定位實驗,採集到的數據爲兩種交替出現的數據。
代碼整體框架如圖所示:
在這裏插入圖片描述

前言

無論是KF處理線性問題還是EKF處理非線性問題,它都只涉及單一傳感器的障礙物跟蹤。激光雷達測量位置精度更高但是無法測量速度,毫米波雷達測量速度準確,但是在位置測量精度上低。爲了充分利用各個傳感器的優勢,需要做多傳感器融合定位。

激光雷達Lidar:發射激光束,利用光的直線傳播能夠直接獲得障礙物在笛卡爾座標系下x、y、z方向上距離。建立三維點雲圖,探測目標的位置、速度等特徵量。
狀態向量分別是位置和速度的x、y方向(x,y,vx,vy)

在這裏插入圖片描述
毫米波雷達Radar:發射毫米波(電磁波),利用多普勒效應,所測數據都是在極座標系下。毫米波導引頭穿透霧、煙、灰塵的能力強,成本低,但是探測距離受到頻段損耗的直接制約。
狀態向量爲極座標系下雷達的距離ρ,方向角ϕ和距離的變化率(徑向速度)ρ’。
在這裏插入圖片描述
Udacity 無人駕駛工程師學位
在這裏插入圖片描述
對比
Lidar探測精度高、探測範圍廣,穩定性強,成本高。但是在雨雪霧等極端天氣下、光束受遮擋後性能較差,採集的數據量過大。64線70萬元人民幣。

Radar穿透霧、煙、灰塵的能力強,探測距離受到頻段損耗的直接制約(想要探測的遠,就必須使用高頻段雷達),也無法感知行人,並且對周邊所有障礙物無法進行精準的建模。100美元左右。

一、輸入數據分析

在data目錄下名爲sample-laser-radar-measurement-data-2.txt的文件,這就是傳感器融合的輸入數據。
假設:Lidar和Radar都固定在原點(0,0)處,且檢測障礙物頻率相同,兩種雷達交替觸發檢測。
在這裏插入圖片描述
每行第一個字母L是Lidar,R是Radar。
第1、2行表示原點位置所以都只有時間戳。
Lidar:X方向測量值、Y方向測量值、時間戳、X方向位置真值、Y方向位置真值、X方向速度真值、Y方向速度真值。
Radar:障礙物在極座標系下的距離ρ、角度ϕ、徑向速度ρ’、時間戳、X方向位置真值、Y方向位置真值、X方向速度真值、Y方向速度真值。

主函數main()

框架爲:
在這裏插入圖片描述
這部分我們重點考慮填充測量數據和真值數據,把傳感器輸入數據sample-laser-radar-measurement-data-2.txt填充進來。

1、導入txt文件

這部分可以說是從txt文件中導入數據的標配了。

// 設置毫米波雷達/激光雷達輸入數據的路徑
    // Set radar & lidar data file path
    std::string input_file_name = "../data/sample-laser-radar-measurement-data-2.txt";

    // 打開數據,若失敗則輸出失敗信息,返回-1,並終止程序
    // Open file. if failed return -1 & end program
    std::ifstream input_file(input_file_name.c_str(), std::ifstream::in);
    if (!input_file.is_open()) {
        std::cout << "Failed to open file named : " << input_file_name << std::endl;
        return -1;
    }

2、測量值類和真值類

測量值MeasurementPackage類:時間戳、傳感器類型、位置(xy)

class MeasurementPackage {
public:
  long long timestamp_;
  enum SensorType{
    LASER,
    RADAR
  } sensor_type_;
  Eigen::VectorXd raw_measurements_;
};

真值GroundTruthPackage類:時間戳、傳感器類型、數據(距離、角度、鏡像速度)

class GroundTruthPackage {
public:
  long timestamp_;
  enum SensorType{
    LASER,
    RADAR
  } sensor_type_;
  Eigen::VectorXd gt_values_;
};

3、txt數據導入測量值類和真值類

值得注意的是,未來主要用的數據是測量數據,真值是爲了做對比才出現的,作用僅限於最後用於畫圖。我們使用測量數據進行process()函數,如果是Lidar數據就用KF更新,如果是Radar數據就用EKF更新。

   std::vector<MeasurementPackage> measurement_pack_list;
    std::vector<GroundTruthPackage> groundtruth_pack_list;

    std::string line;
    while (getline(input_file, line)) {// getline後是string類型
        std::string sensor_type;
        MeasurementPackage meas_package;
        GroundTruthPackage gt_package;
        std::istringstream iss(line);// 從string轉爲數據流格式
        long long timestamp;

        // 讀取當前行的第一個元素,L代表Lidar數據,R代表Radar數據
        // Reads first element from the current line. L stands for Lidar. R stands for Radar.
        iss >> sensor_type;
        if (sensor_type.compare("L") == 0) {// 相等輸出0
            // 激光雷達數據 Lidar data
            // 測量值:x,y,時間戳
            meas_package.sensor_type_ = MeasurementPackage::LASER;
            meas_package.raw_measurements_ = Eigen::VectorXd(2);
            float x;
            float y;
            iss >> x;
            iss >> y;
            meas_package.raw_measurements_ << x, y;
            iss >> timestamp;
            meas_package.timestamp_ = timestamp;
            measurement_pack_list.push_back(meas_package);
        } else if (sensor_type.compare("R") == 0) {
            // 毫米波雷達數據 Radar data
            // 測量值:距離pho,角度phi,徑向速度pho_dot,時間戳
            meas_package.sensor_type_ = MeasurementPackage::RADAR;
            meas_package.raw_measurements_ = Eigen::VectorXd(3);
            float rho;
            float phi;
            float rho_dot;
            iss >> rho;
            iss >> phi;
            iss >> rho_dot;
            meas_package.raw_measurements_ << rho, phi, rho_dot;
            iss >> timestamp;
            meas_package.timestamp_ = timestamp;
            measurement_pack_list.push_back(meas_package);
        }

        // 真實值:x,y,vx,vy
        float x_gt;
        float y_gt;
        float vx_gt;
        float vy_gt;
        iss >> x_gt;
        iss >> y_gt;
        iss >> vx_gt;
        iss >> vy_gt;
        gt_package.gt_values_ = Eigen::VectorXd(4);
        gt_package.gt_values_ << x_gt, y_gt, vx_gt, vy_gt;
        groundtruth_pack_list.push_back(gt_package);
    }

    std::cout << "Success to load data." << std::endl;

二、SensorFusion類

先來看SensorFusion類的構成,主函數是在process()中,通過輸入測量數據不斷進行處理,最終得到(x,y,vx,vy)

class SensorFusion {
public:
    SensorFusion();
    ~SensorFusion();
    // 主函數process()
    void Process(MeasurementPackage measurement_pack);
    KalmanFilter kf_;

private:
    bool is_initialized_;// 是否初始化
    long last_timestamp_;// 上一次時間戳
    // 傳感器測量噪聲R
    Eigen::MatrixXd R_lidar_;//2*2
    Eigen::MatrixXd R_radar_;//3*3
    // 測量矩陣H
    Eigen::MatrixXd H_lidar_;//2*4
};

1、構造函數

構造函數,第一幀初始化H_lidar,R_lidar,R_radar

SensorFusion::SensorFusion()
{
    // 1.第一幀還沒初始化,上次時間戳爲0
    is_initialized_ = false;
    last_timestamp_ = 0.0;

    // 2.初始化激光雷達的測量矩陣 H_lidar_
    // Set Lidar's measurement matrix H_lidar_
    H_lidar_ = Eigen::MatrixXd(2, 4);
    H_lidar_ << 1, 0, 0, 0,
                0, 1, 0, 0;

    // 3.設置兩種雷達的測量噪聲矩陣R,一般由傳感器廠商提供,如不提供,也可通過有經驗的工程師調試得到
    // Set R. R is provided by Sensor supplier, in sensor datasheet
    // set measurement covariance matrix
    R_lidar_ = Eigen::MatrixXd(2, 2);
    R_lidar_ << 0.0225, 0,
                0, 0.0225;

    // Measurement covariance matrix - radar
    R_radar_ = Eigen::MatrixXd(3, 3);
    R_radar_ << 0.09, 0, 0,
                0, 0.0009, 0,
                0, 0, 0.09;
}

2、process()函數

首先讀入傳感器數據,如果是第一次讀入,則需要對卡爾曼濾波器的各個矩陣進行初始化操作(初始化狀態x,協方差矩陣P、過程噪聲Q);如果不是第一次讀入,證明卡爾曼濾波器已完成初始化,先進行狀態預測(根據兩幀時間差給狀態轉移矩陣F賦值、計算出新的狀態x和協方差矩陣P),接下來進行測量更新的步驟(注意EKF不需要測量矩陣H,只需要測量噪聲R即可);最後輸出融合後的障礙物位置、速度。
在這裏插入圖片描述
Udacity學城上對這一過程有更詳細的描述。
在這裏插入圖片描述

void SensorFusion::Process(MeasurementPackage measurement_pack)
{
    // 1.第一幀數據用於初始化 Kalman 濾波器
    if (!is_initialized_) {
        Eigen::Vector4d x;

        // 1.1 激光雷達Lidar初始化(x,y,vx,vy)
        // 第一幀沒有速度信息,因此初始化時只能傳入位置,速度設置爲0
        if (measurement_pack.sensor_type_ == MeasurementPackage::LASER) {
            x << measurement_pack.raw_measurements_[0], measurement_pack.raw_measurements_[1], 0, 0;
        }
        // 1.2 毫米波雷達初始化(x,y,vx,vy)
        // 通過三角函數算出x-y座標系下的位置和速度
         else if (measurement_pack.sensor_type_ == MeasurementPackage::RADAR) {
            float rho = measurement_pack.raw_measurements_[0];
            float phi = measurement_pack.raw_measurements_[1];
            float rho_dot = measurement_pack.raw_measurements_[2];
            float position_x = rho * cos(phi);
            if (position_x < 0.0001) {
                position_x = 0.0001;
            }
            float position_y = rho * sin(phi);
            if (position_y < 0.0001) {
                position_y = 0.0001;
            }
            float velocity_x = rho_dot * cos(phi);
            float velocity_y = rho_dot * sin(phi);
            x << position_x, position_y, velocity_x , velocity_y;
        }
        
        // 避免運算時,0作爲被除數
        if (fabs(x(0)) < 0.001) {
            x(0) = 0.001;
        }
        if (fabs(x(1)) < 0.001) {
            x(1) = 0.001;
        }
        // 1.3 初始化Kalman濾波器
        kf_.Initialization(x);

        // 1.4 設置協方差矩陣P
        Eigen::MatrixXd P = Eigen::MatrixXd(4, 4);
        P << 1.0, 0.0, 0.0, 0.0,
             0.0, 1.0, 0.0, 0.0,
             0.0, 0.0, 1000.0, 0.0,
             0.0, 0.0, 0.0, 1000.0;
        kf_.SetP(P);

        // 1.5 設置過程噪聲Q
        Eigen::MatrixXd Q = Eigen::MatrixXd(4, 4);
        Q << 1.0, 0.0, 0.0, 0.0,
             0.0, 1.0, 0.0, 0.0,
             0.0, 0.0, 1.0, 0.0,
             0.0, 0.0, 0.0, 1.0;
        kf_.SetQ(Q);

        // 1.6 更新時間戳爲當前時間戳,初始化完成
        last_timestamp_ = measurement_pack.timestamp_;
        is_initialized_ = true;
        return;
    }
    // 2. 狀態轉移矩陣F賦值
    // 求前後兩幀的時間差,數據包中的時間戳單位爲微秒,處以1e6,轉換爲秒
    double delta_t = (measurement_pack.timestamp_ - last_timestamp_) / 1000000.0; // unit : s
    last_timestamp_ = measurement_pack.timestamp_;
    // 設置狀態轉移矩陣F
    Eigen::MatrixXd F = Eigen::MatrixXd(4, 4);
    F << 1.0, 0.0, delta_t, 0.0,
         0.0, 1.0, 0.0, delta_t,
         0.0, 0.0, 1.0, 0.0,
         0.0, 0.0, 0.0, 1.0;
    kf_.SetF(F);

    // 3.預測
    kf_.Prediction();

    // 4.更新
    // 4.1 激光雷達,KF更新
    if (measurement_pack.sensor_type_ == MeasurementPackage::LASER) {
        kf_.SetH(H_lidar_);
        kf_.SetR(R_lidar_);
        kf_.KFUpdate(measurement_pack.raw_measurements_);
    } 
    // 4.2 毫米波雷達,EKF更新
    else if (measurement_pack.sensor_type_ == MeasurementPackage::RADAR) {
        kf_.SetR(R_radar_);
        // Jocobian矩陣Hj的運算已包含在EKFUpdate中
        kf_.EKFUpdate(measurement_pack.raw_measurements_);
    }
}

3、結果

將融合結果與真值繪製在同一座標系下,即可看到融合的實際效果
在這裏插入圖片描述
這裏實現的多傳感器融合更多像是一幀按照Lidar數據做KF,下一幀用Radar做EKF,但是並沒有對兩種數據做取捨之類的,就是一個接一下的來。

如下圖所示,在k+1時刻收到激光雷達數據時,根據k時刻的狀態完成一次預測,再根據k+1時刻的激光雷達的觀測數據實現測量值更新(KFUpdate);在k+2時刻收到毫米波雷達的數據時,根據k+1時刻的狀態完成一次預測,再根據k+2時刻的毫米波雷達的觀測數據實現測量值更新(EKFUpdate)。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章