Sunday, 4 October 2015

A working example...

Here's a quick overview of the basic code structure, which should give a functional reconstruction (well "it works for me"(tm)).

This is a little terser than the code detail, and we strip some error checking we'd have in a live application, but this should work as proof of concept code, and the next few posts will go into a bit more detail. It reloads the image features (since they take the bulk of the processing time on my setup) and I've stripped the explicit namespacing to keep the code terse:


#include <iostream>
#include "openMVG/sfm/sfm.hpp"
#include "openMVG/exif/exif_IO_EasyExif.hpp"
#include "openMVG/image/image.hpp"
#include "openMVG/stl/split.hpp"

#include "openMVG/matching_image_collection/Matcher_Regions_AllInMemory.hpp"
#include "openMVG/matching_image_collection/GeometricFilter.hpp"
#include "openMVG/matching_image_collection/F_ACRobust.hpp"
#include "openMVG/sfm/sfm.hpp"

using namespace std;
using namespace openMVG::sfm;
using namespace openMVG::features;
using namespace openMVG::image;
using namespace openMVG::cameras;
using namespace openMVG::exif;
using namespace stlplus;

class ImageList {
public:
  ImageList();
  ~ImageList();
  void setDirectory(const char* nm);
  int countFiles();
  void loadAllImages();
  void loadImage(std::string s);
  void generateFeatures();
  void computeMatches();
  void sequentialReconstruct();

private:
  string _directory;
  string _matches_full;
  string _matches_filtered;
  vector<string> _fnames;
  SfM_Data _sfm_data;
  unique_ptr<Regions> _regionsType;
};


ImageList::ImageList() {
}

ImageList::~ImageList() {
}

void ImageList::setDirectory(const char* nm) {
vector<string> file_list;
  _directory = nm;
  _sfm_data.s_root_path = nm;
  file_list = stlplus::folder_files(_directory);

  sort(file_list.begin(), file_list.end());

  for (vector<string>::const_iterator it = file_list.begin(); it != file_list.end(); it++)  {
    string which = *it;
    string imnm = stlplus::create_filespec(_directory, which);
    _fnames.push_back(imnm);
  }
  _matches_full = string(_directory + "/matches.putative.txt");
  _matches_filtered = string(_directory + "/matches.f.txt");
}

int ImageList::countFiles() {
return _fnames.size();
}

void ImageList::loadAllImages() {
  for ( vector<string>::const_iterator iter_image = _fnames.begin(); iter_image != _fnames.end(); iter_image++ ) {
    string which = *iter_image;
    loadImage(which);
  }
return;
}

void ImageList::loadImage(string which) {
double width, height, image_focal;
double focal, ppx, ppy;

Views& views = _sfm_data.views;
Intrinsics& intrinsics = _sfm_data.intrinsics;
shared_ptr<IntrinsicBase> intrinsic (NULL);

  if (GetFormat(which.c_str()) == openMVG::image::Unknown)
    return;

  unique_ptr<Exif_IO_EasyExif> exifReader(new Exif_IO_EasyExif());
  if (!exifReader->open(which))
    return;

  image_focal = static_cast<double>(exifReader->getFocal());
  width = static_cast<double>(exifReader->getWidth());
  height = static_cast<double>(exifReader->getHeight());

  ppx = width / 2.0;
  ppy = height / 2.0;

  if (image_focal == 0)
    return;

  printf("Image %s: %f x %f, Focal Length %f\n", which.c_str(), width, height, image_focal);

  const double ccdw = 5.75; // Cheap Samsung S890
  //const double ccdw = 4.62;// Also Cheap Canon SX410-IS
  //const double ccdw = 7.39; // Nicer Canon G9
  //const double ccdw = 35.9; // Very Nice Sony DSLR/A850
  focal = std::max (width, height) * image_focal / ccdw;

  View v(which, views.size(), views.size(), views.size(), width, height);

  intrinsic = make_shared<Pinhole_Intrinsic_Radial_K3> (width, height, focal,
                                                        ppx, ppy, 0.0, 0.0, 0.0);
  intrinsics[v.id_intrinsic] = intrinsic;
  views[v.id_view] = std::make_shared<openMVG::sfm::View>(v);
}


void ImageList::generateFeatures(void) {
AKAZEParams params(AKAZEConfig(), AKAZE_MSURF);
unique_ptr<AKAZE_Image_describer> image_describer(new AKAZE_Image_describer(params, true));

  image_describer->Allocate(_regionsType);
  image_describer->Set_configuration_preset(NORMAL_PRESET);

  for(Views::const_iterator iterViews = _sfm_data.views.begin(); iterViews != _sfm_data.views.end(); ++iterViews) {
  const View * view = iterViews->second.get();
  const string sFeat = stlplus::create_filespec(_directory, stlplus::basename_part(view->s_Img_path), "feat");
  const string sDesc = stlplus::create_filespec(_directory, stlplus::basename_part(view->s_Img_path), "desc");

    if (!stlplus::file_exists(sFeat)) {
      Image<unsigned char> imageGray;
      printf("Creating %s\n", sFeat.c_str());
      ReadImage(view->s_Img_path.c_str(), &imageGray);
      unique_ptr<Regions> regions;
      image_describer->Describe(imageGray, regions);
      image_describer->Save(regions.get(), sFeat, sDesc);
    }
    else {
      printf("Using existing features from %s\n", sFeat.c_str());
    }
  }
}

