我们首先从数据集读取入手,包括读取数据集的参数文件来获得相机的内参和外参,以及读取图片,因此,我们还需要抽象出相机类、数据集类、参数配置类。
相机类最简单,我们先来实现它
Camera类存储相机的内参和外参,并完成相机坐标系、像素坐标系、和世界坐标系之间的坐标变换。在抽象的过程中,我们分为参数和函数的确定,首先是参数:
之后是构造函数和功能函数:
// Camera类存储相机的内参和外参,并完成相机坐标系、像素坐标系、和世界坐标系之间的坐标变换。
#pragma once
#ifndef MYSLAM_CAMERA_H
#define MYSLAM_CAMERA_H
#include"MYSLAM/common_include.h"
namespace MYSLAM{
class Camera{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;//内存对齐
typedef std::shared_ptrPtr;//智能指针
double fx_=0,fy_=0,cx_=0, cy_=0,//定义内参
baseline_=0;//基线
SE3 pose_;//外参,双目到单目位姿变换
SE3 pose_inv_;
// 构造函数(无参)
Camera();
// 构造函数(有参,初始化参数量)
Camera(double fx, double fy, double cx, double cy, double baseline,
const SE3 &pose ) : fx_(fx), fy_(fy), cx_(cx), cy_(cy), baseline_(baseline), pose_(pose) {
pose_inv_ = pose_.inverse();
};
// 取出位姿
SE3 pose()const{
return pose_;
}
//读取内参矩阵K
Mat33 K()const{
Mat33 k;
k<
#include"MYSLAM/camera.h"
namespace MYSLAM{
Camera::Camera(){};
//世界to相机:T_c_w变换矩阵 乘 世界坐标系的点位置
Vec3 Camera::world2camera(const Vec3 &p_w, const SE3 &T_c_w){
return pose_*T_c_w*p_w;
}
//相机to世界:T_c_w变换矩阵的逆 乘 相机坐标系的点位置
Vec3 Camera::camera2world(const Vec3 &p_c, const SE3 &T_c_w){
return T_c_w.inverse()*pose_inv_*p_c;
}
//相机to像素
Vec3 Camera::camera2pixel(const Vec3 &p_c){
return Vec2(
fx_*p_c(0,0)/p_c(2,0)+cx_, // u = fx * X / Z+ cx
fy_*p_c(1,0)/p_c(2,0)+cy_ // v = fy * Y / Z+ cy
);
}
//像素to相机
Vec3 Camera::pixel2camera(const Vec2 &p_p, double depth ){
return Vec3(
(p_p(0,0)-cx_)*depth / fx_, // X=(u-cx)*z / fx
(p_p(1,0)-cy_)*depth / fy_, // Y=(v-cy)*z/ fy
depth
);
}
// 像素to世界
Vec3 Camera::pixel2world(const Vec2 &p_p,const SE3 &T_c_w, double depth){
return camera2world(pixel2camera(p_p , depth),T_c_w);//像素to相机,相机to世界
}
//世界to像素
Vec3 Camera::world2pixel(const Vec3 &p_w,const SE3 &T_c_w){
return camera2pixel(world2camera(p_w , T_c_w)); //世界to相机,相机to像素
}
}//namespace MYSL
Config 类负责参数文件的读取,并在程序任意地方都可随时提供参数的值
同样参数:
函数:
// * 配置类,使用SetParameterFile确定配置文件
// * 然后用Get得到对应值
// * 单例模式
// Config 类负责参数文件的读取,并在程序任意地方都可随时提供参数的值。所以我们把 Config 写成单例模式(Singleton)。
// 它只有一个全局对象,当我们设置参数文件时,创建该对象并读取参数文件,随后就可以在任意地方访问参数值,最后在程序结束时自动销毁
#pragma once
#ifndef MYSLAM_CONFIG_H
#define MYSLAM_CONFIG_H
#include"MYSLAM/common_include.h"
namespace MYSLAM{
//配置文件类
class Config{
private:
static std::shared_ptrconfig_;//智能指针
cv::FileStorage file_;//文件
Config(){};//私有构造函数生成单例模式
public:
~Config(){};//析构函数:关闭文件,删除相关buff
//打开一个新的文件
static bool SetParameterFile(std::string &filename);
//函数模板, 模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数功能要求: 我们需要对int、char、string、double等类型的数据做交换操作,
//( 有了模板功能,则只需要编写一个函数即可,编译器可以通过输入参数的类型,推断出形参的类型。)
template
// 根据键值获取参数值
static T Get(const std::string &key) {
return T(Config::config_->file_[key]);
}
};
}//namespace MYSLAM
#endif // MYSLAM_CONFIG_H
#include"MYSLAM/config.h"
namespace MYSLAM{
// 打开文件
bool Config::SetParameterFile(std::string &filename){
//没有config则构建
if (config_ == nullptr ){
config_=std::shared_ptr(new Config);
}
// config_->file_就定义为cv::FileStorage形式,变量是filename.c_str(),参数是cv::Filestorage::READ.是只读模式,不修改。
config_->file_=cv::FileStorage(filename.c_str(),cv::FileStorage::READ);
// 如果文档打不开,就用std::cerr输出错误信息
if (config_->file_.isOpened()==false)
{
LOG(ERROR) << "parameter file " << filename << " does not exist.";
config_->file_.release();//关闭文件并删除buff
return false;
}
return true;//如果打开,返回ture
}
//析构函数:关闭文件,删除相关buff
Config:: ~Config(){
if (config_->file_.isOpened())
{
config_->file_.release();
}
}
std::shared_ptr Config::config_ = nullptr;//智能全局指针
}//namespace MYSLAM
Dataset类用于数据集读取,在构造时传入配置文件路径,初始化之后可获得相机和下一帧图像
参数:
函数:
// 数据集读取
// 构造时传入配置文件路径,配置文件的dataset_dir为数据集路径
// Init之后可获得相机和下一帧图像
#ifndef MYSLAM_DATASET_H
#define MYSLAM_DATASET_H
#include "MYSLAM/camera.h"
#include "MYSLAM/common_include.h"
#include "MYSLAM/frame.h"
namespace MYSLAM{
// 数据集读取类
class Dataset{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
typedef std::shared_ptrPtr;//智能指针
// 构造函数
Dataset(const std::string &dataset_path){};
// 初始化,返回是否成功
bool Init();
//创建并返回下一帧
Frame::Ptr NextFrame();
//根据id返回相机参数
Camera::Ptr GetCamera(int camera_id) const {
return cameras_.at(camera_id);
};
private:
// 参数
std::string dataset_path_; //数据集路径
int current_image_index_ =0 ;//当前图像id
std::vectorcameras_;//每一帧对应的相机参数(应该都是一样的
};
}//MYSLAM
#endif
#include"MYSLAM/dataset.h"
#include "MYSLAM/frame.h"
#include
#include
#include
using namespace std;
namespace MYSLAM{
//构造函数
Dataset::Dataset (const std::string &dataset_path) : dataset_path_(dataset_path){}
// 初始化,返回是否成功
bool Dataset::Init(){
// 从calib.txt中获取相机内外参
// 00序列calib参数文件中一共包含了4个相机矩阵:其中P0代表0号左边灰度相机、P1代表1号右边灰度相机、P2代表2号左边彩色相机、P3代表3号右边彩色相机.每个相机12个参数组成的内参矩阵
std::ifstream fin(dataset_path_ + "/calib.txt");//读取calib.txt
if (!fin)
{
LOG(ERROR)<<"cannot find " << dataset_path_ << "/calib.txt!";
return false;
}
// 遍历文件中的四个相机(P0,P1,P2,P3)
for (int i = 0; i < 4; ++i)
{
//前三个字符是P0:所以这里定义了一个长度为3的字符数组,读完这三个字符后就遇到了第一个空格,fin将会跳过这个空格,读取参数
char camera_name[3];
for (int k = 0; k < 3; ++k)
{
fin >> camera_name[i];
}
double projection_data[12];
for (int k = 0; i < 12; ++k)
{
fin >> projection_data[k];
}
//将相机后面对应的12个参数读入到projection_data[12]中
Mat33 K;//内参矩阵,从相机坐标到像素坐标
K << projection_data[0],projection_data[1],projection_data[2],
projection_data[4],projection_data[5],projection_data[6],
projection_data[8],projection_data[9],projection_data[10];
Vec3 t;
t << projection_data[4], projection_data[7], projection_data[11];
t=K.inverse()*t;
K=0.5*K;
Camera::Ptr new_camera (new Camera (K(0,0),K(1,1),K(0,2),K(1,2), t.norm(), SE3(SO3() , t)));
cameras_.push_back(new_camera);
LOG(INFO) << "Camera " << i << " extrinsics: " << t.transpose();
}
fin.close();
current_image_index_=0;
return true;
}
//创建并返回下一帧
Frame::Ptr Dataset::NextFrame(){
boost::format fmt("%s/image_%d/%06d.png");//数据格式化
// 1.读图
cv::Mat image_left , image_right;
image_left = cv::imread((fmt % dataset_path_ % 0 % current_image_index_).str(), cv::IMREAD_GRAYSCALE);
image_right = cv::imread((fmt % dataset_path_ % 1 % current_image_index_).str(), cv::IMREAD_GRAYSCALE);
// 如果左图或右图为空,则返回空指针
if (image_left.data == nullptr || image_right.data == nullptr)
{
LOG(WARNING) << "cannot find images at index " << current_image_index_;
return nullptr;
}
// 2.改图大小
cv::Mat image_left_resized , image_right_resized;
cv::resize(image_left,image_left_resized, cv::Size(),0.5,0.5 ,cv::INTER_NEAREST);
cv::resize(image_right,image_right_resized, cv::Size(),0.5,0.5 ,cv::INTER_NEAREST);
// 工厂模式建立新帧
auto new_frame = Frame::CreateFrame();
// 将改过尺寸的左右目图片赋给新帧
new_frame ->left_img_ =image_left_resized;
new_frame ->right_img_=image_right_resized;
current_image_index_++;
return new_frame;
}
}//namespace MYSLAM