关于openMVG源码(三)SfM描述文件/相机内参分析

2024-01-16  本文已影响0人  神奇的考拉

主要为了介绍openMVG执行sfm计算的开始步骤: openMVG_main_SfMInit_ImageListing, 来完成对输入图片数据集的描述,进行内参分析,输出SfM_Data文件,包括视图信息和相机内参数
一、输入参数

二、验证脚本
-d ../openMVG/src/openMVG/exif/sensor_width_database/sensor_width_camera_database.txt

-i ../openMVG_Build/image_datasets/ImageDataset_SceauxCastle/images

-o ../openMVG_Build/image_datasets/ImageDataset_SceauxCastle/matches

三、源码深入
1、参数详情

通过使用";"将x;y;z各个坐标轴的权重提取出来,构成std::pair<bool, Vec3>对: (true, 权重值)
5、构建一个带Views和相机配置的空场景

方向矩阵和旋转中心 Mat3 rotation_; Vec3 center_; Eigen类型
关于Intrinsics内参矩阵
using Intrinsics =
Hash_Map<IndexT, std::shared_ptr<cameras::IntrinsicBase>> #相机的内参属性
关于Landmarks
定义由TrackId索引的地标集合,Landmark包含两个成员:
Vec3 X; #3d点
Observations obs; #3d点所对应于图像上的坐标的hash表,
因为一个世界中的坐标可以被多张相机所观测到,
Landmarks点位又分为三角测量获得的点(用于BA)和地面控制点(用于GCP)

// ======= 用户提供了焦点 ====== //

// 1、已知校准K矩阵
if (sKmatrix.size() > 0) // Known user calibration K matrix
{
//校验提供K矩阵参数内容是否有效
if (!checkIntrinsicStringValidity(sKmatrix, focal, ppx, ppy))
focal = -1.0; // 不满足条件时焦点=-1.0
}
else // 2、焦距大小
if (focal_pixels != -1 )
focal = focal_pixels;

// 3、当用户提供的校准K矩阵和焦距都是无效
// 则会读取当前图片的exif信息
if (focal == -1)
{
std::unique_ptr<Exif_IO> exifReader(new Exif_IO_EasyExif);
exifReader->open( sImageFilename );

// 当前image exif是有效的
const bool bHaveValidExifMetadata =
  exifReader->doesHaveExifInfo()
  && !exifReader->getModel().empty()
  && !exifReader->getBrand().empty();

// 当前image的exif中提供了有效的焦距
if (bHaveValidExifMetadata) // If image contains meta data
{
  // 可能会存在焦距=0的情况,需要剔除
  if (exifReader->getFocal() == 0.0f)
  {
    error_report_stream
      << stlplus::basename_part(sImageFilename) << ": Focal length is missing." << "\n";
    focal = -1.0;
  }
  else
  // Create the image entry in the list file
  {
    const std::string sCamModel = exifReader->getBrand() + " " + exifReader->getModel();

    Datasheet datasheet;
    // 根据当前的相机型号从sensor_width_carema_database.txt数据集中获取其对应的焦距
    // 当image拍摄的相机型号及传感器宽度记录不在sensor_width_carema_database.txt,需要手动添加
    if ( getInfo( sCamModel, vec_database, datasheet ))
    {
      // The camera model was found in the database so we can compute it's approximated focal length
      // 根据当前图片的宽、高以及设备传感器sensor_size来计算焦距
      const double ccdw = datasheet.sensorSize_;
      focal = std::max ( width, height ) * exifReader->getFocal() / ccdw;
    }
    else
    {
      error_report_stream
        << stlplus::basename_part(sImageFilename)
        << "\" model \"" << sCamModel << "\" doesn't exist in the database" << "\n"
        << "Please consider add your camera model and sensor width in the database." << "\n";
    }
  }
}

}

// 接下来就是构建View视图关联的相机内参
// Build intrinsic parameter related to the view
std::shared_ptr<IntrinsicBase> intrinsic;

if (focal > 0 && ppx > 0 && ppy > 0 && width > 0 && height > 0)
{
// Create the desired camera type
// 根据提供用户相机类型:不同相机型号 对应的相机内参是不同的格式
switch (e_User_camera_model)
{
case PINHOLE_CAMERA: // 无畸变
intrinsic = std::make_shared<Pinhole_Intrinsic>
(width, height, focal, ppx, ppy);
break;
case PINHOLE_CAMERA_RADIAL1: // 径向畸变K1
intrinsic = std::make_shared<Pinhole_Intrinsic_Radial_K1>
(width, height, focal, ppx, ppy, 0.0); // setup no distortion as initial guess
break;
case PINHOLE_CAMERA_RADIAL3: // 径向畸变K1,K2,K3
intrinsic = std::make_shared<Pinhole_Intrinsic_Radial_K3>
(width, height, focal, ppx, ppy, 0.0, 0.0, 0.0); // setup no distortion as initial guess
break;
case PINHOLE_CAMERA_BROWN: // 径向畸变K1,K2,K3,切向畸变T1, T2
intrinsic = std::make_shared<Pinhole_Intrinsic_Brown_T2>
(width, height, focal, ppx, ppy, 0.0, 0.0, 0.0, 0.0, 0.0); // setup no distortion as initial guess
break;
case PINHOLE_CAMERA_FISHEYE: // 具有4个畸变系数的简单鱼眼畸变模型
intrinsic = std::make_shared<Pinhole_Intrinsic_Fisheye>
(width, height, focal, ppx, ppy, 0.0, 0.0, 0.0, 0.0); // setup no distortion as initial guess
break;
case CAMERA_SPHERICAL: // 球面相机
intrinsic = std::make_shared<Intrinsic_Spherical>
(width, height);
break;
default:
OPENMVG_LOG_ERROR << "Error: unknown camera model: " << (int) e_User_camera_model;
return EXIT_FAILURE;
}
}

