视觉SLAM十四讲CH8代码解析

[复制链接]
 楼主| keer_zu 发表于 2023-3-13 17:31 | 显示全部楼层 |阅读模式
direct_method.cpp
  1. #include <opencv2/opencv.hpp>
  2. #include <sophus/se3.hpp>
  3. #include <boost/format.hpp>
  4. #include <pangolin/pangolin.h>

  5. using namespace std;

  6. typedef vector<Eigen::Vector2d, Eigen::aligned_allocator<Eigen::Vector2d>> VecVector2d;
  7. // Camera intrinsics 相机内参
  8. double fx = 718.856, fy = 718.856, cx = 607.1928, cy = 185.2157;
  9. // baseline   双目相机基线
  10. double baseline = 0.573;
  11. // paths 图像路径
  12. string left_file = "../left.png";
  13. string disparity_file = "../disparity.png";
  14. boost::format fmt_others("../%06d.png");    // other files

  15. // useful typedefs
  16. typedef Eigen::Matrix<double, 6, 6> Matrix6d;
  17. typedef Eigen::Matrix<double, 2, 6> Matrix26d;
  18. typedef Eigen::Matrix<double, 6, 1> Vector6d;

  19. /// class for accumulator jacobians in parallel  用于并行计算雅可比矩阵的类
  20. class JacobianAccumulator {
  21. public:
  22.     //类的构造函数,使用列表进行初始化
  23.     JacobianAccumulator(
  24.         const cv::Mat &img1_,
  25.         const cv::Mat &img2_,
  26.         const VecVector2d &px_ref_,//角点坐标
  27.         const vector<double> depth_ref_,//路标点的Z坐标值
  28.         Sophus::SE3d &T21_) :
  29.         img1(img1_), img2(img2_), px_ref(px_ref_), depth_ref(depth_ref_), T21(T21_) {
  30.         projection = VecVector2d(px_ref.size(), Eigen::Vector2d(0, 0));
  31.     }

  32.     /// accumulate jacobians in a range 在range范围内加速计算雅可比矩阵
  33.     void accumulate_jacobian(const cv::Range &range);

  34.     /// get hessian matrix 获取海塞矩阵
  35.     Matrix6d hessian() const { return H; }

  36.     /// get bias 获取矩阵b
  37.     Vector6d bias() const { return b; }

  38.     /// get total cost  获取总共的代价
  39.     double cost_func() const { return cost; }

  40.     /// get projected points 获取图像2中的角点坐标
  41.     VecVector2d projected_points() const { return projection; }

  42.     /// reset h, b, cost to zero  将海塞矩阵H,矩阵b和代价cost置为0
  43.     void reset() {
  44.         H = Matrix6d::Zero();
  45.         b = Vector6d::Zero();
  46.         cost = 0;
  47.     }

  48. private:
  49.     const cv::Mat &img1;
  50.     const cv::Mat &img2;
  51.     const VecVector2d &px_ref;//图像1中角点坐标
  52.     const vector<double> depth_ref;//图像1中路标点的Z坐标值
  53.     Sophus::SE3d &T21;
  54.     VecVector2d projection; // projected points

  55.     std::mutex hessian_mutex;
  56.     Matrix6d H = Matrix6d::Zero();
  57.     Vector6d b = Vector6d::Zero();
  58.     double cost = 0;
  59. };

  60. /**
  61. * pose estimation using direct method
  62. * @param img1
  63. * @param img2
  64. * @param px_ref
  65. * @param depth_ref
  66. * @param T21
  67. */
  68. void DirectPoseEstimationMultiLayer(
  69.     const cv::Mat &img1,
  70.     const cv::Mat &img2,
  71.     const VecVector2d &px_ref,
  72.     const vector<double> depth_ref,
  73.     Sophus::SE3d &T21
  74. );
  75. //定义DirectPoseEstimationMultiLayer函数 多层直接法
  76. /**
  77. * pose estimation using direct method
  78. * @param img1
  79. * @param img2
  80. * @param px_ref
  81. * @param depth_ref
  82. * @param T21
  83. */
  84. void DirectPoseEstimationSingleLayer(
  85.     const cv::Mat &img1,
  86.     const cv::Mat &img2,
  87.     const VecVector2d &px_ref,
  88.     const vector<double> depth_ref,
  89.     Sophus::SE3d &T21
  90. );
  91. //定义DirectPoseEstimationSingleLayer函数 单层直接法
  92. // bilinear interpolation 双线性插值求灰度值
  93. inline float GetPixelValue(const cv::Mat &img, float x, float y) //inline表示内联函数,它是为了解决一些频繁调用的小函数大量消耗栈空间的问题而引入的
  94. {
  95.     // boundary check
  96.     if (x < 0) x = 0;
  97.     if (y < 0) y = 0;
  98.     if (x >= img.cols) x = img.cols - 1;
  99.     if (y >= img.rows) y = img.rows - 1;
  100.     //...|I1      I2|...
  101.     //...|          |...
  102.     //...|          |...
  103.     //...|I3      I4|...
  104.     uchar *data = &img.data[int(y) * img.step + int(x)];//x和y是整数
  105.     //data[0] -> I1  data[1] -> I2  data[img.step] -> I3  data[img.step + 1] -> I4
  106.     float xx = x - floor(x);//xx算出的是x的小数部分
  107.     float yy = y - floor(y);//yy算出的是y的小数部分
  108.     return float//最终的像素灰度值
  109.     (
  110.         (1 - xx) * (1 - yy) * data[0] +
  111.         xx * (1 - yy) * data[1] +
  112.         (1 - xx) * yy * data[img.step] +
  113.         xx * yy * data[img.step + 1]
  114.     );
  115. }

  116. int main(int argc, char **argv) {

  117.     cv::Mat left_img = cv::imread(left_file, 0);//0表示返回灰度图,left.png表示000000.png
  118.     cv::Mat disparity_img = cv::imread(disparity_file, 0);//0表示返回灰度图,disparity.png是left.png的视差图

  119.     // let's randomly pick pixels in the first image and generate some 3d points in the first image's frame
  120.     //在图像1中随机选择一些像素点,然后恢复深度,得到一些路标点
  121.     cv::RNG rng;
  122.     int nPoints = 2000;
  123.     int boarder = 20;
  124.     VecVector2d pixels_ref;
  125.     vector<double> depth_ref;

  126.     // generate pixels in ref and load depth data
  127.     for (int i = 0; i < nPoints; i++) {
  128.         int x = rng.uniform(boarder, left_img.cols - boarder);  // don't pick pixels close to boarder 不要拾取靠近边界的像素
  129.         int y = rng.uniform(boarder, left_img.rows - boarder);  // don't pick pixels close to boarder 不要拾取靠近边界的像素
  130.         int disparity = disparity_img.at<uchar>(y, x);
  131.         double depth = fx * baseline / disparity; // you know this is disparity to depth
  132.         depth_ref.push_back(depth);
  133.         pixels_ref.push_back(Eigen::Vector2d(x, y));
  134.     }

  135.     // estimates 01~05.png's pose using this information
  136.     Sophus::SE3d T_cur_ref;
  137.     for (int i = 1; i < 6; i++)// 1~5 i从1到5,共5张图
  138.     {  
  139.         cv::Mat img = cv::imread((fmt_others % i).str(), 0);//读取图片,0表示返回一张灰度图
  140.         // try single layer by uncomment this line
  141.         // DirectPoseEstimationSingleLayer(left_img, img, pixels_ref, depth_ref, T_cur_ref);
  142.         //利用单层直接法计算图像img相对于left_img的位姿T_cur_ref,以图片left.png为基准
  143.         DirectPoseEstimationMultiLayer(left_img, img, pixels_ref, depth_ref, T_cur_ref);//调用DirectPoseEstimationMultiLayer 多层直接法
  144.     }
  145.     return 0;
  146. }
  147. void DirectPoseEstimationSingleLayer(
  148.     const cv::Mat &img1,
  149.     const cv::Mat &img2,
  150.     const VecVector2d &px_ref,//第1张图中的角点坐标
  151.     const vector<double> depth_ref,//第1张图中路标点的Z坐标值 就是深度信息
  152.     Sophus::SE3d &T21) {
  153.     const int iterations = 10;//设置迭代次数为10
  154.     double cost = 0, lastCost = 0;//将代价和最终代价初始化为0
  155.     auto t1 = chrono::steady_clock::now();//开始计时
  156.     JacobianAccumulator jaco_accu(img1, img2, px_ref, depth_ref, T21);
  157.     for (int iter = 0; iter < iterations; iter++) {
  158.         jaco_accu.reset();//重置
  159.         //完成并行计算海塞矩阵H,矩阵b和代价cost
  160.         cv::parallel_for_(cv::Range(0, px_ref.size()),
  161.                           std::bind(&JacobianAccumulator::accumulate_jacobian, &jaco_accu, std::placeholders::_1));
  162.         Matrix6d H = jaco_accu.hessian();//计算海塞矩阵
  163.         Vector6d b = jaco_accu.bias();//计算b矩阵
  164.         // solve update and put it into estimation
  165.          //求解增量方程更新优化变量T21
  166.         Vector6d update = H.ldlt().solve(b);;
  167.         T21 = Sophus::SE3d::exp(update) * T21;
  168.         cost = jaco_accu.cost_func();
  169.         if (std::isnan(update[0])) //解出来的更新量不是一个数字,退出迭代
  170.         {
  171.             // sometimes occurred when we have a black or white patch and H is irreversible
  172.             cout << "update is nan" << endl;
  173.             break;
  174.         }
  175.         if (iter > 0 && cost > lastCost) //代价不再减小,退出迭代
  176.         {
  177.             cout << "cost increased: " << cost << ", " << lastCost << endl;
  178.             break;
  179.         }
  180.         if (update.norm() < 1e-3) //更新量的模小于1e-3,退出迭代
  181.         {
  182.             // converge
  183.             break;
  184.         }
  185.         lastCost = cost;
  186.         cout << "iteration: " << iter << ", cost: " << cost << endl;
  187.     }//GN(高斯牛顿法)迭代结束
  188.     cout << "T21 = \n" << T21.matrix() << endl;//输出T21矩阵
  189.     auto t2 = chrono::steady_clock::now();//计时结束
  190.     auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);//计算耗时
  191.     cout << "direct method for single layer: " << time_used.count() << endl;//输出使用单层直接法所用时间
  192.     // plot the projected pixels here
  193.     cv::Mat img2_show;
  194.     cv::cvtColor(img2, img2_show, CV_GRAY2BGR);
  195.     VecVector2d projection = jaco_accu.projected_points();
  196.     for (size_t i = 0; i < px_ref.size(); ++i) {
  197.         auto p_ref = px_ref[i];
  198.         auto p_cur = projection[i];
  199.         if (p_cur[0] > 0 && p_cur[1] > 0) {
  200.             cv::circle(img2_show, cv::Point2f(p_cur[0], p_cur[1]), 2, cv::Scalar(0, 250, 0), 2);
  201.             cv::line(img2_show, cv::Point2f(p_ref[0], p_ref[1]), cv::Point2f(p_cur[0], p_cur[1]),
  202.                      cv::Scalar(0, 250, 0));
  203.         }
  204.     }
  205.     cv::imshow("current", img2_show);
  206.     cv::waitKey();
  207. }
  208. void JacobianAccumulator::accumulate_jacobian(const cv::Range &range) {
  209.     // parameters
  210.     const int half_patch_size = 1;
  211.     int cnt_good = 0;
  212.     Matrix6d hessian = Matrix6d::Zero();
  213.     Vector6d bias = Vector6d::Zero();
  214.     double cost_tmp = 0;
  215.     for (size_t i = range.start; i < range.end; i++) {
  216.         // compute the projection in the second image //point_ref表示图像1中的路标点
  217.         Eigen::Vector3d point_ref =
  218.             depth_ref[i] * Eigen::Vector3d((px_ref[i][0] - cx) / fx, (px_ref[i][1] - cy) / fy, 1);
  219.         Eigen::Vector3d point_cur = T21 * point_ref;//point_cur表示图像2中的路标点
  220.         if (point_cur[2] < 0)   // depth invalid
  221.             continue;
  222.         //u,v表示图像2中对应的角点坐标
  223.         float u = fx * point_cur[0] / point_cur[2] + cx, v = fy * point_cur[1] / point_cur[2] + cy;//视觉slam十四讲p99式5.5
  224.         // u = fx * X / Z + cx v = fy * Y / Z + cy  X  -> point_cur[0]  Y  -> point_cur[1] Z  -> point_cur[2]
  225.         if (u < half_patch_size || u > img2.cols - half_patch_size || v < half_patch_size ||
  226.             v > img2.rows - half_patch_size)
  227.             continue;
  228.         projection[i] = Eigen::Vector2d(u, v);//projection表示图像2中相应的角点坐标值
  229.         double X = point_cur[0], Y = point_cur[1], Z = point_cur[2],
  230.             Z2 = Z * Z, Z_inv = 1.0 / Z, Z2_inv = Z_inv * Z_inv;// Z2_inv = (1 / (Z * Z))
  231.         cnt_good++;
  232.         // and compute error and jacobian   计算海塞矩阵H,矩阵b和代价cost
  233.         for (int x = -half_patch_size; x <= half_patch_size; x++)
  234.             for (int y = -half_patch_size; y <= half_patch_size; y++) {
  235.                 //ei = I1(p1,i) - I(p2,i)其中p1,p2空间点P在两个时刻的像素位置坐标
  236.                 double error = GetPixelValue(img1, px_ref[i][0] + x, px_ref[i][1] + y) -
  237.                                GetPixelValue(img2, u + x, v + y);//视觉slam十四讲p219式8.13
  238.                 Matrix26d J_pixel_xi;
  239.                 Eigen::Vector2d J_img_pixel;
  240.                 //视觉slam十四讲p220式8.18
  241.                 J_pixel_xi(0, 0) = fx * Z_inv;
  242.                 J_pixel_xi(0, 1) = 0;
  243.                 J_pixel_xi(0, 2) = -fx * X * Z2_inv;
  244.                 J_pixel_xi(0, 3) = -fx * X * Y * Z2_inv;
  245.                 J_pixel_xi(0, 4) = fx + fx * X * X * Z2_inv;
  246.                 J_pixel_xi(0, 5) = -fx * Y * Z_inv;
  247.                 J_pixel_xi(1, 0) = 0;
  248.                 J_pixel_xi(1, 1) = fy * Z_inv;
  249.                 J_pixel_xi(1, 2) = -fy * Y * Z2_inv;
  250.                 J_pixel_xi(1, 3) = -fy - fy * Y * Y * Z2_inv;
  251.                 J_pixel_xi(1, 4) = fy * X * Y * Z2_inv;
  252.                 J_pixel_xi(1, 5) = fy * X * Z_inv;
  253.                 // -fx * inv_z 相当于-fx / Z
  254.                 //0
  255.                 // -fx * X * Z2_inv相当于fx * X / ( Z * Z )
  256.                 //--fx * X * Y * Z2_inv相当于fx * X * Y / ( Z * Z) +fx
  257.                 //fx + fx * X * X * Z2_inv相当于fx * X * X / (Z * Z)
  258.                 //-fx * Y * Z_inv相当于 fx * Y / Z
  259.                 //0
  260.                 //fy * Z_inv相当于-fy / Z
  261.                 //-fy * Y * Z2_inv相当于fy * Y / (Z * Z)
  262.                 //-fy - fy * Y * Y * Z2_inv相当于fy + fy * Y * Y / (Z * Z)
  263.                 //fy * X * Y * Z2_inv相当于fy * X * Y / ( Z * Z)
  264.                 //fy * X * Z_inv相当于-fy * X / Z
  265.                 J_img_pixel = Eigen::Vector2d(
  266.                     0.5 * (GetPixelValue(img2, u + 1 + x, v + y) - GetPixelValue(img2, u - 1 + x, v + y)),
  267.                     0.5 * (GetPixelValue(img2, u + x, v + 1 + y) - GetPixelValue(img2, u + x, v - 1 + y))
  268.                 );//dx,dy是优化变量 即(Δu,Δv) 计算雅克比矩阵
  269.                 //dx,dy是优化变量 即(Δu,Δv) 计算雅克比矩阵
  270.                 //相当于 J = - [ {I1( u + i + 1,v + j )-I1(u + i - 1,v + j )}/2,I1( u + i,v + j + 1)-I1( u + i ,v + j - 1)}/2]T T表示转置
  271.                 //I1 -> 图像1的灰度信息
  272.                 //i -> x
  273.                 //j -> y
  274.                 // total jacobian
  275.                 Vector6d J = -1.0 * (J_img_pixel.transpose() * J_pixel_xi).transpose();
  276.                 hessian += J * J.transpose();
  277.                 bias += -error * J;
  278.                 cost_tmp += error * error;
  279.             }
  280.     }
  281.     if (cnt_good) {
  282.         // set hessian, bias and cost
  283.         unique_lock<mutex> lck(hessian_mutex);
  284.         H += hessian;//H = Jij Jij(T)(累加和)
  285.         b += bias;//b = -Jij * eij(累加和)
  286.         cost += cost_tmp / cnt_good;//cost = || eij ||2 2范数
  287.     }
  288. }
  289. void DirectPoseEstimationMultiLayer(
  290.     const cv::Mat &img1,
  291.     const cv::Mat &img2,
  292.     const VecVector2d &px_ref,
  293.     const vector<double> depth_ref,
  294.     Sophus::SE3d &T21) {
  295.     // parameters
  296.     int pyramids = 4;//金字塔层数为4
  297.     double pyramid_scale = 0.5;//每层之间的缩放因子设为0.5
  298.     double scales[] = {1.0, 0.5, 0.25, 0.125};
  299.     // create pyramids 创建图像金字塔
  300.     vector<cv::Mat> pyr1, pyr2; // image pyramids pyr1 -> 图像1的金字塔 pyr2 -> 图像2的金字塔
  301.     for (int i = 0; i < pyramids; i++) {
  302.         if (i == 0) {
  303.             pyr1.push_back(img1);
  304.             pyr2.push_back(img2);
  305.         } else {
  306.             cv::Mat img1_pyr, img2_pyr;
  307.             //将图像pyr1[i-1]的宽和高各缩放0.5倍得到图像img1_pyr
  308.             cv::resize(pyr1[i - 1], img1_pyr,
  309.                        cv::Size(pyr1[i - 1].cols * pyramid_scale, pyr1[i - 1].rows * pyramid_scale));
  310.             //将图像pyr2[i-1]的宽和高各缩放0.5倍得到图像img2_pyr
  311.             cv::resize(pyr2[i - 1], img2_pyr,
  312.                        cv::Size(pyr2[i - 1].cols * pyramid_scale, pyr2[i - 1].rows * pyramid_scale));
  313.             pyr1.push_back(img1_pyr);
  314.             pyr2.push_back(img2_pyr);
  315.         }
  316.     }
  317.     double fxG = fx, fyG = fy, cxG = cx, cyG = cy;  // backup the old values 备份旧值
  318.     for (int level = pyramids - 1; level >= 0; level--) {
  319.         VecVector2d px_ref_pyr; // set the keypoints in this pyramid level  设置此金字塔级别中的关键点
  320.         for (auto &px: px_ref) {
  321.             px_ref_pyr.push_back(scales[level] * px);
  322.         }
  323.         // scale fx, fy, cx, cy in different pyramid levels  在不同的金字塔级别缩放 fx, fy, cx, cy
  324.         fx = fxG * scales[level];
  325.         fy = fyG * scales[level];
  326.         cx = cxG * scales[level];
  327.         cy = cyG * scales[level];
  328.         DirectPoseEstimationSingleLayer(pyr1[level], pyr2[level], px_ref_pyr, depth_ref, T21);
  329.     }
  330. }
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1478

主题

12917

帖子

55

粉丝
快速回复 在线客服 返回列表 返回顶部

1478

主题

12917

帖子

55

粉丝
快速回复 在线客服 返回列表 返回顶部