主要介绍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 图像特征描述方式