├── .clang-format ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .natvis └── eos.natvis ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── appveyor.yml ├── doc ├── CMakeLists.txt ├── Doxyfile.in └── namespaces.doxygen ├── examples ├── CMakeLists.txt ├── data │ ├── image_0010.png │ ├── image_0010.pts │ └── notes.txt ├── fit-model-ceres.cpp ├── fit-model-multi.cpp ├── fit-model-simple.cpp ├── fit-model.cpp └── generate-obj.cpp ├── include └── eos │ ├── core │ ├── Image.hpp │ ├── Landmark.hpp │ ├── LandmarkMapper.hpp │ ├── Mesh.hpp │ ├── Rect.hpp │ ├── image │ │ ├── Pixel.hpp │ │ ├── PixelTraits.hpp │ │ ├── opencv_interop.hpp │ │ └── utils.hpp │ ├── read_obj.hpp │ └── read_pts_landmarks.hpp │ ├── cpp17 │ ├── clamp.hpp │ ├── detail │ │ ├── akrzemi1_optional.hpp │ │ ├── akrzemi1_optional_serialization.hpp │ │ ├── mpark_variant.hpp │ │ └── mpark_variant_serialization.hpp │ ├── optional.hpp │ ├── optional_serialization.hpp │ ├── variant.hpp │ └── variant_serialization.hpp │ ├── fitting │ ├── FittingResult.hpp │ ├── RenderingParameters.hpp │ ├── affine_camera_estimation.hpp │ ├── blendshape_fitting.hpp │ ├── ceres_nonlinear.hpp │ ├── closest_edge_fitting.hpp │ ├── contour_correspondence.hpp │ ├── detail │ │ ├── glm_cerealisation.hpp │ │ └── nonlinear_camera_estimation_detail.hpp │ ├── fitting.hpp │ ├── linear_shape_fitting.hpp │ ├── multi_image_fitting.hpp │ ├── nonlinear_camera_estimation.hpp │ └── orthographic_camera_estimation_linear.hpp │ ├── morphablemodel │ ├── Blendshape.hpp │ ├── EdgeTopology.hpp │ ├── ExpressionModel.hpp │ ├── MorphableModel.hpp │ ├── PcaModel.hpp │ ├── coefficients.hpp │ └── io │ │ ├── cvssp.hpp │ │ ├── eigen_cerealisation.hpp │ │ └── mat_cerealisation.hpp │ ├── pca │ └── pca.hpp │ ├── render │ ├── FragmentShader.hpp │ ├── Rasterizer.hpp │ ├── SoftwareRenderer.hpp │ ├── Texture.hpp │ ├── VertexShader.hpp │ ├── detail │ │ ├── TriangleToRasterize.hpp │ │ ├── Vertex.hpp │ │ ├── render_affine_detail.hpp │ │ ├── render_detail.hpp │ │ ├── render_detail_utils.hpp │ │ ├── texture_extraction_detail.hpp │ │ └── texturing.hpp │ ├── draw_utils.hpp │ ├── render.hpp │ ├── render_affine.hpp │ ├── texture_extraction.hpp │ └── utils.hpp │ └── video │ ├── Keyframe.hpp │ └── keyframe_merging.hpp ├── initial_cache.cmake.template ├── matlab ├── +eos │ ├── +fitting │ │ ├── fit_shape_and_pose.m │ │ └── private │ │ │ └── fitting.cpp │ └── +render │ │ ├── extract_texture.m │ │ ├── private │ │ └── render.cpp │ │ └── render.m ├── CMakeLists.txt ├── demo.m └── include │ ├── mexplus_eigen.hpp │ ├── mexplus_eos_types.hpp │ └── mexplus_opencv.hpp ├── python ├── CMakeLists.txt ├── demo.py ├── generate-python-bindings.cpp ├── pybind11_Image.hpp ├── pybind11_optional.hpp └── pybind11_variant.hpp ├── setup.py ├── share ├── bfm2009_model_contours.json ├── bfm2017-1_bfm_nomouth_model_contours.json ├── expression_blendshapes_3448.bin ├── ibug_to_bfm2009.txt ├── ibug_to_bfm2017-1_bfm_nomouth.txt ├── ibug_to_sfm.txt ├── readme.txt ├── scripts │ ├── compute_edgestruct.m │ ├── convert-bfm2009-to-eos.py │ ├── convert-bfm2017-to-eos.py │ └── generate-edgestruct.py ├── sfm_3448_edge_topology.json ├── sfm_model_contours.json ├── sfm_reference.obj ├── sfm_reference_annotated.obj ├── sfm_reference_symmetry.txt └── sfm_shape_3448.bin └── utils ├── CMakeLists.txt └── scm-to-cereal.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: '-4' 4 | AlwaysBreakTemplateDeclarations: 'true' 5 | BreakBeforeBraces: Custom 6 | BraceWrapping: 7 | AfterClass: true 8 | AfterControlStatement: true 9 | AfterEnum: false 10 | AfterFunction: true 11 | AfterNamespace: false 12 | AfterObjCDeclaration: false 13 | AfterStruct: true 14 | AfterUnion: false 15 | BeforeCatch: false 16 | BeforeElse: false 17 | IndentBraces: false 18 | ColumnLimit: '110' 19 | Cpp11BracedListStyle: 'true' 20 | PointerAlignment: Left 21 | AllowShortFunctionsOnASingleLine: Empty 22 | IndentWidth: '4' 23 | Language: Cpp 24 | NamespaceIndentation: None 25 | SortIncludes: false 26 | Standard: Cpp11 27 | UseTab: Never 28 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 29 | 30 | ... 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.cpp text 8 | *.h text 9 | *.hxx text 10 | *.hpp text 11 | 12 | # Denote all files that are truly binary and should not be modified. 13 | *.png binary 14 | *.jpg binary 15 | 16 | # Standard to msysgit 17 | *.doc diff=astextplain 18 | *.DOC diff=astextplain 19 | *.docx diff=astextplain 20 | *.DOCX diff=astextplain 21 | *.dot diff=astextplain 22 | *.DOT diff=astextplain 23 | *.pdf diff=astextplain 24 | *.PDF diff=astextplain 25 | *.rtf diff=astextplain 26 | *.RTF diff=astextplain 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore (optional) configuration files with user-specific paths: 2 | initial_cache.cmake 3 | setup.cfg 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/pybind11"] 2 | path = 3rdparty/pybind11 3 | url = https://github.com/pybind/pybind11.git 4 | [submodule "3rdparty/nanoflann"] 5 | path = 3rdparty/nanoflann 6 | url = https://github.com/jlblancoc/nanoflann.git 7 | [submodule "3rdparty/glm"] 8 | path = 3rdparty/glm 9 | url = https://github.com/g-truc/glm.git 10 | [submodule "3rdparty/eigen3-nnls"] 11 | path = 3rdparty/eigen3-nnls 12 | url = https://github.com/hmatuschek/eigen3-nnls.git 13 | [submodule "3rdparty/mexplus"] 14 | path = 3rdparty/mexplus 15 | url = https://github.com/kyamagu/mexplus.git 16 | [submodule "3rdparty/cereal"] 17 | path = 3rdparty/cereal 18 | url = https://github.com/USCiLab/cereal.git 19 | [submodule "3rdparty/toml11"] 20 | path = 3rdparty/toml11 21 | url = https://github.com/ToruNiina/toml11.git 22 | [submodule "3rdparty/eigen"] 23 | path = 3rdparty/eigen 24 | url = https://github.com/eigenteam/eigen-git-mirror.git 25 | -------------------------------------------------------------------------------- /.natvis/eos.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | UINT8 17 | 18 | 19 | INT32 20 | 21 | 22 | FLOAT32 23 | 24 | 25 | FLOAT64 26 | 27 | 28 | 29 | 1 30 | 31 | 32 | width_ 33 | height_ 34 | 35 | ($T1*)(&data_[0]) 36 | 37 | row_stride 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | UINT8 46 | 47 | 48 | INT32 49 | 50 | 51 | FLOAT32 52 | 53 | 54 | FLOAT64 55 | 56 | 57 | $T2 58 | 59 | width_ 60 | height_ 61 | 62 | ($T1*)(&data_[0]) 63 | 64 | row_stride 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty # Use 'trusty' unless specified otherwise 2 | os: linux # Use Linux unless specified otherwise 3 | sudo: false 4 | 5 | language: cpp 6 | 7 | matrix: 8 | fast_finish: true # Don't wait for the builds that are allowed to fail 9 | include: 10 | - env: C_COMPILER=gcc-5 CXX_COMPILER=g++-5 GENERATOR_NAME="Unix Makefiles" 11 | addons: { apt: { packages: ["g++-5"], sources: ["ubuntu-toolchain-r-test"] } } 12 | 13 | - env: C_COMPILER=gcc-6 CXX_COMPILER=g++-6 GENERATOR_NAME="Unix Makefiles" 14 | addons: { apt: { packages: ["g++-6"], sources: ["ubuntu-toolchain-r-test"] } } 15 | 16 | - env: C_COMPILER=gcc-7 CXX_COMPILER=g++-7 GENERATOR_NAME="Unix Makefiles" 17 | addons: { apt: { packages: ["g++-7"], sources: ["ubuntu-toolchain-r-test"] } } 18 | 19 | - env: C_COMPILER=gcc-8 CXX_COMPILER=g++-8 GENERATOR_NAME="Unix Makefiles" 20 | addons: { apt: { packages: ["g++-8"], sources: ["ubuntu-toolchain-r-test"] } } 21 | 22 | - env: C_COMPILER=clang-5.0 CXX_COMPILER=clang++-5.0 GENERATOR_NAME="Unix Makefiles" 23 | addons: { apt: { packages: ["g++-7", "clang-5.0"], sources: ["llvm-toolchain-trusty-5.0", "ubuntu-toolchain-r-test"] } } 24 | # g++-7 is needed here - otherwise g++-4.8 libstdc++ is used. 25 | 26 | - env: C_COMPILER=clang-5.0 CXX_COMPILER=clang++-5.0 GENERATOR_NAME="Unix Makefiles" 27 | addons: { apt: { packages: ["g++-8", "clang-5.0"], sources: ["llvm-toolchain-trusty-5.0", "ubuntu-toolchain-r-test"] } } 28 | 29 | - env: C_COMPILER=clang-6.0 CXX_COMPILER=clang++-6.0 GENERATOR_NAME="Unix Makefiles" 30 | addons: { apt: { packages: ["g++-8", "clang-6.0"], sources: ["llvm-toolchain-trusty-6.0", "ubuntu-toolchain-r-test"] } } 31 | 32 | - os: osx 33 | env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 34 | osx_image: xcode9.2 35 | 36 | - os: osx 37 | env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 38 | osx_image: xcode9.3 39 | 40 | - os: osx 41 | env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 42 | osx_image: xcode9.4 43 | 44 | - os: osx 45 | env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 46 | osx_image: xcode10 47 | 48 | allow_failures: 49 | # These are additional combinations that we test but allow to fail, as they're either older compilers (g++-5) or 50 | # uncommon combinations that would need some workarounds or updates to work. 51 | - env: C_COMPILER=gcc-5 CXX_COMPILER=g++-5 GENERATOR_NAME="Unix Makefiles" 52 | - env: C_COMPILER=clang-5.0 CXX_COMPILER=clang++-5.0 GENERATOR_NAME="Unix Makefiles" 53 | - env: C_COMPILER=clang-6.0 CXX_COMPILER=clang++-6.0 GENERATOR_NAME="Unix Makefiles" 54 | - env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 55 | osx_image: xcode9.2 56 | - env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 57 | osx_image: xcode9.3 58 | - env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 59 | osx_image: xcode9.4 60 | - env: C_COMPILER=clang CXX_COMPILER=clang++ GENERATOR_NAME="Xcode" 61 | osx_image: xcode10 62 | 63 | install: 64 | - | 65 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 66 | wget https://cmake.org/files/v3.8/cmake-3.8.2-Linux-x86_64.tar.gz 67 | CMAKE_DIR=${TRAVIS_BUILD_DIR}/cmake && mkdir ${CMAKE_DIR} 68 | tar -xvzf cmake-3.8.2-Linux-x86_64.tar.gz --strip-components=1 -C ${CMAKE_DIR} # extract from inside the top-level directory to CMAKE_DIR 69 | export PATH=${CMAKE_DIR}/bin:${PATH} 70 | pyenv global system 3.6 # Workaround for travis-ci/issues/8363 71 | sudo apt-get update -q 72 | sudo apt-get install libboost-all-dev libopencv-dev -y 73 | else 74 | brew install cmake || brew upgrade cmake 75 | brew install boost 76 | /usr/bin/yes | pip2 uninstall numpy # see: travis-ci/issues/6688 77 | brew install opencv 78 | fi 79 | - cmake --version 80 | 81 | before_script: 82 | - mkdir build 83 | - cd build 84 | - cmake -G "${GENERATOR_NAME}" -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${CXX_COMPILER} -DCMAKE_INSTALL_PREFIX=../install -DEOS_BUILD_EXAMPLES=on -DEOS_BUILD_UTILS=on -DEOS_GENERATE_PYTHON_BINDINGS=on -DPYTHON_EXECUTABLE=`which python3.6` .. 85 | 86 | script: 87 | - cmake --build . --config Release 88 | - cmake --build . --target install --config Release 89 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | global-include CMakeLists.txt *.cmake 3 | exclude initial_cache.cmake 4 | recursive-include 3rdparty/cereal/include * 5 | recursive-include 3rdparty/eigen/Eigen * 6 | recursive-include 3rdparty/eigen3-nnls/src *.h 7 | recursive-include 3rdparty/glm/glm * 8 | recursive-include 3rdparty/nanoflann/include * 9 | recursive-include 3rdparty/toml11 *.hpp 10 | recursive-include 3rdparty/pybind11 * 11 | recursive-include include * 12 | recursive-include python * 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | home: C:\projects 3 | cmake: C:\projects\cmake-3.10.1-win64-x64\bin\cmake.exe 4 | 5 | version: '{branch}-{build}' 6 | 7 | os: Visual Studio 2017 8 | 9 | install: 10 | # Get all submodules, since AppVeyor doesn't clone with '--recursive': 11 | - cmd: git submodule update --init --recursive # inside the repository directory 12 | # Get a recent CMake and our dependencies (Eigen, OpenCV, Boost): 13 | - cmd: cd %home% 14 | - ps: wget https://cmake.org/files/v3.10/cmake-3.10.1-win64-x64.zip -OutFile cmake.zip 15 | - cmd: 7z x cmake.zip -o"C:\projects" -y > nul # will extract to cmake-3.10.1-win64-x64\ 16 | - cmd: '%cmake% --version' 17 | # Get OpenCV (my pre-built binaries of 3.3.0 for VS2017.3): 18 | - ps: wget "https://drive.google.com/uc?export=download&id=1SZpZ4AnEeTKMqFE-rHWuO6XyrzbaiSIx" -OutFile opencv-3.4.0_vc19.12.25834_release.rar 19 | - cmd: mkdir C:\projects\opencv 20 | - cmd: 7z x opencv-3.4.0_vc19.12.25834_release.rar -o"C:\projects\opencv" -y > nul 21 | # Using Boost 1.64.0 from AppVeyor (C:\Libraries\boost_1_64_0) 22 | 23 | before_build: # We're still in %home% 24 | - cmd: mkdir build 25 | - cmd: cd build 26 | - cmd: '%cmake% -G "Visual Studio 15 Win64" -DOpenCV_DIR=C:\projects\opencv -DBOOST_ROOT=C:\Libraries\boost_1_64_0 -DBOOST_LIBRARYDIR=C:\Libraries\boost_1_64_0\lib64-msvc-14.0 -DCMAKE_INSTALL_PREFIX=..\install -DEOS_BUILD_EXAMPLES=on -DEOS_BUILD_UTILS=on -DEOS_GENERATE_PYTHON_BINDINGS=on -DPYTHON_EXECUTABLE:path=C:\Python36-x64\python.exe ..\eos' 27 | 28 | build: 29 | project: C:\projects\build\eos.sln 30 | 31 | after_build: # We're still in %home%\build 32 | - cmd: '%cmake% --build . --target INSTALL --config Release' 33 | 34 | configuration: 35 | - Release 36 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen REQUIRED) 2 | 3 | set(doxyfile_in ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 4 | set(doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 5 | 6 | configure_file(${doxyfile_in} ${doxyfile} @ONLY) 7 | 8 | add_custom_target(doc 9 | COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile} 10 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 11 | COMMENT "Generating API documentation with Doxygen" 12 | VERBATIM 13 | ) 14 | 15 | install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION doc) 16 | -------------------------------------------------------------------------------- /doc/Doxyfile.in: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = "@CMAKE_PROJECT_NAME@" 2 | PROJECT_NUMBER = @eos_VERSION_MAJOR@.@eos_VERSION_MINOR@.@eos_VERSION_PATCH@ 3 | STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ 4 | INPUT = @PROJECT_SOURCE_DIR@/README.md \ 5 | @PROJECT_SOURCE_DIR@/doc/namespaces.doxygen \ 6 | @PROJECT_SOURCE_DIR@/include 7 | RECURSIVE = YES 8 | EXCLUDE_PATTERNS = */detail/* 9 | 10 | USE_MDFILE_AS_MAINPAGE = README.md 11 | 12 | USE_MATHJAX = YES 13 | MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML 14 | -------------------------------------------------------------------------------- /doc/namespaces.doxygen: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace eos 3 | * @brief Namespace containing all of eos's 3D model fitting functionality. 4 | */ 5 | 6 | /** 7 | * @namespace eos::core 8 | * @brief Essential functions and classes to work with 3D face models, meshes and landmarks. 9 | */ 10 | 11 | /** 12 | * @namespace eos::fitting 13 | * @brief Pose and shape fitting of a 3D Morphable Model. 14 | */ 15 | 16 | /** 17 | * @namespace eos::morphablemodel 18 | * @brief Functionality to represent a Morphable Model, its PCA models, and functions to load models and blendshapes. 19 | */ 20 | 21 | /** 22 | * @namespace eos::pca 23 | * @brief PCA and functionality to build statistical models. 24 | */ 25 | 26 | /** 27 | * @namespace eos::render 28 | * @brief Software rendering and texture extraction functionality. 29 | */ 30 | 31 | /** 32 | * @namespace eos::video 33 | * @brief Video keyframe extraction and fusion. 34 | */ 35 | 36 | /** 37 | * @namespace eos::cpp17 38 | * @brief Replacement types and functions for C++17 standard library functionality for compilers who don't have them (for example AppleClang). 39 | */ 40 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The examples need a few additional dependencies (e.g. boost filesystem, program_options, and OpenCV highgui): 2 | 3 | # Check installed version in order to include the correct OpenCV libraries: 4 | # First call find_package without a version to find any OpenCV. OpenCV_VERSION_MAJOR is then defined. 5 | find_package(OpenCV REQUIRED core) 6 | if("${OpenCV_VERSION_MAJOR}$" EQUAL 2) 7 | message(STATUS "OpenCV 2.x detected") 8 | find_package(OpenCV 2.4.3 REQUIRED core imgproc highgui) 9 | elseif("${OpenCV_VERSION_MAJOR}$" EQUAL 3) 10 | message(STATUS "OpenCV 3.x detected - including imgcodecs for compatibility") 11 | find_package(OpenCV 3 REQUIRED core imgproc imgcodecs) 12 | endif() 13 | # This allows us to compile in RelWithDebInfo. It'll use the Release-version of OpenCV: 14 | set_target_properties(${OpenCV_LIBS} PROPERTIES MAP_IMPORTED_CONFIG_RELWITHDEBINFO RELEASE) 15 | 16 | if(MSVC) 17 | # The standard find_package for boost on Win finds the dynamic libs, so for dynamic linking to boost we need to #define: 18 | add_definitions(-DBOOST_ALL_NO_LIB) # Don't use the automatic library linking by boost with VS (#pragma ...). Instead, we specify everything here in cmake. 19 | add_definitions(-DBOOST_ALL_DYN_LINK) # Link against the dynamic boost lib - needs to match with the version that find_package finds. 20 | add_definitions(-D_HAS_AUTO_PTR_ETC) # Boost 1.65.1 still does not work with VS C++17 mode, this is the workaround 21 | endif() 22 | find_package(Boost 1.50.0 COMPONENTS system filesystem program_options REQUIRED) 23 | message(STATUS "Boost found at ${Boost_INCLUDE_DIRS}") 24 | 25 | # Simple model fitting (orthographic camera & shape to landmarks) example: 26 | add_executable(fit-model-simple fit-model-simple.cpp) 27 | target_link_libraries(fit-model-simple eos ${OpenCV_LIBS} ${Boost_LIBRARIES}) 28 | target_link_libraries(fit-model-simple "$<$:-pthread>$<$:-pthreads>") 29 | target_include_directories(fit-model-simple PUBLIC ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 30 | 31 | # Model fitting example that fits orthographic camera, shape, blendshapes, and contours: 32 | add_executable(fit-model fit-model.cpp) 33 | target_link_libraries(fit-model eos ${OpenCV_LIBS} ${Boost_LIBRARIES}) 34 | target_link_libraries(fit-model "$<$:-pthread>$<$:-pthreads>") 35 | target_include_directories(fit-model PUBLIC ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 36 | 37 | # Model fitting example that fits orthographic camera, shape, blendshapes, and contours to multiple images: 38 | add_executable(fit-model-multi fit-model-multi.cpp) 39 | target_link_libraries(fit-model-multi eos ${OpenCV_LIBS} ${Boost_LIBRARIES}) 40 | target_link_libraries(fit-model-multi "$<$:-pthread>$<$:-pthreads>") 41 | target_include_directories(fit-model-multi PUBLIC ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 42 | 43 | # Generate random samples from the model: 44 | add_executable(generate-obj generate-obj.cpp) 45 | target_link_libraries(generate-obj eos ${OpenCV_LIBS} ${Boost_LIBRARIES}) 46 | target_include_directories(generate-obj PUBLIC ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 47 | 48 | # Install these targets: 49 | install(TARGETS fit-model-simple DESTINATION bin) 50 | install(TARGETS fit-model DESTINATION bin) 51 | install(TARGETS fit-model-multi DESTINATION bin) 52 | install(TARGETS generate-obj DESTINATION bin) 53 | install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data DESTINATION bin) 54 | 55 | 56 | if(EOS_BUILD_CERES_EXAMPLE) 57 | # Find Ceres, for the fit-model-ceres app: 58 | find_package(Ceres REQUIRED) 59 | message(STATUS "Ceres locations: Headers: ${CERES_INCLUDE_DIRS} Library: ${CERES_LIBRARIES}") 60 | 61 | # Single and multi-image non-linear model fitting with Ceres example: 62 | add_executable(fit-model-ceres fit-model-ceres.cpp) 63 | target_link_libraries(fit-model-ceres eos ${CERES_LIBRARIES} ${OpenCV_LIBS} ${Boost_LIBRARIES}) 64 | target_include_directories(fit-model-ceres PUBLIC ${CERES_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 65 | install(TARGETS fit-model-ceres DESTINATION bin) 66 | endif() 67 | -------------------------------------------------------------------------------- /examples/data/image_0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVSSP/eos/f8b235bf8da1eef947a62dc09e23ecde2c9c0f3b/examples/data/image_0010.png -------------------------------------------------------------------------------- /examples/data/image_0010.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 611.284152 272.773913 5 | 607.899810 304.319120 6 | 613.094672 339.255590 7 | 622.808910 372.005502 8 | 632.669412 404.678824 9 | 643.511765 432.387059 10 | 651.944706 460.095294 11 | 665.273062 490.435795 12 | 695.836418 505.268304 13 | 732.882854 507.601124 14 | 771.140919 496.682178 15 | 814.580000 468.528235 16 | 852.378512 442.776782 17 | 879.780380 411.071469 18 | 894.313442 365.675605 19 | 905.385571 315.920470 20 | 912.766990 265.622269 21 | 608.920127 214.000560 22 | 624.612439 205.921886 23 | 641.092550 213.028614 24 | 656.643518 218.496370 25 | 675.118172 229.895972 26 | 714.507903 232.709448 27 | 746.296501 221.279247 28 | 780.463683 216.098001 29 | 814.843700 219.577695 30 | 843.144930 231.216340 31 | 691.116694 258.293884 32 | 686.687460 284.649159 33 | 678.837382 314.952208 34 | 671.575170 341.518858 35 | 662.787059 357.695294 36 | 672.577890 363.872007 37 | 684.099935 368.436761 38 | 701.177972 366.977129 39 | 716.657204 364.432781 40 | 629.308705 253.419965 41 | 641.102353 239.634118 42 | 656.763529 242.043529 43 | 675.896682 261.234513 44 | 657.320778 266.311140 45 | 639.427321 264.662791 46 | 746.963287 268.769913 47 | 763.151112 249.645230 48 | 792.895294 251.681176 49 | 810.965882 264.932941 50 | 793.647370 274.870034 51 | 770.089168 277.392535 52 | 650.466760 402.919129 53 | 662.802218 396.521655 54 | 674.655327 393.178631 55 | 685.548727 399.199281 56 | 699.727862 394.238444 57 | 720.617079 403.327949 58 | 762.033016 412.238881 59 | 727.622851 431.747041 60 | 706.539636 437.383098 61 | 691.346572 438.212545 62 | 679.045609 436.837854 63 | 665.204535 428.934293 64 | 657.444159 406.740225 65 | 677.532790 409.255983 66 | 688.027122 410.142694 67 | 701.031914 411.571227 68 | 750.088005 413.800864 69 | 701.031914 411.571227 70 | 688.027122 410.142694 71 | 677.532790 409.255983 72 | } -------------------------------------------------------------------------------- /examples/data/notes.txt: -------------------------------------------------------------------------------- 1 | The data in this folder was downloaded from http://ibug.doc.ic.ac.uk/resources/300-W/ on 15 December 2014. 2 | -------------------------------------------------------------------------------- /examples/generate-obj.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: examples/generate-obj.cpp 5 | * 6 | * Copyright 2016 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #include "eos/core/Image.hpp" 21 | #include "eos/core/image/opencv_interop.hpp" 22 | #include "eos/morphablemodel/MorphableModel.hpp" 23 | #include "eos/render/render.hpp" 24 | #include "eos/cpp17/optional.hpp" 25 | 26 | #include "glm/gtc/matrix_transform.hpp" 27 | 28 | #include "boost/filesystem.hpp" 29 | #include "boost/program_options.hpp" 30 | 31 | #include "opencv2/core/core.hpp" 32 | #include "opencv2/highgui/highgui.hpp" 33 | 34 | #include 35 | 36 | using namespace eos; 37 | namespace po = boost::program_options; 38 | namespace fs = boost::filesystem; 39 | using std::cout; 40 | using std::endl; 41 | using std::vector; 42 | using std::string; 43 | 44 | /** 45 | * This app generates random samples from the model and stores them as obj file 46 | * as well as outputs a frontal rendering of the sample. 47 | * 48 | * A list of shape and/or colour coefficients can be specified. Any coefficient 49 | * not specified will be set to zero. 50 | */ 51 | int main(int argc, char* argv[]) 52 | { 53 | string model_file, output_file; 54 | vector shape_coefficients, color_coefficients; 55 | 56 | try 57 | { 58 | po::options_description desc("Allowed options"); 59 | // clang-format off 60 | desc.add_options() 61 | ("help", "produce help message") 62 | ("model", po::value(&model_file)->required(), "an eos .bin Morphable Model file") 63 | ("shape-coeffs", po::value>(&shape_coefficients)->multitoken(), 64 | "optional parameter list of shape coefficients. All not specified will be set to zero. E.g.: " 65 | "'--shape-coeffs 0.0 1.5'. If omitted, the mean is used.") 66 | ("color-coeffs", po::value>(&color_coefficients)->multitoken(), 67 | "optional parameter list of colour coefficients. All not specified will be set to zero. E.g.: " 68 | "'--colour-coeffs 0.0 1.5'. If omitted, the mean is used.") 69 | ("output", po::value(&output_file)->default_value("output.obj"), 70 | "name of the output obj file (including .obj). Can be a full path."); 71 | // clang-format on 72 | po::variables_map vm; 73 | // disabling short options to allow negative values for the coefficients, e.g. '--shape-coeffs 0.0 -1.5' 74 | po::store( 75 | po::parse_command_line(argc, argv, desc, 76 | po::command_line_style::unix_style ^ po::command_line_style::allow_short), 77 | vm); 78 | if (vm.count("help")) 79 | { 80 | cout << "Usage: generate-obj [options]" << endl; 81 | cout << desc; 82 | return EXIT_SUCCESS; 83 | } 84 | po::notify(vm); 85 | } catch (const po::error& e) 86 | { 87 | cout << "Error while parsing command-line arguments: " << e.what() << endl; 88 | cout << "Use --help to display a list of options." << endl; 89 | return EXIT_FAILURE; 90 | } 91 | 92 | morphablemodel::MorphableModel morphable_model = morphablemodel::load_model(model_file); 93 | 94 | if (shape_coefficients.size() < morphable_model.get_shape_model().get_num_principal_components()) 95 | { 96 | shape_coefficients.resize(morphable_model.get_shape_model().get_num_principal_components()); 97 | } 98 | 99 | if (color_coefficients.size() < morphable_model.get_color_model().get_num_principal_components()) 100 | { 101 | color_coefficients.resize(morphable_model.get_color_model().get_num_principal_components()); 102 | } 103 | 104 | const core::Mesh sample_mesh = morphable_model.draw_sample( 105 | shape_coefficients, color_coefficients); // if one of the two vectors is empty, it uses get_mean() 106 | 107 | core::write_obj(sample_mesh, output_file); 108 | core::Image4u rendering; 109 | std::tie(rendering, std::ignore) = 110 | render::render(sample_mesh, glm::mat4x4(1.0f), glm::ortho(-130.0f, 130.0f, -130.0f, 130.0f), 512, 512, 111 | cpp17::nullopt, true, false, false); 112 | fs::path filename_rendering(output_file); 113 | filename_rendering.replace_extension(".png"); 114 | cv::imwrite(filename_rendering.string(), core::to_mat(rendering)); 115 | 116 | cout << "Wrote the generated obj and a rendering to files with basename " << output_file << "." << endl; 117 | 118 | return EXIT_SUCCESS; 119 | } 120 | -------------------------------------------------------------------------------- /include/eos/core/Image.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/Image.hpp 5 | * 6 | * Copyright 2017, 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_IMAGE_HPP 23 | #define EOS_IMAGE_HPP 24 | 25 | #include "eos/core/image/Pixel.hpp" 26 | #include "eos/core/image/PixelTraits.hpp" 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | namespace eos { 33 | namespace core { 34 | 35 | /** 36 | * @brief Class to represent images. 37 | * 38 | * The class currently uses row-major storage order. 39 | * 40 | * Some of the code is inspired by the Selene library (https://github.com/kmhofmann/selene, MIT license). 41 | */ 42 | template 43 | class Image 44 | { 45 | public: 46 | using pixel_type = PixelType; 47 | 48 | Image() = default; 49 | 50 | // Initialises with all zeros. This is a bit of an overhead. We probably have to use unique_ptr or a raw 51 | // pointer to get rid of this, as a vector can't have "uninitialised" (yet existing) elements. 52 | Image(int height, int width) 53 | : height_(height), width_(width), row_stride(PixelTraits::num_bytes * width) 54 | { 55 | // data_.reserve(row_stride * height); // just reserves, doesn't put any elements into the vector 56 | data_.resize(row_stride * height); 57 | }; 58 | 59 | int height() const noexcept 60 | { 61 | return height_; 62 | }; 63 | 64 | int width() const noexcept 65 | { 66 | return width_; 67 | }; 68 | 69 | PixelType& operator()(int y, int x) noexcept 70 | { 71 | assert(data_.size() > 0); // byte_ptr() checks this but let's put the assert here too, because this 72 | // function is public-facing. 73 | assert(y >= 0 && y < height_); 74 | assert(x >= 0 && x < width_); 75 | return *data(y, x); 76 | }; 77 | const PixelType& operator()(int y, int x) const noexcept 78 | { 79 | assert(data_.size() > 0); // byte_ptr() checks this but let's put the assert here too, because this 80 | // function is public-facing. 81 | assert(y >= 0 && y < height_); 82 | assert(x >= 0 && x < width_); 83 | return *data(y, x); 84 | }; 85 | 86 | private: 87 | int height_ = 0; 88 | int width_ = 0; 89 | 90 | int row_stride = 0; // In bytes. >1 means it's a row-major image, 1 would mean a col-major image. 91 | 92 | std::vector data_; // Storage for the image data as bytes 93 | 94 | // Return a std::ptr_diff? 95 | int compute_data_offset(int y, int x) const noexcept 96 | { 97 | return row_stride * y + PixelTraits::num_bytes * x; 98 | }; 99 | 100 | std::uint8_t* byte_ptr(int y, int x) noexcept 101 | { 102 | assert(data_.size() > 0); 103 | return &data_[0] + compute_data_offset(y, x); 104 | }; 105 | const std::uint8_t* byte_ptr(int y, int x) const noexcept 106 | { 107 | assert(data_.size() > 0); 108 | return &data_[0] + compute_data_offset(y, x); 109 | }; 110 | 111 | PixelType* data(int y, int x) noexcept 112 | { 113 | return reinterpret_cast(byte_ptr(y, x)); 114 | }; 115 | const PixelType* data(int y, int x) const noexcept 116 | { 117 | return reinterpret_cast(byte_ptr(y, x)); 118 | }; 119 | }; 120 | 121 | // Define type aliases for the most commonly used image types: 122 | using Image1u = Image; 123 | using Image3u = Image>; 124 | using Image4u = Image>; 125 | using Image1f = Image; 126 | using Image3f = Image>; 127 | using Image1d = Image; 128 | 129 | } /* namespace core */ 130 | } /* namespace eos */ 131 | 132 | #endif /* EOS_IMAGE_HPP */ 133 | -------------------------------------------------------------------------------- /include/eos/core/Landmark.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/Landmark.hpp 5 | * 6 | * Copyright 2014, 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_LANDMARK_HPP 23 | #define EOS_LANDMARK_HPP 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace eos { 30 | namespace core { 31 | 32 | /** 33 | * @brief Representation of a landmark, consisting of a landmark name and 34 | * coordinates of the given type. Usually, the type would be \c Eigen::Vector2f. 35 | */ 36 | template 37 | struct Landmark 38 | { 39 | std::string name; ///< Name of the landmark, often used as identifier. 40 | LandmarkType coordinates; ///< The position or coordinates of the landmark. 41 | }; 42 | 43 | /** 44 | * @brief A trivial collection of landmarks that belong together. 45 | */ 46 | template 47 | using LandmarkCollection = std::vector>; 48 | 49 | /** 50 | * @brief Shorthand for a 2D floating point landmark type. 51 | */ 52 | // using Landmark2f = Landmark>; 53 | 54 | /** 55 | * @brief Alias for the 2D landmark point type. 56 | */ 57 | // using Point2f = Eigen::Vector2f; 58 | // using Point2f = std::array; 59 | 60 | /** 61 | * @brief Filters the given LandmarkCollection and returns a new LandmarkCollection 62 | * containing all landmarks whose name matches the one given by \p filter. 63 | * 64 | * @param[in] landmarks The input LandmarkCollection to be filtered. 65 | * @param[in] filter A list of landmark names (identifiers) by which the given LandmarkCollection is filtered. 66 | * @return A new, filtered LandmarkCollection. 67 | */ 68 | template 69 | LandmarkCollection filter(const LandmarkCollection& landmarks, const std::vector& filter) 70 | { 71 | LandmarkCollection filtered_landmarks; 72 | using std::begin; 73 | using std::end; 74 | std::copy_if( 75 | begin(landmarks), end(landmarks), std::back_inserter(filtered_landmarks), 76 | [&](const Landmark& lm) { return std::find(begin(filter), end(filter), lm.name) != end(filter); }); 77 | return filtered_landmarks; 78 | }; 79 | 80 | } /* namespace core */ 81 | } /* namespace eos */ 82 | 83 | #endif /* EOS_LANDMARK_HPP */ 84 | -------------------------------------------------------------------------------- /include/eos/core/LandmarkMapper.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/LandmarkMapper.hpp 5 | * 6 | * Copyright 2014-2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef LANDMARKMAPPER_HPP_ 23 | #define LANDMARKMAPPER_HPP_ 24 | 25 | #include "eos/cpp17/optional.hpp" 26 | 27 | #include "toml.hpp" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | namespace eos { 34 | namespace core { 35 | 36 | /** 37 | * @brief Represents a mapping from one kind of landmarks 38 | * to a different format (e.g. model vertices). 39 | * 40 | * When fitting the 3D model to an image, a correspondence must 41 | * be known from the 2D image landmarks to 3D vertex points in 42 | * the Morphable Model. The 3D model defines all its points in 43 | * the form of vertex ids. 44 | * These mappings are stored in a file, see the \c share/ folder for 45 | * an example for mapping 2D ibug landmarks to 3D model vertex indices. 46 | * 47 | * The LandmarkMapper thus has two main use cases: 48 | * - Mapping 2D landmark points to 3D vertices 49 | * - Converting one set of 2D landmarks into another set of 2D 50 | * landmarks with different identifiers. 51 | */ 52 | class LandmarkMapper 53 | { 54 | public: 55 | /** 56 | * @brief Constructs a new landmark mapper that performs an identity 57 | * mapping, that is, its output is the same as the input. 58 | */ 59 | LandmarkMapper() = default; 60 | 61 | /** 62 | * @brief Constructs a new landmark mapper from a file containing 63 | * mappings from one set of landmark identifiers to another. 64 | * 65 | * In case the file contains no mappings, a landmark mapper 66 | * that performs an identity mapping is constructed. 67 | * 68 | * @param[in] filename A file with landmark mappings. 69 | * @throws runtime_error or toml::exception if there is an error loading the mappings from the file. 70 | */ 71 | LandmarkMapper(std::string filename) 72 | { 73 | // parse() as well as extracting the data can throw std::runtime error or toml::exception, 74 | // so ideally you'd want to call this c'tor within a try-catch. 75 | const auto data = toml::parse(filename); 76 | 77 | const auto mappings_table = toml::get(data.at("landmark_mappings")); 78 | // The key in the config is always a string. The value however may be a string or an integer, so we 79 | // check for that and convert to a string. 80 | for (const auto& mapping : mappings_table) 81 | { 82 | std::string f = mapping.first; 83 | std::string value; 84 | switch (mapping.second.type()) 85 | { 86 | case toml::value_t::Integer: 87 | value = std::to_string(toml::get(mapping.second)); 88 | break; 89 | case toml::value_t::String: 90 | value = toml::get(mapping.second); 91 | break; 92 | default: 93 | throw std::runtime_error("unexpected type : " + toml::stringize(mapping.second.type())); 94 | } 95 | 96 | landmark_mappings.emplace(mapping.first, value); 97 | } 98 | }; 99 | 100 | /** 101 | * @brief Constructs a new landmark mapper from a set of existing mappings. 102 | * 103 | * @param[in] mappings A set of landmark mappings. 104 | */ 105 | LandmarkMapper(const std::unordered_map& mappings) : landmark_mappings(mappings) 106 | {}; 107 | 108 | /** 109 | * @brief Converts the given landmark name to the mapped name. 110 | * 111 | * In the case the mapper is empty (no mappings defined at all), the mapper will perform 112 | * an identity mapping and return the \p landmark_name that was input. 113 | * 114 | * @param[in] landmark_name A landmark name to convert. 115 | * @return The mapped landmark name if a mapping exists, an empty optional otherwise. 116 | */ 117 | cpp17::optional convert(std::string landmark_name) const 118 | { 119 | if (landmark_mappings.empty()) 120 | { 121 | // perform identity mapping, i.e. return the input 122 | return landmark_name; 123 | } 124 | 125 | const auto& converted_landmark = landmark_mappings.find(landmark_name); 126 | if (converted_landmark != std::end(landmark_mappings)) 127 | { 128 | // landmark mapping found, return it 129 | return converted_landmark->second; 130 | } else 131 | { // landmark_name does not match the key of any element in the map 132 | return cpp17::nullopt; 133 | } 134 | }; 135 | 136 | /** 137 | * @brief Returns the number of loaded landmark mappings. 138 | * 139 | * @return The number of landmark mappings. 140 | */ 141 | auto num_mappings() const { 142 | return landmark_mappings.size(); 143 | }; 144 | 145 | /** 146 | * @brief Returns the mappings held by this mapper. 147 | * 148 | * @return All mappings contained in the mapper. 149 | */ 150 | const auto& get_mappings() const { 151 | return landmark_mappings; 152 | }; 153 | 154 | private: 155 | std::unordered_map landmark_mappings; ///< Mapping from one 156 | ///< landmark name to a name 157 | ///< in a different format. 158 | }; 159 | 160 | } /* namespace core */ 161 | } /* namespace eos */ 162 | 163 | #endif /* LANDMARKMAPPER_HPP_ */ 164 | -------------------------------------------------------------------------------- /include/eos/core/Mesh.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/Mesh.hpp 5 | * 6 | * Copyright 2014, 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_MESH_HPP_ 23 | #define EOS_MESH_HPP_ 24 | 25 | #include "Eigen/Core" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace eos { 34 | namespace core { 35 | 36 | /** 37 | * @brief This class represents a 3D mesh consisting of vertices, vertex colour 38 | * information and texture coordinates. 39 | * 40 | * Additionally it stores the indices that specify which vertices 41 | * to use to generate the triangle mesh out of the vertices. 42 | */ 43 | struct Mesh 44 | { 45 | std::vector vertices; ///< 3D vertex positions. 46 | std::vector colors; ///< Colour information for each vertex. Expected to be in RGB order. 47 | std::vector texcoords; ///< Texture coordinates for each vertex. 48 | 49 | std::vector> tvi; ///< Triangle vertex indices 50 | std::vector> tci; ///< Triangle color indices 51 | std::vector> tti; ///< Triangle texture indices 52 | }; 53 | 54 | /** 55 | * @brief Writes the given Mesh to an obj file that for example can be read by MeshLab. 56 | * 57 | * If the mesh contains vertex colour information, it will be written to the obj as well. 58 | * 59 | * @param[in] mesh The mesh to save as obj. 60 | * @param[in] filename Output filename (including ".obj"). 61 | */ 62 | inline void write_obj(Mesh mesh, std::string filename) 63 | { 64 | assert(mesh.vertices.size() == mesh.colors.size() || mesh.colors.empty()); 65 | 66 | std::ofstream obj_file(filename); 67 | 68 | if (mesh.colors.empty()) 69 | { 70 | for (std::size_t i = 0; i < mesh.vertices.size(); ++i) 71 | { 72 | obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " 73 | << mesh.vertices[i][2] << std::endl; 74 | } 75 | } else 76 | { 77 | for (std::size_t i = 0; i < mesh.vertices.size(); ++i) 78 | { 79 | obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " 80 | << mesh.vertices[i][2] << " " << mesh.colors[i][0] << " " << mesh.colors[i][1] << " " 81 | << mesh.colors[i][2] << std::endl; 82 | } 83 | } 84 | 85 | if (!mesh.texcoords.empty()) 86 | { 87 | for (auto&& tc : mesh.texcoords) 88 | { 89 | obj_file << "vt " << tc[0] << " " << tc[1] << std::endl; 90 | } 91 | } 92 | 93 | for (auto&& v : mesh.tvi) 94 | { 95 | // Add one because obj starts counting triangle indices at 1 96 | obj_file << "f " << v[0] + 1 << " " << v[1] + 1 << " " << v[2] + 1 << std::endl; 97 | } 98 | 99 | return; 100 | } 101 | 102 | /** 103 | * @brief Writes an obj file of the given Mesh, including texture coordinates, 104 | * and an mtl file containing a reference to the isomap. 105 | * 106 | * The obj will contain texture coordinates for the mesh, and the 107 | * mtl file will link to a file named .isomap.png. 108 | * Note that the texture (isomap) has to be saved separately. 109 | * 110 | * @param[in] mesh The mesh to save as obj. 111 | * @param[in] filename Output filename, including .obj. 112 | */ 113 | inline void write_textured_obj(Mesh mesh, std::string filename) 114 | { 115 | assert((mesh.vertices.size() == mesh.colors.size() || mesh.colors.empty()) && !mesh.texcoords.empty()); 116 | 117 | if (filename.at(filename.size() - 4) != '.') 118 | { 119 | throw std::runtime_error( 120 | "Error in given filename: Expected a dot and a 3-letter extension at the end (i.e. '.obj'). " + 121 | filename); 122 | } 123 | 124 | // Takes a full path to a file and returns only the filename: 125 | const auto get_filename = [](const std::string& path) { 126 | auto last_slash = path.find_last_of("/\\"); 127 | if (last_slash == std::string::npos) 128 | { 129 | return path; 130 | } 131 | return path.substr(last_slash + 1, path.size()); 132 | }; 133 | 134 | std::ofstream obj_file(filename); 135 | 136 | std::string mtl_filename(filename); 137 | // replace '.obj' at the end with '.mtl': 138 | mtl_filename.replace(std::end(mtl_filename) - 4, std::end(mtl_filename), ".mtl"); 139 | 140 | obj_file << "mtllib " << get_filename(mtl_filename) << std::endl; // first line of the obj file 141 | 142 | // same as in write_obj(): 143 | if (mesh.colors.empty()) 144 | { 145 | for (std::size_t i = 0; i < mesh.vertices.size(); ++i) 146 | { 147 | obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " 148 | << mesh.vertices[i][2] << " " << std::endl; 149 | } 150 | } else 151 | { 152 | for (std::size_t i = 0; i < mesh.vertices.size(); ++i) 153 | { 154 | obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " 155 | << mesh.vertices[i][2] << " " << mesh.colors[i][0] << " " << mesh.colors[i][1] << " " 156 | << mesh.colors[i][2] << " " << std::endl; 157 | } 158 | } 159 | // end 160 | 161 | for (std::size_t i = 0; i < mesh.texcoords.size(); ++i) 162 | { 163 | obj_file << "vt " << mesh.texcoords[i][0] << " " << 1.0f - mesh.texcoords[i][1] << std::endl; 164 | // We invert y because Meshlab's uv origin (0, 0) is on the bottom-left 165 | } 166 | 167 | obj_file << "usemtl FaceTexture" << std::endl; // the name of our texture (material) will be 'FaceTexture' 168 | 169 | for (auto&& v : mesh.tvi) 170 | { 171 | // This assumes mesh.texcoords.size() == mesh.vertices.size(). The texture indices could theoretically be different (for example in the cube-mapped 3D scan). 172 | // Add one because obj starts counting triangle indices at 1 173 | obj_file << "f " << v[0] + 1 << "/" << v[0] + 1 << " " << v[1] + 1 << "/" << v[1] + 1 << " " 174 | << v[2] + 1 << "/" << v[2] + 1 << std::endl; 175 | } 176 | 177 | std::ofstream mtl_file(mtl_filename); 178 | std::string texture_filename(filename); 179 | // replace '.obj' at the end with '.isomap.png': 180 | texture_filename.replace(std::end(texture_filename) - 4, std::end(texture_filename), ".isomap.png"); 181 | 182 | mtl_file << "newmtl FaceTexture" << std::endl; 183 | mtl_file << "map_Kd " << get_filename(texture_filename) << std::endl; 184 | 185 | return; 186 | }; 187 | 188 | } /* namespace core */ 189 | } /* namespace eos */ 190 | 191 | #endif /* EOS_MESH_HPP_ */ 192 | -------------------------------------------------------------------------------- /include/eos/core/Rect.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/Rect.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_RECT_HPP 23 | #define EOS_RECT_HPP 24 | 25 | namespace eos { 26 | namespace core { 27 | 28 | /** 29 | * @brief A simple type representing a rectangle. 30 | */ 31 | template 32 | struct Rect 33 | { 34 | T x, y; ///< Top-left corner x and y position 35 | T width, height; 36 | }; 37 | 38 | } /* namespace core */ 39 | } /* namespace eos */ 40 | 41 | #endif /* EOS_RECT_HPP */ 42 | -------------------------------------------------------------------------------- /include/eos/core/image/Pixel.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/image/Pixel.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_IMAGE_PIXEL_HPP 23 | #define EOS_IMAGE_PIXEL_HPP 24 | 25 | #include 26 | #include 27 | 28 | namespace eos { 29 | namespace core { 30 | 31 | /** 32 | * @brief Represents a pixel with given type and number of channels. 33 | */ 34 | template 35 | class Pixel 36 | { 37 | public: 38 | template > 39 | constexpr Pixel(Args... args) noexcept : data_{{static_cast(args)...}} {}; 40 | 41 | constexpr explicit Pixel(const std::array& arr) noexcept : data_(arr){}; 42 | 43 | constexpr ElementType& operator[](int i) noexcept 44 | { 45 | return data_[i]; 46 | }; 47 | constexpr const ElementType& operator[](int i) const noexcept 48 | { 49 | return data_[i]; 50 | }; 51 | 52 | const std::array& data() const noexcept 53 | { 54 | return data_; 55 | }; 56 | 57 | private: 58 | std::array data_; 59 | }; 60 | 61 | template 62 | constexpr bool operator==(const Pixel& lhs, 63 | const Pixel& rhs) noexcept 64 | { 65 | return lhs.data() == rhs.data(); 66 | }; 67 | 68 | } /* namespace core */ 69 | } /* namespace eos */ 70 | 71 | #endif /* EOS_IMAGE_PIXEL_HPP */ 72 | -------------------------------------------------------------------------------- /include/eos/core/image/PixelTraits.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/image/PixelTraits.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_IMAGE_PIXELTRAITS_HPP 23 | #define EOS_IMAGE_PIXELTRAITS_HPP 24 | 25 | #include "eos/core/image/Pixel.hpp" 26 | 27 | #include 28 | 29 | namespace eos { 30 | namespace core { 31 | 32 | /** 33 | * @brief Todo. 34 | */ 35 | template 36 | struct PixelTraits 37 | { 38 | static constexpr std::uint16_t num_bytes = sizeof(ElementType); 39 | 40 | static constexpr ElementType zero_element = ElementType{0}; 41 | }; 42 | 43 | /** 44 | * @brief Todo. 45 | */ 46 | template 47 | struct PixelTraits> 48 | { 49 | static constexpr std::uint16_t num_bytes = sizeof(Pixel); 50 | 51 | static constexpr Pixel zero_element = 52 | Pixel(std::array()); 53 | }; 54 | 55 | } /* namespace core */ 56 | } /* namespace eos */ 57 | 58 | #endif /* EOS_IMAGE_PIXELTRAITS_HPP */ 59 | -------------------------------------------------------------------------------- /include/eos/core/image/opencv_interop.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/image/opencv_interop.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_IMAGE_OPENCV_INTEROP_HPP_ 23 | #define EOS_IMAGE_OPENCV_INTEROP_HPP_ 24 | 25 | #include "eos/core/Image.hpp" 26 | 27 | #include "opencv2/core/core.hpp" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | namespace eos { 34 | namespace core { 35 | 36 | // We can support different types by making this a template and constexpr if? :-) 37 | inline cv::Mat to_mat(const Image4u& image) 38 | { 39 | cv::Mat opencv_matrix(static_cast(image.height()), static_cast(image.width()), CV_8UC4); 40 | for (int c = 0; c < image.width(); ++c) 41 | { // size_t 42 | for (int r = 0; r < image.height(); ++r) 43 | { 44 | // auto vals = image(r, c); 45 | opencv_matrix.at(r, c) = 46 | cv::Vec4b(image(r, c)[0], image(r, c)[1], image(r, c)[2], image(r, c)[3]); 47 | } 48 | } 49 | return opencv_matrix; 50 | }; 51 | 52 | inline cv::Mat to_mat(const Image3u& image) 53 | { 54 | cv::Mat opencv_matrix(image.height(), image.width(), CV_8UC3); 55 | for (int c = 0; c < image.width(); ++c) 56 | { // size_t 57 | for (int r = 0; r < image.height(); ++r) 58 | { 59 | // auto vals = image(r, c); 60 | opencv_matrix.at(r, c) = cv::Vec3b(image(r, c)[0], image(r, c)[1], image(r, c)[2]); 61 | } 62 | } 63 | return opencv_matrix; 64 | }; 65 | 66 | inline cv::Mat to_mat(const Image1d& image) 67 | { 68 | cv::Mat opencv_matrix(static_cast(image.height()), static_cast(image.width()), CV_64FC1); 69 | for (int c = 0; c < image.width(); ++c) 70 | { // size_t 71 | for (int r = 0; r < image.height(); ++r) 72 | { 73 | // auto vals = image(r, c); 74 | opencv_matrix.at(r, c) = image(r, c); 75 | } 76 | } 77 | return opencv_matrix; 78 | }; 79 | 80 | inline cv::Mat to_mat(const Image1u& image) 81 | { 82 | cv::Mat opencv_matrix(static_cast(image.height()), static_cast(image.width()), CV_8UC1); 83 | for (int c = 0; c < image.width(); ++c) 84 | { // size_t 85 | for (int r = 0; r < image.height(); ++r) 86 | { 87 | // auto vals = image(r, c); 88 | opencv_matrix.at(r, c) = image(r, c); 89 | } 90 | } 91 | return opencv_matrix; 92 | }; 93 | 94 | inline Image3u from_mat(const cv::Mat& image) 95 | { 96 | if (image.type() != CV_8UC3) 97 | { 98 | throw std::runtime_error("Can only convert a CV_8UC3 cv::Mat to an eos::core::Image3u."); 99 | } 100 | 101 | Image3u converted(image.rows, image.cols); 102 | for (int r = 0; r < image.rows; ++r) 103 | { 104 | for (int c = 0; c < image.cols; ++c) 105 | { 106 | converted(r, c) = {image.at(r, c)[0], image.at(r, c)[1], 107 | image.at(r, c)[2]}; 108 | } 109 | } 110 | return converted; 111 | }; 112 | 113 | inline Image4u from_mat_with_alpha(const cv::Mat& image) 114 | { 115 | if (image.type() != CV_8UC4) 116 | { 117 | throw std::runtime_error("Can only convert a CV_8UC4 cv::Mat to an eos::core::Image4u."); 118 | } 119 | 120 | Image4u converted(image.rows, image.cols); 121 | for (int r = 0; r < image.rows; ++r) 122 | { 123 | for (int c = 0; c < image.cols; ++c) 124 | { 125 | converted(r, c) = {image.at(r, c)[0], image.at(r, c)[1], 126 | image.at(r, c)[2], image.at(r, c)[3]}; 127 | } 128 | } 129 | return converted; 130 | }; 131 | 132 | } /* namespace core */ 133 | } /* namespace eos */ 134 | 135 | #endif /* EOS_IMAGE_OPENCV_INTEROP_HPP_ */ 136 | -------------------------------------------------------------------------------- /include/eos/core/image/utils.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/image/utils.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_IMAGE_UTILS_HPP 23 | #define EOS_IMAGE_UTILS_HPP 24 | 25 | #include "eos/core/image/Pixel.hpp" 26 | 27 | #include 28 | 29 | namespace eos { 30 | namespace core { 31 | namespace image { 32 | 33 | /** 34 | * @brief Creates an image of given type, height and width, with all zeros. 35 | */ 36 | template 37 | Image zeros(int height, int width) noexcept 38 | { 39 | Image image(height, width); 40 | for (int y = 0; y < height; ++y) 41 | { 42 | for (int x = 0; x < width; ++x) 43 | { 44 | image(y, x) = PixelTraits::zero_element; 45 | } 46 | } 47 | return image; 48 | }; 49 | 50 | /** 51 | * @brief Creates an image of given type, height and width, with a constant value for all pixels. 52 | */ 53 | template 54 | Image constant(int height, int width, PixelType value) noexcept 55 | { 56 | Image image(height, width); 57 | for (int y = 0; y < height; ++y) 58 | { 59 | for (int x = 0; x < width; ++x) 60 | { 61 | image(y, x) = value; 62 | } 63 | } 64 | return image; 65 | }; 66 | 67 | } // namespace image 68 | } // namespace core 69 | } // namespace eos 70 | 71 | #endif /* EOS_IMAGE_UTILS_HPP */ 72 | -------------------------------------------------------------------------------- /include/eos/core/read_pts_landmarks.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/core/read_pts_landmarks.hpp 5 | * 6 | * Copyright 2014, 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef READ_PTS_LANDMARKS_HPP_ 23 | #define READ_PTS_LANDMARKS_HPP_ 24 | 25 | #include "eos/core/Landmark.hpp" 26 | 27 | #include "Eigen/Core" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | namespace eos { 35 | namespace core { 36 | 37 | /** 38 | * Reads an ibug .pts landmark file and returns an ordered vector with 39 | * the 68 2D landmark coordinates. 40 | * 41 | * @param[in] filename Path to a .pts file. 42 | * @return An ordered vector with the 68 ibug landmarks. 43 | */ 44 | inline LandmarkCollection read_pts_landmarks(std::string filename) 45 | { 46 | using Eigen::Vector2f; 47 | using std::getline; 48 | using std::string; 49 | LandmarkCollection landmarks; 50 | landmarks.reserve(68); 51 | 52 | std::ifstream file(filename); 53 | if (!file) 54 | { 55 | throw std::runtime_error(string("Could not open landmark file: " + filename)); 56 | } 57 | 58 | string line; 59 | // Skip the first 3 lines, they're header lines: 60 | getline(file, line); // 'version: 1' 61 | getline(file, line); // 'n_points : 68' 62 | getline(file, line); // '{' 63 | 64 | int ibugId = 1; 65 | while (getline(file, line)) 66 | { 67 | if (line == "}") 68 | { // end of the file 69 | break; 70 | } 71 | std::stringstream lineStream(line); 72 | 73 | Landmark landmark; 74 | landmark.name = std::to_string(ibugId); 75 | if (!(lineStream >> landmark.coordinates[0] >> landmark.coordinates[1])) 76 | { 77 | throw std::runtime_error(string("Landmark format error while parsing the line: " + line)); 78 | } 79 | // From the iBug website: 80 | // "Please note that the re-annotated data for this challenge are saved in the Matlab convention of 1 81 | // being the first index, i.e. the coordinates of the top left pixel in an image are x=1, y=1." 82 | // ==> So we shift every point by 1: 83 | landmark.coordinates[0] -= 1.0f; 84 | landmark.coordinates[1] -= 1.0f; 85 | landmarks.emplace_back(landmark); 86 | ++ibugId; 87 | } 88 | return landmarks; 89 | }; 90 | 91 | } /* namespace core */ 92 | } /* namespace eos */ 93 | 94 | #endif /* READ_PTS_LANDMARKS_HPP_ */ 95 | -------------------------------------------------------------------------------- /include/eos/cpp17/clamp.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/cpp17/clamp.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_CLAMP_HPP_ 23 | #define EOS_CLAMP_HPP_ 24 | 25 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 26 | #include 27 | namespace eos { 28 | namespace cpp17 { 29 | using ::std::clamp; 30 | } 31 | } 32 | #else 33 | namespace eos { 34 | namespace cpp17 { 35 | // Returns val constrained to [min, max] 36 | template 37 | constexpr const T& clamp(const T& val, const T& min, const T& max) 38 | { 39 | // this is the implementation that uses: 40 | return ((max < val) ? max : (val < min) ? min : val); 41 | } 42 | } 43 | } 44 | #endif 45 | 46 | #endif /* EOS_CLAMP_HPP_ */ 47 | -------------------------------------------------------------------------------- /include/eos/cpp17/detail/akrzemi1_optional_serialization.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/cpp17/detail/akrzemi1_optional_serialization.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_AKRZEMI1_OPTIONAL_SERIALIZATION_HPP_ 23 | #define EOS_AKRZEMI1_OPTIONAL_SERIALIZATION_HPP_ 24 | 25 | #include "eos/cpp17/detail/akrzemi1_optional.hpp" 26 | 27 | #include "cereal/cereal.hpp" 28 | 29 | namespace cereal { 30 | 31 | //! Saving for akrzemi1::optional 32 | template 33 | inline void CEREAL_SAVE_FUNCTION_NAME(Archive& ar, const akrzemi1::optional& optional) 34 | { 35 | if (!optional) 36 | { 37 | ar(CEREAL_NVP_("nullopt", true)); 38 | } else 39 | { 40 | ar(CEREAL_NVP_("nullopt", false), CEREAL_NVP_("data", *optional)); 41 | } 42 | } 43 | 44 | //! Loading for akrzemi1::optional 45 | template 46 | inline void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, akrzemi1::optional& optional) 47 | { 48 | bool nullopt; 49 | ar(CEREAL_NVP_("nullopt", nullopt)); 50 | 51 | if (nullopt) 52 | { 53 | optional = akrzemi1::nullopt; 54 | } else 55 | { 56 | T value; 57 | ar(CEREAL_NVP_("data", value)); 58 | optional = std::move(value); 59 | } 60 | } 61 | 62 | } // namespace cereal 63 | 64 | #endif /* EOS_AKRZEMI1_OPTIONAL_SERIALIZATION_HPP_ */ 65 | -------------------------------------------------------------------------------- /include/eos/cpp17/detail/mpark_variant_serialization.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/cpp17/detail/mpark_variant_serialization.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_MPARK_VARIANT_SERIALIZATION_HPP_ 23 | #define EOS_MPARK_VARIANT_SERIALIZATION_HPP_ 24 | 25 | #include "eos/cpp17/detail/mpark_variant.hpp" 26 | 27 | #include "cereal/cereal.hpp" 28 | 29 | #include 30 | #include 31 | 32 | namespace cereal { 33 | namespace variant_detail { 34 | 35 | //! @internal 36 | template 37 | struct variant_save_visitor 38 | { 39 | variant_save_visitor(Archive& ar_) : ar(ar_) {} 40 | 41 | template 42 | void operator()(T const& value) const 43 | { 44 | ar(CEREAL_NVP_("data", value)); 45 | } 46 | 47 | Archive& ar; 48 | }; 49 | 50 | //! @internal 51 | template 52 | typename std::enable_if, void>::type 53 | load_variant(Archive& /*ar*/, int /*target*/, Variant& /*variant*/) 54 | { 55 | throw ::cereal::Exception("Error traversing variant during load"); 56 | } 57 | 58 | //! @internal 59 | template 60 | typename std::enable_if < 61 | N, void>::type load_variant(Archive& ar, int target, Variant& variant) 62 | { 63 | if (N == target) 64 | { 65 | H value; 66 | ar(CEREAL_NVP_("data", value)); 67 | variant = std::move(value); 68 | } else 69 | load_variant(ar, target, variant); 70 | } 71 | 72 | } // namespace variant_detail 73 | 74 | //! Saving for mpark::variant 75 | template 76 | inline void CEREAL_SAVE_FUNCTION_NAME(Archive& ar, 77 | mpark::variant const& variant) 78 | { 79 | std::int32_t index = static_cast(variant.index()); 80 | ar(CEREAL_NVP_("index", index)); 81 | variant_detail::variant_save_visitor visitor(ar); 82 | mpark::visit(visitor, variant); 83 | } 84 | 85 | //! Loading for mpark::variant 86 | template 87 | inline void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, mpark::variant& variant) 88 | { 89 | using variant_t = typename mpark::variant; 90 | 91 | std::int32_t index; 92 | ar(CEREAL_NVP_("index", index)); 93 | if (index >= static_cast(mpark::variant_size_v)) 94 | throw Exception("Invalid 'index' selector when deserializing mpark::variant"); 95 | 96 | variant_detail::load_variant<0, variant_t, VariantTypes...>(ar, index, variant); 97 | } 98 | 99 | } // namespace cereal 100 | 101 | #endif /* EOS_MPARK_VARIANT_SERIALIZATION_HPP_ */ 102 | -------------------------------------------------------------------------------- /include/eos/cpp17/optional.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/cpp17/optional.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_OPTIONAL_HPP_ 23 | #define EOS_OPTIONAL_HPP_ 24 | 25 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 26 | #include 27 | namespace eos { 28 | namespace cpp17 { 29 | using ::std::optional; 30 | using ::std::nullopt; 31 | } 32 | } 33 | #else 34 | #include "eos/cpp17/detail/akrzemi1_optional.hpp" 35 | namespace eos { 36 | namespace cpp17 { 37 | using ::akrzemi1::optional; 38 | using ::akrzemi1::nullopt; 39 | } 40 | } 41 | #endif 42 | 43 | #endif /* EOS_OPTIONAL_HPP_ */ 44 | -------------------------------------------------------------------------------- /include/eos/cpp17/optional_serialization.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/cpp17/optional_serialization.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_OPTIONAL_SERIALIZATION_HPP_ 23 | #define EOS_OPTIONAL_SERIALIZATION_HPP_ 24 | 25 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 26 | #include "cereal/types/optional.hpp" 27 | #else 28 | #include "eos/cpp17/detail/akrzemi1_optional_serialization.hpp" 29 | #endif 30 | 31 | #endif /* EOS_OPTIONAL_SERIALIZATION_HPP_ */ 32 | -------------------------------------------------------------------------------- /include/eos/cpp17/variant.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/cpp17/variant.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_VARIANT_HPP_ 23 | #define EOS_VARIANT_HPP_ 24 | 25 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 26 | #include 27 | namespace eos { 28 | namespace cpp17 { 29 | using ::std::variant; 30 | using ::std::holds_alternative; 31 | using ::std::get; 32 | } 33 | } 34 | #else 35 | #include "eos/cpp17/detail/mpark_variant.hpp" 36 | namespace eos { 37 | namespace cpp17 { 38 | using ::mpark::variant; 39 | using ::mpark::holds_alternative; 40 | using ::mpark::get; 41 | } 42 | } 43 | #endif 44 | 45 | #endif /* EOS_VARIANT_HPP_ */ 46 | -------------------------------------------------------------------------------- /include/eos/cpp17/variant_serialization.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/cpp17/variant_serialization.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_VARIANT_SERIALIZATION_HPP_ 23 | #define EOS_VARIANT_SERIALIZATION_HPP_ 24 | 25 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 26 | #include "cereal/types/variant.hpp" 27 | #else 28 | #include "eos/cpp17/detail/mpark_variant_serialization.hpp" 29 | #endif 30 | 31 | #endif /* EOS_VARIANT_SERIALIZATION_HPP_ */ 32 | -------------------------------------------------------------------------------- /include/eos/fitting/FittingResult.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/fitting/FittingResult.hpp 5 | * 6 | * Copyright 2016 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef FITTINGRESULT_HPP_ 23 | #define FITTINGRESULT_HPP_ 24 | 25 | #include "eos/fitting/RenderingParameters.hpp" 26 | 27 | #include 28 | 29 | namespace eos { 30 | namespace fitting { 31 | 32 | /** 33 | * @brief A struct holding the result from a fitting. 34 | * 35 | * Holds the parameters to store and reproduce a shape fitting result: 36 | * Rendering parameters, PCA shape and expression coefficients. 37 | */ 38 | struct FittingResult 39 | { 40 | RenderingParameters rendering_parameters; 41 | std::vector pca_shape_coefficients; 42 | std::vector expression_coefficients; 43 | }; 44 | 45 | } /* namespace fitting */ 46 | } /* namespace eos */ 47 | 48 | #endif /* FITTINGRESULT_HPP_ */ 49 | -------------------------------------------------------------------------------- /include/eos/fitting/closest_edge_fitting.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVSSP/eos/f8b235bf8da1eef947a62dc09e23ecde2c9c0f3b/include/eos/fitting/closest_edge_fitting.hpp -------------------------------------------------------------------------------- /include/eos/fitting/contour_correspondence.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVSSP/eos/f8b235bf8da1eef947a62dc09e23ecde2c9c0f3b/include/eos/fitting/contour_correspondence.hpp -------------------------------------------------------------------------------- /include/eos/fitting/detail/glm_cerealisation.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/fitting/detail/glm_cerealisation.hpp 5 | * 6 | * Copyright 2016 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef GLMCEREALISATION_HPP_ 23 | #define GLMCEREALISATION_HPP_ 24 | 25 | #include "cereal/cereal.hpp" 26 | 27 | #include "glm/gtc/quaternion.hpp" 28 | 29 | /** 30 | * @brief Serialisation of GLM \c glm::quat quaternion for the serialisation 31 | * library cereal (http://uscilab.github.io/cereal/index.html). 32 | * 33 | * Contains serialisation for \c glm::quat. 34 | */ 35 | namespace glm { 36 | 37 | /** 38 | * @brief Serialisation of a glm::quat using cereal. 39 | * 40 | * @param[in] ar The archive to (de)serialise. 41 | * @param[in] vec The vector to (de)serialise. 42 | */ 43 | template 44 | void serialize(Archive& ar, glm::quat& q) 45 | { 46 | ar(q.w, q.x, q.y, q.z); 47 | }; 48 | 49 | } /* namespace glm */ 50 | 51 | #endif /* GLMCEREALISATION_HPP_ */ 52 | -------------------------------------------------------------------------------- /include/eos/fitting/detail/nonlinear_camera_estimation_detail.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/fitting/detail/nonlinear_camera_estimation_detail.hpp 5 | * 6 | * Copyright 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef NONLINEARCAMERAESTIMATION_DETAIL_HPP_ 23 | #define NONLINEARCAMERAESTIMATION_DETAIL_HPP_ 24 | 25 | #include "glm/gtc/matrix_transform.hpp" 26 | 27 | #include "Eigen/Geometry" 28 | 29 | #include "opencv2/core/core.hpp" 30 | 31 | #include 32 | 33 | namespace eos { 34 | namespace fitting { 35 | namespace detail { 36 | 37 | // ret: 3rd entry is the z 38 | // radians 39 | // expects the landmark points to be in opencv convention, i.e. origin TL 40 | inline glm::vec3 project_ortho(glm::vec3 point, float rot_x_pitch, float rot_y_yaw, float rot_z_roll, 41 | float tx, float ty, float frustum_scale, /* fixed params now: */ int width, 42 | int height) 43 | { 44 | // We could use quaternions in here, to be independent of the RPY... etc convention. 45 | // Then, the user can decompose the quaternion as he wishes to. But then we'd have to estimate 4 46 | // parameters? 47 | // This can of course be optimised, but we keep it this way while we're debugging and as long as 48 | // it's not a performance issue. 49 | auto rot_mtx_x = glm::rotate(glm::mat4(1.0f), rot_x_pitch, glm::vec3{1.0f, 0.0f, 0.0f}); 50 | auto rot_mtx_y = glm::rotate(glm::mat4(1.0f), rot_y_yaw, glm::vec3{0.0f, 1.0f, 0.0f}); 51 | auto rot_mtx_z = glm::rotate(glm::mat4(1.0f), rot_z_roll, glm::vec3{0.0f, 0.0f, 1.0f}); 52 | auto t_mtx = glm::translate(glm::mat4(1.0f), 53 | glm::vec3{tx, ty, 0.0f}); // glm: Col-major memory layout. [] gives the column 54 | 55 | // Note/Todo: Is this the full ortho? n/f missing? or do we need to multiply it with Proj...? See Shirley CG! 56 | // glm::frustum()? 57 | const float aspect = static_cast(width) / height; 58 | auto ortho_mtx = glm::ortho(-1.0f * aspect * frustum_scale, 1.0f * aspect * frustum_scale, 59 | -1.0f * frustum_scale, 1.0f * frustum_scale); 60 | 61 | glm::vec4 viewport(0, height, width, -height); // flips y, origin top-left, like in OpenCV 62 | // P = RPY * P 63 | glm::vec3 res = glm::project(point, t_mtx * rot_mtx_z * rot_mtx_x * rot_mtx_y, ortho_mtx, viewport); 64 | return res; 65 | }; 66 | 67 | /** 68 | * @brief Generic functor for Eigen's optimisation algorithms. 69 | */ 70 | template 71 | struct Functor 72 | { 73 | typedef _Scalar Scalar; 74 | enum { InputsAtCompileTime = NX, ValuesAtCompileTime = NY }; 75 | typedef Eigen::Matrix InputType; 76 | typedef Eigen::Matrix ValueType; 77 | typedef Eigen::Matrix JacobianType; 78 | 79 | const int m_inputs, m_values; 80 | 81 | Functor() : m_inputs(InputsAtCompileTime), m_values(ValuesAtCompileTime) {} 82 | Functor(int inputs, int values) : m_inputs(inputs), m_values(values) {} 83 | 84 | int inputs() const { return m_inputs; } 85 | int values() const { return m_values; } 86 | }; 87 | 88 | /** 89 | * @brief LevenbergMarquardt cost function for the orthographic camera estimation. 90 | */ 91 | struct OrthographicParameterProjection : Functor 92 | { 93 | public: 94 | // Creates a new OrthographicParameterProjection object with given data. 95 | OrthographicParameterProjection(std::vector image_points, std::vector model_points, 96 | int width, int height) 97 | : Functor(6, image_points.size()), image_points(image_points), model_points(model_points), 98 | width(width), height(height){}; 99 | 100 | // x = current params, fvec = the errors/differences of the proj with current params and the GT 101 | // (image_points) 102 | int operator()(const Eigen::VectorXd& x, Eigen::VectorXd& fvec) const 103 | { 104 | const float aspect = static_cast(width) / height; 105 | for (int i = 0; i < values(); i++) 106 | { 107 | // opencv to glm: 108 | glm::vec3 point_3d(model_points[i][0], model_points[i][1], model_points[i][2]); 109 | // projection given current params x: 110 | glm::vec3 proj_with_current_param_esti = 111 | project_ortho(point_3d, x[0], x[1], x[2], x[3], x[4], x[5], width, height); 112 | cv::Vec2f proj_point_2d(proj_with_current_param_esti.x, proj_with_current_param_esti.y); 113 | // diff of current proj to ground truth, our error 114 | auto diff = cv::norm(proj_point_2d, image_points[i]); 115 | // fvec should contain the differences 116 | // don't square it. 117 | fvec[i] = diff; 118 | } 119 | return 0; 120 | }; 121 | 122 | private: 123 | std::vector image_points; 124 | std::vector model_points; 125 | int width; 126 | int height; 127 | }; 128 | 129 | } /* namespace detail */ 130 | } /* namespace fitting */ 131 | } /* namespace eos */ 132 | 133 | #endif /* NONLINEARCAMERAESTIMATION_DETAIL_HPP_ */ 134 | -------------------------------------------------------------------------------- /include/eos/fitting/nonlinear_camera_estimation.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/fitting/nonlinear_camera_estimation.hpp 5 | * 6 | * Copyright 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef NONLINEARCAMERAESTIMATION_HPP_ 23 | #define NONLINEARCAMERAESTIMATION_HPP_ 24 | 25 | #include "eos/fitting/detail/nonlinear_camera_estimation_detail.hpp" 26 | #include "eos/fitting/RenderingParameters.hpp" 27 | 28 | #include "Eigen/Geometry" 29 | #include "unsupported/Eigen/NonLinearOptimization" 30 | 31 | #include "opencv2/core/core.hpp" 32 | 33 | #include 34 | #include 35 | 36 | namespace eos { 37 | namespace fitting { 38 | 39 | /** 40 | * @brief This algorithm estimates the rotation angles and translation of the model, as 41 | * well as the viewing frustum of the camera, given a set of corresponding 2D-3D points. 42 | * 43 | * It assumes an orthographic camera and estimates 6 parameters, 44 | * [r_x, r_y, r_z, t_x, t_y, frustum_scale], where the first five describe how to transform 45 | * the model, and the last one describes the cameras viewing frustum (see CameraParameters). 46 | * This 2D-3D correspondence problem is solved using Eigen's LevenbergMarquardt algorithm. 47 | * 48 | * The method is slightly inspired by "Computer Vision: Models Learning and Inference", 49 | * Simon J.D. Prince, 2012, but different in a lot of respects. 50 | * 51 | * Eigen's LM implementation requires at least 6 data points, so we require >= 6 corresponding points. 52 | * 53 | * Notes/improvements: 54 | * The algorithm works reliable as it is, however, it could be improved with the following: 55 | * - A better initial guess (see e.g. Prince) 56 | * - We could add a parameter to pass an initial guess 57 | * - Using the analytic derivatives instead of Eigen::NumericalDiff - they're easy to calculate. 58 | * 59 | * @param[in] image_points A list of 2D image points. 60 | * @param[in] model_points Corresponding points of a 3D model. 61 | * @param[in] width Width of the image (or viewport). 62 | * @param[in] height Height of the image (or viewport). 63 | * @return The estimated model and camera parameters. 64 | */ 65 | inline RenderingParameters estimate_orthographic_camera(std::vector image_points, 66 | std::vector model_points, int width, 67 | int height) 68 | { 69 | using cv::Mat; 70 | assert(image_points.size() == model_points.size()); 71 | assert(image_points.size() >= 6); // Number of correspondence points given needs to be equal to or larger than 6 72 | 73 | const float aspect = static_cast(width) / height; 74 | 75 | // Set up the initial parameter vector and the cost function: 76 | int num_params = 6; 77 | Eigen::VectorXd parameters; // [rot_x_pitch, rot_y_yaw, rot_z_roll, t_x, t_y, frustum_scale] 78 | parameters.setConstant(num_params, 0.0); // Set all 6 values to zero (except frustum_scale, see next line) 79 | parameters[5] = 110.0; // This is just a rough hand-chosen scaling estimate - we could do a lot better. But it works. 80 | detail::OrthographicParameterProjection cost_function(image_points, model_points, width, height); 81 | 82 | // Note: we have analytical derivatives, so we should use them! 83 | Eigen::NumericalDiff cost_function_with_derivative(cost_function, 84 | 0.0001); 85 | // I had to change the default value of epsfcn, it works well around 0.0001. It couldn't produce the 86 | // derivative with the default, I guess the changes in the gradient were too small. 87 | 88 | Eigen::LevenbergMarquardt> lm( 89 | cost_function_with_derivative); 90 | auto info = lm.minimize(parameters); // we could or should use the return value 91 | // 'parameters' contains the solution now. 92 | 93 | Frustum camera_frustum{ 94 | -1.0f * aspect * static_cast(parameters[5]), 1.0f * aspect * static_cast(parameters[5]), 95 | -1.0f * static_cast(parameters[5]), 1.0f * static_cast(parameters[5])}; 96 | return RenderingParameters(CameraType::Orthographic, camera_frustum, static_cast(parameters[0]), 97 | static_cast(parameters[1]), static_cast(parameters[2]), 98 | static_cast(parameters[3]), static_cast(parameters[4]), width, 99 | height); 100 | }; 101 | 102 | } /* namespace fitting */ 103 | } /* namespace eos */ 104 | 105 | #endif /* NONLINEARCAMERAESTIMATION_HPP_ */ 106 | -------------------------------------------------------------------------------- /include/eos/fitting/orthographic_camera_estimation_linear.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/fitting/orthographic_camera_estimation_linear.hpp 5 | * 6 | * Copyright 2016 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef ORTHOGRAPHICCAMERAESTIMATIONLINEAR_HPP_ 23 | #define ORTHOGRAPHICCAMERAESTIMATIONLINEAR_HPP_ 24 | 25 | #include "eos/cpp17/optional.hpp" 26 | 27 | #include "glm/mat3x3.hpp" 28 | 29 | #include "Eigen/Core" 30 | #include "Eigen/Dense" 31 | #include "Eigen/SVD" 32 | #include "Eigen/Dense" 33 | 34 | #include 35 | #include 36 | 37 | namespace eos { 38 | namespace fitting { 39 | 40 | /** 41 | * Parameters of an estimated scaled orthographic projection. 42 | */ 43 | struct ScaledOrthoProjectionParameters 44 | { 45 | glm::mat3x3 R; ///< 3x3 rotation matrix 46 | float tx, ty; ///< x and y translation 47 | float s; ///< Scaling 48 | }; 49 | 50 | /** 51 | * Estimates the parameters of a scaled orthographic projection. 52 | * 53 | * Given a set of 2D-3D correspondences, this algorithm estimates rotation, 54 | * translation (in x and y) and a scaling parameters of the scaled orthographic 55 | * projection model using a closed-form solution. It does so by first computing 56 | * an affine camera matrix using algorithm [1], and then finds the closest 57 | * orthonormal matrix to the estimated affine transform using SVD. 58 | * This algorithm follows the original implementation [2] of William Smith, 59 | * University of York. 60 | * 61 | * Requires >= 4 corresponding points. 62 | * 63 | * [1]: Gold Standard Algorithm for estimating an affine camera matrix from 64 | * world to image correspondences, Algorithm 7.2 in Multiple View Geometry, 65 | * Hartley & Zisserman, 2nd Edition, 2003. 66 | * [2]: https://github.com/waps101/3DMM_edges/blob/master/utils/POS.m 67 | * 68 | * @param[in] image_points A list of 2D image points. 69 | * @param[in] model_points Corresponding points of a 3D model. 70 | * @param[in] is_viewport_upsidedown Flag to set whether the viewport of the image points is upside-down (e.g. as in OpenCV). 71 | * @param[in] viewport_height Height of the viewport of the image points (needs to be given if is_viewport_upsidedown == true). 72 | * @return Rotation, translation and scaling of the estimated scaled orthographic projection. 73 | */ 74 | inline ScaledOrthoProjectionParameters estimate_orthographic_projection_linear( 75 | std::vector image_points, std::vector model_points, 76 | bool is_viewport_upsidedown, cpp17::optional viewport_height = cpp17::nullopt) 77 | { 78 | using Eigen::Matrix; 79 | assert(image_points.size() == model_points.size()); 80 | assert(image_points.size() >= 4); // Number of correspondence points given needs to be equal to or larger than 4 81 | 82 | const int num_correspondences = static_cast(image_points.size()); 83 | 84 | if (is_viewport_upsidedown) 85 | { 86 | if (viewport_height == cpp17::nullopt) 87 | { 88 | throw std::runtime_error( 89 | "Error: If is_viewport_upsidedown is set to true, viewport_height needs to be given."); 90 | } 91 | for (auto&& ip : image_points) 92 | { 93 | ip[1] = viewport_height.value() - ip[1]; 94 | } 95 | } 96 | 97 | Matrix A = Matrix::Zero(2 * num_correspondences, 8); 98 | for (int i = 0; i < model_points.size(); ++i) 99 | { 100 | A.block<1, 4>(2 * i, 0) = model_points[i]; // even row - copy to left side (first row is row 0) 101 | A.block<1, 4>((2 * i) + 1, 4) = model_points[i]; // odd row - copy to right side 102 | } // 4th coord (homogeneous) is already 1 103 | 104 | Matrix b(2 * num_correspondences, 1); 105 | for (int i = 0; i < image_points.size(); ++i) 106 | { 107 | b.segment<2>(2 * i) = image_points[i]; 108 | } 109 | 110 | // The original implementation used SVD here to solve the linear system, but 111 | // QR seems to do the job fine too. 112 | const Matrix k = A.colPivHouseholderQr().solve(b); // resulting affine matrix (8x1) 113 | 114 | // Extract all values from the estimated affine parameters k: 115 | const Eigen::Vector3f R1 = k.segment<3>(0); 116 | const Eigen::Vector3f R2 = k.segment<3>(4); 117 | const float sTx = k(3); 118 | const float sTy = k(7); 119 | const auto s = (R1.norm() + R2.norm()) / 2.0f; 120 | Eigen::Matrix3f R; 121 | Eigen::Vector3f r1 = R1.normalized(); // Not sure why R1.normalize() (in-place) produces a compiler error. 122 | Eigen::Vector3f r2 = R2.normalized(); 123 | R.block<1, 3>(0, 0) = r1; 124 | R.block<1, 3>(1, 0) = r2; 125 | R.block<1, 3>(2, 0) = r1.cross(r2); 126 | 127 | // Set R to the closest orthonormal matrix to the estimated affine transform: 128 | Eigen::JacobiSVD svd(R, Eigen::ComputeFullU | Eigen::ComputeFullV); 129 | Eigen::Matrix3f U = svd.matrixU(); 130 | const Eigen::Matrix3f V = svd.matrixV(); 131 | Eigen::Matrix3f R_ortho = U * V.transpose(); 132 | 133 | // The determinant of R must be 1 for it to be a valid rotation matrix 134 | if (R_ortho.determinant() < 0) 135 | { 136 | U.block<1, 3>(2, 0) = -U.block<1, 3>(2, 0); // not tested 137 | R_ortho = U * V.transpose(); 138 | } 139 | 140 | // Remove the scale from the translations: 141 | const auto t1 = sTx / s; 142 | const auto t2 = sTy / s; 143 | 144 | // Convert to a glm::mat4x4: 145 | glm::mat3x3 R_glm; // identity 146 | for (int r = 0; r < 3; ++r) 147 | { 148 | for (int c = 0; c < 3; ++c) 149 | { 150 | R_glm[c][r] = R_ortho(r, c); 151 | } 152 | } 153 | return ScaledOrthoProjectionParameters{R_glm, t1, t2, s}; 154 | }; 155 | 156 | } /* namespace fitting */ 157 | } /* namespace eos */ 158 | 159 | #endif /* ORTHOGRAPHICCAMERAESTIMATIONLINEAR_HPP_ */ 160 | -------------------------------------------------------------------------------- /include/eos/morphablemodel/Blendshape.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/morphablemodel/Blendshape.hpp 5 | * 6 | * Copyright 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_BLENDSHAPE_HPP 23 | #define EOS_BLENDSHAPE_HPP 24 | 25 | #include "cereal/archives/binary.hpp" 26 | #include "cereal/types/string.hpp" 27 | #include "eos/morphablemodel/io/eigen_cerealisation.hpp" 28 | 29 | #include "Eigen/Core" 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace eos { 37 | namespace morphablemodel { 38 | 39 | /** 40 | * @brief A class representing a 3D blendshape. 41 | * 42 | * A blendshape is a vector of offsets that transform the vertices of 43 | * a given mesh or shape instance. Usually, a blendshape is associated 44 | * with a deformation like a particular facial expression or a phoneme. 45 | */ 46 | struct Blendshape 47 | { 48 | std::string name; ///< Name of the blendshape. 49 | Eigen::VectorXf deformation; ///< A 3m x 1 col-vector (xyzxyz...)', where m is the number of 50 | ///< model-vertices. Has the same format as PcaModel::mean. 51 | 52 | friend class cereal::access; 53 | /** 54 | * Serialises this class using cereal. 55 | * 56 | * @param[in] archive The archive to serialise to (or to serialise from). 57 | */ 58 | template 59 | void serialize(Archive& archive) 60 | { 61 | archive(CEREAL_NVP(name), CEREAL_NVP(deformation)); 62 | }; 63 | }; 64 | 65 | /** 66 | * Shorthand notation for an std::vector. 67 | */ 68 | using Blendshapes = std::vector; 69 | 70 | /** 71 | * Helper method to load a file with blendshapes from 72 | * a cereal::BinaryInputArchive from the harddisk. 73 | * 74 | * @param[in] filename Filename to a blendshapes-file. 75 | * @return The loaded blendshapes. 76 | * @throw std::runtime_error When the file given in \c filename fails to be opened (most likely because the 77 | * file doesn't exist). 78 | */ 79 | inline std::vector load_blendshapes(std::string filename) 80 | { 81 | std::vector blendshapes; 82 | 83 | std::ifstream file(filename, std::ios::binary); 84 | if (!file) 85 | { 86 | throw std::runtime_error("Error opening given file: " + filename); 87 | } 88 | cereal::BinaryInputArchive input_archive(file); 89 | input_archive(blendshapes); 90 | 91 | return blendshapes; 92 | }; 93 | 94 | /** 95 | * Helper method to save a set of blendshapes to the 96 | * harddisk as a cereal::BinaryOutputArchive. 97 | * 98 | * @param[in] blendshapes The blendshapes to be saved. 99 | * @param[in] filename Filename for the blendshapes. 100 | */ 101 | inline void save_blendshapes(const std::vector& blendshapes, std::string filename) 102 | { 103 | std::ofstream file(filename, std::ios::binary); 104 | if (!file) 105 | { 106 | throw std::runtime_error("Error creating given file: " + filename); 107 | } 108 | cereal::BinaryOutputArchive output_archive(file); 109 | output_archive(blendshapes); 110 | }; 111 | 112 | /** 113 | * @brief Copies the blendshapes into a matrix, with each column being a blendshape. 114 | * 115 | * @param[in] blendshapes Vector of blendshapes. 116 | * @return The resulting matrix. 117 | */ 118 | inline Eigen::MatrixXf to_matrix(const std::vector& blendshapes) 119 | { 120 | assert(blendshapes.size() > 0); 121 | // Todo: Assert all blendshapes have to have the same number of rows, and one col 122 | 123 | Eigen::MatrixXf blendshapes_as_basis(blendshapes[0].deformation.rows(), blendshapes.size()); 124 | for (int i = 0; i < blendshapes.size(); ++i) 125 | { 126 | blendshapes_as_basis.col(i) = blendshapes[i].deformation; 127 | } 128 | return blendshapes_as_basis; 129 | }; 130 | 131 | /** 132 | * @brief Maps an std::vector of coefficients with Eigen::Map, so it can be multiplied 133 | * with a blendshapes matrix. 134 | * 135 | * Note that the resulting Eigen::Map only lives as long as the data given lives and is in scope. 136 | * 137 | * @param[in] coefficients Vector of blendshape coefficients. 138 | * @return An Eigen::Map pointing to the given coefficients data. 139 | */ 140 | inline Eigen::Map to_vector(const std::vector& coefficients) 141 | { 142 | return Eigen::Map(coefficients.data(), coefficients.size()); 143 | }; 144 | 145 | } /* namespace morphablemodel */ 146 | } /* namespace eos */ 147 | 148 | #endif /* EOS_BLENDSHAPE_HPP */ 149 | -------------------------------------------------------------------------------- /include/eos/morphablemodel/EdgeTopology.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/morphablemodel/EdgeTopology.hpp 5 | * 6 | * Copyright 2016 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_EDGETOPOLOGY_HPP 23 | #define EOS_EDGETOPOLOGY_HPP 24 | 25 | #include "cereal/cereal.hpp" 26 | #include "cereal/types/array.hpp" 27 | #include "cereal/types/vector.hpp" 28 | #include "cereal/archives/json.hpp" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | namespace eos { 35 | namespace morphablemodel { 36 | 37 | /** 38 | * @brief A struct containing a 3D shape model's edge topology. 39 | * 40 | * This struct contains all edges of a 3D mesh, and for each edge, it 41 | * contains the two faces and the two vertices that are adjacent to that 42 | * edge. This is used in the iterated closest edge fitting (ICEF). 43 | * 44 | * Note: The indices are 1-based, so 1 needs to be subtracted before using 45 | * them as mesh indices. An index of 0 as first array element means that 46 | * it's an edge that lies on the mesh boundary, i.e. they are only 47 | * adjacent to one face. 48 | * We should explore a less error-prone way to store this data, but that's 49 | * how it is done in Matlab by the original code. 50 | * 51 | * adjacent_faces.size() is equal to adjacent_vertices.size(). 52 | */ 53 | struct EdgeTopology 54 | { 55 | std::vector> 56 | adjacent_faces; ///< num_edges x 2 matrix storing faces adjacent to each edge 57 | std::vector> 58 | adjacent_vertices; ///< num_edges x 2 matrix storing vertices adjacent to each edge 59 | 60 | friend class cereal::access; 61 | /** 62 | * Serialises this class using cereal. 63 | * 64 | * @param[in] archive The archive to serialise to (or to serialise from). 65 | */ 66 | template 67 | void serialize(Archive& archive) 68 | { 69 | archive(CEREAL_NVP(adjacent_faces), CEREAL_NVP(adjacent_vertices)); 70 | }; 71 | }; 72 | 73 | /** 74 | * Saves a 3DMM edge topology file to a json file. 75 | * 76 | * @param[in] edge_topology A model's edge topology. 77 | * @param[in] filename The file to write. 78 | * @throws std::runtime_error if unable to open the given file for writing. 79 | */ 80 | inline void save_edge_topology(EdgeTopology edge_topology, std::string filename) 81 | { 82 | std::ofstream file(filename); 83 | if (!file) 84 | { 85 | throw std::runtime_error("Error creating given file: " + filename); 86 | } 87 | cereal::JSONOutputArchive output_archive(file); 88 | output_archive(cereal::make_nvp("edge_topology", edge_topology)); 89 | }; 90 | 91 | /** 92 | * Load a 3DMM edge topology file from a json file. 93 | * 94 | * @param[in] filename The file to load the edge topology from. 95 | * @return A struct containing the edge topology. 96 | * @throws std::runtime_error if unable to open the given file for writing. 97 | */ 98 | inline EdgeTopology load_edge_topology(std::string filename) 99 | { 100 | EdgeTopology edge_topology; 101 | std::ifstream file(filename); 102 | if (!file) 103 | { 104 | throw std::runtime_error("Error opening file for reading: " + filename); 105 | } 106 | cereal::JSONInputArchive output_archive(file); 107 | output_archive(cereal::make_nvp("edge_topology", edge_topology)); 108 | 109 | return edge_topology; 110 | }; 111 | 112 | } /* namespace morphablemodel */ 113 | } /* namespace eos */ 114 | 115 | #endif /* EOS_EDGETOPOLOGY_HPP */ 116 | -------------------------------------------------------------------------------- /include/eos/morphablemodel/ExpressionModel.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/morphablemodel/ExpressionModel.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_EXPRESSION_MODEL_HPP 23 | #define EOS_EXPRESSION_MODEL_HPP 24 | 25 | #include "eos/morphablemodel/PcaModel.hpp" 26 | #include "eos/morphablemodel/Blendshape.hpp" 27 | #include "eos/cpp17/variant.hpp" 28 | 29 | #include "Eigen/Core" 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | namespace eos { 36 | namespace morphablemodel { 37 | 38 | /** 39 | * @brief Type alias to represent an expression model, which can either consist of blendshapes or a PCA model. 40 | * 41 | * Defining a type alias so that we don't have to spell out the type everywhere. 42 | */ 43 | using ExpressionModel = cpp17::variant; 44 | 45 | /** 46 | * Returns a sample from the model with the given expression coefficients. 47 | * 48 | * The underlying expression model can be both a PcaModel as well as a Blendshapes model. 49 | * If a partial coefficient vector is given, it is filled with zeros up to the end. 50 | * 51 | * @param[in] expression_coefficients The coefficients used to generate the expression sample. 52 | * @return A model instance with given coefficients. 53 | */ 54 | inline Eigen::VectorXf draw_sample(const ExpressionModel& expression_model, 55 | std::vector expression_coefficients) 56 | { 57 | Eigen::VectorXf expression_sample; 58 | // Get a sample of the expression model, depending on whether it's a PcaModel or Blendshapes: 59 | if (cpp17::holds_alternative(expression_model)) 60 | { 61 | const auto& pca_expression_model = cpp17::get(expression_model); 62 | if (expression_coefficients.empty()) 63 | { 64 | expression_sample = pca_expression_model.get_mean(); 65 | } else 66 | { 67 | expression_sample = pca_expression_model.draw_sample(expression_coefficients); 68 | } 69 | } else if (cpp17::holds_alternative(expression_model)) 70 | { 71 | const auto& expression_blendshapes = cpp17::get(expression_model); 72 | assert(expression_blendshapes.size() > 0); 73 | if (expression_coefficients.empty()) 74 | { 75 | expression_sample.setZero(expression_blendshapes[0].deformation.rows()); 76 | } else 77 | { 78 | expression_sample = to_matrix(expression_blendshapes) * 79 | Eigen::Map(expression_coefficients.data(), 80 | expression_coefficients.size()); 81 | } 82 | } else 83 | { 84 | throw std::runtime_error("The given ExpressionModel doesn't contain an expression model in the form " 85 | "of a PcaModel or Blendshapes."); 86 | } 87 | return expression_sample; 88 | }; 89 | 90 | } /* namespace morphablemodel */ 91 | } /* namespace eos */ 92 | 93 | #endif /* EOS_EXPRESSION_MODEL_HPP */ 94 | -------------------------------------------------------------------------------- /include/eos/morphablemodel/coefficients.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/morphablemodel/coefficients.hpp 5 | * 6 | * Copyright 2016 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_COEFFICIENTS_HPP 23 | #define EOS_COEFFICIENTS_HPP 24 | 25 | #include "cereal/cereal.hpp" 26 | #include "cereal/archives/json.hpp" 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | namespace eos { 33 | namespace morphablemodel { 34 | 35 | /** 36 | * Saves coefficients (for example PCA shape coefficients) to a json file. 37 | * 38 | * @param[in] coefficients A vector of coefficients. 39 | * @param[in] filename The file to write. 40 | * @throws std::runtime_error if unable to open the given file for writing. 41 | */ 42 | inline void save_coefficients(std::vector coefficients, std::string filename) 43 | { 44 | std::ofstream file(filename); 45 | if (!file) 46 | { 47 | throw std::runtime_error("Error opening file for writing: " + filename); 48 | } 49 | cereal::JSONOutputArchive output_archive(file); 50 | output_archive(cereal::make_nvp("shape_coefficients", coefficients)); 51 | }; 52 | 53 | /** 54 | * Loads coefficients (for example PCA shape coefficients) from a json file. 55 | * 56 | * @param[in] filename The file to write. 57 | * @return Returns vector of floats. 58 | * @throws std::runtime_error if unable to open the given file for reading. 59 | */ 60 | inline std::vector load_coefficients(std::string filename) 61 | { 62 | std::vector coefficients; 63 | std::ifstream file(filename); 64 | if (!file) 65 | { 66 | throw std::runtime_error("Error opening file for reading: " + filename); 67 | } 68 | cereal::JSONInputArchive input_archive(file); 69 | input_archive(cereal::make_nvp("shape_coefficients", coefficients)); 70 | return coefficients; 71 | }; 72 | 73 | } /* namespace morphablemodel */ 74 | } /* namespace eos */ 75 | 76 | #endif /* EOS_COEFFICIENTS_HPP */ 77 | -------------------------------------------------------------------------------- /include/eos/morphablemodel/io/eigen_cerealisation.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/morphablemodel/io/eigen_cerealisation.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EIGENCEREALISATION_HPP_ 23 | #define EIGENCEREALISATION_HPP_ 24 | 25 | #include "cereal/cereal.hpp" 26 | 27 | #include "Eigen/Core" 28 | 29 | #include 30 | 31 | /** 32 | * @brief Serialisation of Eigen matrices for the serialisation 33 | * library cereal (http://uscilab.github.io/cereal/index.html). 34 | * 35 | * Contains serialisation for Eigen matrices to binary archives, i.e. matrices like 36 | * \c Eigen::MatrixXf, \c Eigen::Matrix4d, or \c Eigen::Vector3f. 37 | * 38 | * Todo: Add serialisation to and from JSON. Need to find out how to combine the two 39 | * variants of SFINAE that are used. 40 | */ 41 | namespace cereal { 42 | 43 | /** 44 | * @brief Serialise an Eigen::Matrix using cereal. 45 | * 46 | * Note: Writes the binary data from Matrix::data(), so not sure what happens if a matrix ever has 47 | * non-contiguous data (if that can ever happen with Eigen). 48 | * 49 | * @param[in] ar The archive to serialise to. 50 | * @param[in] matrix The matrix to serialise. 51 | */ 52 | template 53 | inline 54 | typename std::enable_if, Archive>::value, void>::type 55 | save(Archive& ar, const Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>& matrix) 56 | { 57 | const std::int32_t rows = static_cast(matrix.rows()); 58 | const std::int32_t cols = static_cast(matrix.cols()); 59 | ar(rows); 60 | ar(cols); 61 | ar(binary_data(matrix.data(), rows * cols * sizeof(_Scalar))); 62 | }; 63 | 64 | /** 65 | * @brief De-serialise an Eigen::Matrix using cereal. 66 | * 67 | * Reads the block of binary data back from a cereal archive into the Eigen::Matrix. 68 | * 69 | * @param[in] ar The archive to deserialise from. 70 | * @param[in] matrix The matrix to deserialise into. 71 | */ 72 | template 73 | inline 74 | typename std::enable_if, Archive>::value, void>::type 75 | load(Archive& ar, Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>& matrix) 76 | { 77 | std::int32_t rows; 78 | std::int32_t cols; 79 | ar(rows); 80 | ar(cols); 81 | 82 | matrix.resize(rows, cols); 83 | 84 | ar(binary_data(matrix.data(), static_cast(rows * cols * sizeof(_Scalar)))); 85 | }; 86 | 87 | } /* namespace cereal */ 88 | 89 | #endif /* EIGENCEREALISATION_HPP_ */ 90 | -------------------------------------------------------------------------------- /include/eos/render/Texture.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/Texture.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef TEXTURE_HPP_ 23 | #define TEXTURE_HPP_ 24 | 25 | #include "opencv2/core/core.hpp" 26 | #include "opencv2/imgproc/imgproc.hpp" 27 | 28 | #include 29 | #include 30 | 31 | namespace eos { 32 | namespace render { 33 | 34 | // TODO: Should go to detail:: namespace, or texturing/utils or whatever. 35 | inline unsigned int get_max_possible_mipmaps_num(unsigned int width, unsigned int height) 36 | { 37 | unsigned int mipmapsNum = 1; 38 | unsigned int size = std::max(width, height); 39 | 40 | if (size == 1) 41 | return 1; 42 | 43 | do 44 | { 45 | size >>= 1; 46 | mipmapsNum++; 47 | } while (size != 1); 48 | 49 | return mipmapsNum; 50 | }; 51 | 52 | inline bool is_power_of_two(int x) { return !(x & (x - 1)); }; 53 | 54 | /** 55 | * @brief Represents a texture for rendering. 56 | * 57 | * Represents a texture and mipmap levels for use in the renderer. 58 | * Todo: This whole class needs a major overhaul and documentation. 59 | */ 60 | class Texture 61 | { 62 | public: 63 | std::vector mipmaps; // make Texture a friend class of renderer, then move this to private? 64 | unsigned char widthLog, heightLog; // log2 of width and height of the base mip-level 65 | 66 | // private: 67 | // std::string filename; 68 | unsigned int mipmaps_num; 69 | }; 70 | 71 | // throws: ocv exc, runtime_ex 72 | inline Texture create_mipmapped_texture(cv::Mat image, unsigned int mipmapsNum = 0) 73 | { 74 | assert(image.type() == CV_8UC3 || image.type() == CV_8UC4); 75 | 76 | Texture texture; 77 | 78 | texture.mipmaps_num = 79 | (mipmapsNum == 0 ? get_max_possible_mipmaps_num(image.cols, image.rows) : mipmapsNum); 80 | /*if (mipmapsNum == 0) 81 | { 82 | uchar mmn = render::utils::MatrixUtils::getMaxPossibleMipmapsNum(image.cols, image.rows); 83 | this->mipmapsNum = mmn; 84 | } else 85 | { 86 | this->mipmapsNum = mipmapsNum; 87 | }*/ 88 | 89 | if (texture.mipmaps_num > 1) 90 | { 91 | if (!is_power_of_two(image.cols) || !is_power_of_two(image.rows)) 92 | { 93 | throw std::runtime_error("Error: Couldn't generate mipmaps, width or height not power of two."); 94 | } 95 | } 96 | if (image.type() == CV_8UC3) 97 | { 98 | image.convertTo(image, CV_8UC4); // Most often, the input img is CV_8UC3. Img is BGR. Add an alpha 99 | // channel. TODO: Actually I think this doesn't do anything. Below 100 | // line adds the 4th channel... 101 | cv::cvtColor(image, image, CV_BGR2BGRA); 102 | } 103 | 104 | int currWidth = image.cols; 105 | int currHeight = image.rows; 106 | std::vector mipmaps; 107 | for (unsigned int i = 0; i < texture.mipmaps_num; i++) 108 | { 109 | if (i == 0) 110 | { 111 | mipmaps.push_back(image); 112 | } else 113 | { 114 | cv::Mat currMipMap(currHeight, currWidth, CV_8UC4); 115 | cv::resize(mipmaps[i - 1], currMipMap, currMipMap.size()); 116 | mipmaps.push_back(currMipMap); 117 | } 118 | 119 | if (currWidth > 1) 120 | currWidth >>= 1; 121 | if (currHeight > 1) 122 | currHeight >>= 1; 123 | } 124 | texture.mipmaps = mipmaps; 125 | texture.widthLog = (uchar)(std::log(mipmaps[0].cols) / CV_LOG2 + 126 | 0.0001f); // std::epsilon or something? or why 0.0001f here? 127 | texture.heightLog = (uchar)( 128 | std::log(mipmaps[0].rows) / CV_LOG2 + 129 | 0.0001f); // Changed std::logf to std::log because it doesnt compile in linux (gcc 4.8). CHECK THAT 130 | return texture; 131 | }; 132 | 133 | } /* namespace render */ 134 | } /* namespace eos */ 135 | 136 | #endif /* TEXTURE_HPP_ */ 137 | -------------------------------------------------------------------------------- /include/eos/render/VertexShader.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/VertexShader.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef VERTEXSHADER_HPP_ 23 | #define VERTEXSHADER_HPP_ 24 | 25 | #include "glm/mat4x4.hpp" 26 | #include "glm/vec4.hpp" 27 | 28 | namespace eos { 29 | namespace render { 30 | 31 | /** 32 | * @brief A simple vertex shader that projects the vertex and returns the vertex in clip-space coordinates. 33 | */ 34 | class VertexShader 35 | { 36 | public: 37 | /** 38 | * @brief Projects the given vertex into clip-space and returns it. 39 | * 40 | * @param[in] vertex The vertex to project. 41 | * @param[in] model_view_matrix The model-view matrix. 42 | * @param[in] projection_matrix The projection matrix. 43 | * @tparam VT Vertex type. 44 | * @tparam VP Vertex precision. 45 | * @tparam MT Matrix type. 46 | * @tparam MP Matrix precision. 47 | * @return Vertex projected to clip space. 48 | */ 49 | template 50 | glm::tvec4 operator()(const glm::tvec4& vertex, 51 | const glm::tmat4x4& model_view_matrix, 52 | const glm::tmat4x4& projection_matrix) 53 | { 54 | return projection_matrix * model_view_matrix * vertex; 55 | }; 56 | }; 57 | 58 | } /* namespace render */ 59 | } /* namespace eos */ 60 | 61 | #endif /* VERTEXSHADER_HPP_ */ 62 | -------------------------------------------------------------------------------- /include/eos/render/detail/TriangleToRasterize.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/detail/TriangleToRasterize.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef TRIANGLETORASTERIZE_HPP_ 23 | #define TRIANGLETORASTERIZE_HPP_ 24 | 25 | #include "eos/render/detail/Vertex.hpp" 26 | 27 | #include "glm/glm.hpp" 28 | 29 | #include 30 | 31 | /** 32 | * The detail namespace contains implementations of internal functions, not part of the API we expose and not 33 | * meant to be used by a user. 34 | */ 35 | namespace eos { 36 | namespace render { 37 | namespace detail { 38 | 39 | // plane should probably go into its own file as well. 40 | class plane 41 | { 42 | public: 43 | plane() {} 44 | 45 | plane(float a, float b, float c, float d) 46 | { 47 | this->a = a; 48 | this->b = b; 49 | this->c = c; 50 | this->d = d; 51 | } 52 | 53 | plane(const glm::vec3& normal, float d = 0.0f) 54 | { 55 | this->a = normal[0]; 56 | this->b = normal[1]; 57 | this->c = normal[2]; 58 | this->d = d; 59 | } 60 | 61 | plane(const glm::vec3& point, const glm::vec3& normal) 62 | { 63 | a = normal[0]; 64 | b = normal[1]; 65 | c = normal[2]; 66 | d = -glm::dot(point, normal); 67 | } 68 | 69 | template 70 | plane(const glm::tvec3& point1, const glm::tvec3& point2, const glm::tvec3& point3) 71 | { 72 | const glm::tvec3 v1 = point2 - point1; 73 | const glm::tvec3 v2 = point3 - point1; 74 | glm::tvec3 normal = glm::cross(v1, v2); 75 | normal = glm::normalize(normal); 76 | 77 | a = normal[0]; 78 | b = normal[1]; 79 | c = normal[2]; 80 | d = -glm::dot(point1, normal); 81 | } 82 | 83 | void normalize() 84 | { 85 | const float length = std::sqrt(a * a + b * b + c * c); 86 | 87 | a /= length; 88 | b /= length; 89 | c /= length; 90 | } 91 | 92 | float getSignedDistanceFromPoint(const glm::vec3& point) const 93 | { 94 | return a * point[0] + b * point[1] + c * point[2] + d; 95 | } 96 | 97 | float getSignedDistanceFromPoint(const glm::vec4& point) const 98 | { 99 | return a * point[0] + b * point[1] + c * point[2] + d; 100 | } 101 | 102 | public: 103 | float a, b, c; 104 | float d; 105 | }; 106 | 107 | /** 108 | * A representation for a triangle that is to be rasterised. 109 | * Stores the enclosing bounding box of the triangle that is 110 | * calculated during rendering and used during rasterisation. 111 | * 112 | * Used in render_affine and render. 113 | */ 114 | struct TriangleToRasterize 115 | { 116 | Vertex v0, v1, v2; 117 | int min_x; 118 | int max_x; 119 | int min_y; 120 | int max_y; 121 | // Everything below is only used in the "normal" renderer, but not 122 | // in render_affine. 123 | double one_over_z0; 124 | double one_over_z1; 125 | double one_over_z2; 126 | // double one_over_v0ToLine12; 127 | // double one_over_v1ToLine20; 128 | // double one_over_v2ToLine01; 129 | plane alphaPlane; 130 | plane betaPlane; 131 | plane gammaPlane; 132 | double one_over_alpha_c; // those are only used for texturing -> float 133 | double one_over_beta_c; 134 | double one_over_gamma_c; 135 | float alpha_ffx; 136 | float beta_ffx; 137 | float gamma_ffx; 138 | float alpha_ffy; 139 | float beta_ffy; 140 | float gamma_ffy; 141 | }; 142 | 143 | } /* namespace detail */ 144 | } /* namespace render */ 145 | } /* namespace eos */ 146 | 147 | #endif /* TRIANGLETORASTERIZE_HPP_ */ 148 | -------------------------------------------------------------------------------- /include/eos/render/detail/Vertex.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/detail/Vertex.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef VERTEX_HPP_ 23 | #define VERTEX_HPP_ 24 | 25 | #include "glm/vec2.hpp" 26 | #include "glm/vec3.hpp" 27 | #include "glm/vec4.hpp" 28 | 29 | /** 30 | * The detail namespace contains implementations of internal functions, not part of the API we expose and not 31 | * meant to be used by a user. 32 | */ 33 | namespace eos { 34 | namespace render { 35 | namespace detail { 36 | 37 | /** 38 | * @brief A representation for a vertex during rendering, used internally. 39 | * 40 | * It's used to build the vertices that will be rendered, and new vertices that are generated by the renderer 41 | * (during clipping). 42 | * I should check at some point that no unnecessary copies of the vertex data is created in some places, but I 43 | * think it's pretty much fine. 44 | * 45 | * FragmentShader and SoftwareRenderer use this. 46 | * 47 | * This is the same as the one in the current render_detail.hpp, except that this is fully templated. 48 | * 49 | */ 50 | template 51 | struct Vertex 52 | { 53 | glm::tvec4 position; // XYZW 54 | glm::tvec3 color; // RGB order 55 | glm::tvec2 texcoords; // UV 56 | }; 57 | 58 | } /* namespace detail */ 59 | } /* namespace render */ 60 | } /* namespace eos */ 61 | 62 | #endif /* VERTEX_HPP_ */ 63 | -------------------------------------------------------------------------------- /include/eos/render/detail/texture_extraction_detail.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/detail/texture_extraction_detail.hpp 5 | * 6 | * Copyright 2014, 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef TEXTURE_EXTRACTION_DETAIL_HPP_ 23 | #define TEXTURE_EXTRACTION_DETAIL_HPP_ 24 | 25 | #include "eos/core/Image.hpp" 26 | #include "eos/core/Rect.hpp" 27 | #include "eos/render/detail/render_detail_utils.hpp" 28 | 29 | #include "glm/vec2.hpp" 30 | #include "glm/vec4.hpp" 31 | 32 | #include "Eigen/Core" 33 | 34 | /** 35 | * Implementations of internal functions, not part of the 36 | * API we expose and not meant to be used by a user. 37 | */ 38 | namespace eos { 39 | namespace render { 40 | namespace detail { 41 | 42 | /** 43 | * Computes whether the given point is inside (or on the border of) the triangle 44 | * formed out of the given three vertices. 45 | * 46 | * @param[in] point The point to check. 47 | * @param[in] triV0 First vertex. 48 | * @param[in] triV1 Second vertex. 49 | * @param[in] triV2 Third vertex. 50 | * @return Whether the point is inside the triangle. 51 | */ 52 | inline bool is_point_in_triangle(Eigen::Vector2f point, Eigen::Vector2f triV0, Eigen::Vector2f triV1, 53 | Eigen::Vector2f triV2) 54 | { 55 | // See http://www.blackpawn.com/texts/pointinpoly/ 56 | // Compute vectors 57 | const Eigen::Vector2f v0 = triV2 - triV0; 58 | const Eigen::Vector2f v1 = triV1 - triV0; 59 | const Eigen::Vector2f v2 = point - triV0; 60 | 61 | // Compute dot products 62 | const float dot00 = v0.dot(v0); 63 | const float dot01 = v0.dot(v1); 64 | const float dot02 = v0.dot(v2); 65 | const float dot11 = v1.dot(v1); 66 | const float dot12 = v1.dot(v2); 67 | 68 | // Compute barycentric coordinates 69 | const float invDenom = 1 / (dot00 * dot11 - dot01 * dot01); 70 | const float u = (dot11 * dot02 - dot01 * dot12) * invDenom; 71 | const float v = (dot00 * dot12 - dot01 * dot02) * invDenom; 72 | 73 | // Check if point is in triangle 74 | return (u >= 0) && (v >= 0) && (u + v < 1); 75 | }; 76 | 77 | /** 78 | * Checks whether all pixels in the given triangle are visible and 79 | * returns true if and only if the whole triangle is visible. 80 | * The vertices should be given in screen coordinates, but with their 81 | * z-values preserved, so they can be compared against the depthbuffer. 82 | * 83 | * Obviously the depthbuffer given should have been created with the same projection 84 | * matrix than the texture extraction is called with. 85 | * 86 | * Also, we don't do perspective-correct interpolation here I think, so only 87 | * use it with affine and orthographic projection matrices. 88 | * 89 | * @param[in] v0 First vertex, in screen coordinates (but still with their z-value). 90 | * @param[in] v1 Second vertex. 91 | * @param[in] v2 Third vertex. 92 | * @param[in] depthbuffer Pre-calculated depthbuffer. 93 | * @return True if the whole triangle is visible in the image. 94 | */ 95 | inline bool is_triangle_visible(const glm::tvec4& v0, const glm::tvec4& v1, 96 | const glm::tvec4& v2, const core::Image1d& depthbuffer) 97 | { 98 | // #Todo: Actually, only check the 3 vertex points, don't loop over the pixels - this should be enough. 99 | 100 | const auto viewport_width = depthbuffer.width(); 101 | const auto viewport_height = depthbuffer.height(); 102 | 103 | // Well, in in principle, we'd have to do the whole stuff as in render(), like 104 | // clipping against the frustums etc. 105 | // But as long as our model is fully on the screen, we're fine. Todo: Doublecheck that. 106 | 107 | if (!detail::are_vertices_ccw_in_screen_space(glm::tvec2(v0), glm::tvec2(v1), 108 | glm::tvec2(v2))) 109 | return false; 110 | 111 | const core::Rect bbox = detail::calculate_clipped_bounding_box( 112 | glm::tvec2(v0), glm::tvec2(v1), glm::tvec2(v2), viewport_width, viewport_height); 113 | const int minX = bbox.x; 114 | const int maxX = bbox.x + bbox.width; 115 | const int minY = bbox.y; 116 | const int maxY = bbox.y + bbox.height; 117 | 118 | // if (t.maxX <= t.minX || t.maxY <= t.minY) // Note: Can the width/height of the bbox be negative? Maybe we only need to check for equality here? 119 | // continue; // Also, I'm not entirely sure why I commented this out 120 | 121 | bool whole_triangle_is_visible = true; 122 | for (int yi = minY; yi <= maxY; yi++) 123 | { 124 | for (int xi = minX; xi <= maxX; xi++) 125 | { 126 | // we want centers of pixels to be used in computations. Do we? 127 | const float x = static_cast(xi) + 0.5f; 128 | const float y = static_cast(yi) + 0.5f; 129 | // these will be used for barycentric weights computation 130 | const double one_over_v0ToLine12 = 1.0 / detail::implicit_line(v0[0], v0[1], v1, v2); 131 | const double one_over_v1ToLine20 = 1.0 / detail::implicit_line(v1[0], v1[1], v2, v0); 132 | const double one_over_v2ToLine01 = 1.0 / detail::implicit_line(v2[0], v2[1], v0, v1); 133 | // affine barycentric weights 134 | const double alpha = detail::implicit_line(x, y, v1, v2) * one_over_v0ToLine12; 135 | const double beta = detail::implicit_line(x, y, v2, v0) * one_over_v1ToLine20; 136 | const double gamma = detail::implicit_line(x, y, v0, v1) * one_over_v2ToLine01; 137 | 138 | // if pixel (x, y) is inside the triangle or on one of its edges 139 | if (alpha >= 0 && beta >= 0 && gamma >= 0) 140 | { 141 | const double z_affine = alpha * static_cast(v0[2]) + 142 | beta * static_cast(v1[2]) + 143 | gamma * static_cast(v2[2]); 144 | if (z_affine < depthbuffer(yi, xi)) 145 | { 146 | whole_triangle_is_visible = false; 147 | break; 148 | } 149 | } 150 | } 151 | if (!whole_triangle_is_visible) 152 | { 153 | break; 154 | } 155 | } 156 | 157 | if (!whole_triangle_is_visible) 158 | { 159 | return false; 160 | } 161 | return true; 162 | }; 163 | 164 | } /* namespace detail */ 165 | } /* namespace render */ 166 | } /* namespace eos */ 167 | 168 | #endif /* TEXTURE_EXTRACTION_DETAIL_HPP_ */ 169 | -------------------------------------------------------------------------------- /include/eos/render/draw_utils.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/draw_utils.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef RENDER_DRAW_UTILS_HPP_ 23 | #define RENDER_DRAW_UTILS_HPP_ 24 | 25 | #include "eos/core/Mesh.hpp" 26 | #include "eos/render/detail/render_detail.hpp" 27 | 28 | #include "glm/gtc/matrix_transform.hpp" 29 | #include "glm/mat4x4.hpp" 30 | #include "glm/vec4.hpp" 31 | 32 | #include "opencv2/core/core.hpp" 33 | #include "opencv2/imgproc/imgproc.hpp" 34 | 35 | namespace eos { 36 | namespace render { 37 | 38 | /** 39 | * Draws the given mesh as wireframe into the image. 40 | * 41 | * It does backface culling, i.e. draws only vertices in CCW order. 42 | * 43 | * @param[in] image An image to draw into. 44 | * @param[in] mesh The mesh to draw. 45 | * @param[in] modelview Model-view matrix to draw the mesh. 46 | * @param[in] projection Projection matrix to draw the mesh. 47 | * @param[in] viewport Viewport to draw the mesh. 48 | * @param[in] color Colour of the mesh to be drawn. 49 | */ 50 | inline void draw_wireframe(cv::Mat image, const core::Mesh& mesh, glm::mat4x4 modelview, 51 | glm::mat4x4 projection, glm::vec4 viewport, 52 | cv::Scalar color = cv::Scalar(0, 255, 0, 255)) 53 | { 54 | for (const auto& triangle : mesh.tvi) 55 | { 56 | const auto p1 = glm::project( 57 | {mesh.vertices[triangle[0]][0], mesh.vertices[triangle[0]][1], mesh.vertices[triangle[0]][2]}, 58 | modelview, projection, viewport); 59 | const auto p2 = glm::project( 60 | {mesh.vertices[triangle[1]][0], mesh.vertices[triangle[1]][1], mesh.vertices[triangle[1]][2]}, 61 | modelview, projection, viewport); 62 | const auto p3 = glm::project( 63 | {mesh.vertices[triangle[2]][0], mesh.vertices[triangle[2]][1], mesh.vertices[triangle[2]][2]}, 64 | modelview, projection, viewport); 65 | if (render::detail::are_vertices_ccw_in_screen_space(glm::vec2(p1), glm::vec2(p2), glm::vec2(p3))) 66 | { 67 | cv::line(image, cv::Point(p1.x, p1.y), cv::Point(p2.x, p2.y), color); 68 | cv::line(image, cv::Point(p2.x, p2.y), cv::Point(p3.x, p3.y), color); 69 | cv::line(image, cv::Point(p3.x, p3.y), cv::Point(p1.x, p1.y), color); 70 | } 71 | } 72 | }; 73 | 74 | /** 75 | * Draws the texture coordinates (uv-coords) of the given mesh 76 | * into an image by looping over the triangles and drawing each 77 | * triangle's texcoords. 78 | * 79 | * Note/Todo: This function has a slight problems, the lines do not actually get 80 | * drawn blue, if the image is 8UC4. Well if I save a PNG, it is blue. Not sure. 81 | * 82 | * @param[in] mesh A mesh with texture coordinates. 83 | * @param[in] image An optional image to draw onto. 84 | * @return An image with the texture coordinate triangles drawn in it, 512x512 if no image is given. 85 | */ 86 | inline cv::Mat draw_texcoords(core::Mesh mesh, cv::Mat image = cv::Mat()) 87 | { 88 | using cv::Point2f; 89 | using cv::Scalar; 90 | if (image.empty()) 91 | { 92 | image = cv::Mat(512, 512, CV_8UC4, Scalar(0.0f, 0.0f, 0.0f, 255.0f)); 93 | } 94 | 95 | for (const auto& triIdx : mesh.tvi) 96 | { 97 | cv::line( 98 | image, 99 | Point2f(mesh.texcoords[triIdx[0]][0] * image.cols, mesh.texcoords[triIdx[0]][1] * image.rows), 100 | Point2f(mesh.texcoords[triIdx[1]][0] * image.cols, mesh.texcoords[triIdx[1]][1] * image.rows), 101 | Scalar(255.0f, 0.0f, 0.0f)); 102 | cv::line( 103 | image, 104 | Point2f(mesh.texcoords[triIdx[1]][0] * image.cols, mesh.texcoords[triIdx[1]][1] * image.rows), 105 | Point2f(mesh.texcoords[triIdx[2]][0] * image.cols, mesh.texcoords[triIdx[2]][1] * image.rows), 106 | Scalar(255.0f, 0.0f, 0.0f)); 107 | cv::line( 108 | image, 109 | Point2f(mesh.texcoords[triIdx[2]][0] * image.cols, mesh.texcoords[triIdx[2]][1] * image.rows), 110 | Point2f(mesh.texcoords[triIdx[0]][0] * image.cols, mesh.texcoords[triIdx[0]][1] * image.rows), 111 | Scalar(255.0f, 0.0f, 0.0f)); 112 | } 113 | return image; 114 | }; 115 | 116 | } /* namespace render */ 117 | } /* namespace eos */ 118 | 119 | #endif /* RENDER_DRAW_UTILS_HPP_ */ 120 | -------------------------------------------------------------------------------- /include/eos/render/render_affine.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/render_affine.hpp 5 | * 6 | * Copyright 2014, 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef RENDER_AFFINE_HPP_ 23 | #define RENDER_AFFINE_HPP_ 24 | 25 | #include "eos/core/Image.hpp" 26 | #include "eos/core/image/utils.hpp" 27 | #include "eos/core/Mesh.hpp" 28 | #include "eos/core/Rect.hpp" 29 | #include "eos/render/detail/render_affine_detail.hpp" 30 | #include "eos/render/detail/render_detail_utils.hpp" 31 | 32 | #include "glm/vec2.hpp" 33 | #include "glm/vec3.hpp" 34 | #include "glm/vec4.hpp" 35 | 36 | #include "Eigen/Core" 37 | 38 | #include 39 | 40 | namespace eos { 41 | namespace render { 42 | 43 | /** 44 | * Renders the mesh using the given affine camera matrix and returns the colour and depth buffer images. 45 | * The camera matrix should be one estimated with fitting::estimate_affine_camera (Hartley & Zisserman 46 | * algorithm). 47 | * 48 | * If the given mesh is a shape-only mesh without vertex-colour information, the vertices will be rendered in 49 | * grey. 50 | * 51 | * #Todo: May consider an overload where we pass in an image, use that as colourbuffer and draw over it. 52 | * #Todo: Add texture rendering to this. Then, create an additional function in extract_texure that is fully 53 | * optimised for only the extraction. 54 | * 55 | * @param[in] mesh A 3D mesh. 56 | * @param[in] affine_camera_matrix 3x4 affine camera matrix. 57 | * @param[in] viewport_width Screen width. 58 | * @param[in] viewport_height Screen height. 59 | * @param[in] do_backface_culling Whether the renderer should perform backface culling. 60 | * @return A pair with the colourbuffer as its first element and the depthbuffer as the second element. 61 | */ 62 | inline std::pair render_affine(const core::Mesh& mesh, 63 | Eigen::Matrix affine_camera_matrix, 64 | int viewport_width, int viewport_height, 65 | bool do_backface_culling = true) 66 | { 67 | assert(mesh.vertices.size() == mesh.colors.size() || 68 | mesh.colors.empty()); // The number of vertices has to be equal for both shape and colour, or, 69 | // alternatively, it has to be a shape-only model. 70 | // assert(mesh.vertices.size() == mesh.texcoords.size() || mesh.texcoords.empty()); // same for the 71 | // texcoords 72 | 73 | using eos::core::Image1d; 74 | using eos::core::Image4u; 75 | using std::vector; 76 | 77 | core::Image4u colorbuffer = 78 | core::image::zeros>(viewport_height, viewport_width); 79 | core::Image1d depthbuffer = 80 | core::image::constant(viewport_height, viewport_width, std::numeric_limits::max()); 81 | 82 | const Eigen::Matrix affine_with_z = 83 | detail::calculate_affine_z_direction(affine_camera_matrix); 84 | 85 | vector> projected_vertices; 86 | projected_vertices.reserve(mesh.vertices.size()); 87 | for (int i = 0; i < mesh.vertices.size(); ++i) 88 | { 89 | const Eigen::Vector4f vertex_screen_coords = 90 | affine_with_z * 91 | Eigen::Vector4f(mesh.vertices[i][0], mesh.vertices[i][1], mesh.vertices[i][2], 1.0f); 92 | const glm::tvec4 vertex_screen_coords_glm(vertex_screen_coords(0), vertex_screen_coords(1), 93 | vertex_screen_coords(2), vertex_screen_coords(3)); 94 | glm::tvec3 vertex_colour; 95 | if (mesh.colors.empty()) 96 | { 97 | vertex_colour = glm::tvec3(0.5f, 0.5f, 0.5f); 98 | } else 99 | { 100 | vertex_colour = glm::tvec3(mesh.colors[i][0], mesh.colors[i][1], mesh.colors[i][2]); 101 | } 102 | projected_vertices.push_back( 103 | detail::Vertex{vertex_screen_coords_glm, vertex_colour, 104 | glm::tvec2(mesh.texcoords[i][0], mesh.texcoords[i][1])}); 105 | } 106 | 107 | // All vertices are screen-coordinates now 108 | vector triangles_to_raster; 109 | for (const auto& tri_indices : mesh.tvi) 110 | { 111 | if (do_backface_culling) 112 | { 113 | if (!detail::are_vertices_ccw_in_screen_space( 114 | glm::tvec2(projected_vertices[tri_indices[0]].position), 115 | glm::tvec2(projected_vertices[tri_indices[1]].position), 116 | glm::tvec2(projected_vertices[tri_indices[2]].position))) 117 | continue; // don't render this triangle 118 | } 119 | 120 | // Get the bounding box of the triangle: 121 | // take care: What do we do if all 3 vertices are not visible. Seems to work on a test case. 122 | const core::Rect bounding_box = detail::calculate_clipped_bounding_box( 123 | glm::tvec2(projected_vertices[tri_indices[0]].position), 124 | glm::tvec2(projected_vertices[tri_indices[1]].position), 125 | glm::tvec2(projected_vertices[tri_indices[2]].position), viewport_width, viewport_height); 126 | const auto min_x = bounding_box.x; 127 | const auto max_x = bounding_box.x + bounding_box.width; 128 | const auto min_y = bounding_box.y; 129 | const auto max_y = bounding_box.y + bounding_box.height; 130 | 131 | if (max_x <= min_x || max_y <= min_y) // Note: Can the width/height of the bbox be negative? Maybe we only need to check for equality here? 132 | continue; 133 | 134 | detail::TriangleToRasterize t; 135 | t.min_x = min_x; 136 | t.max_x = max_x; 137 | t.min_y = min_y; 138 | t.max_y = max_y; 139 | t.v0 = projected_vertices[tri_indices[0]]; 140 | t.v1 = projected_vertices[tri_indices[1]]; 141 | t.v2 = projected_vertices[tri_indices[2]]; 142 | 143 | triangles_to_raster.push_back(t); 144 | } 145 | 146 | // Raster all triangles, i.e. colour the pixel values and write the z-buffer 147 | for (auto&& triangle : triangles_to_raster) 148 | { 149 | detail::raster_triangle_affine(triangle, colorbuffer, depthbuffer); 150 | } 151 | return std::make_pair(colorbuffer, depthbuffer); 152 | }; 153 | 154 | } /* namespace render */ 155 | } /* namespace eos */ 156 | 157 | #endif /* RENDER_AFFINE_HPP_ */ 158 | -------------------------------------------------------------------------------- /include/eos/render/texture_extraction.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVSSP/eos/f8b235bf8da1eef947a62dc09e23ecde2c9c0f3b/include/eos/render/texture_extraction.hpp -------------------------------------------------------------------------------- /include/eos/render/utils.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: include/eos/render/utils.hpp 5 | * 6 | * Copyright 2014, 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef RENDER_UTILS_HPP_ 23 | #define RENDER_UTILS_HPP_ 24 | 25 | #include "glm/vec3.hpp" 26 | #include "glm/geometric.hpp" 27 | 28 | #include "Eigen/Core" 29 | 30 | namespace eos { 31 | namespace render { 32 | 33 | /** 34 | * Transforms a point from clip space ([-1, 1] x [-1, 1]) to 35 | * image (screen) coordinates, i.e. the window transform. 36 | * Note that the y-coordinate is flipped because the image origin 37 | * is top-left while in clip space top is +1 and bottom is -1. 38 | * No z-division is performed. 39 | * Note: It should rather be called from NDC to screen space? 40 | * 41 | * Exactly conforming to the OpenGL viewport transform, except that 42 | * we flip y at the end. 43 | * Qt: Origin top-left. OpenGL: bottom-left. OCV: top-left. 44 | * 45 | * @param[in] clip_coordinates A point in clip coordinates. 46 | * @param[in] screen_width Width of the screen or window. 47 | * @param[in] screen_height Height of the screen or window. 48 | * @return A vector with x and y coordinates transformed to screen space. 49 | */ 50 | inline glm::vec2 clip_to_screen_space(const glm::vec2& clip_coordinates, int screen_width, int screen_height) 51 | { 52 | // Window transform: 53 | const float x_ss = (clip_coordinates[0] + 1.0f) * (screen_width / 2.0f); 54 | const float y_ss = 55 | screen_height - (clip_coordinates[1] + 1.0f) * 56 | (screen_height / 2.0f); // also flip y; Qt: Origin top-left. OpenGL: bottom-left. 57 | return glm::vec2(x_ss, y_ss); 58 | /* Note: What we do here is equivalent to 59 | x_w = (x * vW/2) + vW/2; 60 | However, Shirley says we should do: 61 | x_w = (x * vW/2) + (vW-1)/2; 62 | (analogous for y) 63 | Todo: Check the consequences. 64 | */ 65 | }; 66 | 67 | /** 68 | * Transforms a point from image (screen) coordinates to 69 | * clip space ([-1, 1] x [-1, 1]). 70 | * Note that the y-coordinate is flipped because the image origin 71 | * is top-left while in clip space top is +1 and bottom is -1. 72 | * 73 | * @param[in] screen_coordinates A point in screen coordinates. 74 | * @param[in] screen_width Width of the screen or window. 75 | * @param[in] screen_height Height of the screen or window. 76 | * @return A vector with x and y coordinates transformed to clip space. 77 | */ 78 | /*inline cv::Vec2f screen_to_clip_space(const cv::Vec2f& screen_coordinates, int screen_width, int screen_height) 79 | { 80 | const float x_cs = screen_coordinates[0] / (screen_width / 2.0f) - 1.0f; 81 | float y_cs = screen_coordinates[1] / (screen_height / 2.0f) - 1.0f; 82 | y_cs *= -1.0f; 83 | return cv::Vec2f(x_cs, y_cs); 84 | };*/ 85 | 86 | template 87 | glm::tvec2 clip_to_screen_space(const T clip_coord_x, const T clip_coord_y, int screen_width, 88 | int screen_height) 89 | { 90 | // Todo: See/copy notes from utils.hpp/clip_to_screen_space. 91 | const T x_ss = (clip_coord_x + T(1)) * (screen_width / 2.0); 92 | const T y_ss = 93 | screen_height - (clip_coord_y + T(1)) * 94 | (screen_height / 2.0); // also flip y; Qt: Origin top-left. OpenGL: bottom-left. 95 | return glm::tvec2(x_ss, y_ss); 96 | }; 97 | 98 | /** 99 | * Calculates the normal of a face (or triangle), i.e. the 100 | * per-face normal. Return normal will be normalised. 101 | * Assumes the triangle is given in CCW order, i.e. vertices 102 | * in counterclockwise order on the screen are front-facing. 103 | * 104 | * @param[in] v0 First vertex. 105 | * @param[in] v1 Second vertex. 106 | * @param[in] v2 Third vertex. 107 | * @return The unit-length normal of the given triangle. 108 | */ 109 | inline Eigen::Vector3f compute_face_normal(const Eigen::Vector3f& v0, const Eigen::Vector3f& v1, 110 | const Eigen::Vector3f& v2) 111 | { 112 | Eigen::Vector3f n = (v1 - v0).cross(v2 - v0); // v0-to-v1 x v0-to-v2 113 | return n.normalized(); 114 | }; 115 | 116 | // Todo: Doxygen. Actually this is the overload that's probably most used? 117 | inline Eigen::Vector3f compute_face_normal(const Eigen::Vector4f& v0, const Eigen::Vector4f& v1, 118 | const Eigen::Vector4f& v2) 119 | { 120 | Eigen::Vector4f n = (v1 - v0).cross3(v2 - v0); // v0-to-v1 x v0-to-v2 121 | return n.head<3>().normalized(); 122 | }; 123 | 124 | /** 125 | * Computes the normal of a face (or triangle), i.e. the 126 | * per-face normal. Return normal will be normalised. 127 | * Assumes the triangle is given in CCW order, i.e. vertices 128 | * in counterclockwise order on the screen are front-facing. 129 | * 130 | * @param[in] v0 First vertex. 131 | * @param[in] v1 Second vertex. 132 | * @param[in] v2 Third vertex. 133 | * @return The unit-length normal of the given triangle. 134 | */ 135 | inline glm::vec3 compute_face_normal(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2) 136 | { 137 | glm::vec3 n = glm::cross(v1 - v0, v2 - v0); // v0-to-v1 x v0-to-v2 138 | n = glm::normalize(n); 139 | return n; 140 | }; 141 | 142 | } /* namespace render */ 143 | } /* namespace eos */ 144 | 145 | #endif /* RENDER_UTILS_HPP_ */ 146 | -------------------------------------------------------------------------------- /include/eos/video/Keyframe.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVSSP/eos/f8b235bf8da1eef947a62dc09e23ecde2c9c0f3b/include/eos/video/Keyframe.hpp -------------------------------------------------------------------------------- /initial_cache.cmake.template: -------------------------------------------------------------------------------- 1 | # NOTE: It is recommended to use vcpkg (https://github.com/Microsoft/vcpkg/) to install the dependencies for eos. 2 | # However, for users who wish to install the dependencies manually, this file might be helpful to set the library paths explicitly. 3 | 4 | # Mechanism via FindLIB.cmake 5 | # ============================== 6 | # Boost: 7 | # ------- 8 | # Windows: Download the pre-built binaries from http://sourceforge.net/projects/boost/files/boost-binaries/ for VS2017 (msvc-14.1 64bit). 9 | # Either set the windows PATH variable to "\lib64-msvc-14.1" and CMake will find it, or, set: 10 | #set(BOOST_ROOT "C:/boost" CACHE PATH "Boost search location" FORCE) 11 | # Linux: Boost can usually be installed via a package manager (e.g. apt-get install boost-all-dev) and this variable can be left uncommented. 12 | #set(BOOST_ROOT "/home/user/boost/install" CACHE PATH "Boost search location" FORCE) 13 | 14 | 15 | # Mechanism via ConfigLIB.cmake in 3rd party library directory 16 | # ============================== 17 | # OpenCV: 18 | # ------- 19 | # Windows: Download the package from opencv.org, use 2.4.13 or newer (e.g. 3.x), which it include binaries for VS2017. Set this path accordingly. 20 | #set(OpenCV_DIR "C:/opencv/install" CACHE PATH "Location of OpenCVConfig.cmake" FORCE) 21 | # Linux: Usually can be left blank but it can be used if OpenCV is not found. 22 | #set(OpenCV_DIR "/home/user/opencv/install/share/OpenCV" CACHE PATH "Location of OpenCVConfig.cmake" FORCE) 23 | # 24 | # Ceres: 25 | # ------- 26 | #set(Ceres_DIR "C:/ceres/install-vs2017/CMake" CACHE PATH "Location of CeresConfig.cmake" FORCE) 27 | 28 | # Set the path to the python interpreter if you want to build the python bindings and it doesn't find it automatically: 29 | # ============================== 30 | #set(PYTHON_EXECUTABLE "C:/Users/user/AppData/Local/Programs/Python/Python36/python.exe" CACHE PATH "Path to the python interpreter." FORCE) 31 | 32 | # Set the path to the Matlab root directory if you want to build the Matlab bindings and it doesn't find Matlab automatically: 33 | # ============================== 34 | #set(Matlab_ROOT_DIR "/opt/matlab" CACHE PATH "Path to the Matlab installation directory." FORCE) 35 | 36 | # Configuration options 37 | # ============================== 38 | set(EOS_BUILD_EXAMPLES ON CACHE BOOL "Build the example applications." FORCE) 39 | set(EOS_BUILD_CERES_EXAMPLE OFF CACHE BOOL "Build the fit-model-ceres example (requires Ceres)." FORCE) 40 | set(EOS_BUILD_UTILS OFF CACHE BOOL "Build utility applications." FORCE) 41 | set(EOS_BUILD_DOCUMENTATION OFF CACHE BOOL "Build the library documentation." FORCE) 42 | set(EOS_GENERATE_PYTHON_BINDINGS OFF CACHE BOOL "Build python bindings. Requires python to be installed." FORCE) 43 | set(EOS_GENERATE_MATLAB_BINDINGS OFF CACHE BOOL "Build Matlab bindings. Requires Matlab with the compiler installed or the Matlab Compiler Runtime." FORCE) 44 | -------------------------------------------------------------------------------- /matlab/+eos/+fitting/fit_shape_and_pose.m: -------------------------------------------------------------------------------- 1 | function [mesh, rendering_parameters] = fit_shape_and_pose(morphable_model, ... 2 | blendshapes, landmarks, landmark_mapper, image_width, image_height, ... 3 | edge_topology, contour_landmarks, model_contour, num_iterations, ... 4 | num_shape_coefficients_to_fit, lambda) 5 | % FIT_SHAPE_AND_POSE Fit a 3DMM shape model to landmarks. 6 | % [ mesh, rendering_parameters ] = FIT_SHAPE_AND_POSE(morphable_model, ... 7 | % blendshapes, landmarks, landmark_mapper, image_width, image_height, ... 8 | % edge_topology, contour_landmarks, model_contour, num_iterations, ... 9 | % num_shape_coefficients_to_fit, lambda) 10 | % 11 | % This function fits a 3D Morphable Model to landmarks in an image. 12 | % It fits the pose (camera), PCA shape model, and expression blendshapes 13 | % in an iterative way. 14 | % 15 | % landmarks must be a 68 x 2 matrix with ibug landmarks, in the order 16 | % from 1 to 68. 17 | % 18 | % Default values for some of the parameters: num_iterations = 5, 19 | % num_shape_coefficients_to_fit = all (-1), and lambda = 30.0. 20 | % 21 | % Please see the C++ documentation for the description of the parameters: 22 | % http://patrikhuber.github.io/eos/doc/ (TODO: Update to v0.9.1!) 23 | % 24 | % NOTE: In contrast to the C++ function, this Matlab function expects the 25 | % morphable_model, blendshapes, landmark_mapper, edge_topology, 26 | % contour_landmarks and model_contour as *filenames* to the respective 27 | % files in the eos/share/ directory, and not the objects directly. 28 | 29 | if(~isa(landmarks,'double')) 30 | error('Please specify the landmarks as type double.'); 31 | end 32 | 33 | % We'll use default values to the following arguments, if they're not 34 | % provided: 35 | if (~exist('edge_topology', 'var')), edge_topology = '../share/sfm_3448_edge_topology.json'; end 36 | if (~exist('contour_landmarks', 'var')), contour_landmarks = '../share/ibug_to_sfm.txt'; end 37 | if (~exist('model_contour', 'var')), model_contour = '../share/sfm_model_contours.json'; end 38 | if (~exist('num_iterations', 'var')), num_iterations = 5; end 39 | if (~exist('num_shape_coefficients_to_fit', 'var')), num_shape_coefficients_to_fit = -1; end 40 | if (~exist('lambda', 'var')), lambda = 30.0; end 41 | 42 | [ mesh, rendering_parameters ] = fitting(morphable_model, blendshapes, landmarks, landmark_mapper, image_width, image_height, edge_topology, contour_landmarks, model_contour, num_iterations, num_shape_coefficients_to_fit, lambda); 43 | 44 | end 45 | -------------------------------------------------------------------------------- /matlab/+eos/+fitting/private/fitting.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: matlab/+eos/+fitting/private/fitting.cpp 5 | * 6 | * Copyright 2016-2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #include "eos/fitting/fitting.hpp" 21 | #include "eos/core/LandmarkMapper.hpp" 22 | #include "eos/core/Mesh.hpp" 23 | #include "eos/fitting/RenderingParameters.hpp" 24 | #include "eos/fitting/contour_correspondence.hpp" 25 | #include "eos/morphablemodel/Blendshape.hpp" 26 | #include "eos/morphablemodel/EdgeTopology.hpp" 27 | #include "eos/morphablemodel/MorphableModel.hpp" 28 | #include "eos/cpp17/optional.hpp" 29 | 30 | #include "mexplus_eigen.hpp" 31 | #include "mexplus_eos_types.hpp" 32 | 33 | #include "mexplus.h" 34 | 35 | #include "Eigen/Core" 36 | 37 | #include "mex.h" 38 | 39 | #include 40 | 41 | void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) 42 | { 43 | using std::string; 44 | using namespace eos; 45 | using namespace mexplus; 46 | 47 | // Check for proper number of input and output arguments: 48 | int num_input_args = 12; 49 | int num_output_args = 2; 50 | if (nrhs != num_input_args) 51 | { 52 | mexErrMsgIdAndTxt("eos:fitting:nargin", "fit_shape_and_pose requires 12 input arguments."); 53 | } 54 | if (nlhs != num_output_args) 55 | { 56 | mexErrMsgIdAndTxt("eos:fitting:nargout", "fit_shape_and_pose returns two output arguments."); 57 | } 58 | 59 | InputArguments input(nrhs, prhs, num_input_args); 60 | const auto morphablemodel_file = input.get(0); 61 | const auto blendshapes_file = input.get(1); 62 | const auto landmarks_in = input.get(2); 63 | const auto mapper_file = input.get(3); 64 | const auto image_width = input.get(4); 65 | const auto image_height = input.get(5); 66 | const auto edgetopo_file = input.get(6); 67 | const auto contour_lms_file = input.get(7); 68 | const auto model_cnt_file = input.get(8); 69 | const auto num_iterations = input.get(9); 70 | const auto num_shape_coeffs = input.get(10); 71 | const auto lambda = input.get(11); 72 | 73 | if (landmarks_in.rows() != 68 || landmarks_in.cols() != 2) 74 | { 75 | mexErrMsgIdAndTxt( 76 | "eos:fitting:argin", 77 | "Given landmarks must be a 68 x 2 vector with ibug landmarks, in the order from 1 to 68."); 78 | } 79 | // Convert the landmarks (given as matrix in Matlab) to a LandmarkCollection: 80 | core::LandmarkCollection landmarks; 81 | for (int i = 0; i < 68; ++i) 82 | { 83 | landmarks.push_back(core::Landmark{ 84 | std::to_string(i + 1), Eigen::Vector2f(landmarks_in(i, 0), landmarks_in(i, 1))}); 85 | } 86 | 87 | // Load everything: 88 | morphablemodel::MorphableModel morphable_model_with_expressions; 89 | { 90 | const auto morphable_model = morphablemodel::load_model(morphablemodel_file); 91 | const auto blendshapes = morphablemodel::load_blendshapes(blendshapes_file); 92 | morphable_model_with_expressions = morphablemodel::MorphableModel( 93 | morphable_model.get_shape_model(), blendshapes, morphable_model.get_color_model(), cpp17::nullopt, 94 | morphable_model.get_texture_coordinates()); 95 | } 96 | const core::LandmarkMapper landmark_mapper(mapper_file); 97 | const auto edge_topology = morphablemodel::load_edge_topology(edgetopo_file); 98 | const auto contour_landmarks = fitting::ContourLandmarks::load(contour_lms_file); 99 | const auto model_contour = fitting::ModelContour::load(model_cnt_file); 100 | const cpp17::optional num_shape_coefficients_to_fit = 101 | num_shape_coeffs == -1 ? cpp17::nullopt : cpp17::optional(num_shape_coeffs); 102 | 103 | // Now do the actual fitting: 104 | core::Mesh mesh; 105 | fitting::RenderingParameters rendering_parameters; 106 | std::tie(mesh, rendering_parameters) = 107 | fitting::fit_shape_and_pose(morphable_model_with_expressions, landmarks, landmark_mapper, image_width, 108 | image_height, edge_topology, contour_landmarks, model_contour, 109 | num_iterations, num_shape_coefficients_to_fit, lambda); 110 | 111 | // Return the mesh and the rendering_parameters: 112 | OutputArguments output(nlhs, plhs, num_output_args); 113 | output.set(0, mesh); 114 | output.set(1, rendering_parameters); 115 | }; 116 | -------------------------------------------------------------------------------- /matlab/+eos/+render/extract_texture.m: -------------------------------------------------------------------------------- 1 | function [ texturemap ] = extract_texture(mesh, rendering_params, image, ... 2 | compute_view_angle, isomap_resolution) 3 | % EXTRACT_TEXTURE Extracts the texture from an image and returns the texture map. 4 | % [ texturemap ] = EXTRACT_TEXTURE(mesh, rendering_params, image, ... 5 | % compute_view_angle, isomap_resolution) 6 | % 7 | % Extracts the texture of the face from the given image and stores it as 8 | % isomap (a rectangular texture map). 9 | % 10 | % Default values for the parameters: compute_view_angle = false, 11 | % isomap_resolution = 512. 12 | % 13 | % Please see the C++ documentation for the full description: 14 | % http://patrikhuber.github.io/eos/doc/ (TODO: Update to v0.10.1!) 15 | 16 | % We'll use default values to the following arguments, if they're not 17 | % provided: 18 | if (~exist('compute_view_angle', 'var')), compute_view_angle = false; end 19 | if (~exist('isomap_resolution', 'var')), isomap_resolution = 512; end 20 | 21 | [ texturemap ] = render('extract_texture', mesh, rendering_params, image, compute_view_angle, isomap_resolution); 22 | 23 | end 24 | -------------------------------------------------------------------------------- /matlab/+eos/+render/private/render.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: matlab/+eos/+render/private/render.cpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #include "eos/render/render.hpp" 21 | #include "eos/core/Image_opencv_interop.hpp" 22 | #include "eos/core/Mesh.hpp" 23 | #include "eos/fitting/RenderingParameters.hpp" 24 | #include "eos/render/Texture.hpp" 25 | #include "eos/render/texture_extraction.hpp" 26 | 27 | #include "mexplus_eos_types.hpp" 28 | #include "mexplus_opencv.hpp" 29 | 30 | #include "mexplus.h" 31 | #include "mexplus/dispatch.h" 32 | 33 | #include "opencv2/core/core.hpp" 34 | 35 | #include "mex.h" 36 | 37 | using namespace eos; 38 | using namespace mexplus; 39 | 40 | MEX_DEFINE(extract_texture)(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) 41 | { 42 | // Check for proper number of input and output arguments: 43 | if (nrhs != 5) 44 | { 45 | mexErrMsgIdAndTxt("eos:render:nargin", "extract_texture requires 5 input arguments."); 46 | } 47 | if (nlhs != 1) 48 | { 49 | mexErrMsgIdAndTxt("eos:render:nargout", "extract_texture returns one output argument."); 50 | } 51 | 52 | InputArguments input(nrhs, prhs, 5); 53 | const auto mesh = input.get(0); 54 | const auto rendering_params = input.get(1); 55 | const auto image = input.get(2); 56 | const auto compute_view_angle = input.get(3); // default: false 57 | const auto isomap_resolution = input.get(4); // default: 512 58 | 59 | // We expect to be given a RGB image. Let's convert it to BGR for OpenCV. 60 | // Actually, it doesn't matter at all for the texture extraction - just keep it! 61 | // cv::Mat image_as_bgr;// = image.clone(); 62 | // cv::cvtColor(image, image_as_bgr, cv::COLOR_RGB2BGR); 63 | 64 | // Now do the actual extraction: 65 | const auto affine_from_ortho = 66 | fitting::get_3x4_affine_camera_matrix(rendering_params, image.cols, image.rows); 67 | const auto isomap = 68 | render::extract_texture(mesh, affine_from_ortho, core::from_mat(image), compute_view_angle, 69 | render::TextureInterpolation::NearestNeighbour, isomap_resolution); 70 | const auto isomap_mat = core::to_mat(isomap); 71 | 72 | // Return the extracted texture map: 73 | OutputArguments output(nlhs, plhs, 1); 74 | output.set(0, isomap_mat); 75 | }; 76 | 77 | MEX_DEFINE(render)(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) 78 | { 79 | // Check for proper number of input and output arguments: 80 | if (nrhs != 6) 81 | { 82 | mexErrMsgIdAndTxt("eos:render:nargin", "render requires 6 input arguments."); 83 | } 84 | if (nlhs != 2) 85 | { 86 | mexErrMsgIdAndTxt("eos:render:nargout", "render returns two output arguments."); 87 | } 88 | 89 | InputArguments input(nrhs, prhs, 6); 90 | const auto mesh = input.get(0); 91 | const auto modelview_matrix = input.get(1); 92 | const auto projection_matrix = input.get(2); 93 | const auto image_width = input.get(3); 94 | const auto image_height = input.get(4); 95 | const auto texture = input.get(5); 96 | 97 | core::Image4u colorbuffer; 98 | core::Image1d depthbuffer; 99 | std::tie(colorbuffer, depthbuffer) = 100 | render::render(mesh, modelview_matrix, projection_matrix, image_width, image_height, 101 | render::create_mipmapped_texture(texture), true, false, 102 | false); // backface culling = true, near & far plane clipping = false 103 | 104 | const cv::Mat colorbuffer_mat = core::to_mat(colorbuffer); 105 | const cv::Mat depthbuffer_mat = core::to_mat(depthbuffer); 106 | 107 | OutputArguments output(nlhs, plhs, 2); 108 | output.set(0, colorbuffer_mat); 109 | output.set(1, depthbuffer_mat); 110 | }; 111 | 112 | MEX_DISPATCH; 113 | -------------------------------------------------------------------------------- /matlab/+eos/+render/render.m: -------------------------------------------------------------------------------- 1 | function [ framebuffer, depthbuffer ] = render(mesh, modelview_matrix, ... 2 | projection_matrix, image_width, image_height, texture) 3 | % RENDER Renders the given mesh in the given pose. 4 | % [ framebuffer, depthbuffer ] = RENDER(mesh, modelview_matrix, ... 5 | % projection_matrix, image_width, image_height, texture) 6 | % 7 | % Renders the mesh with given model-view and projection matrix, and given 8 | % texture, and returns the rendered framebuffer as well as the depthbuffer. 9 | % 10 | % Please see the C++ documentation for the full description: 11 | % http://patrikhuber.github.io/eos/doc/ (TODO: Update to v0.10.1!) 12 | 13 | [ framebuffer, depthbuffer ] = render('render', mesh, modelview_matrix, ... 14 | projection_matrix, image_width, image_height, texture); 15 | 16 | end 17 | -------------------------------------------------------------------------------- /matlab/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7.0) 2 | 3 | # If Matlab_ROOT_DIR is set, the Matlab at that location is used. 4 | find_package(Matlab COMPONENTS MX_LIBRARY REQUIRED) 5 | 6 | # Our helper-headers, and Matlab-specific headers - to make them show up in IDEs: 7 | set(EOS_MATLAB_HEADERS 8 | ${CMAKE_CURRENT_SOURCE_DIR}/include/mexplus_eigen.hpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/include/mexplus_eos_types.hpp 10 | ) 11 | add_custom_target(eos-matlab-headers SOURCES ${EOS_MATLAB_HEADERS}) 12 | 13 | # See: https://cmake.org/cmake/help/latest/module/FindMatlab.html?#command:matlab_add_mex 14 | matlab_add_mex( 15 | NAME eos-matlab-fitting 16 | #[EXECUTABLE | MODULE | SHARED] # SHARED is the default. 17 | SRC +eos/+fitting/private/fitting.cpp 18 | OUTPUT_NAME fitting 19 | # DOCUMENTATION +eos/+fitting/fit_shape_and_pose.m # doesn't work - wrong path probably. But it renames the file to fitting.m, so not what we want anyway. 20 | LINK_TO eos 21 | #[...] 22 | ) 23 | 24 | #matlab_add_mex( 25 | # NAME eos-matlab-render 26 | # SRC +eos/+render/private/render.cpp 27 | # OUTPUT_NAME render 28 | # LINK_TO eos 29 | #) 30 | 31 | # Group the matlab bindings targets into one folder (in IDEs): 32 | set_target_properties(eos-matlab-headers eos-matlab-fitting PROPERTIES FOLDER "matlab-bindings") # eos-matlab-render 33 | 34 | target_include_directories(eos-matlab-fitting PRIVATE ${eos_3RDPARTY_DIR}/mexplus/include ${CMAKE_CURRENT_SOURCE_DIR}/include) # the latter one we can use the eos-matlab-headers target? 35 | # target_include_directories(eos-matlab-render PRIVATE ${eos_3RDPARTY_DIR}/mexplus/include ${CMAKE_CURRENT_SOURCE_DIR}/include) 36 | 37 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/demo.m DESTINATION matlab) 38 | install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include DESTINATION matlab) 39 | install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/+eos DESTINATION matlab PATTERN "*.cpp" EXCLUDE) 40 | install(TARGETS eos-matlab-fitting DESTINATION matlab/+eos/+fitting/private) 41 | # install(TARGETS eos-matlab-render DESTINATION matlab/+eos/+render/private) 42 | -------------------------------------------------------------------------------- /matlab/demo.m: -------------------------------------------------------------------------------- 1 | %% Demo for running the eos fitting from Matlab 2 | % 3 | %% Set up some required paths to files: 4 | model_file = '../share/sfm_shape_3448.bin'; 5 | blendshapes_file = '../share/expression_blendshapes_3448.bin'; 6 | landmark_mappings = '../share/ibug_to_sfm.txt'; 7 | 8 | %% Load an image and its landmarks in ibug format: 9 | image = imread('../bin/data/image_0010.png'); 10 | landmarks = read_pts_landmarks('../bin/data/image_0010.pts'); 11 | image_width = size(image, 2); image_height = size(image, 1); 12 | 13 | %% Run the fitting, get back the fitted mesh and pose: 14 | [mesh, render_params] = eos.fitting.fit_shape_and_pose(model_file, blendshapes_file, landmarks, landmark_mappings, image_width, image_height); 15 | % Note: The function actually has a few more arguments to files it 16 | % needs. If you're not running it from within eos/matlab/, you need to 17 | % provide them. See its documentation and .m file. 18 | 19 | %% Visualise the fitted mesh using your favourite plot, for example... 20 | figure(1); 21 | plot3(mesh.vertices(:, 1), mesh.vertices(:, 2), mesh.vertices(:, 3), '.'); 22 | % or... 23 | FV.vertices = mesh.vertices(:, 1:3); 24 | FV.faces = mesh.tvi; 25 | figure(2); 26 | patch(FV, 'FaceColor', [1 1 1], 'EdgeColor', 'none', 'FaceLighting', 'phong'); light; axis equal; axis off; 27 | 28 | %% Visualise the fitting in 2D, on top of the input image: 29 | % Project all vertices to 2D (we extend mesh.vertices to homogeneous coordinates and multiply with 4x4 matrices): 30 | points_2d = [mesh.vertices, ones(size(mesh.vertices, 1), 1)] * (render_params.viewport*render_params.projection*render_params.modelview)'; 31 | % Display the image and plot the projected mesh points on top of it: 32 | figure(3); 33 | imshow(image); 34 | hold on; 35 | plot(points_2d(:, 1), points_2d(:, 2), 'g.'); 36 | % We can also plot the landmarks the mesh was fitted to: 37 | plot(landmarks(:, 1), landmarks(:, 2), 'ro'); 38 | 39 | 40 | %% Just a helper function to read ibug .pts landmarks from a file: 41 | function [ landmarks ] = read_pts_landmarks(filename) 42 | 43 | file = fopen(filename, 'r'); 44 | file_content = textscan(file, '%s'); 45 | 46 | landmarks = zeros(68, 2); 47 | 48 | row_idx = 1; 49 | for i=6:2:141 50 | landmarks(row_idx, 1) = str2double(file_content{1}{i}); 51 | landmarks(row_idx, 2) = str2double(file_content{1}{i + 1}); 52 | row_idx = row_idx + 1; 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /matlab/include/mexplus_eigen.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: matlab/include/mexplus_eigen.hpp 5 | * 6 | * Copyright 2016-2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_MEXPLUS_EIGEN_HPP 23 | #define EOS_MEXPLUS_EIGEN_HPP 24 | 25 | #include "mexplus/mxarray.h" 26 | 27 | #include "Eigen/Core" 28 | 29 | #include "mex.h" 30 | 31 | namespace mexplus { 32 | 33 | /** 34 | * @brief Define a template specialisation for Eigen::MatrixXd for ... . 35 | * 36 | * The default precision in Matlab is double, but most matrices in eos (for example the PCA basis matrices 37 | * are stored as float values, so this defines conversion from these matrices to Matlab. 38 | * 39 | * Todo: Documentation. 40 | */ 41 | template <> 42 | mxArray* MxArray::from(const Eigen::MatrixXf& eigen_matrix) 43 | { 44 | const int num_rows = static_cast(eigen_matrix.rows()); 45 | const int num_cols = static_cast(eigen_matrix.cols()); 46 | MxArray out_array(MxArray::Numeric(num_rows, num_cols)); 47 | 48 | // This might not copy the data but it's evil and probably really dangerous!!!: 49 | // mxSetData(const_cast(matrix.get()), (void*)value.data()); 50 | 51 | // This copies the data. But I suppose it makes sense that we copy the data when we go 52 | // from C++ to Matlab, since Matlab can unload the C++ mex module at any time I think. 53 | // Loop is column-wise 54 | for (int c = 0; c < num_cols; ++c) 55 | { 56 | for (int r = 0; r < num_rows; ++r) 57 | { 58 | out_array.set(r, c, eigen_matrix(r, c)); 59 | } 60 | } 61 | return out_array.release(); 62 | }; 63 | 64 | /** 65 | * @brief Define a template specialisation for Eigen::MatrixXd for ... . 66 | * 67 | * Todo: Documentation. 68 | * TODO: Maybe provide this one as MatrixXf as well as MatrixXd? Matlab's default is double? 69 | */ 70 | template <> 71 | void MxArray::to(const mxArray* in_array, Eigen::MatrixXd* eigen_matrix) 72 | { 73 | MxArray array(in_array); 74 | 75 | if (array.dimensionSize() > 2) 76 | { 77 | mexErrMsgIdAndTxt( 78 | "eos:matlab", 79 | "Given array has > 2 dimensions. Can only create 2-dimensional matrices (and vectors)."); 80 | } 81 | 82 | if (array.dimensionSize() == 1 || array.dimensionSize() == 0) 83 | { 84 | mexErrMsgIdAndTxt("eos:matlab", "Given array has 0 or 1 dimensions but we expected a 2-dimensional " 85 | "matrix (or row/column vector)."); 86 | // Even when given a single value dimensionSize() is 2, with n=m=1. When does this happen? 87 | } 88 | 89 | if (!array.isDouble()) 90 | { 91 | mexErrMsgIdAndTxt( 92 | "eos:matlab", 93 | "Trying to create an Eigen::MatrixXd in C++, but the given data is not of type double."); 94 | } 95 | 96 | // We can be sure now that the array is 2-dimensional (or 0, but then we're screwed anyway) 97 | const auto nrows = array.dimensions()[0]; // or use array.rows() 98 | const auto ncols = array.dimensions()[1]; 99 | 100 | // I think I can just use Eigen::Matrix, not a Map - the Matrix c'tor that we call creates a Map anyway? 101 | Eigen::Map> eigen_map( 102 | array.getData(), nrows, ncols); 103 | // Not sure that's alright - who owns the data? I think as it is now, everything points to the data in the 104 | // mxArray owned by Matlab, but I'm not 100% sure. 105 | // Actually, doesn't eigen_map go out of scope and get destroyed? This might be trouble? But this 106 | // assignment should (or might) copy, then it's fine? Check if it invokes the copy c'tor. 107 | // 2 May 2018: Yes this copies. 108 | *eigen_matrix = eigen_map; 109 | }; 110 | 111 | } /* namespace mexplus */ 112 | 113 | #endif /* EOS_MEXPLUS_EIGEN_HPP */ 114 | -------------------------------------------------------------------------------- /matlab/include/mexplus_opencv.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: matlab/include/mexplus_opencv.hpp 5 | * 6 | * Copyright 2017 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef MEXPLUS_OPENCV_HPP_ 23 | #define MEXPLUS_OPENCV_HPP_ 24 | 25 | #include "mexplus/mxarray.h" 26 | 27 | #include "opencv2/core/core.hpp" 28 | 29 | #include "mex.h" 30 | 31 | #include 32 | #include 33 | 34 | namespace mexplus { 35 | 36 | /** 37 | * @brief Convert a cv::Mat to a Matlab matrix. 38 | * 39 | * Conversion should work for matrices of any type, with any number of channels. 40 | * The function creates a new MxArray of the same data type and copies the data over. 41 | * 42 | * Note: Even non-standard step/stride sizes should work, but that is not tested. 43 | */ 44 | template 45 | void deepcopy_and_transpose(const cv::Mat& in, MxArray& out) 46 | { 47 | const std::size_t num_rows = in.rows; 48 | const std::size_t num_cols = in.cols; 49 | const std::size_t num_chans = in.channels(); 50 | out = MxArray::Numeric({num_rows, num_cols, num_chans}); 51 | 52 | for (std::size_t c = 0; c < num_cols; ++c) 53 | { // outer loop over rows would be faster if OpenCV stores data row-major? 54 | for (std::size_t r = 0; r < num_rows; ++r) 55 | { 56 | for (std::size_t chan = 0; chan < num_chans; ++chan) 57 | { 58 | out.set(std::vector{r, c, chan}, in.ptr(r, c)[chan]); 59 | } 60 | } 61 | } 62 | }; 63 | 64 | // Note/Todo: Currently only works for 3-chan and 4-chan U8 images 65 | // Matlab stores matrices in col - major order in memory, OpenCV stores them in row - major.Thus, we copy & transpose... 66 | template 67 | void deepcopy_and_transpose(const MxArray& in, cv::Mat& out) 68 | { 69 | auto r = in.rows(); 70 | auto c = in.cols(); // if dimensionSize() == 3, this returns cols * third_dim 71 | auto d = in.dimensions(); 72 | auto ds = in.dimensionSize(); 73 | auto e = in.elementSize(); // Number of bytes required to store each data element 74 | auto s = in.size(); 75 | auto n = in.isNumeric(); 76 | 77 | const auto num_channels = [ num_dims = in.dimensionSize(), dims = in.dimensions() ]() 78 | { 79 | if (num_dims == 1 || num_dims == 2) 80 | { 81 | return std::size_t(1); 82 | } else if (num_dims == 3) 83 | { // the dims vector has 3 entries, the 3rd entry tell us if there are 3 or 4 channels. 84 | return dims[2]; 85 | } 86 | } 87 | (); 88 | 89 | const auto actual_cols = [&num_channels, num_cols = in.cols() ]() 90 | { 91 | if (num_channels == 3 || num_channels == 4) // aargh... simplify all this... just use in.dimensions()? 92 | { 93 | return num_cols / num_channels; 94 | } else 95 | { 96 | return num_cols; 97 | } 98 | } 99 | (); 100 | 101 | std::vector channels; 102 | for (size_t c = 0; c < num_channels; ++c) 103 | { 104 | cv::Mat outmat; 105 | // Note: I think this doesn't actually copy the data, does it? 106 | // We construct with (cols, rows) because it'll later get transposed. 107 | cv::Mat inmat(actual_cols, in.rows(), cv::DataType::type, 108 | static_cast( 109 | const_cast(in.getData() + actual_cols * in.rows() * c))); 110 | inmat.convertTo(outmat, cv::DataType::type); 111 | cv::transpose(outmat, outmat); 112 | channels.push_back(outmat); 113 | } 114 | cv::merge(channels, out); 115 | }; 116 | 117 | /** 118 | * @brief Convert a cv::Mat to a Matlab matrix. 119 | * 120 | * Conversion should work for matrices of type CV_8U, CV_32F and CV_64F, with any number of channels. 121 | */ 122 | template <> 123 | mxArray* MxArray::from(const cv::Mat& opencv_matrix) 124 | { 125 | MxArray out_array; 126 | if (opencv_matrix.depth() == CV_8U) 127 | { 128 | deepcopy_and_transpose(opencv_matrix, out_array); 129 | } else if (opencv_matrix.depth() == CV_32F) 130 | { 131 | deepcopy_and_transpose(opencv_matrix, out_array); 132 | } else if (opencv_matrix.depth() == CV_64F) 133 | { 134 | deepcopy_and_transpose(opencv_matrix, out_array); 135 | } else 136 | { 137 | mexErrMsgIdAndTxt("eos:matlab", "Can only convert CV_8U, CV_32F and CV_64F matrices at this point."); 138 | } 139 | return out_array.release(); 140 | }; 141 | 142 | /** 143 | * @brief Convert a 3-channel or 4-channel uint8 Matlab matrix to a cv::Mat. 144 | * 145 | * Currently only works for 3-channel and 4-channel uint8 Matlab matrices (i.e. images and isomaps). 146 | * Todo: Add some error detection, it will silently fail or crash when the type/channels are 147 | * not correct. 148 | */ 149 | template <> 150 | void MxArray::to(const mxArray* in_array, cv::Mat* opencv_matrix) 151 | { 152 | MxArray array(in_array); 153 | deepcopy_and_transpose(array, *opencv_matrix); 154 | }; 155 | 156 | } /* namespace mexplus */ 157 | 158 | #endif /* MEXPLUS_OPENCV_HPP_ */ 159 | -------------------------------------------------------------------------------- /python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | pybind11_add_module(python-bindings generate-python-bindings.cpp pybind11_Image.hpp pybind11_optional.hpp pybind11_variant.hpp) 2 | target_link_libraries(python-bindings PRIVATE eos) 3 | set_target_properties(python-bindings PROPERTIES OUTPUT_NAME eos) 4 | 5 | install(TARGETS python-bindings DESTINATION python) 6 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/demo.py DESTINATION python) 7 | -------------------------------------------------------------------------------- /python/demo.py: -------------------------------------------------------------------------------- 1 | import eos 2 | import numpy as np 3 | 4 | def main(): 5 | """Demo for running the eos fitting from Python.""" 6 | landmarks = read_pts('../bin/data/image_0010.pts') 7 | image_width = 1280 # Make sure to adjust these when using your own images! 8 | image_height = 1024 9 | 10 | model = eos.morphablemodel.load_model("../share/sfm_shape_3448.bin") 11 | blendshapes = eos.morphablemodel.load_blendshapes("../share/expression_blendshapes_3448.bin") 12 | # Create a MorphableModel with expressions from the loaded neutral model and blendshapes: 13 | morphablemodel_with_expressions = eos.morphablemodel.MorphableModel(model.get_shape_model(), blendshapes, 14 | color_model=eos.morphablemodel.PcaModel(), 15 | vertex_definitions=None, 16 | texture_coordinates=model.get_texture_coordinates()) 17 | landmark_mapper = eos.core.LandmarkMapper('../share/ibug_to_sfm.txt') 18 | edge_topology = eos.morphablemodel.load_edge_topology('../share/sfm_3448_edge_topology.json') 19 | contour_landmarks = eos.fitting.ContourLandmarks.load('../share/ibug_to_sfm.txt') 20 | model_contour = eos.fitting.ModelContour.load('../share/sfm_model_contours.json') 21 | 22 | (mesh, pose, shape_coeffs, blendshape_coeffs) = eos.fitting.fit_shape_and_pose(morphablemodel_with_expressions, 23 | landmarks, landmark_mapper, image_width, image_height, edge_topology, contour_landmarks, model_contour) 24 | 25 | # Now you can use your favourite plotting/rendering library to display the fitted mesh, using the rendering 26 | # parameters in the 'pose' variable. 27 | 28 | # Or for example extract the texture map, like this: 29 | # import cv2 30 | # image = cv2.imread('../bin/data/image_0010.png') 31 | # isomap = eos.render.extract_texture(mesh, pose, image) 32 | 33 | 34 | def read_pts(filename): 35 | """A helper function to read the 68 ibug landmarks from a .pts file.""" 36 | lines = open(filename).read().splitlines() 37 | lines = lines[3:71] 38 | 39 | landmarks = [] 40 | ibug_index = 1 # count from 1 to 68 for all ibug landmarks 41 | for l in lines: 42 | coords = l.split() 43 | landmarks.append(eos.core.Landmark(str(ibug_index), [float(coords[0]), float(coords[1])])) 44 | ibug_index = ibug_index + 1 45 | 46 | return landmarks 47 | 48 | if __name__ == "__main__": 49 | main() 50 | -------------------------------------------------------------------------------- /python/pybind11_optional.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: python/pybind11_optional.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_PYBIND11_OPTIONAL_HPP_ 23 | #define EOS_PYBIND11_OPTIONAL_HPP_ 24 | 25 | /** 26 | * @file python/pybind11_optional.hpp 27 | * @brief Define a type_caster for akrzemi1::optional, which is used when the compiler doesn't have (e.g. on Apple). 28 | */ 29 | 30 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 31 | 32 | #include "pybind11/stl.h" 33 | 34 | #else 35 | 36 | #include "eos/cpp17/optional.hpp" 37 | #include "pybind11/stl.h" 38 | 39 | namespace pybind11 { 40 | namespace detail { 41 | 42 | /** 43 | * @brief Type caster for akrzemi1::optional, which is used when the compiler doesn't have (e.g. on Apple). 44 | */ 45 | template 46 | struct type_caster> : optional_caster> 47 | { 48 | }; 49 | 50 | } /* namespace detail */ 51 | } /* namespace pybind11 */ 52 | 53 | #endif 54 | 55 | #endif /* EOS_PYBIND11_OPTIONAL_HPP_ */ 56 | -------------------------------------------------------------------------------- /python/pybind11_variant.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: python/pybind11_variant.hpp 5 | * 6 | * Copyright 2018 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #pragma once 21 | 22 | #ifndef EOS_PYBIND11_VARIANT_HPP_ 23 | #define EOS_PYBIND11_VARIANT_HPP_ 24 | 25 | /** 26 | * @file python/pybind11_variant.hpp 27 | * @brief Define a type_caster for mpark::variant, which is used when the compiler doesn't have (e.g. on Apple). 28 | */ 29 | 30 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 31 | 32 | #include "pybind11/stl.h" 33 | 34 | #else 35 | 36 | #include "eos/cpp17/variant.hpp" 37 | #include "pybind11/stl.h" 38 | 39 | namespace pybind11 { 40 | namespace detail { 41 | 42 | /** 43 | * @brief Type caster for mpark::variant, which is used when the compiler doesn't have (e.g. on Apple). 44 | */ 45 | template 46 | struct type_caster> : variant_caster> 47 | { 48 | }; 49 | 50 | } /* namespace detail */ 51 | } /* namespace pybind11 */ 52 | 53 | #endif 54 | 55 | #endif /* EOS_PYBIND11_VARIANT_HPP_ */ 56 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import platform 5 | import subprocess 6 | 7 | from setuptools import setup, Extension 8 | from setuptools.command.build_ext import build_ext 9 | from distutils.version import LooseVersion 10 | 11 | 12 | class CMakeExtension(Extension): 13 | def __init__(self, name, sourcedir=''): 14 | Extension.__init__(self, name, sources=[]) 15 | self.sourcedir = os.path.abspath(sourcedir) 16 | 17 | 18 | class CMakeBuild(build_ext): 19 | 20 | def run(self): 21 | try: 22 | out = subprocess.check_output(['cmake', '--version']) 23 | except OSError: 24 | raise RuntimeError("CMake must be installed to build the following extensions: " + 25 | ", ".join(e.name for e in self.extensions)) 26 | 27 | if platform.system() == "Windows": 28 | cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) 29 | if cmake_version < '3.1.0': 30 | raise RuntimeError("CMake >= 3.1.0 is required on Windows") 31 | 32 | for ext in self.extensions: 33 | self.build_extension(ext) 34 | 35 | def build_extension(self, ext): 36 | extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) 37 | cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, 38 | '-DPYTHON_EXECUTABLE=' + sys.executable, 39 | '-DEOS_BUILD_EXAMPLES=OFF', 40 | '-DEOS_GENERATE_PYTHON_BINDINGS=ON' 41 | ] 42 | 43 | cfg = 'Debug' if self.debug else 'Release' 44 | build_args = ['--config', cfg] 45 | 46 | if platform.system() == "Windows": 47 | cmake_args += ['-G', 'Visual Studio 15 2017 Win64'] 48 | cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] 49 | build_args += ['--', '/m'] 50 | else: 51 | cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] 52 | build_args += ['--', '-j2'] 53 | 54 | env = os.environ.copy() 55 | env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), 56 | self.distribution.get_version()) 57 | if not os.path.exists(self.build_temp): 58 | os.makedirs(self.build_temp) 59 | subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) 60 | subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) 61 | 62 | 63 | this_directory = os.path.abspath(os.path.dirname(__file__)) 64 | with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f: 65 | long_description = f.read() 66 | 67 | setup( 68 | name='eos-py', 69 | version='1.0.1', 70 | author='Patrik Huber', 71 | author_email='patrikhuber@gmail.com', 72 | description='Python bindings for eos - A lightweight 3D Morphable Face Model fitting library in modern C++11/14', 73 | long_description=long_description, 74 | long_description_content_type='text/markdown', 75 | url='https://github.com/patrikhuber/eos', 76 | license='Apache-2.0', 77 | ext_modules=[CMakeExtension('eos')], 78 | cmdclass=dict(build_ext=CMakeBuild), 79 | zip_safe=False, 80 | ) 81 | -------------------------------------------------------------------------------- /share/bfm2009_model_contours.json: -------------------------------------------------------------------------------- 1 | { 2 | "model_contour": { 3 | "right_contour": [ 4 | 27302, 5 | 46587, 6 | 45866, 7 | 2445, 8 | 1260, 9 | 721, 10 | 316, 11 | 27431 12 | ], 13 | "left_contour": [ 14 | 27689, 15 | 16312, 16 | 15943, 17 | 15450, 18 | 14313, 19 | 50480, 20 | 49788, 21 | 27818 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /share/bfm2017-1_bfm_nomouth_model_contours.json: -------------------------------------------------------------------------------- 1 | { 2 | "model_contour": { 3 | "right_contour": [ 4 | 22451, 5 | 22463, 6 | 22231, 7 | 21961, 8 | 21591, 9 | 21737, 10 | 23037, 11 | 43151, 12 | 44368, 13 | 45617, 14 | 46999 15 | ], 16 | "left_contour": [ 17 | 31991, 18 | 32002, 19 | 32272, 20 | 32795, 21 | 33069, 22 | 33088, 23 | 32329, 24 | 52325, 25 | 50233, 26 | 49299, 27 | 48560 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /share/expression_blendshapes_3448.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVSSP/eos/f8b235bf8da1eef947a62dc09e23ecde2c9c0f3b/share/expression_blendshapes_3448.bin -------------------------------------------------------------------------------- /share/ibug_to_bfm2009.txt: -------------------------------------------------------------------------------- 1 | # Mapping from the 68-point ibug annotations to the BFM (2009) (3DMM vertex indices). 2 | # The numbers in brackets are MPEG-4 facial feature point numbers. 3 | 4 | [landmark_mappings] # A mapping from input landmarks (ibug, lhs) to output landmarks (BFM, rhs) 5 | # 1 to 8 are the right contour landmarks 6 | # chin bottom (2.1, MPEG point not marked in the BFM) 7 | # 10 to 17 are the left contour landmarks 8 | 18 = 38792 # right eyebrow outer-corner (4.6) 9 | 20 = 40087 # right eyebrow middle, vertical middle (4.4, the MPEG point is on top of the brow though) 10 | 22 = 40514 # right eyebrow inner-corner (4.2) 11 | 23 = 41091 # left eyebrow inner-corner (4.1) 12 | 25 = 41511 # left eyebrow middle (4.3, the MPEG point is on top of the brow though) 13 | 27 = 42825 # left eyebrow outer-corner (4.5) 14 | 31 = 8319 # nose-tip (9.3) 15 | 34 = 8334 # nose-lip junction (9.15) 16 | 37 = 2088 # right eye outer-corner (3.12) 17 | 40 = 5959 # right eye inner-corner (3.8) 18 | 43 = 10603 # left eye inner-corner (3.11) 19 | 46 = 14472 # left eye outer-corner (3.7) 20 | 49 = 5006 # right mouth corner (8.4) 21 | 52 = 8344 # upper lip middle top (8.1) 22 | 55 = 11714 # left mouth corner (8.3) 23 | 58 = 8374 # lower lip middle bottom (8.2) 24 | #61 # right inner corner of the mouth (2.5) 25 | #62 # upper lip right bottom outer (2.7) 26 | 63 = 8354 # upper lip middle bottom (2.2) 27 | #64 # upper lip left bottom outer (2.6) 28 | #65 # left inner corner of the mouth (2.4) 29 | #66 # lower lip left top outer (2.8) 30 | 67 = 8366 # lower lip middle top (2.3) 31 | #68 # lower lip right top outer (2.9) 32 | 33 | 34 | # Definitions of which 2D landmarks make up the right and left face contours: 35 | [contour_landmarks] 36 | right = [ 1, 37 | 2, 38 | 3, 39 | 4, 40 | 5, 41 | 6, 42 | 7, 43 | 8 44 | ] 45 | left = [ 10, 46 | 11, 47 | 12, 48 | 13, 49 | 14, 50 | 15, 51 | 16, 52 | 17 53 | ] 54 | -------------------------------------------------------------------------------- /share/ibug_to_bfm2017-1_bfm_nomouth.txt: -------------------------------------------------------------------------------- 1 | # Mapping from the 68-point ibug annotations to the BFM2017 head model (vertex indices). 2 | 3 | [landmark_mappings] # A mapping from input landmarks (ibug, lhs) to output landmarks (BFM, rhs) 4 | # 1 to 8 are the right contour landmarks 5 | 9 = 47846 # chin bottom 6 | # 10 to 17 are the left contour landmarks 7 | #18 = # right eyebrow outer-corner 8 | #20 = # right eyebrow middle, vertical middle 9 | #22 = # right eyebrow inner-corner 10 | #23 = # left eyebrow inner-corner 11 | #25 = # left eyebrow middle 12 | #27 = # left eyebrow outer-corner 13 | 31 = 8156 # nose-tip 14 | #34 = # nose-lip junction 15 | 37 = 2602 # right eye outer-corner 16 | 40 = 5830 # right eye inner-corner 17 | 43 = 10390 # left eye inner-corner 18 | 46 = 13481 # left eye outer-corner 19 | 49 = 5522 # right mouth corner 20 | 50 = 6026 # upper lip right-right top 21 | 51 = 7355 # upper lip middle-right top 22 | 52 = 8181 # upper lip middle top 23 | 53 = 9007 # upper lip middle-left top 24 | 54 = 10329 # upper lip left-left top 25 | 55 = 10857 # left mouth corner 26 | 56 = 9730 # 27 | 57 = 8670 # 28 | 58 = 8199 # lower lip middle bottom 29 | 59 = 7726 # 30 | 60 = 6898 # 31 | 61 = 6291 # right inner corner of the mouth 32 | 62 = 7364 # upper lip right bottom outer 33 | 63 = 8190 # upper lip middle bottom 34 | 64 = 9016 # upper lip left bottom outer 35 | 65 = 10088 # left inner corner of the mouth 36 | 66 = 8663 # lower lip left top outer 37 | 67 = 8191 # lower lip middle top 38 | 68 = 7719 # lower lip right top outer 39 | 40 | 41 | # Definitions of which 2D landmarks make up the right and left face contours: 42 | [contour_landmarks] 43 | right = [ 1, 44 | 2, 45 | 3, 46 | 4, 47 | 5, 48 | 6, 49 | 7, 50 | 8 51 | ] 52 | left = [ 10, 53 | 11, 54 | 12, 55 | 13, 56 | 14, 57 | 15, 58 | 16, 59 | 17 60 | ] 61 | -------------------------------------------------------------------------------- /share/ibug_to_sfm.txt: -------------------------------------------------------------------------------- 1 | # Mapping from the 68-point ibug annotations to the Surrey Face Model (SFM) mesh vertex indices. 2 | # Note: Points above vertex id 845 are not defined on the reference and thus not available in all model resolutions. 3 | # This file uses TOML syntax (https://github.com/toml-lang/toml). 4 | 5 | # Mappings from input landmarks (ibug, lhs) to output landmarks (SFM, rhs): 6 | [landmark_mappings] 7 | # 1 to 8 are the right contour landmarks 8 | 9 = 33 # chin bottom 9 | # 10 to 17 are the left contour landmarks 10 | 18 = 225 # right eyebrow outer-corner (18) 11 | 19 = 229 # right eyebrow between middle and outer corner 12 | 20 = 233 # right eyebrow middle, vertical middle (20) 13 | 21 = 2086 # right eyebrow between middle and inner corner 14 | 22 = 157 # right eyebrow inner-corner (19) 15 | 23 = 590 # left eyebrow inner-corner (23) 16 | 24 = 2091 # left eyebrow between inner corner and middle 17 | 25 = 666 # left eyebrow middle (24) 18 | 26 = 662 # left eyebrow between middle and outer corner 19 | 27 = 658 # left eyebrow outer-corner (22) 20 | 28 = 2842 # bridge of the nose (parallel to upper eye lids) 21 | 29 = 379 # middle of the nose, a bit below the lower eye lids 22 | 30 = 272 # above nose-tip (1cm or so) 23 | 31 = 114 # nose-tip (3) 24 | 32 = 100 # right nostril, below nose, nose-lip junction 25 | 33 = 2794 # nose-lip junction 26 | 34 = 270 # nose-lip junction (28) 27 | 35 = 2797 # nose-lip junction 28 | 36 = 537 # left nostril, below nose, nose-lip junction 29 | 37 = 177 # right eye outer-corner (1) 30 | 38 = 172 # right eye pupil top right (from subject's perspective) 31 | 39 = 191 # right eye pupil top left 32 | 40 = 181 # right eye inner-corner (5) 33 | 41 = 173 # right eye pupil bottom left 34 | 42 = 174 # right eye pupil bottom right 35 | 43 = 614 # left eye inner-corner (8) 36 | 44 = 624 # left eye pupil top right 37 | 45 = 605 # left eye pupil top left 38 | 46 = 610 # left eye outer-corner (2) 39 | 47 = 607 # left eye pupil bottom left 40 | 48 = 606 # left eye pupil bottom right 41 | 49 = 398 # right mouth corner (12) 42 | 50 = 315 # upper lip right top outer 43 | 51 = 413 # upper lip middle top right 44 | 52 = 329 # upper lip middle top (14) 45 | 53 = 825 # upper lip middle top left 46 | 54 = 736 # upper lip left top outer 47 | 55 = 812 # left mouth corner (13) 48 | 56 = 841 # lower lip left bottom outer 49 | 57 = 693 # lower lip middle bottom left 50 | 58 = 411 # lower lip middle bottom (17) 51 | 59 = 264 # lower lip middle bottom right 52 | 60 = 431 # lower lip right bottom outer 53 | # 61 not defined - would be right inner corner of the mouth 54 | 62 = 416 # upper lip right bottom outer 55 | 63 = 423 # upper lip middle bottom 56 | 64 = 828 # upper lip left bottom outer 57 | # 65 not defined - would be left inner corner of the mouth 58 | 66 = 817 # lower lip left top outer 59 | 67 = 442 # lower lip middle top 60 | 68 = 404 # lower lip right top outer 61 | 62 | 63 | # Definitions of which 2D landmarks make up the right and left face contours: 64 | [contour_landmarks] 65 | right = [ 1, 66 | 2, 67 | 3, 68 | 4, 69 | 5, 70 | 6, 71 | 7, 72 | 8 73 | ] 74 | left = [ 10, 75 | 11, 76 | 12, 77 | 13, 78 | 14, 79 | 15, 80 | 16, 81 | 17 82 | ] 83 | -------------------------------------------------------------------------------- /share/readme.txt: -------------------------------------------------------------------------------- 1 | eos: A lightweight header-only 3D Morphable Model fitting library in modern C++11/14 2 | ========= 3 | 4 | Files in this directory: 5 | 6 | - ibug_to_sfm.txt: 7 | Mappings from the popular ibug 68-point 2D facial landmarks markup to 8 | Surrey Face Model indices. 9 | 10 | - sfm_shape_3448.bin: 11 | The public shape-only Surrey 3D Morphable Face Model. 12 | To obtain a full 3DMM and higher resolution levels, follow the instructions 13 | at cvssp.org/facemodel. 14 | Details about the different models can be found in: 15 | "A Multiresolution 3D Morphable Face Model and Fitting Framework", 16 | P. Huber, G. Hu, R. Tena, P. Mortazavian, W. Koppen, W. Christmas, M. Rätsch, J. Kittler, 17 | VISAPP 2016, Rome, Italy. 18 | 19 | - expression_blendshapes_3448.bin: 20 | 6 expression blendshapes for the sfm_shape_3448 model. Contains the expressions anger, 21 | disgust, fear, happiness, sadness and surprise. 22 | 23 | - sfm_3448_edge_topology.json: 24 | Contains a precomputed list of the model's edges, and the two faces and vertices that are 25 | adjacent to each edge. Uses 1-based indexing ("0" has a special meaning of "no adjacent 26 | vertex/edge") - this may change to 0-based in the future to be consistent with the rest of 27 | the library. The file is used in the edge-fitting. 28 | 29 | - sfm_model_contours.json: 30 | Definition of the SFM's contour vertices of the right and left side of the face. 31 | 32 | - sfm_reference.obj: 33 | The reference 3D shape used to built the Surrey Face Model. We make it available so 34 | that new user-defined landmark points can be marked in this lowest-resolution 35 | model, if the points exist here. 36 | 37 | - sfm_reference_annotated.obj: 38 | Visualisation of the landmark points defined in the ibug_to_sfm.txt mapping file. 39 | * Red: Annotated ibug points that are defined on the reference shape. 40 | * Green: Contour vertices from the file model_contours.json. 41 | The file ibug_to_sfm.txt contains a few more mappings of landmarks that are not present 42 | in the reference, for example the middle-inner eyebrow points - they are not visualised. 43 | 44 | - sfm_reference_symmetry.txt: 45 | Contains a list of vertex symmetries of the reference shape, i.e. each 46 | vertex's symmetric counterpart. See the top of the file for more information. 47 | -------------------------------------------------------------------------------- /share/scripts/compute_edgestruct.m: -------------------------------------------------------------------------------- 1 | %% The code in this file is largely copied and modified from 2 | % https://github.com/waps101/3DMM_edges: 3 | % A. Bas, W.A.P. Smith, T. Bolkart and S. Wuhrer, "Fitting a 3D Morphable 4 | % Model to Edges: A Comparison Between Hard and Soft Correspondences", 5 | % ACCV Workshop 2016. 6 | % The code is licensed under the Apache-2.0 license. 7 | 8 | %% Read the instructions in share/generate-edgestruct.py for how to use this script. 9 | 10 | function [] = compute_edgestruct(trianglelist_file) 11 | load(trianglelist_file); % loads 'triangle_list' from the file 12 | num_vertices = max(max(triangle_list)); % we assume that the largest triangle 13 | % index that we're going to find is the 14 | % number of vertices of the model. 15 | % Get the edge list: 16 | TR = triangulation(double(triangle_list), ones(num_vertices, 1), ones(num_vertices, 1), ones(num_vertices, 1)); 17 | Ev = TR.edges; % This should be a list of all the edges. 18 | clear TR; 19 | Ef = meshFaceEdges(triangle_list, Ev); 20 | save('edgestruct.mat', 'Ef', 'Ev'); % Load this file back into generate-edgestruct.py. 21 | end 22 | 23 | % This function is copied from: 24 | % https://github.com/waps101/3DMM_edges/blob/master/utils/meshFaceEdges.m, 25 | % on 3 Oct 2016. 26 | function Ef = meshFaceEdges(faces, edges) 27 | %MESHFACEEDGES Compute faces adjacent to each edge in the mesh 28 | % faces - nverts by 3 matrix of mesh faces 29 | % edges - nedges by 2 matrix containing vertices adjacent to each edge 30 | % 31 | % This function is slow! But it only needs to be run once for a morphable 32 | % model and the edge-face list can then be saved 33 | 34 | nedges = size(edges, 1); 35 | 36 | faces = sort(faces, 2); 37 | edges = sort(edges, 2); 38 | 39 | disp(' '); 40 | for i=1:nedges 41 | idx = find(((faces(:,1)==edges(i,1)) & ( (faces(:,2)==edges(i,2)) | (faces(:,3)==edges(i,2)) )) | ((faces(:,2)==edges(i,1)) & (faces(:,3)==edges(i,2)))); 42 | if length(idx)==1 43 | idx = [0 idx]; 44 | end 45 | Ef(i,:)=[idx(1) idx(2)]; 46 | fprintf('\b\b\b\b\b\b%05.2f%%', i/nedges*100); 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /share/scripts/convert-bfm2009-to-eos.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import eos 3 | import scipy.io 4 | 5 | # This script converts the Basel Face Model 2009 (BFM2009, [1]) to the eos model format, 6 | # specifically the file PublicMM1/01_MorphableModel.mat from the BFM2009 distribution. 7 | # 8 | # The script does not use or convert the segments of the BFM2009, just the global PCA. 9 | # The BFM2009 also does not come with texture (uv-) coordinates. If you have texture coordinates for the BFM, they can be 10 | # added to the eos.morphablemodel.MorphableModel(...) constructor in the third argument. Note that eos only supports one 11 | # uv-coordinate per vertex. 12 | # 13 | # [1]: A 3D Face Model for Pose and Illumination Invariant Face Recognition, 14 | # P. Paysan, R. Knothe, B. Amberg, S. Romdhani, and T. Vetter, 15 | # AVSS 2009. 16 | # http://faces.cs.unibas.ch/bfm/main.php?nav=1-0&id=basel_face_model 17 | 18 | # Set this to the path of the PublicMM1/01_MorphableModel.mat file from the BFM2009 distribution: 19 | bfm2009_path = r"./PublicMM1/01_MorphableModel.mat" 20 | bfm2009 = scipy.io.loadmat(bfm2009_path) 21 | 22 | # The PCA shape model: 23 | # Note: All the matrices are of type float32, so we're good and don't need to convert anything. 24 | shape_mean = bfm2009['shapeMU'] 25 | shape_orthogonal_pca_basis = bfm2009['shapePC'] 26 | # Their basis is unit norm: np.linalg.norm(shape_pca_basis[:,0]) == 1.0 27 | # And the basis vectors are orthogonal: np.dot(shape_pca_basis[:,0], shape_pca_basis[:,0]) == 1.0 28 | # np.dot(shape_pca_basis[:,0], shape_pca_basis[:,1]) == 1e-08 29 | shape_pca_standard_deviations = bfm2009['shapeEV'] # These are standard deviations, not eigenvalues! 30 | shape_pca_eigenvalues = np.square(shape_pca_standard_deviations) 31 | triangle_list = bfm2009['tl'] - 1 # Convert from 1-based Matlab indexing to 0-based C++ indexing 32 | # The BFM has front-facing triangles defined the wrong way round (not in accordance with OpenGL) - we swap the indices: 33 | for t in triangle_list: 34 | t[1], t[2] = t[2], t[1] 35 | shape_model = eos.morphablemodel.PcaModel(shape_mean, shape_orthogonal_pca_basis, shape_pca_eigenvalues, 36 | triangle_list.tolist()) 37 | 38 | # PCA colour model: 39 | color_mean = bfm2009['texMU'] 40 | # The BFM2009's colour data is in the range [0, 255], while the SFM is in [0, 1], so we divide by 255: 41 | color_mean /= 255 42 | color_orthogonal_pca_basis = bfm2009['texPC'] 43 | color_pca_standard_deviations = bfm2009['texEV'] # Again, these are standard deviations, not eigenvalues 44 | color_pca_standard_deviations /= 255 # Divide the standard deviations by the same amount as the mean 45 | color_pca_eigenvalues = np.square(color_pca_standard_deviations) 46 | 47 | color_model = eos.morphablemodel.PcaModel(color_mean, color_orthogonal_pca_basis, color_pca_eigenvalues, 48 | triangle_list.tolist()) 49 | 50 | # Construct and save the BFM2009 model in the eos format: 51 | model = eos.morphablemodel.MorphableModel(shape_model, color_model, vertex_definitions=None, 52 | texture_coordinates=[], 53 | texture_triangle_indices=[]) # uv-coordinates can be added here 54 | eos.morphablemodel.save_model(model, "bfm2009.bin") 55 | print("Converted and saved model as bfm2009.bin.") 56 | -------------------------------------------------------------------------------- /share/scripts/convert-bfm2017-to-eos.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import eos 3 | import h5py 4 | 5 | # This script converts the Basel Face Model 2017 (BFM2017, [1]) to the eos model format, 6 | # specifically the files model2017-1_face12_nomouth.h5 and model2017-1_bfm_nomouth.h5 from the BFM2017 download. 7 | # 8 | # The BFM2017 does not come with texture (uv-) coordinates. If you have texture coordinates for the BFM, they can be 9 | # added to the eos.morphablemodel.MorphableModel(...) constructor in the third argument. Note that eos only supports one 10 | # uv-coordinate per vertex. 11 | # 12 | # [1]: Morphable Face Models - An Open Framework, 13 | # T. Gerig, A. Morel-Forster, C. Blumer, B. Egger, M. Lüthi, S. Schönborn and T. Vetter, 14 | # arXiv preprint, 2017. 15 | # http://faces.cs.unibas.ch/bfm/bfm2017.html 16 | 17 | # Set this to the path of the model2017-1_bfm_nomouth.h5 or model2017-1_face12_nomouth.h5 file from the BFM2017 download: 18 | bfm2017_file = r"./model2017-1_bfm_nomouth.h5" 19 | 20 | with h5py.File(bfm2017_file, 'r') as hf: 21 | # The PCA shape model: 22 | shape_mean = np.array(hf['shape/model/mean']) 23 | shape_orthogonal_pca_basis = np.array(hf['shape/model/pcaBasis']) 24 | # Their basis is unit norm: np.linalg.norm(shape_pca_basis[:,0]) == ~1.0 25 | # And the basis vectors are orthogonal: np.dot(shape_pca_basis[:,0], shape_pca_basis[:,0]) == 1.0 26 | # np.dot(shape_pca_basis[:,0], shape_pca_basis[:,1]) == 1e-10 27 | shape_pca_variance = np.array(hf['shape/model/pcaVariance']) # the PCA variances are the eigenvectors 28 | 29 | triangle_list = np.array(hf['shape/representer/cells']) 30 | 31 | shape_model = eos.morphablemodel.PcaModel(shape_mean, shape_orthogonal_pca_basis, shape_pca_variance, 32 | triangle_list.transpose().tolist()) 33 | 34 | # PCA colour model: 35 | color_mean = np.array(hf['color/model/mean']) 36 | color_orthogonal_pca_basis = np.array(hf['color/model/pcaBasis']) 37 | color_pca_variance = np.array(hf['color/model/pcaVariance']) 38 | 39 | color_model = eos.morphablemodel.PcaModel(color_mean, color_orthogonal_pca_basis, color_pca_variance, 40 | triangle_list.transpose().tolist()) 41 | 42 | # PCA expression model: 43 | expression_mean = np.array(hf['expression/model/mean']) 44 | expression_pca_basis = np.array(hf['expression/model/pcaBasis']) 45 | expression_pca_variance = np.array(hf['expression/model/pcaVariance']) 46 | 47 | expression_model = eos.morphablemodel.PcaModel(expression_mean, expression_pca_basis, expression_pca_variance, 48 | triangle_list.transpose().tolist()) 49 | 50 | # Construct and save an eos model from the BFM data: 51 | model = eos.morphablemodel.MorphableModel(shape_model, expression_model, color_model, vertex_definitions=None, 52 | texture_coordinates=[], 53 | texture_triangle_indices=[]) # uv-coordinates can be added here 54 | eos.morphablemodel.save_model(model, "bfm2017-1_bfm_nomouth.bin") 55 | print("Converted and saved model as bfm2017-1_bfm_nomouth.bin.") 56 | -------------------------------------------------------------------------------- /share/scripts/generate-edgestruct.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import eos 3 | import scipy.io 4 | 5 | # This script computes an edge_topology.json file for a given model, which is used in eos's contour fitting. 6 | # The script can be used for any Morphable Model, for example the SFM, BFM2009, BFM2017, and others. 7 | 8 | # Set this to the path of the model that you want to generate an edgestruct from: 9 | model_path = "bfm2017-1_bfm_nomouth.bin" 10 | 11 | # Step 1: 12 | # Save the triangle list of the model to Matlab (to be read by Matlab in Step 2): 13 | model = eos.morphablemodel.load_model(model_path) 14 | triangle_list = np.array(model.get_shape_model().get_triangle_list()) + 1 # add 1 to make 1-based indices for Matlab 15 | scipy.io.savemat("bfm2017-1_bfm_nomouth_trianglelist.mat", {'triangle_list': triangle_list}) 16 | 17 | # Step 2: 18 | # Open Matlab and run compute_edgestruct.m on the generated triangle-list .mat file. 19 | # Matlab will save an edgestruct.mat file with face and vertex adjacency information. 20 | 21 | # Step 3: 22 | # Load the generated edgestruct.mat from Matlab and save it as an eos EdgeTopology in json format: 23 | edgestruct_path = r"edgestruct.mat" 24 | edge_info = scipy.io.loadmat(edgestruct_path) 25 | Ef = edge_info['Ef'] 26 | Ev = edge_info['Ev'] 27 | edge_topology = eos.morphablemodel.EdgeTopology(Ef.tolist(), Ev.tolist()) 28 | eos.morphablemodel.save_edge_topology(edge_topology, "bfm2017-1_bfm_nomouth_edge_topology.json") 29 | 30 | print("Finished generating edge-topology file and saved it as bfm2017-1_bfm_nomouth_edge_topology.json.") 31 | -------------------------------------------------------------------------------- /share/sfm_model_contours.json: -------------------------------------------------------------------------------- 1 | { 2 | "model_contour": { 3 | "right_contour": [ 4 | 380, 5 | 373, 6 | 356, 7 | 358, 8 | 359, 9 | 360, 10 | 365, 11 | 363, 12 | 364, 13 | 388, 14 | 391, 15 | 392, 16 | 393, 17 | 11, 18 | 21, 19 | 25, 20 | 22 21 | ], 22 | "left_contour": [ 23 | 795, 24 | 790, 25 | 773, 26 | 775, 27 | 776, 28 | 777, 29 | 782, 30 | 780, 31 | 781, 32 | 802, 33 | 805, 34 | 806, 35 | 807, 36 | 454, 37 | 464, 38 | 466, 39 | 465 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /share/sfm_shape_3448.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVSSP/eos/f8b235bf8da1eef947a62dc09e23ecde2c9c0f3b/share/sfm_shape_3448.bin -------------------------------------------------------------------------------- /utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The utils need a few additional dependencies (e.g. boost::split, boost filesystem, program_options, and OpenCV highgui): 2 | 3 | # Check installed version in order to include the correct OpenCV libraries: 4 | # First call find_package without a version to find any OpenCV. OpenCV_VERSION_MAJOR is then defined. 5 | find_package(OpenCV REQUIRED core) 6 | if("${OpenCV_VERSION_MAJOR}$" EQUAL 2) 7 | message(STATUS "OpenCV 2.x detected") 8 | find_package(OpenCV 2.4.3 REQUIRED core imgproc highgui) 9 | elseif("${OpenCV_VERSION_MAJOR}$" EQUAL 3) 10 | message(STATUS "OpenCV 3.x detected - including imgcodecs for compatibility") 11 | find_package(OpenCV 3 REQUIRED core imgproc imgcodecs) 12 | endif() 13 | # This allows us to compile in RelWithDebInfo. It'll use the Release-version of OpenCV: 14 | set_target_properties(${OpenCV_LIBS} PROPERTIES MAP_IMPORTED_CONFIG_RELWITHDEBINFO RELEASE) 15 | 16 | if(MSVC) 17 | # The standard find_package for boost on Win finds the dynamic libs, so for dynamic linking to boost we need to #define: 18 | add_definitions(-DBOOST_ALL_NO_LIB) # Don't use the automatic library linking by boost with VS (#pragma ...). Instead, we specify everything here in cmake. 19 | add_definitions(-DBOOST_ALL_DYN_LINK) # Link against the dynamic boost lib - needs to match with the version that find_package finds. 20 | add_definitions(-D_HAS_AUTO_PTR_ETC) # Boost 1.65.1 still does not work with VS C++17 mode, this is the workaround 21 | endif() 22 | find_package(Boost 1.50.0 COMPONENTS system filesystem program_options REQUIRED) 23 | message(STATUS "Boost found at ${Boost_INCLUDE_DIRS}") 24 | 25 | # Converts a CVSSP .scm Morphable Model to a cereal binary file: 26 | add_executable(scm-to-cereal scm-to-cereal.cpp) 27 | target_link_libraries(scm-to-cereal eos ${OpenCV_LIBS} ${Boost_LIBRARIES}) 28 | target_include_directories(scm-to-cereal PUBLIC ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 29 | 30 | # Install targets: 31 | install(TARGETS scm-to-cereal DESTINATION bin) 32 | -------------------------------------------------------------------------------- /utils/scm-to-cereal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * eos - A 3D Morphable Model fitting library written in modern C++11/14. 3 | * 4 | * File: utils/scm-to-cereal.cpp 5 | * 6 | * Copyright 2015 Patrik Huber 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | #include "eos/morphablemodel/MorphableModel.hpp" 21 | #include "eos/morphablemodel/io/cvssp.hpp" 22 | 23 | #include "boost/program_options.hpp" 24 | 25 | #include 26 | 27 | using namespace eos; 28 | namespace po = boost::program_options; 29 | using std::cout; 30 | using std::endl; 31 | 32 | /** 33 | * Reads a CVSSP .scm Morphable Model file and converts it 34 | * to a cereal binary file. 35 | */ 36 | int main(int argc, char* argv[]) 37 | { 38 | std::string scmmodelfile, isomapfile, outputfile; 39 | bool save_shape_only; 40 | try 41 | { 42 | po::options_description desc("Allowed options"); 43 | // clang-format off 44 | desc.add_options() 45 | ("help,h", "display the help message") 46 | ("model,m", po::value(&scmmodelfile)->required(), 47 | "a CVSSP .scm Morphable Model file") 48 | ("isomap,t", po::value(&isomapfile), 49 | "optional isomap containing the texture mapping coordinates") 50 | ("shape-only,s", po::value(&save_shape_only)->default_value(false)->implicit_value(true), 51 | "save only the shape-model part of the full 3DMM") 52 | ("output,o", po::value(&outputfile)->required()->default_value("converted_model.bin"), 53 | "output filename for the Morphable Model in cereal binary format"); 54 | // clang-format on 55 | po::variables_map vm; 56 | po::store(po::command_line_parser(argc, argv).options(desc).run(), vm); 57 | if (vm.count("help")) 58 | { 59 | cout << "Usage: scm-to-cereal [options]" << endl; 60 | cout << desc; 61 | return EXIT_SUCCESS; 62 | } 63 | po::notify(vm); 64 | } catch (const po::error& e) 65 | { 66 | cout << "Error while parsing command-line arguments: " << e.what() << endl; 67 | cout << "Use --help to display a list of options." << endl; 68 | return EXIT_FAILURE; 69 | } 70 | 71 | // Load the .scm Morphable Model and save it as cereal model: 72 | morphablemodel::MorphableModel morphable_model = morphablemodel::load_scm_model(scmmodelfile, isomapfile); 73 | 74 | if (save_shape_only) 75 | { 76 | // Save only the shape model - to generate the public sfm_shape_3448.bin 77 | const morphablemodel::MorphableModel shape_only_model(morphable_model.get_shape_model(), 78 | morphablemodel::PcaModel(), cpp17::nullopt, 79 | morphable_model.get_texture_coordinates()); 80 | morphablemodel::save_model(shape_only_model, outputfile); 81 | } else 82 | { 83 | morphablemodel::save_model(morphable_model, outputfile); 84 | } 85 | 86 | cout << "Saved converted model as " << outputfile << "." << endl; 87 | return EXIT_SUCCESS; 88 | } 89 | --------------------------------------------------------------------------------