diff --git a/.github/workflows/build-new.yml b/.github/workflows/build-new.yml new file mode 100644 index 00000000..e84673bb --- /dev/null +++ b/.github/workflows/build-new.yml @@ -0,0 +1,76 @@ +name: Pybind11 CI + +on: + push: + branches: + - master + pull_request: + types: [ assigned, opened, synchronize, reopened ] + release: + types: [published] + workflow_dispatch: + +jobs: + linux-build: + name: Wrapper Linux Build + runs-on: ubuntu-latest + strategy: + matrix: + pyversion: ["cp37-cp37m","cp38-cp38"] # "cp36-cp36m", "cp39-cp39" + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build the Linux wheels + run: | + # must be run in docker, cannot be run as freestanding script for toolchain issues + # run on old CentOS. but it's CentOS, so it's yum(RPM) not apt. very old glibc. glibc API is back-compatible but not forward. See https://github.com/pypa/manylinux + # see https://github.com/varunagrawal/docker-images/blob/master/gtsam-manylinux/Dockerfile for an example + # container, but it does not have some necesssary boost packages --> varunagrawal/gtsam-manylinux:latest + sudo docker run --rm -e PLAT=manylinux2014_x86_64 -e PYTHON_VERSION=${{ matrix.pyversion }} -v `pwd`:/io quay.io/pypa/manylinux2014_x86_64 /io/package/build-wheels-linux.sh ${{ matrix.pyversion }} + # cleanup for custom runner + sudo chown -R $(whoami):$(whoami) . + - name: Archive wheels + uses: actions/upload-artifact@v2 + with: + # we strip the version number from the artifact name + name: pycolmap-${{ matrix.pyversion }}-manylinux2014_x86_64 + path: wheelhouse/pycolmap-*-${{ matrix.pyversion }}-manylinux2014_x86_64.whl + - name: Publish package + # We publish the wheel to pypi when a new tag is pushed, + # either by creating a new GitHub release or explictly with `git tag` + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: wheelhouse/ + + mac-build: + name: Wrapper macOS Build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-11, macos-10.15] + pyversion: ["python@3.8", "python@3.9"] # "python@3.7" not supported in Github Actions + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build the macOS wheels + run: | + ./package/build-wheels-macos.sh ${{ matrix.pyversion }} + - name: Archive wheels + uses: actions/upload-artifact@v2 + with: + name: pycolmap-${{ matrix.pyversion }}-${{ matrix.os }} + path: ./wheelhouse/pycolmap-*.whl + - name: Publish package + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: wheelhouse/ diff --git a/.gitignore b/.gitignore index 722d5e71..a2c362e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .vscode +*.so +*.egg-info/ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f6d3e825..35206934 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,16 @@ cmake_minimum_required(VERSION 3.6) project(PyCOLMAP) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - find_package(COLMAP REQUIRED) include_directories(${COLMAP_INCLUDE_DIRS}) link_directories(${COLMAP_LINK_DIRS}) +if (${CERES_VERSION} VERSION_LESS "2.0.0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") +endif() + add_subdirectory(pybind11) pybind11_add_module(pycolmap main.cc) diff --git a/homography_estimation.cc b/homography_estimation.cc new file mode 100644 index 00000000..67eb062c --- /dev/null +++ b/homography_estimation.cc @@ -0,0 +1,88 @@ +#include +#include + +#include "colmap/estimators/homography_matrix.h" +#include "colmap/optim/loransac.h" + +using namespace colmap; + +#include +#include +#include + +namespace py = pybind11; + +/** + * Recover the most probable pose from the inputted homography matrix. + * + * @param points2D1 First set of corresponding points. + * @param points2D2 Second set of corresponding points. + * @param max_error_px + * @param min_inlier_ratio + * @param min_num_trials + * @param max_num_trials + * @return The estimated homography matrix (3x3), ... + */ +py::dict homography_matrix_estimation( + const std::vector points2D1, + const std::vector points2D2, + const double max_error_px, + const double min_inlier_ratio, + const int min_num_trials, + const int max_num_trials, + const double confidence +) { + SetPRNGSeed(0); + + // Check that both vectors have the same size. + assert(points2D1.size() == points2D2.size()); + + // Failure output dictionary. + py::dict failure_dict; + failure_dict["success"] = false; + + // Fundamental matrix estimation parameters. + RANSACOptions ransac_options; + ransac_options.max_error = max_error_px; + ransac_options.min_inlier_ratio = min_inlier_ratio; + ransac_options.min_num_trials = min_num_trials; + ransac_options.max_num_trials = max_num_trials; + ransac_options.confidence = confidence; + + // Estimate planar or panoramic model. + LORANSAC< + HomographyMatrixEstimator, + HomographyMatrixEstimator + > H_ransac(ransac_options); + + // Homography matrix estimation. + const auto report = H_ransac.Estimate(points2D1, points2D2); + + if (!report.success) { + return failure_dict; + } + + // Recover data from report. + const Eigen::Matrix3d H = report.model; + const size_t num_inliers = report.support.num_inliers; + const auto inlier_mask = report.inlier_mask; + + // Convert vector to vector. + std::vector inliers; + for (auto it : inlier_mask) { + if (it) { + inliers.push_back(true); + } else { + inliers.push_back(false); + } + } + + // Success output dictionary. + py::dict success_dict; + success_dict["success"] = true; + success_dict["H"] = H; + success_dict["num_inliers"] = num_inliers; + success_dict["inliers"] = inliers; + + return success_dict; +} diff --git a/main.cc b/main.cc index e7b99e71..55dea646 100644 --- a/main.cc +++ b/main.cc @@ -6,13 +6,20 @@ namespace py = pybind11; #include "generalized_absolute_pose.cc" #include "essential_matrix.cc" #include "fundamental_matrix.cc" +#include "homography_estimation.cc" #include "homography_decomposition.cc" #include "transformations.cc" #include "sift.cc" #include "pose_refinement.cc" +#include "two_view_geometry_estimation.cc" PYBIND11_MODULE(pycolmap, m) { m.doc() = "COLMAP plugin"; +#ifdef VERSION_INFO + m.attr("__version__") = py::str(VERSION_INFO); +#else + m.attr("__version__") = py::str("dev"); +#endif // Absolute pose. m.def("absolute_pose_estimation", &absolute_pose_estimation, @@ -57,6 +64,16 @@ PYBIND11_MODULE(pycolmap, m) { py::arg("confidence") = 0.9999, "LORANSAC + 7-point algorithm."); + // Homography matrix estimation. + m.def("homography_matrix_estimation", &homography_matrix_estimation, + py::arg("points2D1"), py::arg("points2D2"), + py::arg("max_error_px") = 4.0, + py::arg("min_inlier_ratio") = 0.01, + py::arg("min_num_trials") = 1000, + py::arg("max_num_trials") = 100000, + py::arg("confidence") = 0.9999, + "LORANSAC + 4-point DLT algorithm."); + // Homography Decomposition. m.def("homography_decomposition", &homography_decomposition_estimation, py::arg("H"), @@ -84,4 +101,17 @@ PYBIND11_MODULE(pycolmap, m) { py::arg("inlier_mask"), py::arg("camera_dict"), "Non-linear refinement."); + + // Generic two view geometry estimation. + m.def("two_view_geometry_estimation", &two_view_geometry_estimation, + py::arg("points2D1"), + py::arg("points2D2"), + py::arg("camera_dict1"), + py::arg("camera_dict2"), + py::arg("max_error_px") = 4.0, + py::arg("min_inlier_ratio") = 0.01, + py::arg("min_num_trials") = 1000, + py::arg("max_num_trials") = 100000, + py::arg("confidence") = 0.9999, + "Generic two-view geometry estimation"); } diff --git a/package/build-wheels-linux.sh b/package/build-wheels-linux.sh new file mode 100755 index 00000000..953dd5d7 --- /dev/null +++ b/package/build-wheels-linux.sh @@ -0,0 +1,183 @@ +#!/bin/bash + +# Installation based off of https://colmap.github.io/install.html (COLMAP) +# and https://github.com/mihaidusmanu/pycolmap#getting-started (pycolmap) +# and http://ceres-solver.org/installation.html (Ceres) +# However, the OS is centOS 7, instead of Ubuntu. + +uname -a +echo "Current CentOS Version:" +cat /etc/centos-release + +yum -y install wget + +ls -ltrh /io/ + +# we cannot simply use `pip` or `python`, since points to old 2.7 version +PYBIN="/opt/python/$PYTHON_VERSION/bin" +PYVER_NUM=$($PYBIN/python -c "import sys;print(sys.version.split(\" \")[0])") +PYTHONVER="$(basename $(dirname $PYBIN))" + +echo "Python bin path: $PYBIN" +echo "Python version number: $PYVER_NUM" +echo "Python version: $PYTHONVER" + +export PATH=$PYBIN:$PATH + +${PYBIN}/pip install auditwheel + +PYTHON_EXECUTABLE=${PYBIN}/python +# We use distutils to get the include directory and the library path directly from the selected interpreter +# We provide these variables to CMake to hint what Python development files we wish to use in the build. +PYTHON_INCLUDE_DIR=$(${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())") +PYTHON_LIBRARY=$(${PYTHON_EXECUTABLE} -c "import distutils.sysconfig as sysconfig; print(sysconfig.get_config_var('LIBDIR'))") + +CURRDIR=$(pwd) + +echo "Num. processes to use for building: ${nproc}" + +# ------ Install boost (build it staticly) ------ +cd $CURRDIR +yum install -y wget libicu libicu-devel centos-release-scl-rh devtoolset-7-gcc-c++ + +# Download and install Boost-1.65.1 +# colmap needs only program_options filesystem graph system unit_test_framework +mkdir -p boost && \ + cd boost && \ + wget -nv https://boostorg.jfrog.io/artifactory/main/release/1.65.1/source/boost_1_65_1.tar.gz && \ + tar xzf boost_1_65_1.tar.gz && \ + cd boost_1_65_1 && \ + ./bootstrap.sh --with-libraries=serialization,filesystem,thread,system,atomic,date_time,timer,chrono,program_options,regex,graph,test && \ + ./b2 -j$(nproc) cxxflags="-fPIC" runtime-link=static variant=release link=static install + +# Boost should now be visible under /usr/local +ls -ltrh /usr/local + +# ------ Install dependencies from the default Ubuntu repositories ------ +cd $CURRDIR +yum install \ + git \ + cmake \ + build-essential \ + libboost-program-options-dev \ + libboost-filesystem-dev \ + libboost-graph-dev \ + libboost-system-dev \ + libboost-test-dev \ + libeigen3-dev \ + libsuitesparse-dev \ + libfreeimage-dev \ + libgoogle-glog-dev \ + libgflags-dev \ + libglew-dev \ + libcgal-dev + + +# Note: `yum install gflags` will not work, since the version is too old (2.1) +# Note: `yum install glog` will also not work, since the version is too old +# Cloning and building https://github.com/google/glog.git will also not work, due to linker issues. +yum -y install gflags-devel glog-devel + +cd $CURRDIR +# Using Eigen 3.3, not Eigen 3.4 +wget https://gitlab.com/libeigen/eigen/-/archive/3.3.9/eigen-3.3.9.tar.gz +tar -xvzf eigen-3.3.9.tar.gz +export EIGEN_DIR="$CURRDIR/eigen-3.3.9" + +# While Eigen is a header-only library, it still has to be built! +# Creates Eigen3Config.cmake from Eigen3Config.cmake.in +cd $EIGEN_DIR +mkdir build +cd build +cmake .. + +ls -ltrh "$EIGEN_DIR/cmake/" + +# ------ Install CERES solver ------ +cd $CURRDIR +yum install libeigen3-dev # was not in COLMAP instructions +yum install libatlas-base-dev libsuitesparse-dev +yum install libgoogle-glog-dev libgflags-dev # was not in COLMAP instructions + +git clone https://ceres-solver.googlesource.com/ceres-solver +cd ceres-solver +git checkout $(git describe --tags) # Checkout the latest release +mkdir build +cd build +cmake .. -DBUILD_TESTING=OFF \ + -DBUILD_EXAMPLES=OFF \ + -DEigen3_DIR="$EIGEN_DIR/cmake/" +make -j$(nproc) +make install + +echo "PYTHON_EXECUTABLE:${PYTHON_EXECUTABLE}" +echo "PYTHON_INCLUDE_DIR:${PYTHON_INCLUDE_DIR}" +echo "PYTHON_LIBRARY:${PYTHON_LIBRARY}" + +# ------ Fix broken dependencies ------ +cd $CURRDIR +# try new boost install +yum install libboost-all-dev +yum install git +yum install cmake +yum install build-essential +yum install libboost-program-options-dev +yum install libboost-filesystem-dev +yum install libboost-graph-dev +yum install libboost-system-dev +yum install libboost-test-dev +yum install libeigen3-dev +yum install libsuitesparse-dev +# yum install libfreeimage-dev +yum install libgoogle-glog-dev +yum install libgflags-dev +yum install libglew-dev +yum install libcgal-dev + +yum -y install freeimage + +# ------ Build FreeImage from source and install ------ +cd $CURRDIR +wget http://downloads.sourceforge.net/freeimage/FreeImage3180.zip +unzip FreeImage3180.zip +cd FreeImage +make +make install + +# Install GLEW +yum -y install glew-devel + +# ------ Build COLMAP ------ +cd $CURRDIR +git clone https://github.com/colmap/colmap.git +cd colmap +git checkout dev +mkdir build/ +cd build/ +CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DBoost_USE_STATIC_LIBS=ON \ + -DBOOST_ROOT=/usr/local \ + -DGUI_ENABLED=OFF \ + -DEIGEN3_INCLUDE_DIRS=$EIGEN_DIR + +if [ $ec -ne 0 ]; then + echo "Error:" + cat ./CMakeCache.txt + exit $ec +fi +set -e -x +make -j$(nproc) install + +# ------ Build pycolmap wheel ------ +cd /io/ +cat setup.py + +PLAT=manylinux2014_x86_64 +EIGEN3_INCLUDE_DIRS="$EIGEN_DIR" "${PYBIN}/python" setup.py bdist_wheel --plat-name=$PLAT + +# Bundle external shared libraries into the wheels +mkdir -p /io/wheelhouse +for whl in ./dist/*.whl; do + auditwheel repair "$whl" -w /io/wheelhouse/ --no-update-tags +done +ls -ltrh /io/wheelhouse/ diff --git a/package/build-wheels-macos.sh b/package/build-wheels-macos.sh new file mode 100755 index 00000000..49586c3c --- /dev/null +++ b/package/build-wheels-macos.sh @@ -0,0 +1,116 @@ +#!/bin/bash +set -x -e + +function retry { + local retries=$1 + shift + + local count=0 + until "$@"; do + exit=$? + wait=$((2 ** $count)) + count=$(($count + 1)) + if [ $count -lt $retries ]; then + echo "Retry $count/$retries exited $exit, retrying in $wait seconds..." + sleep $wait + else + echo "Retry $count/$retries exited $exit, no more retries left." + return $exit + fi + done + return 0 +} + +brew update +brew upgrade +brew install wget python cmake || true +# TODO: try without brew install of boost, but use version below +brew install git cmake boost eigen freeimage glog gflags suite-sparse ceres-solver glew cgal + +brew install llvm libomp + +brew info gcc +brew upgrade gcc +brew info gcc + +CURRDIR=$(pwd) +ls -ltrh $CURRDIR + +# Build Boost staticly +mkdir -p boost_build +cd boost_build +retry 3 wget https://boostorg.jfrog.io/artifactory/main/release/1.73.0/source/boost_1_73_0.tar.gz +tar xzf boost_1_73_0.tar.gz +cd boost_1_73_0 +./bootstrap.sh --prefix=$CURRDIR/boost_install --with-libraries=serialization,filesystem,thread,system,atomic,date_time,timer,chrono,program_options,regex clang-darwin +./b2 -j$(sysctl -n hw.logicalcpu) cxxflags="-fPIC" runtime-link=static variant=release link=static install + +echo "CURRDIR is: ${CURRDIR}" + +cd $CURRDIR +mkdir -p $CURRDIR/wheelhouse_unrepaired +mkdir -p $CURRDIR/wheelhouse + +PYTHON_LIBRARY=$(cd $(dirname $0); pwd)/libpython-not-needed-symbols-exported-by-interpreter +touch ${PYTHON_LIBRARY} + +declare -a PYTHON_VERS=( $1 ) + +git clone https://github.com/colmap/colmap.git + +for compiler in cc c++ gcc g++ clang clang++ +do + which $compiler + $compiler --version +done + +# Get the python version numbers only by splitting the string +PYBIN="/usr/local/opt/$PYTHON_VERS/bin" +PYTHONVER="$(basename $(dirname $PYBIN))" +export PATH=$PYBIN:/usr/local/bin:$PATH +echo "Python bin path: $PYBIN" +echo "Python version: $PYTHONVER" + +# Install `delocate` -- OSX equivalent of `auditwheel` +# see https://pypi.org/project/delocate/ for more details +cd $CURRDIR +"${PYBIN}/pip3" install delocate==0.10.0 + +ls -ltrh /usr/local +ls -ltrh /usr/local/opt + +cd $CURRDIR +cd colmap +git checkout dev +mkdir build +cd build +cmake .. -DGUI_ENABLED=OFF + +# examine exit code of last command +ec=$? +if [ $ec -ne 0 ]; then + echo "Error:" + cat ./CMakeCache.txt + exit $ec +fi +set -e -x + +NUM_LOGICAL_CPUS=$(sysctl -n hw.logicalcpu) +echo "Number of logical CPUs is: ${NUM_LOGICAL_CPUS}" +make -j $NUM_LOGICAL_CPUS install +sudo make install + +cd $CURRDIR +cat setup.py +# flags must be passed, to avoid the issue: `Unsupported compiler -- pybind11 requires C++11 support!` +# see https://github.com/quantumlib/qsim/issues/242 for more details +CC=/usr/local/opt/llvm/bin/clang CXX=/usr/local/opt/llvm/bin/clang++ LDFLAGS=-L/usr/local/opt/libomp/lib "${PYBIN}/python3" setup.py bdist_wheel +cp ./dist/*.whl $CURRDIR/wheelhouse_unrepaired + +# Bundle external shared libraries into the wheels +ls -ltrh $CURRDIR/wheelhouse_unrepaired/ +for whl in $CURRDIR/wheelhouse_unrepaired/*.whl; do + delocate-listdeps --all "$whl" + delocate-wheel -w "$CURRDIR/wheelhouse" -v "$whl" + rm $whl +done diff --git a/setup.py b/setup.py index 27f10724..e78a1ebf 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,17 @@ def run(self): def build_extension(self, ext): extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable] + cmake_args = [ + '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, + '-DPYTHON_EXECUTABLE=' + sys.executable, + '-DVERSION_INFO={}'.format(self.distribution.get_version()), + ] + eigen_dir = os.environ.get('EIGEN3_INCLUDE_DIRS') + if eigen_dir is not None: + cmake_args += ['-DEIGEN3_INCLUDE_DIRS={}'.format(eigen_dir)] + qt5_dir = os.environ.get("Qt5_DIR") + if qt5_dir is not None: + cmake_args += ['-DQt5_DIR={}'.format(qt5_dir)] cfg = 'Debug' if self.debug else 'Release' build_args = ['--config', cfg] diff --git a/two_view_geometry_estimation.cc b/two_view_geometry_estimation.cc new file mode 100644 index 00000000..945c8e6c --- /dev/null +++ b/two_view_geometry_estimation.cc @@ -0,0 +1,111 @@ + +#include +#include + +#include "colmap/base/camera.h" +#include "colmap/base/pose.h" +#include "colmap/estimators/two_view_geometry.h" +#include "colmap/optim/loransac.h" +#include "colmap/util/random.h" + +using namespace colmap; + +#include +#include +#include + +namespace py = pybind11; + +/* +* Estimate two-view geometry relationship for a calibrated camera. +* +* param points2D1, +* param points2D2, +* param camera_dict1 +* param camera_dict2 +* param max_error_px, +* param min_inlier_ratio, +* param min_num_trials, +* param max_num_trials, +* param confidence +* @return The most probable rotation matrix (3x3), translation vector (3x1), and two view +* configuration type (UNDEFINED, DEGENERATE, CALIBRATED, UNCALIBRATED, PLANAR +* PANORAMIC, PLANAR_OR_PANORAMIC, WATERMARK, or MULTIPLE). +* See https://github.com/colmap/colmap/blob/dev/src/estimators/two_view_geometry.h#L48 +*/ +py::dict two_view_geometry_estimation( + const std::vector points2D1, + const std::vector points2D2, + const py::dict camera_dict1, + const py::dict camera_dict2, + const double max_error_px, + const double min_inlier_ratio, + const int min_num_trials, + const int max_num_trials, + const double confidence +) { + SetPRNGSeed(0); + + // Check that both vectors have the same size. + assert(points2D1.size() == points2D2.size()); + + // Failure output dictionary. + py::dict failure_dict; + failure_dict["success"] = false; + + // Create cameras. + Camera camera1; + camera1.SetModelIdFromName(camera_dict1["model"].cast()); + camera1.SetWidth(camera_dict1["width"].cast()); + camera1.SetHeight(camera_dict1["height"].cast()); + camera1.SetParams(camera_dict1["params"].cast>()); + + Camera camera2; + camera2.SetModelIdFromName(camera_dict2["model"].cast()); + camera2.SetWidth(camera_dict2["width"].cast()); + camera2.SetHeight(camera_dict2["height"].cast()); + camera2.SetParams(camera_dict2["params"].cast>()); + + FeatureMatches matches; + matches.reserve(points2D1.size()); + + for (size_t i=0; i < points2D1.size(); i++) { + matches.emplace_back(i,i); + } + + TwoViewGeometry two_view_geometry; + TwoViewGeometry::Options two_view_geometry_options; + two_view_geometry_options.ransac_options.max_error = max_error_px; + two_view_geometry_options.ransac_options.min_inlier_ratio = min_inlier_ratio; + two_view_geometry_options.ransac_options.min_num_trials = min_num_trials; + two_view_geometry_options.ransac_options.max_num_trials = max_num_trials; + two_view_geometry_options.ransac_options.confidence = confidence; + + two_view_geometry.EstimateCalibrated(camera1, points2D1, camera2, points2D2, matches, two_view_geometry_options); + + // Success output dictionary. + py::dict success_dict; + + if (!two_view_geometry.EstimateRelativePose(camera1, points2D1, camera2, points2D2)) { + return failure_dict; + } else { + success_dict["success"] = true; + } + + const FeatureMatches inlier_matches = two_view_geometry.inlier_matches; + + // Convert vector to vector. + std::vector inliers(points2D1.size(), false); + for (auto m : inlier_matches) { + inliers[m.point2D_idx1] = true; + } + + // Recover data. + success_dict["configuration_type"] = two_view_geometry.config; + success_dict["qvec"] = two_view_geometry.qvec; + success_dict["tvec"] = two_view_geometry.tvec; + success_dict["num_inliers"] = inlier_matches.size(); + success_dict["inliers"] = inliers; + + return success_dict; +}