// Build the view corresponding to the image
// 接着就是构建与图像对应的视图
Vec3 pose_center;
// 当有GPS权重就需要定义为优先旋转,并将gps转为地球坐标,其中涉及坐标系转换
// 从当前图片exif信息中提取经度、纬度、海拔;再根据指定的gps转为其他坐标系的类型进行对应的转换
// - ECEF(Earth-Centered, Earth-Fixed, 地心地固坐标系
// - UTM(Universal Transverse Mercator Grid System,通用横墨卡托格网系统
if (getGPS(sImageFilename, i_GPS_XYZ_method, pose_center) && b_Use_pose_prior)
{
// Views的子类,可以选择是否优先旋转,或者优先调整位置
ViewPriors v(*iter_image, views.size(), views.size(), views.size(), width, height);

// Add intrinsic related to the image (if any)
// 根据前面的相机型号,得到对应的相机内参数,将其添加到sfm_data中intrinsics块中
if (!intrinsic) // 若是没有相机内参数,则用无效字段填充
{
  //Since the view have invalid intrinsic data
  // (export the view, with an invalid intrinsic field value)
  v.id_intrinsic = UndefinedIndexT;
}
else // 若是有,则直接添加
{
  // Add the defined intrinsic to the sfm_container
  intrinsics[v.id_intrinsic] = intrinsic;
}

v.b_use_pose_center_ = true;
v.pose_center_ = pose_center;

// 使用坐标轴的权重
// prior weights
if (prior_w_info.first == true)
{
  v.center_weight_ = prior_w_info.second;
}

// 将当前image的视图view添加到sfm
// Add the view to the sfm_container
views[v.id_view] = std::make_shared<ViewPriors>(v);

}
else // 也会存在没有GPS信息:比如在室内GPS较差,出现漂移的情况,在后期重建带来模型偏差大的问题;则会关闭GPS
{
View v(*iter_image, views.size(), views.size(), views.size(), width, height);

// Add intrinsic related to the image (if any)
if (!intrinsic)
{
  //Since the view have invalid intrinsic data
  // (export the view, with an invalid intrinsic field value)
  v.id_intrinsic = UndefinedIndexT;
}
else
{
  // Add the defined intrinsic to the sfm_container
  intrinsics[v.id_intrinsic] = intrinsic;
}

// Add the view to the sfm_container
views[v.id_view] = std::make_shared<View>(v);

}
}
以上即为将images集所有的图片进行出来,最终将满足要求的image的视图View、相机内参intrinsic、位姿Poses、物方点Landmarks等信息添加到SfM中,如下图
{
"sfm_data_version": "0.3",
"root_path": "/Users/dalan/tests/openmvg_openmvs/video_images",
"views": [ // 视图
{
"key": 59,
"value": {
"polymorphic_id": 1073741824,
"ptr_wrapper": {
"id": 2147483649,
"data": {
"local_path": "",
"filename": "IMG_20240112_225353_TIMEBURST9.jpg",
"width": 2248,
"height": 4000,
"id_view": 59,
"id_intrinsic": 0,
"id_pose": 59
}
}
}
},
// 省略
],
"intrinsics": [ // 相机内参
{
"key": 0,
"value": {
"polymorphic_id": 2147483649,
"polymorphic_name": "pinhole_radial_k3",
"ptr_wrapper": {
"id": 2147483709,
"data": {
"width": 2248,
"height": 4000,
"focal_length": 2966.929157887857,
"principal_point": [
1124.0,
2000.0
],
"disto_k3": [
0.0,
0.0,
0.0
]
}
}
}
}
],
"extrinsics": [], // 相机外参
"structure": [], // 结构:三维点及其二维观测
"control_points": [] // 控制点:地标
}

// 主要是为了相机内参; 由于存在同源相机特性的图片可以合并处理
// Build hash & build a set of the hash in order to maintain unique Ids
std::set<size_t> hash_index;
std::vector<size_t> hash_value;

for (const auto & intrinsic_it : intrinsics)
{
const cameras::IntrinsicBase * intrinsicData = intrinsic_it.second.get();
const size_t hashVal = intrinsicData->hashValue();
hash_index.insert(hashVal);
hash_value.push_back(hashVal);
}

// 更新相机内参集中的intrinsic
// From hash_value(s) compute the new index (old to new indexing)
Hash_Map<IndexT, IndexT> old_new_reindex;
size_t i = 0;
for (const auto & intrinsic_it : intrinsics)
{
old_new_reindex[intrinsic_it.first] = std::distance(hash_index.cbegin(), hash_index.find(hash_value[i]));
++i;
}
//--> Save only the required Intrinsics (do not need to keep all the copy)
Intrinsics intrinsic_updated;
for (const auto & intrinsic_it : intrinsics)
{
intrinsic_updated[old_new_reindex[intrinsic_it.first]] = intrinsics[intrinsic_it.first];
}
// Update intrinsics (keep only the necessary ones) -> swapping
intrinsics.swap(intrinsic_updated);

// 有必要的话更新View中关于intrinsic的索引信息
// Update views intrinsic IDs (since some intrinsic position have changed in the map)
for (auto & view_it: views)
{
View * v = view_it.second.get();
// Update the Id only if a corresponding index exists
if (old_new_reindex.count(v->id_intrinsic))
v->id_intrinsic = old_new_reindex[v->id_intrinsic];
}

上一篇 下一篇

猜你喜欢

热点阅读