void EstimateScales(double max_reproj_error, const Pose& Pa1,
    const Pose& Pa2,
    const Pose& Pb1,
    const std::vector<Vector2d>& yb1s,
    const std::vector<Vector2d>& yb2s,
    std::vector<double>& scale_nums,
    std::vector<double>& scale_dens,
    std::vector<double>& iratios)
{
    Pose Pba = Pb1 * Pa1.inverse(); // Known relative transform from camera a to b
    Pose Pa21 = Pa2 * Pa1.inverse(); // Relative transform from Pa1 to Pa2

    auto Rba = Pba.rotation(); // Must not be identity
    auto tba = Pba.translation();

    auto Pab = Pba.inverse();
    auto Rab = Pab.rotation();
    auto tab = Pab.translation();

    auto Ra21 = Pa21.rotation();
    auto ta21 = Pa21.translation();

    auto Rb21 = Rba * Ra21 * Rab; // Relative rotation from Pb1 to Pb2

    auto A = crossMatrix(tba + Rba * Ra21 * tab) * Rb21;
    auto B = crossMatrix(Rba * ta21) * Rb21;

#if 0
    int id = 2;
    printf("[%d] Tba = %s\n", id, toString(Tba).c_str());
    printf("[%d] Ta21 = %s\n", id, toString(Ta21).c_str());
    printf("[%d] Tab = %s\n", id, toString(Tab).c_str());
    printf("[%d] Rab = %s\n", id, toString(Ra21).c_str());
    printf("[%d] Ra21 = %s\n", id, toString(Ra21).c_str());
    printf("[%d] Rb21 = %s\n", id, toString(Rb21).c_str());
    printf("[%d] A = %s\n", id, toString(A).c_str());
    printf("[%d] B = %s\n", id, toString(B).c_str());
#endif
    uint n = (uint)yb1s.size();

    scale_nums.clear();
    scale_dens.clear();
    iratios.clear();

    for (uint i = 0; i < n; i++) {

        // Get a scale estimate from one correspondence pair in camera b

        auto y1 = yb1s[i].homogeneous();
        auto y2 = yb2s[i].homogeneous();

        double s_num = y2.dot(A * y1);
        double s_den = (-y2).dot(B * y1);
        double s = s_num / s_den;

        // Count the inliers 

        Pose Pb2 = Pba * Pose(Pa2.rotation(), s * Pa2.translation());
        Pose Pb21 = Pb2 * Pb1.inverse(); // Relative pose

        vector<bool> inliers(n, true);
        uint icnt = getReprojectionInliers(max_reproj_error, yb1s, yb2s, Pb21, inliers);

        iratios.push_back(icnt / (float)n);
        scale_nums.push_back(s_num);
        scale_dens.push_back(s_den);
    }
}
bool OPTCalibrationNode::save()
{
  // Save tfs between sensors and world coordinate system (last checherboard) to file
  std::string file_name = ros::package::getPath("opt_calibration") + "/conf/camera_poses.yaml";
  std::ofstream file;
  file.open(file_name.c_str());

  if (file.is_open())
  {
    ros::Time time = ros::Time::now();
    file << "# Auto generated file." << std::endl;
    file << "calibration_id: " << time.sec << std::endl << std::endl;

    Pose new_world_pose = Pose::Identity();

    if (world_computation_ == LAST_CHECKERBOARD)
    {
      new_world_pose = calibration_->getLastCheckerboardPose().inverse();
      for (size_t i = 0; i < sensor_vec_.size(); ++i)
      {
        const Sensor::Ptr & sensor = sensor_vec_[i];
        Pose pose = new_world_pose * sensor->pose();
        if(pose.translation().z() < 0)
        {
          ROS_INFO_STREAM("[" << sensor->frameId() << "] z < 0. Flipping /world orientation.");
          AngleAxis rotation(M_PI, Vector3(1.0, 1.0, 0.0).normalized());
          new_world_pose = rotation * calibration_->getLastCheckerboardPose().inverse();
          break;
        }
      }
    }
    else if (world_computation_ == UPDATE)
    {
      new_world_pose = fixed_sensor_pose_ * fixed_sensor_->pose().inverse();
    }

    // Write TF transforms between cameras and world frame
    file << "# Poses w.r.t. the \"world\" reference frame" << std::endl;
    file << "poses:" << std::endl;
    for (size_t i = 0; i < sensor_vec_.size(); ++i)
    {
      const Sensor::Ptr & sensor = sensor_vec_[i];

      Pose pose = new_world_pose * sensor->pose();

      file << "  " << sensor->frameId().substr(1) << ":" << std::endl;

      file << "    translation:" << std::endl
           << "      x: " << pose.translation().x() << std::endl
           << "      y: " << pose.translation().y() << std::endl
           << "      z: " << pose.translation().z() << std::endl;

      Quaternion rotation(pose.rotation());
      file << "    rotation:" << std::endl
           << "      x: " << rotation.x() << std::endl
           << "      y: " << rotation.y() << std::endl
           << "      z: " << rotation.z() << std::endl
           << "      w: " << rotation.w() << std::endl;

    }

    file << std::endl << "# Inverse poses" << std::endl;
    file << "inverse_poses:" << std::endl;
    for (size_t i = 0; i < sensor_vec_.size(); ++i)
    {
      const Sensor::Ptr & sensor = sensor_vec_[i];

      Pose pose = new_world_pose * sensor->pose();
      pose = pose.inverse();

      file << "  " << sensor->frameId().substr(1) << ":" << std::endl;

      file << "    translation:" << std::endl
           << "      x: " << pose.translation().x() << std::endl
           << "      y: " << pose.translation().y() << std::endl
           << "      z: " << pose.translation().z() << std::endl;

      Quaternion rotation(pose.rotation());
      file << "    rotation:" << std::endl
           << "      x: " << rotation.x() << std::endl
           << "      y: " << rotation.y() << std::endl
           << "      z: " << rotation.z() << std::endl
           << "      w: " << rotation.w() << std::endl;

    }

  }
  file.close();

  ROS_INFO_STREAM(file_name << " created!");

  return true;

}