Commit 509f30cc authored by Patrik Huber's avatar Patrik Huber

Merge branch 'devel' of https://github.com/patrikhuber/eos into devel

parents 1c412822 c1b4f43d
......@@ -164,7 +164,7 @@ int main(int argc, char *argv[])
catch (const po::error& e) {
cout << "Error while parsing command-line arguments: " << e.what() << endl;
cout << "Use --help to display a list of options." << endl;
return EXIT_SUCCESS;
return EXIT_FAILURE;
}
google::InitGoogleLogging(argv[0]); // Ceres logging initialisation
......
......@@ -140,7 +140,7 @@ int main(int argc, char *argv[])
catch (const po::error& e) {
cout << "Error while parsing command-line arguments: " << e.what() << endl;
cout << "Use --help to display a list of options." << endl;
return EXIT_SUCCESS;
return EXIT_FAILURE;
}
// Load the image, landmarks, LandmarkMapper and the Morphable Model:
......
......@@ -174,7 +174,7 @@ int main(int argc, char *argv[])
catch (const po::error& e) {
cout << "Error while parsing command-line arguments: " << e.what() << endl;
cout << "Use --help to display a list of options." << endl;
return EXIT_SUCCESS;
return EXIT_FAILURE;
}
// Load the image, landmarks, LandmarkMapper and the Morphable Model:
......
......@@ -76,7 +76,7 @@ int main(int argc, char *argv[])
catch (const po::error& e) {
cout << "Error while parsing command-line arguments: " << e.what() << endl;
cout << "Use --help to display a list of options." << endl;
return EXIT_SUCCESS;
return EXIT_FAILURE;
}
morphablemodel::MorphableModel morphable_model = morphablemodel::load_model(model_file.string());
......
......@@ -335,7 +335,7 @@ inline Eigen::MatrixXf unnormalise_pca_basis(const Eigen::MatrixXf& normalised_b
{
using Eigen::MatrixXf;
MatrixXf unnormalised_basis(normalised_basis.rows(), normalised_basis.cols()); // empty matrix with the same dimensions
Eigen::VectorXf one_over_sqrt_of_eigenvalues = eigenvalues.array().rsqrt();
Eigen::VectorXf one_over_sqrt_of_eigenvalues = eigenvalues.array().sqrt().inverse(); // Eigen added Array::rsqrt() in 3.3, switch back to that eventually
// De-normalise the basis: We multiply each eigenvector (i.e. each column) with 1 over the square root of its corresponding eigenvalue
for (int basis = 0; basis < normalised_basis.cols(); ++basis) {
unnormalised_basis.col(basis) = normalised_basis.col(basis) * one_over_sqrt_of_eigenvalues(basis);
......
......@@ -49,7 +49,7 @@ std::vector<std::array<double, 2>> load_isomap(boost::filesystem::path isomap_fi
* Note: For new landmarks we add, this might not be the case if we add them
* in the highest resolution model, so take care!
*
* - The pcaBasis matrix stored in the file and loaded is the orthogonal PCA basis, i.e. it is not normalised by the eigenvalues.
* - The PCA basis matrix stored in the file and loaded is the orthogonal PCA basis, i.e. it is not normalised by the eigenvalues.
*
* @param[in] model_filename A binary .scm-file containing the model.
* @param[in] isomap_file An optional path to an isomap containing texture coordinates.
......@@ -152,8 +152,13 @@ inline MorphableModel load_scm_model(boost::filesystem::path model_filename, boo
}
// We read the unnormalised basis from the file. Now let's normalise it and store the normalised basis separately.
Mat normalisedPcaBasisShape = normalise_pca_basis(unnormalisedPcaBasisShape, eigenvaluesShape);
PcaModel shapeModel(meanShape, normalisedPcaBasisShape, eigenvaluesShape, triangleList);
// Todo: We should change these to read into an Eigen matrix directly, and not into a cv::Mat first.
using RowMajorMatrixXf = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
Eigen::Map<RowMajorMatrixXf> unnormalisedPcaBasisShape_(unnormalisedPcaBasisShape.ptr<float>(), unnormalisedPcaBasisShape.rows, unnormalisedPcaBasisShape.cols);
Eigen::Map<RowMajorMatrixXf> eigenvaluesShape_(eigenvaluesShape.ptr<float>(), eigenvaluesShape.rows, eigenvaluesShape.cols);
Eigen::Map<RowMajorMatrixXf> meanShape_(meanShape.ptr<float>(), meanShape.rows, meanShape.cols);
Eigen::MatrixXf normalisedPcaBasisShape_ = normalise_pca_basis(unnormalisedPcaBasisShape_, eigenvaluesShape_);
PcaModel shapeModel(meanShape_, normalisedPcaBasisShape_, eigenvaluesShape_, triangleList);
// Reading the color model
// Read number of rows and columns of projection matrix
......@@ -201,13 +206,16 @@ inline MorphableModel load_scm_model(boost::filesystem::path model_filename, boo
}
// We read the unnormalised basis from the file. Now let's normalise it and store the normalised basis separately.
Mat normalisedPcaBasisColor = normalise_pca_basis(unnormalisedPcaBasisColor, eigenvaluesColor);
PcaModel colorModel(meanColor, normalisedPcaBasisColor, eigenvaluesColor, triangleList);
Eigen::Map<RowMajorMatrixXf> unnormalisedPcaBasisColor_(unnormalisedPcaBasisColor.ptr<float>(), unnormalisedPcaBasisColor.rows, unnormalisedPcaBasisColor.cols);
Eigen::Map<RowMajorMatrixXf> eigenvaluesColor_(eigenvaluesColor.ptr<float>(), eigenvaluesColor.rows, eigenvaluesColor.cols);
Eigen::Map<RowMajorMatrixXf> meanColor_(meanColor.ptr<float>(), meanColor.rows, meanColor.cols);
Eigen::MatrixXf normalisedPcaBasisColor_ = normalise_pca_basis(unnormalisedPcaBasisColor_, eigenvaluesColor_);
PcaModel colorModel(meanColor_, normalisedPcaBasisColor_, eigenvaluesColor_, triangleList);
modelFile.close();
// Load the isomap with texture coordinates if a filename has been given:
std::vector<cv::Vec2f> texCoords;
std::vector<std::array<double, 2>> texCoords;
if (!isomap_file.empty()) {
texCoords = load_isomap(isomap_file);
if (shapeModel.get_data_dimension() / 3.0f != texCoords.size()) {
......@@ -228,7 +236,7 @@ inline MorphableModel load_scm_model(boost::filesystem::path model_filename, boo
* @return The 2D texture coordinates for every vertex.
* @throws ...
*/
inline std::vector<cv::Vec2f> load_isomap(boost::filesystem::path isomapFile)
inline std::vector<std::array<double, 2>> load_isomap(boost::filesystem::path isomapFile)
{
using std::string;
std::vector<float> xCoords, yCoords;
......@@ -253,11 +261,11 @@ inline std::vector<cv::Vec2f> load_isomap(boost::filesystem::path isomapFile)
auto minMaxX = std::minmax_element(begin(xCoords), end(xCoords)); // minMaxX is a pair, first=min, second=max
auto minMaxY = std::minmax_element(begin(yCoords), end(yCoords));
std::vector<cv::Vec2f> texCoords;
std::vector<std::array<double, 2>> texCoords;
float divisorX = *minMaxX.second - *minMaxX.first;
float divisorY = *minMaxY.second - *minMaxY.first;
for (int i = 0; i < xCoords.size(); ++i) {
texCoords.push_back(cv::Vec2f((xCoords[i] - *minMaxX.first) / divisorX, 1.0f - (yCoords[i] - *minMaxY.first) / divisorY)); // We rescale to [0, 1] and at the same time flip the y-coords (because in the isomap, the coordinates are stored upside-down).
texCoords.push_back(std::array<double, 2>{(xCoords[i] - *minMaxX.first) / divisorX, 1.0f - (yCoords[i] - *minMaxY.first) / divisorY}); // We rescale to [0, 1] and at the same time flip the y-coords (because in the isomap, the coordinates are stored upside-down).
}
return texCoords;
......
......@@ -56,6 +56,7 @@ PYBIND11_PLUGIN(eos) {
* Bindings for the eos::core namespace:
* - LandmarkMapper
* - Mesh
* - write_obj()
*/
py::module core_module = eos_module.def_submodule("core", "Essential functions and classes to work with 3D face models and landmarks.");
py::class_<core::LandmarkMapper>(core_module, "LandmarkMapper", "Represents a mapping from one kind of landmarks to a different format(e.g.model vertices).")
......@@ -74,29 +75,39 @@ PYBIND11_PLUGIN(eos) {
.def_readwrite("texcoords", &core::Mesh::texcoords, "Texture coordinates")
;
core_module.def("write_obj", &core::write_obj, "Writes the given Mesh to an obj file.", py::arg("mesh"), py::arg("filename"));
/**
* Bindings for the eos::morphablemodel namespace:
* - PcaModel
* - MorphableModel
* - load_model()
* - save_model()
*/
py::module morphablemodel_module = eos_module.def_submodule("morphablemodel", "Functionality to represent a Morphable Model, its PCA models, and functions to load models and blendshapes.");
py::class_<morphablemodel::PcaModel>(morphablemodel_module, "PcaModel", "Class representing a PcaModel with a mean, eigenvectors and eigenvalues, as well as a list of triangles to build a mesh.")
.def(py::init<>(), "Creates an empty model.")
.def(py::init<Eigen::VectorXf, Eigen::MatrixXf, Eigen::VectorXf, std::vector<std::array<int, 3>>>(), "Construct a PCA model from given mean, normalised PCA basis, eigenvalues and triangle list.", py::arg("mean"), py::arg("pca_basis"), py::arg("eigenvalues"), py::arg("triangle_list"))
.def("get_num_principal_components", &morphablemodel::PcaModel::get_num_principal_components, "Returns the number of principal components in the model.")
.def("get_data_dimension", &morphablemodel::PcaModel::get_data_dimension, "Returns the dimension of the data, i.e. the number of shape dimensions.")
.def("get_triangle_list", &morphablemodel::PcaModel::get_triangle_list, "Returns a list of triangles on how to assemble the vertices into a mesh.")
.def("get_mean", &morphablemodel::PcaModel::get_mean, "Returns the mean of the model.")
.def("get_mean_at_point", &morphablemodel::PcaModel::get_mean_at_point, "Return the value of the mean at a given vertex index.", py::arg("vertex_index"))
.def("get_normalised_pca_basis", [](const morphablemodel::PcaModel& m) { return m.get_normalised_pca_basis(); }, "Returns the PCA basis matrix, i.e. the eigenvectors. Each column of the matrix is an eigenvector.") // use py::overload in VS2017
.def("get_eigenvalues", &morphablemodel::PcaModel::get_eigenvalues, "Returns the models eigenvalues.")
.def("draw_sample", (Eigen::VectorXf(morphablemodel::PcaModel::*)(std::vector<float>) const)&morphablemodel::PcaModel::draw_sample, "Returns a sample from the model with the given PCA coefficients. The given coefficients should follow a standard normal distribution, i.e. not be scaled with their eigenvalues/variances.", py::arg("coefficients"))
;
py::class_<morphablemodel::MorphableModel>(morphablemodel_module, "MorphableModel", "A class representing a 3D Morphable Model, consisting of a shape- and colour (albedo) PCA model, as well as texture (uv) coordinates.")
.def(py::init<morphablemodel::PcaModel, morphablemodel::PcaModel, std::vector<std::array<double, 2>>>(), "Create a Morphable Model from a shape and a colour PCA model, and optional texture coordinates.", py::arg("shape_model"), py::arg("color_model"), py::arg("texture_coordinates") = std::vector<std::array<double, 2>>())
.def("get_shape_model", [](const morphablemodel::MorphableModel& m) { return m.get_shape_model(); }, "Returns the PCA shape model of this Morphable Model.") // Not sure if that'll really be const in Python? I think Python does a copy each time this gets called?
.def("get_color_model", [](const morphablemodel::MorphableModel& m) { return m.get_color_model(); }, "Returns the PCA colour (albedo) model of this Morphable Model.")
.def("get_color_model", [](const morphablemodel::MorphableModel& m) { return m.get_color_model(); }, "Returns the PCA colour (albedo) model of this Morphable Model.") // (continued from above:) We may want to use py::overload, but in any case, we need to tell pybind11 if it should use the const or non-const overload.
.def("get_mean", &morphablemodel::MorphableModel::get_mean, "Returns the mean of the shape- and colour model as a Mesh.")
;
morphablemodel_module.def("load_model", &morphablemodel::load_model, "Load a Morphable Model from a cereal::BinaryInputArchive (.bin) from the harddisk.", py::arg("filename"));
morphablemodel_module.def("save_model", &morphablemodel::save_model, "Save a Morphable Model as cereal::BinaryOutputArchive.", py::arg("model"), py::arg("filename"));
/**
* - Blendshape
......
......@@ -28,16 +28,13 @@ target_link_libraries(scm-to-cereal eos ${OpenCV_LIBS} ${Boost_LIBRARIES})
add_executable(bfm-binary-to-cereal bfm-binary-to-cereal.cpp)
target_link_libraries(bfm-binary-to-cereal eos ${OpenCV_LIBS} ${Boost_LIBRARIES})
# Reads an edgestruct CSV file created from Matlab, and converts it to json:
add_executable(edgestruct-csv-to-json edgestruct-csv-to-json.cpp)
target_link_libraries(edgestruct-csv-to-json eos ${Boost_LIBRARIES})
# Store a json file as cereal .bin:
add_executable(json-to-cereal-binary json-to-cereal-binary.cpp)
target_link_libraries(json-to-cereal-binary eos ${OpenCV_LIBS} ${Boost_LIBRARIES})
# install target:
install(TARGETS scm-to-cereal DESTINATION bin)
install(TARGETS bfm-binary-to-cereal DESTINATION bin)
install(TARGETS json-to-cereal-binary DESTINATION bin)
install(TARGETS edgestruct-csv-to-json DESTINATION bin)
......@@ -90,7 +90,7 @@ int main(int argc, char *argv[])
catch (const po::error& e) {
cout << "Error while parsing command-line arguments: " << e.what() << endl;
cout << "Use --help to display a list of options." << endl;
return EXIT_SUCCESS;
return EXIT_FAILURE;
}
morphablemodel::EdgeTopology edge_info;
......
/*
* eos - A 3D Morphable Model fitting library written in modern C++11/14.
*
* File: utils/json-to-cereal-binary.cpp
*
* Copyright 2016 Patrik Huber
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "eos/morphablemodel/MorphableModel.hpp"
#include "eos/morphablemodel/Blendshape.hpp"
#include "cereal/archives/json.hpp"
#include "boost/program_options.hpp"
#include "boost/filesystem.hpp"
#include <vector>
#include <iostream>
#include <fstream>
using namespace eos;
namespace po = boost::program_options;
namespace fs = boost::filesystem;
using std::cout;
using std::endl;
/**
* Reads a json Morphable Model or blendshape file and outputs it
* as an eos .bin file.
*/
int main(int argc, char *argv[])
{
fs::path jsonfile, outputfile;
std::string file_type;
try {
po::options_description desc("Allowed options");
desc.add_options()
("help,h",
"display the help message")
("input,i", po::value<fs::path>(&jsonfile)->required(),
"input json file (model or blendshapes)")
("type,t", po::value<std::string>(&file_type)->required(),
"type of the file to convert - 'model' or 'blendshape'")
("output,o", po::value<fs::path>(&outputfile)->required()->default_value("converted_model.bin"),
"output filename for the converted .bin file")
;
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
if (vm.count("help")) {
cout << "Usage: json-to-cereal-binary [options]" << endl;
cout << desc;
return EXIT_SUCCESS;
}
po::notify(vm);
}
catch (const po::error& e) {
cout << "Error while parsing command-line arguments: " << e.what() << endl;
cout << "Use --help to display a list of options." << endl;
return EXIT_SUCCESS;
}
if (file_type == "model")
{
morphablemodel::MorphableModel morphable_model;
std::ifstream file(jsonfile.string());
cereal::JSONInputArchive input_archive(file);
input_archive(morphable_model);
morphablemodel::save_model(morphable_model, outputfile.string());
}
else if (file_type == "blendshape")
{
std::vector<morphablemodel::Blendshape> blendshapes;
std::ifstream file(jsonfile.string());
cereal::JSONInputArchive input_archive(file);
input_archive(blendshapes);
std::ofstream out_file(outputfile.string(), std::ios::binary);
cereal::BinaryOutputArchive output_archive(out_file);
output_archive(blendshapes);
}
else
{
cout << "Type given is neither 'model' nor 'blendshape'." << endl;
return EXIT_SUCCESS;
}
cout << "Saved eos .bin model as " << outputfile.string() << "." << endl;
return EXIT_SUCCESS;
}
......@@ -20,8 +20,6 @@
#include "eos/morphablemodel/MorphableModel.hpp"
#include "eos/morphablemodel/io/cvssp.hpp"
#include "opencv2/core/core.hpp"
#include "boost/program_options.hpp"
#include "boost/filesystem.hpp"
......@@ -69,7 +67,7 @@ int main(int argc, char *argv[])
catch (const po::error& e) {
cout << "Error while parsing command-line arguments: " << e.what() << endl;
cout << "Use --help to display a list of options." << endl;
return EXIT_SUCCESS;
return EXIT_FAILURE;
}
// Load the .scm Morphable Model and save it as cereal model:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment