OKVIS代码解读(1)——main函数

一、主函数——okvis_app_synchronous.cpp

1. 引用库、函数
#include <Eigen/Core>
#include <opencv2/opencv.hpp>
#include <okvis/VioParametersReader.hpp>
#include <okvis/ThreadedKFVio.hpp>
#include <boost/filesystem.hpp>
2. 包含的两部分
class PoseViewer// PoseViewer类的主要作用是画出小黑窗口,实时显示运动轨迹

// this is just a workbench. 这里的大部分内容都会用到前端类
int main(int argc, char **argv)   //argc 是外部命令参数的个数,argv[] 存放各参数的内容
2.1 main函数流程

系统入口在ThreadedKFVio类的构造函数中( ThreadedKFVio类继承自VioInterface,该类的定义在okvis_multisensor_processing文件夹)

int main(int argc, char **argv)   //argc 是外部命令参数的个数,argv[] 存放各参数的内容
{  
// 运行时,窗口输入: argv[0]运行程序  argv[1]配置文件路径   argv[2]数据集路径   
 //1.  读入argv[0],运行程序。出错时输出警告信息
  google::InitGoogleLogging(argv[0]); 
  FLAGS_stderrthreshold = 0;  // INFO: 0, WARNING: 1, ERROR: 2, FATAL: 3    
  FLAGS_colorlogtostderr = 1;
  if (argc != 3 && argc != 4) {    
    LOG(ERROR)<< // 出错,输出警告信息
    "Usage: ./" << argv[0] << " configuration-yaml-file dataset-folder [skip-first-seconds]";    
    return -1;  
    }
  okvis::Duration deltaT(0.0); // 持续时间 delT(0.0)默认值为0.0秒  
  if (argc == 4) { // 也可以读入第四个参数argv[3]    
    deltaT = okvis::Duration(atof(argv[3]));  
  }
 //2.读入arg[1]路径下的配置文件 
  //2.1 将argv[1]得到的配置文件名赋给configFilename
  std::string configFilename(argv[1]); 

  //2.2 读取configFilename文件下的配置文件,将所有参数和配置文件组合起来
  okvis::/VioParametersReader vio_parameters_reader(configFilename); 
  //2.4 得到parameters下的所有参数设置
  okvis::VioParameters parameters;  
  vio_parameters_reader.getParameters(parameters);
 //3. 得到配置文件里的参数设置parameter后,开启系统(okvis_estimator)
   //系统入口在ThreadedKFVio类的构造函数中( ThreadedKFVio类继承自VioInterface,该类的定义在okvis_multisensor_processing文件夹)
   // 定义一个ThreadedKFVio类对象okvis_estimator,并且将获得的参数设置传入(一旦使用该类,系统在该类的构造函数中将进行初始化,系统正式开启!
  okvis::ThreadedKFVio okvis_estimator(parameters);

  // 3.1 定义窗口显示类(显示运动轨迹窗口)   PoseViewer poseViewer;//定义了一个poseViewer类的对象poseViewer
  PoseViewer poseViewer;  
  // 3.2 调用对象okvis_estimator的全状态回调函数 setFullStateCallback();
  okvis_estimator.setFullStateCallback(     
      std::bind(&PoseViewer::publishFullStateAsCallback, &poseViewer,                                       
      std::placeholders::_1, std::placeholders::_2,
      std::placeholders::_3, std::placeholders::_4));
  // 3.3 调用对象okvis_estimator的阻塞设置函数 okvis_estimator.setBlocking(true);
   // 设置阻塞变量,addmeasurement()函数是否应立即返回(阻塞= false),或者仅在处理完成时返回
  okvis_estimator.setBlocking(true); // 使IMU与图像帧尽可能对齐

// 4 读入argv[2]路径下的IMU数据
  // 4.1 the folder path  
  std::string path(argv[2]); //  "path"被定义为string类型,并将argv[2]得到的值赋给"path"

 // 4.2 通过配置文件得到相机数量numCameras
  const unsigned int numCameras = parameters.nCameraSystem.numCameras();

  // 4.3 open the IMU file,打开IMU文件,读入IMU数据,“imu_file”是类"ifstream"的对象,作用是读入数据
  std::string line;  
  std::ifstream imu_file(path + "/imu0/data.csv");  // 读取IMU的csv文件的行
    // 如果没有发现IMU文件,告警
  if (!imu_file.good()) {    
  LOG(ERROR)<< "no imu file found at " << path+"/imu0/data.csv";   
  return -1;  
  }  
    // 如果IMU文件中某一行缺少IMU数据,告警
  int number_of_lines = 0;  
  while (std::getline(imu_file, line))    
    ++number_of_lines;  
  LOG(INFO)<< "No. IMU measurements: " << number_of_lines-1;  
  if (number_of_lines - 1 <= 0) {    
    LOG(ERROR)<< "no imu messages present in " << path+"/imu0/data.csv";    
    return -1;  
    }  
  // set reading position to second line  将读取位置设置为第二行
  imu_file.clear();  
  imu_file.seekg(0, std::ios::beg);  
  std::getline(imu_file, line);
  
// 5. 打开Camera文件夹,读取图像文件名(还没有读取图像)
  std::vector<okvis::Time> times;    
  okvis::Time latest(0);  
  int num_camera_images = 0;
  // 5.1 给每个相机定义一个vector,向量内的数据类型为“ std::string”(字符串类型),存放图像名称  
  // 5.2 开始读取图像名称(实际上是图像的时间标签)
  std::vector < std::vector < std::string >> image_names(numCameras);
  // 5.3 读取给定文件路径下的所有文件的名称,用的是folder(/path)函数  
  for (size_t i = 0; i < numCameras; ++i) {   
   num_camera_images = 0;    
   std::string folder(path + "/cam" + std::to_string(i) + "/data");
   
   // 5.4 不断在vector中push_back图像名称,也就是在vector的末尾插入string
   for (auto it = boost::filesystem::directory_iterator(folder);        
       it != boost::filesystem::directory_iterator(); it++) {      
    if (!boost::filesystem::is_directory(it->path())) {  //we eliminate directories        
       num_camera_images++;        
       image_names.at(i).push_back(it->path().filename().string());  // ".at(i)"提供访问vector向量的入口,i为哪一个数据需要访问
    } else {        
      continue;      
    }    
  }
    // 5.5 如果文件夹内没有图像,输出告警信息
    if (num_camera_images == 0) {      
      LOG(ERROR)<< "no images at " << folder;      
      return 1;    
    }
    
    // 5.6 读取完毕后,对vector向量image_names里的元素进行排序 。因为图像名称是时间标签,事件标签值反映图像的先后顺序,按大小顺序排列即可
    LOG(INFO)<< "No. cam " << i << " images: " << num_camera_images;    
    // the filenames are not going to be sorted. So do this here    
    std::sort(image_names.at(i).begin(), i/mage_names.at(i).end()); 
  }
  
 // 6. 定义了两个迭代器,通过从头到尾访问图像文件名容器,得到对应的图像
 // 迭代器iterator允许程序员检查容器内元素,并实现元素遍历的数据类型。跟下标操作类似。
  // 6.1 每个相机设置一个迭代器,从头到尾访问每个相机的图像
  std::vector < std::vector < std::string > ::iterator      
      > cam_iterators(numCameras);  
  for (size_t i = 0; i < numCameras; ++i) {    
  cam_iterators.at(i) = image_names.at(i).begin();  // 为迭代器赋初值,迭代器的初值就是图像文件名容器的起始值
  }
  
  // 7. 打开窗口显示
  int counter = 0;  
  okvis::Time start(0.0);  
  while (true) {    
    okvis_estimator.display();    // 线程图像及特征点匹配显示窗口
    poseViewer.display();   // 位姿显示窗口(黑色窗口)
    
    // 检查是否已读取全部图像,读取完毕后输出信息    
    for (size_t i = 0; i < numCameras; ++i) {     
     if (cam_iterators[i] == image_names[i].end()) { // 判断条件为,迭代器的值为图像文件名容器的最后一个值       
      std::cout << std::endl << "Finished. Press any key to exit." << std::endl << std::flush;        
      cv::waitKey();        
      return 0;      
      }    
    }
   // 8.  根据文件名开始读入图像   
    // 8.1 定义类存放图像的真实时间(秒、纳秒)
    okvis::Time t; // 将图像文件名(时间标签)转换为真实的时间(秒、 纳秒)???
    
    // 8.2 调用OpenCV库,读取每个相机的图像
    for (size_t i = 0; i < numCameras; ++i) {      
       cv::Mat filtered = cv::imread(         
           path + "/cam" + std::to_string(i) + "/data/" + *cam_iterators.at(i), 
           cv::IMREAD_GRAYSCALE); // 读取路径下的灰度图像(文件名由迭代器访问图像文件名容器得来  
        // 时间标签总/共18位,前9位位秒,后9位为纳秒    
       std::string nanoseconds = cam_iterators.at(i)->substr(          
           cam_iterators.at(i)->size() - 13, 9);      
       std::string seconds = cam_iterators.at(i)->substr(          
           0, cam_iterators.at(i)->size() - 13);      
       t = okvis::Time(std::stoi(seconds), std::stoi(nanoseconds)); // 时间      
       if (start == okvis::Time(0.0)) {        
          start = t;     
       }
       
     // 9.  get all IMU measurements till then   
      // 9.1 定义类存放IMU数据的真实时间   
      okvis::Time t_imu = start;
      // 9.2 获取所有的IMU测量,如果完成,则输出信息(当IMU的时间标签小于图像时,添加IMU值)     
      do {        
        if (!std::getline(imu_file, line)) {          
           std::cout << std::endl << "Finished. Press any key to exit." << std::endl << std::flush;          
           cv::waitKey();          
           return 0;     
          }
        
        std::stringstream stream(line);        
        std::string s;        
        std::getline(stream, s, ',');
        // Ⅰ 根据时间标签,计算IMU数据的真实时间        
        std::string nanoseconds = s.substr(s.size() - 9, 9);        
        std::string seconds = s.substr(0, s.size() - 9);
        
        // Ⅱ 读取陀螺仪 gyr[ ] 数据(x,y,z)
        Eigen::Vector3d gyr;        
        for (int j = 0; j < 3; ++j) {          
            std::getline(stream, s, ',');          
            gyr[j] = std::stof(s); // stof():将字符串转换为数字        
        }
        
        // Ⅲ 读取加速度计 acc[ ] 数据(x,y,z)
        Eigen::Vector3d acc;        
        for (int j = 0; j < 3; ++j) {          
            std::getline(stream, s, /','); // 从s中读取至多n个字符保存在stream对应的数组中,遇到“,”则读取终止         
            acc[j] = std::stof(s);       
        }
        t_imu = okvis::Time(std::stoi(seconds), std::stoi(nanoseconds));
        
        // Ⅳ 加入IMU测量值进行(阻塞)处理 
          if (t_imu - start + okvis::Duration(1.0) > deltaT) {
          // 添加IMU测量(测量数据的时间标签、加速度、角速度)                  
          okvis_estimator.addImuMeasurement(t_imu, acc, gyr);       
         }
         
      } while (t_imu <= t); // 当IMU的时间标签小于图像时,添加IMU值
      
      // Ⅴ 将图像添加到前端进行(阻塞)处理      
      if (t - start > deltaT) {        
         okvis_estimator.addImage(t, i, filtered); // 添加新的图像(图像时间标签、相机编号、图像)      
      }
      
      cam_iterators[i]++;    
 }    
 ++counter;
 
 // display progress显示进度    
 if (counter % 20 == 0) {      
   std::cout << "\rProgress: "          
      << int(double(counter) / double(num_camera_images) * 100) << "%  "          
      << std::flush;    
   }
  
 }

  std::cout << std::endl << std::flush;  
  return 0
}
2.2 Pose Viewer类

