二、ceres学习——ceres在视觉导航中的简单应用

前述

ceres在求解视觉导航方面,就依赖一个数学模型,就是相机的投影模型。在视觉导航中,已知的条件只有特征点在图像中的二维坐标 ( u , v ) (u,v) (u,v),特征点在相机坐标系下的描述应该是一个三维坐标 ( X , Y , Z ) (X,Y,Z) (X,Y,Z),那么从相机坐标系到图像坐标系的变换,涉及到相机的内参矩阵:
( X , Y , Z ) → ( u , v ) : Z [ u v 1 ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ X Y Z ] (X,Y,Z)\rightarrow(u,v):Z\begin{bmatrix}u\\v\\1\end{bmatrix}=\begin{bmatrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{bmatrix}\begin{bmatrix}X\\Y\\Z\end{bmatrix} (X,Y,Z)(u,v):Z uv1 = fx000fy0cxcy1 XYZ
在世界坐标系下描述这个特征点是 ( X W , Y W , Z W ) (X_W,Y_W,Z_W) (XW,YW,ZW),世界坐标系到相机坐标系的变换,涉及到相机的外参矩阵:
[ X Y Z ] = R [ X W Y W Z W ] + t \begin{bmatrix}X\\Y\\Z\end{bmatrix}=R\begin{bmatrix}X_W\\Y_W\\Z_W\end{bmatrix}+t XYZ =R XWYWZW +t
其中 R ∈ R 3 × 3 R\in R^{3\times 3} RR3×3 t ∈ R 3 × 1 t\in R^{3\times 1} tR3×1
那么问题就是,知道了 ( u , v ) (u,v) (u,v),求解剩下的所有未知量,包括内参矩阵,外参矩阵,特征点在世界坐标系下的描述,一共九个参数,因为一般 f x = f y = f f_x=f_y=f fx=fy=f c x = c y = c c_x=c_y=c cx=cy=c R R R虽然是 3 × 3 3\times 3 3×3的矩阵,但是实际上可以用Rodrigues角来表示,而Rodrigues角只有三个变量,因此,最终要确认的参数如下: f f f c c c Z Z Z R R R t t t一共九个参数,用 F F F表示上述投影关系, F F F的参数是 ( f , c , Z , R , t ) (f,c,Z,R,t) (f,c,Z,R,t)
对于视觉导航来说,在某一个时刻,有 m m m个路标,其中第 i i i个路标被 k i k_i ki个相机观测到,那么就会有相应数量的观测方程,就可以用ceres来求解,很像前面的曲线拟合:有许多组测量值和已知的模型,拟合模型的参数就可以。

实例

实例分析

这里使用bal数据集进行说明,对于BAL数据集来说,关于相机的投影模型是这样的,对于世界坐标系里面某个点 ( X W , Y W , Z W ) (X_W,Y_W,Z_W) (XW,YW,ZW),经过旋转 R R R和平移 t t t投影到相机坐标系下,经过两个畸变参数和一个焦距参数投影到图像坐标系下,和以往讨论的相机模型不同的是,这里数据集不考虑光心参数,总的来说,可以用以下方程描述:
{ [ X Y Z ] = R [ X W Y W Z W ] + t [ u u n d i s v u n d i s 1 ] = [ l × f 0 0 0 l × f 0 0 0 1 ] [ X / Z Y / Z 1 ] 其中 l = 1 + l 1 × r 2 + l 2 × r 4 , r 2 = X 2 + Y 2 Z 2 \begin{cases}\begin{bmatrix}X\\Y\\Z\end{bmatrix}=R\begin{bmatrix}X_W\\Y_W\\Z_W\end{bmatrix}+t\\\begin{bmatrix}u_{undis}\\v_{undis}\\1\end{bmatrix}=\begin{bmatrix}l\times f&0&0\\0&l\times f&0\\0&0&1\end{bmatrix}\begin{bmatrix}X/Z\\Y/Z\\1\end{bmatrix}&其中l=1+l_1\times r^2+l_2\times r^4,r^2=\frac{X^2+Y^2}{Z^2}\end{cases} XYZ =R XWYWZW +t uundisvundis1 = l×f000l×f0001 X/ZY/Z1 其中l=1+l1×r2+l2×r4,r2=Z2X2+Y2
其中相机涉及到九个参数:以罗德里格角表示的旋转(三个),位移参数(三个),畸变参数(两个),焦距参数(一个)
点涉及到的参数有三个: X Y Z XYZ XYZ世界坐标
因此,涉及到的ceres的残差方程可以这样写,是关于从世界坐标系重投影到二维图像坐标系的残差的

template<typename T>
    bool operator()(const T* const camera,const T* const land_mark,T* residual)const{
        
        T point[3];
        ceres::AngleAxisRotatePoint(camera,land_mark,point);
        point[0]+=camera[3];
        point[1]+=camera[4];
        point[2]+=camera[5];

        T point_x=-point[0]/point[2];
        T point_y=-point[1]/point[2];

        const T& l1 = camera[7];
        const T& l2 = camera[8];
        T r2 = point_x * point_x + point_y * point_y;
        T distortion = 1.0 + r2 * (l1 + l2 * r2);

        const T& focal = camera[6];
        T predicted_x = focal * distortion * point_x;
        T predicted_y = focal * distortion * point_y;

        // The error is the difference between the predicted and observed position.
        residual[0] = predicted_x - observe_x;
        residual[1] = predicted_y - observe_y;

        return true;
    }

实例代码

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(ceres-tutorial)
find_package(Ceres REQUIRED)
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
include_directories(${CERES_INCLUDE_DIRS})

file(GLOB SOURCE_FILE ${CMAKE_SOURCE_DIR}/*.cpp)
foreach(CPP IN LISTS SOURCE_FILE)
    get_filename_component(CPP_FILENAME ${CPP} NAME_WE)
    add_executable(${CPP_FILENAME} ${CPP})
    target_link_libraries(${CPP_FILENAME} ${CERES_LIBRARIES} Python3::Python Python3::Module)
endforeach(CPP IN LISTS SOURCE_FILE)

C++源码

#include<iostream>
#include<fstream>
#include<vector>
#include<ceres/ceres.h>
#include<ceres/rotation.h>
#include<matplotlibcpp.h>

class bal_data{
public:
    bal_data(std::string data_path_):data_path(data_path_){
        std::ifstream in_file_obj(data_path);
        if(in_file_obj.is_open()){
            std::string str1,str2,str3,str4;
            in_file_obj>>str1;
            in_file_obj>>str2;
            in_file_obj>>str3;
            camera_cnt=std::stoi(str1);
            land_mark_cnt=std::stoi(str2);
            observation_cnt=std::stoi(str3);

            for(int i=0;i<observation_cnt;i++){
                in_file_obj>>str1;
                in_file_obj>>str2;
                in_file_obj>>str3;
                in_file_obj>>str4;

                land_mark_observations.push_back(
                    std::make_pair(
                        std::make_pair(std::stoi(str1),std::stoi(str2)),
                        std::make_pair(std::stod(str3),std::stod(str4))
                    ));
            }
            for(int i=0;i<camera_cnt;i++){
                double* temp(new double[9]);
                for(int j=0;j<9;j++){
                    std::string str;
                    in_file_obj>>str;
                    temp[j]=std::stod(str);
                }
                cameras.push_back(temp);
            }
            for(int i=0;i<land_mark_cnt;i++){
                double* temp(new double[3]);
                for(int j=0;j<3;j++){
                    std::string str;
                    in_file_obj>>str;
                    temp[j]=std::stod(str);
                }
                land_marks.push_back(temp);
            }
            in_file_obj.clear();
        }
        else{
            std::cout<<"load data fails"<<std::endl;
            return;
        }
    }
    bal_data():bal_data("../data"){}
    void out_data(){
        int cnt=0;
        for(auto land_mark_observation:land_mark_observations){
            std::cout<<cnt++<<" ";
            std::cout<<land_mark_observation.first.first<<" "
                     <<land_mark_observation.first.second<<" "
                     <<land_mark_observation.second.first<<" "
                     <<land_mark_observation.second.second<<"\n";
        }
    }
    double* load_camera(int i){
        return cameras.at(land_mark_observations.at(i).first.first);
    }
    double* load_land_mark(int i){
        return land_marks.at(land_mark_observations.at(i).first.second);
    }
    int observation_cnt_(){
        return observation_cnt;
    }
    int land_mark_cnt_(){
        return land_mark_cnt;
    }
    std::pair<double,double> load_observation(int i){
        return land_mark_observations.at(i).second;
    }
private:
    std::string data_path{"../data"};
    int camera_cnt{0};
    int land_mark_cnt{0};
    int observation_cnt{0};
    //<<camera_index,land_mark_index>,<observation_x,observation_y>>
    std::vector<std::pair<std::pair<int,int>,std::pair<double,double>>> land_mark_observations;
    std::vector<double*> cameras;
    std::vector<double*> land_marks;
};
struct reproject{
public:
    reproject(double x_,double y_):observe_x(x_),observe_y(y_){}
    template<typename T>
    bool operator()(const T* const camera,const T* const land_mark,T* residual)const{
        
        T point[3];
        ceres::AngleAxisRotatePoint(camera,land_mark,point);
        point[0]+=camera[3];
        point[1]+=camera[4];
        point[2]+=camera[5];

        T point_x=-point[0]/point[2];
        T point_y=-point[1]/point[2];

        const T& l1 = camera[7];
        const T& l2 = camera[8];
        T r2 = point_x * point_x + point_y * point_y;
        T distortion = 1.0 + r2 * (l1 + l2 * r2);

        const T& focal = camera[6];
        T predicted_x = focal * distortion * point_x;
        T predicted_y = focal * distortion * point_y;

        // The error is the difference between the predicted and observed position.
        residual[0] = predicted_x - observe_x;
        residual[1] = predicted_y - observe_y;

        return true;
    } 
private:
    double observe_x,observe_y;
};
int main(int argc,char**argv){
    bal_data object;
    // object.out_data();
    std::vector<double> x_;
    std::vector<double> y_;
    std::vector<double> z_;

    for(int i=0;i<object.land_mark_cnt_();i++){
        x_.push_back(*(object.load_land_mark(i)));
        y_.push_back(*(object.load_land_mark(i)+1));
        z_.push_back(*(object.load_land_mark(i)+2));
    }
    ceres::Problem problem;
    for(int i=0;i<object.observation_cnt_();i++){
        // std::cout<<"point = "<<object.load_observation(i).first<<","<<object.load_observation(i).second<<std::endl;
        ceres::CostFunction* cost_function(
            new ceres::AutoDiffCostFunction<reproject,2,9,3>(
                new reproject(object.load_observation(i).first,object.load_observation(i).second)
            )
        );
        ceres::LossFunction* loss(new ceres::HuberLoss(1.0));
        problem.AddResidualBlock(cost_function,nullptr,object.load_camera(i),object.load_land_mark(i));
    }

    ceres::Solver::Options option;
    option.linear_solver_type=ceres::DENSE_SCHUR;
    option.minimizer_progress_to_stdout=true;

    ceres::Solver::Summary summary;

    ceres::Solve(option,&problem,&summary);
    std::cout<<summary.FullReport()<<std::endl;

    std::vector<double> x;
    std::vector<double> y;
    std::vector<double> z;

    for(int i=0;i<object.land_mark_cnt_();i++){
        x.push_back(*(object.load_land_mark(i)));
        y.push_back(*(object.load_land_mark(i)+1));
        z.push_back(*(object.load_land_mark(i)+2));
    }
    std::map<std::string,std::string> kw;
    kw["marker"]="v";
    kw["color"]="blue";
    kw["label"]="optimized";
    matplotlibcpp::scatter(x,y,z,1.,kw);
    matplotlibcpp::legend();

    kw["marker"]="o";
    kw["color"]="red";
    kw["label"]="original";
    matplotlibcpp::scatter(x,y,z,1.,kw);
    matplotlibcpp::legend();
    matplotlibcpp::show();
    return 0;
}

使用

在源码文件夹中新建build文件夹编译,把从BAL官网下载数据集,数据集文件放在和build文件夹同级文件夹里面,执行以下

./simple_bal ../data

实例结果

这里输出的结果是对于某一个固定时刻内,由五十台相机确定的一组路标点的坐标
请添加图片描述似乎看不出来差别,哈哈。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值