关于openMVG源码(四)计算特征点

主要介绍openMVG特征提取的实现,对每张影像进行特征提取与特征描述,输出.feat, .desc结果文件,常用默认的SIFT算子。特征提取两种实现: main_ComputeFeatures.cpp/main_ComputeFeatures_OpenCV.cpp;一个是使用openMVG内部耦合的特征点进行图像描述的,另一个是使用opencv提供的特征描述方法;如下主要还是以openMVG内嵌的特征计算实现为主。

一、参数

-i/--input_file: 输入sfm描述文件 sfm_data.json
-o/--outdir: 输出目录:比如特征描述文件

-m/--describerMethod:图像特征描述方法; 
    SIFT (默认下) 
    SIFT_ANATOMY
    AKAZE_FLOAT: AKAZE浮点数描述
    AKAZE_MLDB:  AKAZE二进制描述
-u/--upright:AKAZE描述子使用,是否计算方向
-f/--fore:是否强制重新计算特征点和描述子
-p/--describerPreset:描述子质量:NORMAL,HIGH,ULTRA
-n/--numThreads:执行的thread个数(使用openMP才需要设置)

二、特征提取步骤

1、加载scene文件:sfm_data.json文件:

Load(sfm_data, sSfM_Data_Filename, ESfM_Data(VIEWS|INTRINSICS)

bool bStatus = false;
// 获取当前sfm数据描述文件后缀;根据后缀名不同采用的load
const std::string ext = stlplus::extension_part(filename);
if (ext == "json")
  bStatus = Load_Cereal<cereal::JSONInputArchive>(sfm_data, filename, flags_part);
else if (ext == "bin")
  bStatus = Load_Cereal<cereal::PortableBinaryInputArchive>(sfm_data, filename, flags_part);
else if (ext == "xml")
  bStatus = Load_Cereal<cereal::XMLInputArchive>(sfm_data, filename, flags_part);
else
{
  OPENMVG_LOG_ERROR << "Unknown sfm_data input format: " << filename;
  return false;
}

// 读取sfm数据描述文件并将相机内参或外参关联到有效的视图view上
if ( bStatus &&
  (flags_part & VIEWS) == VIEWS && (
  (flags_part & INTRINSICS) == INTRINSICS ||
  (flags_part & EXTRINSICS) == EXTRINSICS))
{
  // 
  return ValidIds(sfm_data, flags_part);
}
return bStatus;

2、当加载完成sence文件后,接下来就是要初始化Image Describer,此时有两种处理方式

  • 当image_describer.json文件存在时

此时会使用已存在的image_describer.json,将使用旧的配置加载的所用图像描述符和区域类型;

image.png
  • 不存在image_describer.json文件时

会根据指定的-m/--describerMethod参数来reset对应的图片描述;需要注意一点-u/--upright除了SIFT_ANATOMY都需要的

3、提取特征

会对sfm_data.json文件中的每个view项目进行处理:

  • 当已经存在region文件时,则继续后续的处理

  • 若是没有,则需要重新计算特征

{
    system::Timer timer;
    Image<unsigned char> imageGray;

    system::LoggerProgress my_progress_bar(sfm_data.GetViews().size(), "- EXTRACT FEATURES -" );

    // 追踪特征提取是否结束 
    // Use a boolean to track if we must stop feature extraction
    std::atomic<bool> preemptive_exit(false);

    // 当时openMP多线程时,则需要读取提供的iNumThreads
#ifdef OPENMVG_USE_OPENMP
    const unsigned int nb_max_thread = omp_get_max_threads();

    if (iNumThreads > 0) {
        omp_set_num_threads(iNumThreads);
    } else {
        omp_set_num_threads(nb_max_thread);
    }

    #pragma omp parallel for schedule(dynamic) if (iNumThreads > 0) private(imageGray)
#endif

    // 循环遍历sfm_data.json文件中的各个view也就是每张图片
    for (int i = 0; i < static_cast<int>(sfm_data.views.size()); ++i)
    {

      // 针对每张图片,结合其文件名生成xxx.feat(特征点文件)和xxx.desc(描述子文件)
      Views::const_iterator iterViews = sfm_data.views.begin();
      std::advance(iterViews, i);
      const View * view = iterViews->second.get();
      const std::string
        sView_filename = stlplus::create_filespec(sfm_data.s_root_path, view->s_Img_path),
        sFeat = stlplus::create_filespec(sOutDir, stlplus::basename_part(sView_filename), "feat"),
        sDesc = stlplus::create_filespec(sOutDir, stlplus::basename_part(sView_filename), "desc");

      // 此时需要需要检查对应图片的关联feat文件和desc文件是否存在;不存在则需要重新计算
      // 这里需要注意点当调整了对应的参数或导致特征提取操作发生变化时
      // 建议将原有已生成的文件删除或指定-f/--fore参数来强制重新执行特征计算
      // If features or descriptors file are missing, compute them
      if (!preemptive_exit && (bForce || !stlplus::file_exists(sFeat) || !stlplus::file_exists(sDesc)))
      {
        // 这里需要将图片进行灰度化处理
        if (!ReadImage(sView_filename.c_str(), &imageGray))
          continue;

        //
        // Look if there is an occlusion feature mask
        //
        // 需要查看是否存在遮挡特征遮罩
        Image<unsigned char> * mask = nullptr; // The mask is null by default

        // 此处会有两种类型mask矩阵:局部mask和全局mask
        // 两者的差异:局部mask是指每张图有各自的mask,而全局的是整个数据集采用同一的mask矩阵。
        const std::string
          mask_filename_local =
            stlplus::create_filespec(sfm_data.s_root_path,
              stlplus::basename_part(sView_filename) + "_mask", "png"),
          mask_filename_global =
            stlplus::create_filespec(sfm_data.s_root_path, "mask", "png");

        Image<unsigned char> imageMask;
        // Try to read the local mask
        // 局部mask
        if (stlplus::file_exists(mask_filename_local))
        {
          if (!ReadImage(mask_filename_local.c_str(), &imageMask))
          {
            OPENMVG_LOG_ERROR
              << "Invalid mask: " << mask_filename_local << ';'
              << "Stopping feature extraction.";
            preemptive_exit = true;
            continue;
          }
          // Use the local mask only if it fits the current image size
          // 仅当本地遮罩符合当前图像大小时使用
          if (imageMask.Width() == imageGray.Width() && imageMask.Height() == imageGray.Height())
            mask = &imageMask;
        }
        else // 全局mask
        {
          // Try to read the global mask
          if (stlplus::file_exists(mask_filename_global))
          {
            if (!ReadImage(mask_filename_global.c_str(), &imageMask))
            {
              OPENMVG_LOG_ERROR
                << "Invalid mask: " << mask_filename_global << ';'
                << "Stopping feature extraction.";
              preemptive_exit = true;
              continue;
            }
            // Use the global mask only if it fits the current image size
            // 仅当全局遮罩适合当前图像大小时才使用它
            if (imageMask.Width() == imageGray.Width() && imageMask.Height() == imageGray.Height())
              mask = &imageMask;
          }
        }

        //这里才是核心,计算特征和描述符,并输出到对应的文件中 
        // Compute features and descriptors and export them to files
        auto regions = image_describer->Describe(imageGray, mask);
        if (regions && !image_describer->Save(regions.get(), sFeat, sDesc)) {
          OPENMVG_LOG_ERROR
            << "Cannot save regions for image: " << sView_filename << ';'
            << "Stopping feature extraction.";
          preemptive_exit = true;
          continue;
        }
      }
      ++my_progress_bar;
    }
    OPENMVG_LOG_INFO << "Task done in (s): " << timer.elapsed();
  }

如上特征计算也就完结了。

三、使用

openMVG_main_ComputeFeatures 
  -i sfm_data.json  
  -o 输出特征点和描述子文件的输出目录
  -m SIFT 图像特征描述方式
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容