Colmap 命令model_merger源码解析

目录

一、找到model_merger的具体实现函数:RunModelMerge

二、分析RunModelMerge代码

三、合并重建MergeAndFilterReconstructions代码

四、MergeReconstructions

五、sim3计算:

六、总结

1. RunModelMerger 函数

2. MergeAndFilterReconstructions 函数

3. MergeReconstructions 函数

4. AlignReconstructionsViaReprojections 函数

5. 核心步骤概括


一、找到model_merger的具体实现函数:RunModelMerge

二、分析RunModelMerge代码

int RunModelMerger(int argc, char** argv) {
  std::string input_path1;
  std::string input_path2;
  std::string output_path;
  double max_reproj_error = 64.0;

  OptionManager options;
  options.AddRequiredOption("input_path1", &input_path1);
  options.AddRequiredOption("input_path2", &input_path2);
  options.AddRequiredOption("output_path", &output_path);
  options.AddDefaultOption("max_reproj_error", &max_reproj_error);
  options.Parse(argc, argv);

  Reconstruction reconstruction1;
  reconstruction1.Read(input_path1);
  PrintHeading2("Reconstruction 1");
  LOG(INFO) << StringPrintf("Images: %d", reconstruction1.NumRegImages());
  LOG(INFO) << StringPrintf("Points: %d", reconstruction1.NumPoints3D());

  Reconstruction reconstruction2;
  reconstruction2.Read(input_path2);
  PrintHeading2("Reconstruction 2");
  LOG(INFO) << StringPrintf("Images: %d", reconstruction2.NumRegImages());
  LOG(INFO) << StringPrintf("Points: %d", reconstruction2.NumPoints3D());

  PrintHeading2("Merging reconstructions");
  if (MergeAndFilterReconstructions(
          max_reproj_error, reconstruction1, reconstruction2)) {
    LOG(INFO) << "=> Merge succeeded";
    PrintHeading2("Merged reconstruction");
    LOG(INFO) << StringPrintf("Images: %d", reconstruction2.NumRegImages());
    LOG(INFO) << StringPrintf("Points: %d", reconstruction2.NumPoints3D());
  } else {
    LOG(INFO) << "=> Merge failed";
  }

  reconstruction2.Write(output_path);

  return EXIT_SUCCESS;
}

        这段代码包含了7步操作分别是:

        1.定义输入输出路径变量以及重投影误差变量

        2.OptionManager 设置:使用 OptionManager 来解析命令行参数,指定输入文件路径、输出路径和最大重投影误差等。

        3.加载和打印第一个重建(reconstruction1):创建一个 Reconstruction 对象并通过 Read 方法从文件 input_path1 加载数据。然后打印该重建的图片数量和三维点数量。

        4.加载和打印第二个重建(reconstruction2

        5.合并重建:调用 MergeAndFilterReconstructions 函数合并两个重建,如果合并成功,打印合并后的重建的图片和三维点数量;如果失败,输出失败信息。

        6.写入合并后的重建:将合并后的重建结果保存到指定的 output_path 文件中。

        7.程序执行成功后返回退出代码 EXIT_SUCCESS

        可以看出核心代码在于第5步合并重建MergeAndFilterReconstructions

三、合并重建MergeAndFilterReconstructions代码

        

bool MergeAndFilterReconstructions(const double max_reproj_error,
                                   const Reconstruction& src_reconstruction,
                                   Reconstruction& tgt_reconstruction) {
  if (!MergeReconstructions(
          max_reproj_error, src_reconstruction, tgt_reconstruction)) {
    return false;
  }
  ObservationManager(tgt_reconstruction)
      .FilterAllPoints3D(max_reproj_error, /*min_tri_angle=*/0);
  return true;
}

        可以看出,第一步是合并两个模型,第二步根据重投影误差过滤3d点

四、MergeReconstructions

        

bool MergeReconstructions(const double max_reproj_error,
                          const Reconstruction& src_reconstruction,
                          Reconstruction& tgt_reconstruction) {
  Sim3d tgt_from_src;
  if (!AlignReconstructionsViaReprojections(src_reconstruction,
                                            tgt_reconstruction,
                                            /*min_inlier_observations=*/0.3,
                                            max_reproj_error,
                                            &tgt_from_src)) {
    return false;
  }

  // Find common and missing images in the two reconstructions.
  std::unordered_set<image_t> common_image_ids;
  common_image_ids.reserve(src_reconstruction.NumRegImages());
  std::unordered_set<image_t> missing_image_ids;
  missing_image_ids.reserve(src_reconstruction.NumRegImages());
  for (const auto& image_id : src_reconstruction.RegImageIds()) {
    if (tgt_reconstruction.ExistsImage(image_id)) {
      common_image_ids.insert(image_id);
    } else {
      missing_image_ids.insert(image_id);
    }
  }

  // Register the missing images in this src_reconstruction.
  for (const auto image_id : missing_image_ids) {
    auto src_image = src_reconstruction.Image(image_id);
    src_image.ResetCameraPtr();
    src_image.SetCamFromWorld(
        TransformCameraWorld(tgt_from_src, src_image.CamFromWorld()));
    if (!tgt_reconstruction.ExistsCamera(src_image.CameraId())) {
      tgt_reconstruction.AddCamera(
          src_reconstruction.Camera(src_image.CameraId()));
    }
    tgt_reconstruction.AddImage(src_image);
    tgt_reconstruction.RegisterImage(image_id);
  }

  // Merge the two point clouds using the following two rules:
  //    - copy points to this src_reconstruction with non-conflicting tracks,
  //      i.e. points that do not have an already triangulated observation
  //      in this src_reconstruction.
  //    - merge tracks that are unambiguous, i.e. only merge points in the two
  //      reconstructions if they have a one-to-one mapping.
  // Note that in both cases no cheirality or reprojection test is performed.

  for (const auto& point3D : src_reconstruction.Points3D()) {
    Track new_track;
    Track old_track;
    std::unordered_set<point3D_t> old_point3D_ids;
    for (const auto& track_el : point3D.second.track.Elements()) {
      if (common_image_ids.count(track_el.image_id) > 0) {
        const auto& point2D = tgt_reconstruction.Image(track_el.image_id)
                                  .Point2D(track_el.point2D_idx);
        if (point2D.HasPoint3D()) {
          old_track.AddElement(track_el);
          old_point3D_ids.insert(point2D.point3D_id);
        } else {
          new_track.AddElement(track_el);
        }
      } else if (missing_image_ids.count(track_el.image_id) > 0) {
        tgt_reconstruction.Image(track_el.image_id)
            .ResetPoint3DForPoint2D(track_el.point2D_idx);
        new_track.AddElement(track_el);
      }
    }

    const bool create_new_point = new_track.Length() >= 2;
    const bool merge_new_and_old_point =
        (new_track.Length() + old_track.Length()) >= 2 &&
        old_point3D_ids.size() == 1;
    if (create_new_point || merge_new_and_old_point) {
      const Eigen::Vector3d xyz = tgt_from_src * point3D.second.xyz;
      const auto point3D_id =
          tgt_reconstruction.AddPoint3D(xyz, new_track, point3D.second.color);
      if (old_point3D_ids.size() == 1) {
        tgt_reconstruction.MergePoints3D(point3D_id, *old_point3D_ids.begin());
      }
    }
  }
  return true;
}

   合并过程如下:

        1.估计相似变换:

Sim3d tgt_from_src;
if (!AlignReconstructionsViaReprojections(src_reconstruction,
                                          tgt_reconstruction,
                                          /*min_inlier_observations=*/0.3,
                                          max_reproj_error,
                                          &tgt_from_src)) {
  return false;
}

        2. 查找共有和缺失的图像:

        这段代码首先查找 src_reconstruction 中的图像 ID,确定哪些图像在目标重建中存在,哪些是缺失的。图像的 ID 被分别存储在 common_image_ids(共有图像)和 missing_image_ids(缺失图像)中。

std::unordered_set<image_t> common_image_ids;
common_image_ids.reserve(src_reconstruction.NumRegImages());
std::unordered_set<image_t> missing_image_ids;
missing_image_ids.reserve(src_reconstruction.NumRegImages());
for (const auto& image_id : src_reconstruction.RegImageIds()) {
  if (tgt_reconstruction.ExistsImage(image_id)) {
    common_image_ids.insert(image_id);
  } else {
    missing_image_ids.insert(image_id);
  }
}

        3.注册缺失的图像:

        对于 missing_image_ids 中的每个图像,首先获取源重建中的图像对象 src_image。然后将其相机从世界坐标系转换到目标重建中的坐标系(通过 TransformCameraWorldtgt_from_src)。如果目标重建中不存在相机,添加新的相机。接着,将图像添加到目标重建中,并注册图像。

for (const auto image_id : missing_image_ids) {
  auto src_image = src_reconstruction.Image(image_id);
  src_image.ResetCameraPtr();
  src_image.SetCamFromWorld(
      TransformCameraWorld(tgt_from_src, src_image.CamFromWorld()));
  if (!tgt_reconstruction.ExistsCamera(src_image.CameraId())) {
    tgt_reconstruction.AddCamera(
        src_reconstruction.Camera(src_image.CameraId()));
  }
  tgt_reconstruction.AddImage(src_image);
  tgt_reconstruction.RegisterImage(image_id);
}

        4.合并点云:

        对于源重建中的每个 3D 点(point3D),代码检查其轨迹(track)中包含的每个 2D 点(track_el)。如果该 2D 点存在于目标重建的图像中,且其关联的 3D 点已经存在,则将其加入 old_track。如果该点在目标重建中没有关联的 3D 点,则加入 new_track。如果该 2D 点来自缺失的图像,目标重建的该点云数据被重置。

for (const auto& point3D : src_reconstruction.Points3D()) {
  Track new_track;
  Track old_track;
  std::unordered_set<point3D_t> old_point3D_ids;
  for (const auto& track_el : point3D.second.track.Elements()) {
    if (common_image_ids.count(track_el.image_id) > 0) {
      const auto& point2D = tgt_reconstruction.Image(track_el.image_id)
                                .Point2D(track_el.point2D_idx);
      if (point2D.HasPoint3D()) {
        old_track.AddElement(track_el);
        old_point3D_ids.insert(point2D.point3D_id);
      } else {
        new_track.AddElement(track_el);
      }
    } else if (missing_image_ids.count(track_el.image_id) > 0) {
      tgt_reconstruction.Image(track_el.image_id)
          .ResetPoint3DForPoint2D(track_el.point2D_idx);
      new_track.AddElement(track_el);
    }
  }

        5.创建或合并点:

        如果 new_track 的长度大于或等于 2(即该轨迹包含足够的观测数据来创建一个新的 3D 点),或者 new_trackold_track 的长度之和大于或等于 2,并且 old_point3D_ids 只有一个 ID(即只有一个已有的 3D 点),则进行点云的创建或合并。创建新点时,使用变换后的 3D 坐标。若有可以合并的点,则合并 3D 点。

const bool create_new_point = new_track.Length() >= 2;
const bool merge_new_and_old_point =
    (new_track.Length() + old_track.Length()) >= 2 &&
    old_point3D_ids.size() == 1;
if (create_new_point || merge_new_and_old_point) {
  const Eigen::Vector3d xyz = tgt_from_src * point3D.second.xyz;
  const auto point3D_id =
      tgt_reconstruction.AddPoint3D(xyz, new_track, point3D.second.color);
  if (old_point3D_ids.size() == 1) {
    tgt_reconstruction.MergePoints3D(point3D_id, *old_point3D_ids.begin());
  }
}

五、sim3计算:

这步colmap中分析从一个模型转换到另外一个模型的sim3计算方法,从第四步中可以看到,该部分的关键函数是

AlignReconstructionsViaReprojections(src_reconstruction,tgt_reconstruction, /*min_inlier_observations=*/0.3,max_reproj_error, &tgt_from_src))

bool AlignReconstructionsViaReprojections(
    const Reconstruction& src_reconstruction,
    const Reconstruction& tgt_reconstruction,
    const double min_inlier_observations,
    const double max_reproj_error,
    Sim3d* tgt_from_src) {
  THROW_CHECK_GE(min_inlier_observations, 0.0);
  THROW_CHECK_LE(min_inlier_observations, 1.0);

  RANSACOptions ransac_options;
  ransac_options.max_error = 1.0 - min_inlier_observations;
  ransac_options.min_inlier_ratio = 0.2;

  LORANSAC<ReconstructionAlignmentEstimator, ReconstructionAlignmentEstimator>
      ransac(ransac_options,
             ReconstructionAlignmentEstimator(
                 max_reproj_error, &src_reconstruction, &tgt_reconstruction),
             ReconstructionAlignmentEstimator(
                 max_reproj_error, &src_reconstruction, &tgt_reconstruction));

  const std::vector<std::pair<image_t, image_t>> common_image_ids =
      src_reconstruction.FindCommonRegImageIds(tgt_reconstruction);

  if (common_image_ids.size() < 3) {
    return false;
  }

  std::vector<const Image*> src_images(common_image_ids.size());
  std::vector<const Image*> tgt_images(common_image_ids.size());
  for (size_t i = 0; i < common_image_ids.size(); ++i) {
    src_images[i] = &src_reconstruction.Image(common_image_ids[i].first);
    tgt_images[i] = &tgt_reconstruction.Image(common_image_ids[i].second);
  }

  const auto report = ransac.Estimate(src_images, tgt_images);

  if (report.success) {
    *tgt_from_src = report.model;
  }

  return report.success;
}

        核心代码分析如下:
        

        1.查找共有图像

        FindCommonRegImageIds 函数用于找到源重建和目标重建中共有的图像对,并返回一个包含这些图像 ID 的对列表(common_image_ids)。

const std::vector<std::pair<image_t, image_t>> common_image_ids =
    src_reconstruction.FindCommonRegImageIds(tgt_reconstruction);

        2.检查共有图像数量

        如果共有的图像对少于 3 个,说明数据不足以进行有效的对齐,因此函数返回 false

if (common_image_ids.size() < 3) {
  return false;
}

        3.准备源图像和目标图像

        将源重建和目标重建中的每对共有图像提取出来,分别存储在 src_imagestgt_images 向量中,以便后续用于 RANSAC 对齐过程。

std::vector<const Image*> src_images(common_image_ids.size());
std::vector<const Image*> tgt_images(common_image_ids.size());
for (size_t i = 0; i < common_image_ids.size(); ++i) {
  src_images[i] = &src_reconstruction.Image(common_image_ids[i].first);
  tgt_images[i] = &tgt_reconstruction.Image(common_image_ids[i].second);
}

        4.执行 RANSAC 对齐

        调用 ransac.Estimate() 方法,传入 src_imagestgt_images,执行 RANSAC 对齐过程。该过程会尝试找到一个变换模型(例如,仿射变换或其他几何变换),将源重建的坐标系对齐到目标重建的坐标系。

        

const auto report = ransac.Estimate(src_images, tgt_images);

        5.拿到相似变换:

        

if (report.success) {
  *tgt_from_src = report.model;
}

六、总结

model_merger命令整体目的是实现两个重建模型(Reconstruction)的合并操作,特别是通过计算相似变换(Sim3d)对两个重建模型进行对齐,并合并它们的图像和三维点云数据。

1. RunModelMerger 函数

  • 输入输出路径和参数设置:使用 OptionManager 来解析命令行参数,获取两个输入路径和一个输出路径。还设定了最大重投影误差(默认为 64.0)。

  • 加载重建数据:分别加载两个重建数据,打印每个重建中包含的图像数量和三维点数量。

  • 合并过程:调用 MergeAndFilterReconstructions 函数来合并两个重建。如果合并成功,打印合并后的重建数据(包括图像和三维点数量);否则,输出失败信息。

  • 保存结果:将合并后的重建数据保存到指定的输出路径。

2. MergeAndFilterReconstructions 函数

  • 通过调用 MergeReconstructions 合并两个重建。如果合并成功,进一步对目标重建中的三维点进行过滤(基于重投影误差)。

  • 重投影误差过滤:通过 ObservationManager 对目标重建中的三维点进行过滤,移除误差过大的点。

3. MergeReconstructions 函数

  • 相似变换估计:首先,使用 AlignReconstructionsViaReprojections 函数通过重投影误差对两个重建模型进行对齐,估计出源重建到目标重建的相似变换(Sim3d)。

  • 查找共有和缺失的图像:比较两个重建中的图像,找出共有图像和缺失图像。

  • 注册缺失的图像:将源重建中缺失的图像以及相关的相机和三维点注册到目标重建中。

  • 合并点云:对于每个源重建中的三维点,检查其在目标重建中的对应点,并根据情况创建新点或合并已有点。

4. AlignReconstructionsViaReprojections 函数

  • 查找共有图像:使用 FindCommonRegImageIds 找到源重建和目标重建中共有的图像。

  • 执行 RANSAC 对齐:通过 RANSAC 算法估计源重建和目标重建之间的变换关系,确保只有足够的共有图像时才进行对齐。

  • 获取相似变换:如果 RANSAC 对齐成功,返回相似变换模型(Sim3d)。

5. 核心步骤概括

  • 估计相似变换:通过 AlignReconstructionsViaReprojections 对源重建和目标重建进行对齐,计算相似变换。

  • 查找和注册缺失图像:在目标重建中注册源重建中的缺失图像。

  • 合并点云:通过对轨迹数据的处理,创建新的三维点或者合并已有的点,确保两个重建中的三维点云合并无冲突。

  • 过滤误差:通过重投影误差和最小角度限制,过滤掉不符合要求的点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值