PoseViewer类的主要作用是画出小黑窗口,实时显示运动轨迹。在该类中,可以定义输出数据流savetrajectory(ofstream savetrajectory),将轨迹保存在“results.txt”中。

 class PoseViewer // PoseViewer类的主要作用是画出小黑窗口,实时显示运动轨迹
 { 
  public:  
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW  
    constexpr static const double imageSize = 500.0;  
    // 1. 构造函数:OpenCV打开和显示窗口
    PoseViewer()  
    {    
       cv::namedWindow("OKVIS Top View");    
       _image.create(imageSize, imageSize, CV_8UC3);    
       drawing_ = false;    
       showing_ = false; 
     }  
     // 2. 回调函数 void publishFullStateAsCallback() ,用回调函数发布所有状态(包括位置和速度),显示实时的运动轨迹(小黑窗口) 
      // 2.1 函数需要的输入参数包括:time时间、Transformation变换矩阵T_WS、速度和偏差矩阵SpeedAndBias 
     void publishFullStateAsCallback(      
         const okvis::Time & /*t*/, const okvis::kinematics::Transformation & T_WS, // 时间,变换矩阵      
         const Eigen::Matrix<double, 9, 1> & speedAndBiases, // 速度和偏差矩阵      
         const Eigen::Matrix<double, 3, 1> & /*omega_S*/) // 角速率矩阵(陀螺仪测量.?)  
    {
     // just append the path 
     // 2.2 获取平移矩阵(T_WS.r())、旋转矩阵(T_WS.C())   
    Eigen::Vector3d r = T_WS.r();  // 获取平移矩阵
    Eigen::Matrix3d C = T_WS.C();  // 获取旋转矩阵
    _path.push_back(cv::Point2d(r[0], r[1]));    
    _heights.push_back(r[2]);    
     // 2.3 maintain scaling 保持缩放   
    if (r[0] - _frameScale < _min_x)      
       _min_x = r[0] - _frameScale;   
    if (r[1] - _frameScale < _min_y)      
       _min_y = r[1] - _frameScale;    
    if (r[2] < _min_z)      
       _min_z = r[2];    
    if (r[0] + _frameScale > _max_x)      
       _max_x = r[0] + _frameScale;   
    if (r[1] + _frameScale > _max_y)      
       _max_y = r[1] + _frameScale;    
    if (r[2] > _max_z)      
       _max_z = r[2];    
    _scale = std::min(imageSize / (_max_x - _min_x), imageSize / (_max_y - _min_y));
    
     // 2.4 draw it 画出轨迹    
    while (showing_) {    
    }    
    drawing_ = true;    
    // erase    
    _image.setTo(cv::Scalar(10, 10, 10));    
    drawPath(); // 绘制路径    
    // 2.5 draw axes    
    Eigen::Vector3d e_x = C.col(0);    
    Eigen::Vector3d e_y = C.col(1);    
    Eigen::Vector3d e_z = C.col(2);    
    cv::line(        
       _image,        
       convertToImageCoordinates(_path.back()),        
       convertToImageCoordinates(            
          _path.back() + cv::Point2d(e_x[0], e_x[1]) * _frameScale),        
       cv::Scalar(0, 0, 255), 1, CV_AA);    
    cv::line(        
        _image,        
       convertToImageCoordinates(_path.back()),        
       convertToImageCoordinates(            
             _path.back() + cv::Point2d(e_y[0], e_y[1]) * _frameScale),        
       cv::Scalar(0, 255, 0), 1, CV_AA);    
    cv::line(        
       _image,        
       convertToImageCoordinates(_path.back()),        
       convertToImageCoordinates(            
            _path.back() + cv::Point2d(e_z[0], e_z[1]) * _frameScale),        
       cv::Scalar(255, 0, 0), 1, CV_AA);
    
   // some text:    
   std::stringstream postext;    
   postext << "position = [" << r[0] << ", " << r[1] << ", " << r[2] << "]";    
   cv::putText(_image, postext.str(), cv::Point(15,15),                
               cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(255,255,255), 1);    
   std::stringstream veltext;    
   veltext << "velocity = [" << speedAndBiases[0] << ", " << speedAndBiases[1] << ", " << speedAndBiases[2] << "]";      
   cv::putText(_image, veltext.str(), cv::Point(15,35),                    
                   cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(255,255,255), 1);
   drawing_ = false; // notify  
  }
  // 3. 显示函数  
  void display()  
  {    
   while (drawing_) {    
   }    
   showing_ = true;    
   cv::imshow("OKVIS Top View", _image);    
   showing_ = false;    
   cv::waitKey(1);  
  } 
  private:  
    // 3.1 将二维点坐标转换为图像坐标系坐标
    cv::Point2d convertToImageCoordinates(const cv::Point2d & pointInMeters) const  
    {    
      cv::Point2d pt = (pointInMeters - cv::Point2d(_min_x, _min_y)) * _scale;    
      return cv::Point2d(pt.x, imageSize - pt.y); // reverse y for more intuitive top-down plot反转更直观的自上而下的绘图  
    }  
    // 3.2 画出轨迹
    void drawPath()  
    {    
      for (size_t i = 0; i + 1 < _path.size(); ) {      
        cv::Point2d p0 = convertToImageCoordinates(_path[i]);      
        cv::Point2d p1 = convertToImageCoordinates(_path[i + 1]);      
        cv::Point2d diff = p1-p0;      
        if(diff.dot(diff)<2.0){        
          _path.erase(_path.begin() + i + 1);  // clean short segment        
          _heights.erase(_heights.begin() + i + 1);        
          continue;      
         }      
         double rel_height = (_heights[i] - _min_z + _heights[i + 1] - _min_z)                      
                        * 0.5 / (_max_z - _min_z);      
         cv::line(          
             _image,          
             p0,          
             p1,          
             rel_height * cv::Scalar(255, 0, 0)              
                 + (1.0 - rel_height) * cv::Scalar(0, 0, 255),          
             1, CV_AA);      
         i++;   
       } 
   }  
   cv::Mat _image;  
   std::vector<cv::Point2d> _path;  
   std::vector<double> _heights;  
   double _scale = 1.0;  
   double _min_x = -0.5;  
   double _min_y = -0.5;  
   double _min_z = -0.5;  
   double _max_x = 0.5;  
   double _max_y = 0.5;  
   double _max_z = 0.5;  
   const double _frameScale = 0.2;  // [m]  
   std::atomic_bool drawing_;  
   std::atomic_bool showing_;
};

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值