@@ -32,9 +32,10 @@ At the moment, it mainly provides the following functionality:
...
@@ -32,9 +32,10 @@ At the moment, it mainly provides the following functionality:
To use the library in your own project, just add the following directories to your include path:
To use the library in your own project, just add the following directories to your include path:
*`eos/include`
*`eos/include`
*`eos/3rdparty/cereal-1.1.1/include`
*`eos/3rdparty/cereal/include`
*`eos/3rdparty/glm`
*`eos/3rdparty/glm`
*`eos/3rdparty/nanoflann/include`
*`eos/3rdparty/nanoflann/include`
*`eos/3rdparty/eigen/Eigen`
*`eos/3rdparty/eigen3-nnls/src`
*`eos/3rdparty/eigen3-nnls/src`
**Make sure to clone with `--recursive` to download the required submodules!**
**Make sure to clone with `--recursive` to download the required submodules!**
...
@@ -84,9 +85,10 @@ The full model is available at [http://www.cvssp.org/facemodel](http://www.cvssp
...
@@ -84,9 +85,10 @@ The full model is available at [http://www.cvssp.org/facemodel](http://www.cvssp
## Python bindings
## Python bindings
eos includes python bindings for some of its functionality (and more can be added!). Set `-DEOS_GENERATE_PYTHON_BINDINGS=on` when running `cmake` to build them (and optionally set `PYTHON_EXECUTABLE` to point to your python interpreter if it's not found automatically).
eos includes python bindings for some of its functionality (and more can be added!). An experimental package is on PyPI: Try `pip install eos-py`. You will still need the data files from this repository.
In case of issues, build the bindings manually: Clone the repository and set `-DEOS_GENERATE_PYTHON_BINDINGS=on` when running `cmake` (and optionally set `PYTHON_EXECUTABLE` to point to your python interpreter if it's not found automatically).
After building the bindings, they can be used like any python module:
After having obtained the bindings, they can be used like any python module:
@@ -380,7 +380,7 @@ int main(int argc, char *argv[])
...
@@ -380,7 +380,7 @@ int main(int argc, char *argv[])
// Colour model fitting (this needs a Morphable Model with colour (albedo) model, see note above main()):
// Colour model fitting (this needs a Morphable Model with colour (albedo) model, see note above main()):
if(!morphable_model.has_color_model())
if(!morphable_model.has_color_model())
{
{
cout<<"The MorphableModel used does not contain a colour (albedo) model. ImageCost requires a model that contains a colour PCA model. You may want to use the full Surrey Face Model or remove this section.";
cout<<"Error: The MorphableModel used does not contain a colour (albedo) model. ImageCost requires a model that contains a colour PCA model. You may want to use the full Surrey Face Model or remove this section.";
// Above converts to a RowMajor matrix on return - for now, since the core algorithm still uses cv::Mat (and OpenCV stores data in row-major memory order).
// Above converts to a RowMajor matrix on return - for now, since the core algorithm still uses cv::Mat (and OpenCV stores data in row-major memory order).
assert(affine_camera_matrix.size()==landmarks.size()&&landmarks.size()==vertex_ids.size());// same number of instances (i.e. images/frames) for each of them
assert(affine_camera_matrix.size()==landmarks.size()&&landmarks.size()==vertex_ids.size());// same number of instances (i.e. images/frames) for each of them
Eigen::Matrix<float,Eigen::Dynamic,Eigen::Dynamic,Eigen::RowMajor>basis_rows_=morphable_model.get_shape_model().get_rescaled_pca_basis_at_point(vertex_ids[k][i]);// In the paper, the orthonormal basis might be used? I'm not sure, check it. It's even a mess in the paper. PH 26.5.2014: I think the rescaled basis is fine/better.
MatrixXfbasis_rows_=morphable_model.get_shape_model().get_rescaled_pca_basis_at_point(vertex_ids[k][i]);// In the paper, the orthonormal basis might be used? I'm not sure, check it. It's even a mess in the paper. PH 26.5.2014: I think the rescaled basis is fine/better.
// Above converts to a RowMajor matrix on return - for now, since the core algorithm still uses cv::Mat (and OpenCV stores data in row-major memory order).
// Sigma(i, i) = sqrt(sigma_squared_2D), but then Omega is Sigma.t() * Sigma (squares the diagonal) - so we just assign 1/sigma_squared_2D to Omega here:
Omega.at<float>(Omega_index,Omega_index)=1.0f/sigma_squared_2D;// the higher the sigma_squared_2D, the smaller the diagonal entries of Sigma will be
++Omega_index;
}
// The landmarks in matrix notation (in homogeneous coordinates), $3N\times 1$
// The landmarks in matrix notation (in homogeneous coordinates), $3N\times 1$
//Mat y = Mat::ones(3 * num_landmarks, 1, CV_32FC1);
//Mat y = Mat::ones(3 * num_landmarks, 1, CV_32FC1);
constMatrixXfrhs=-A.transpose()*Omega.asDiagonal()*b;// It's -A^t*Omega^t*b, but we don't need to transpose Omega, since it's a diagonal matrix, and Omega^t = Omega.
constintnum_shape_pc=num_coeffs_to_fit;
// c_s: The 'x' that we solve for. (The variance-normalised shape parameter vector, $c_s = [a_1/sigma_{s,1} , ..., a_m-1/sigma_{s,m-1}]^t$.)
MatAtOmegaA=A.t()*Omega*A;
// We get coefficients ~ N(0, 1), because we're fitting with the rescaled basis. The coefficients are not multiplied with their eigenvalues.
Eigen::FullPivLU<RowMajorMatrixXf> luOfAtOmegaAReg(AtOmegaAReg_Eigen); // Calculate the full-pivoting LU decomposition of the regularized AtA. Note: We could also try FullPivHouseholderQR if our system is non-minimal (i.e. there are more constraints than unknowns).
float threshold = std::abs(luOfAtOmegaAReg.maxPivot()) * luOfAtOmegaAReg.threshold(); // originaly "2 * ..." but I commented it out
RowMajorMatrixXf AtARegInv_EigenFullLU = luOfAtOmegaAReg.inverse(); // Note: We should use ::solve() instead
Mat AtOmegaARegInvFullLU(AtARegInv_EigenFullLU.rows(), AtARegInv_EigenFullLU.cols(), CV_32FC1, AtARegInv_EigenFullLU.data()); // create an OpenCV Mat header for the Eigen data
*/
// Solve using OpenCV:
Matc_s;// Note/Todo: We get coefficients ~ N(0, sigma) I think. They are not multiplied with the eigenvalues.
boolnon_singular=cv::solve(AtOmegaAReg,-A.t()*Omega.t()*b,c_s,cv::DECOMP_SVD);// DECOMP_SVD calculates the pseudo-inverse if the matrix is not invertible.
// Because we're using SVD, non_singular will always be true. If we were to use e.g. Cholesky, we could return an expected<T>.
returnstd::vector<float>(c_s);
};
};
/**
/**
* Fits the shape of a Morphable Model to given 2D landmarks (i.e. estimates the maximum likelihood solution of the shape coefficients) as proposed in [1].
* Fits the shape of a Morphable Model to given 2D landmarks (i.e. estimates the maximum likelihood solution of the shape coefficients) as proposed in [1].
* It's a linear, closed-form solution fitting of the shape, with regularisation (prior towards the mean).
* It's a linear, closed-form solution fitting of the shape, with regularisation (prior towards the mean).
.def("get_shape_model",[](constmorphablemodel::MorphableModel&m){returnm.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_shape_model",[](constmorphablemodel::MorphableModel&m){returnm.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",[](constmorphablemodel::MorphableModel&m){returnm.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_color_model",[](constmorphablemodel::MorphableModel&m){returnm.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.")
.def("get_mean",&morphablemodel::MorphableModel::get_mean,"Returns the mean of the shape- and colour model as a Mesh.")
.def("draw_sample",(core::Mesh(morphablemodel::MorphableModel::*)(std::vector<float>,std::vector<float>)const)&morphablemodel::MorphableModel::draw_sample,"Returns a sample from the model with the given shape- and colour PCA coefficients.",py::arg("shape_coefficients"),py::arg("color_coefficients"))
.def("has_color_model",&morphablemodel::MorphableModel::has_color_model,"Returns true if this Morphable Model contains a colour model, and false if it is a shape-only model.")
;
;
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("load_model",&morphablemodel::load_model,"Load a Morphable Model from a cereal::BinaryInputArchive (.bin) from the harddisk.",py::arg("filename"));