void ImageList::computeMatches() {
float fDistRatio = 0.6f; // Higher is stricter
openMVG::matching::PairWiseMatches map_PutativesMatches;
vector<string> vec_fileNames;
vector<pair<size_t, size_t> > vec_imagesSize;

  for (Views::const_iterator iter = _sfm_data.GetViews().begin(); iter != _sfm_data.GetViews().end(); ++iter) {
    const View * v = iter->second.get();
    vec_fileNames.push_back(stlplus::create_filespec(_sfm_data.s_root_path, v->s_Img_path));
    vec_imagesSize.push_back(make_pair( v->ui_width, v->ui_height) );
  }

  unique_ptr<openMVG::Matcher_Regions_AllInMemory> collectionMatcher;
  collectionMatcher.reset(new openMVG::Matcher_Regions_AllInMemory(fDistRatio, openMVG::ANN_L2));

  if (collectionMatcher->loadData(_regionsType, vec_fileNames, _directory)) {
    openMVG::Pair_Set pairs;
    pairs = openMVG::exhaustivePairs(_sfm_data.GetViews().size());
    collectionMatcher->Match(vec_fileNames, pairs, map_PutativesMatches);
    ofstream file(_matches_full);
    if (file.is_open())
      PairedIndMatchToStream(map_PutativesMatches, file);
    file.close();
  }

  shared_ptr<Features_Provider> feats_provider = make_shared<Features_Provider>();

  if (!feats_provider->load(_sfm_data, _directory, _regionsType))
    return;

  openMVG::PairWiseMatches map_GeometricMatches;

  ImageCollectionGeometricFilter collectionGeomFilter(feats_provider.get());

  const double maxResidualError = 1.0; // dflt 1.0; // Lower is stricter

  collectionGeomFilter.Filter(
            GeometricFilter_FMatrix_AC(maxResidualError),
            map_PutativesMatches,
            map_GeometricMatches,
            vec_imagesSize);

  ofstream file (_matches_filtered);
  if (file.is_open())
    PairedIndMatchToStream(map_GeometricMatches, file);
  file.close();

}


void ImageList::sequentialReconstruct() {
string output_directory = "sequential";
string sfm_data = stlplus::create_filespec(output_directory, "sfm_data", ".json");
string cloud_data = stlplus::create_filespec(output_directory, "cloud_and_poses", ".ply");
string report_name = stlplus::create_filespec(output_directory, "Reconstruction_Report", ".html");

  if (!stlplus::folder_exists(output_directory))
    stlplus::folder_create(output_directory);

  SequentialSfMReconstructionEngine sfmEngine(_sfm_data, output_directory, report_name);
  shared_ptr<Features_Provider> feats_provider = std::make_shared<Features_Provider>();
  shared_ptr<Matches_Provider> matches_provider = std::make_shared<Matches_Provider>();

  feats_provider->load(_sfm_data, _directory, _regionsType);
  matches_provider->load(_sfm_data, _matches_filtered);

  sfmEngine.SetFeaturesProvider(feats_provider.get());
  sfmEngine.SetMatchesProvider(matches_provider.get());

  openMVG::Pair initialPairIndex;
  Views::const_iterator it;

  it = _sfm_data.GetViews().begin();
  const View *v1 = it->second.get();
  it++;
  const View *v2 = it->second.get();

  initialPairIndex.first = v1->id_view;
  initialPairIndex.second = v2->id_view;

  sfmEngine.setInitialPair(initialPairIndex);
  sfmEngine.Set_bFixedIntrinsics(false);
  sfmEngine.SetUnknownCameraType(EINTRINSIC(PINHOLE_CAMERA_RADIAL3));

  sfmEngine.Process();
  Save(sfmEngine.Get_SfM_Data(), sfm_data, ESfM_Data(openMVG::sfm::ALL));
  Save(sfmEngine.Get_SfM_Data(), cloud_data, ESfM_Data(openMVG::sfm::ALL));
}

int LoadImageListing(ImageList* iml, const char* imagedir) {
  iml->setDirectory(imagedir);

  if (iml->countFiles() > 0) {
    iml->loadAllImages();
  }
return 0;
}



int main(int argc, char* argv[])
{
ImageList iml;

  if (argc != 2) {
    printf("supply a target directory\n");
    return 0;
  }
  LoadImageListing(&iml, argv[1]);
  iml.generateFeatures();
  iml.computeMatches() ;
  iml.sequentialReconstruct();
}