-
-
Notifications
You must be signed in to change notification settings - Fork 596
/
generate-python-bindings.cpp
338 lines (301 loc) · 26.4 KB
/
generate-python-bindings.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
/*
* eos - A 3D Morphable Model fitting library written in modern C++11/14.
*
* File: python/generate-python-bindings.cpp
*
* Copyright 2016-2019 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/core/Rect.hpp"
#include "eos/core/Landmark.hpp"
#include "eos/core/LandmarkMapper.hpp"
#include "eos/core/Mesh.hpp"
#include "eos/core/read_obj.hpp"
#include "eos/core/Image.hpp"
#include "eos/fitting/RenderingParameters.hpp"
#include "eos/fitting/contour_correspondence.hpp"
#include "eos/fitting/fitting.hpp"
#include "eos/fitting/orthographic_camera_estimation_linear.hpp"
#include "eos/morphablemodel/Blendshape.hpp"
#include "eos/morphablemodel/EdgeTopology.hpp"
#include "eos/morphablemodel/MorphableModel.hpp"
#include "eos/morphablemodel/PcaModel.hpp"
#include "eos/pca/pca.hpp"
#include "eos/render/texture_extraction.hpp"
#include "pybind11/pybind11.h"
#include "pybind11/eigen.h"
#include "pybind11/stl.h"
#include "eos/cpp17/optional.hpp"
#include "pybind11_optional.hpp"
#include "pybind11_variant.hpp"
#include "pybind11_Image.hpp"
#include "Eigen/Core"
#include <cassert>
#include <string>
namespace py = pybind11;
using namespace eos;
/**
* Generate python bindings for the eos library using pybind11.
*/
PYBIND11_MODULE(eos, eos_module)
{
eos_module.doc() = "Python bindings for the eos 3D Morphable Face Model fitting library.\n\nFor an "
"overview of the functionality, see the documentation of the submodules. For the full "
"documentation, see the C++ doxygen documentation.";
/**
* Bindings for the eos::core namespace:
* - Landmark
* - LandmarkMapper
* - Mesh
* - write_obj(), write_textured_obj()
* - Rect
*/
py::module core_module = eos_module.def_submodule("core", "Essential functions and classes to work with 3D face models and landmarks.");
py::class_<core::Landmark<Eigen::Vector2f>>(core_module, "Landmark",
"Representation of a landmark, consisting of a landmark name "
"and coordinates of the given type. Usually, the type would "
"be Eigen::Vector2f.")
.def(py::init<std::string, Eigen::Vector2f>(),
"Construct a Landmark with the given name (identifier) and point coordinates.", py::arg("name"),
py::arg("coordinates"))
.def_readwrite("name", &core::Landmark<Eigen::Vector2f>::name,
"Name of the landmark, often used as identifier")
.def_readwrite("coordinates", &core::Landmark<Eigen::Vector2f>::coordinates,
"The position or coordinates of the landmark")
.def("__repr__", [](const core::Landmark<Eigen::Vector2f>& l) {
return "<eos.core.Landmark [name=" + l.name + ", [x=" + std::to_string(l.coordinates(0)) +
", y=" + std::to_string(l.coordinates(1)) + "]]>";
});
py::class_<core::LandmarkMapper>(core_module, "LandmarkMapper", "Represents a mapping from one kind of landmarks to a different format(e.g.model vertices).")
.def(py::init<>(), "Constructs a new landmark mapper that performs an identity mapping, that is, its output is the same as the input.")
.def(py::init<std::string>(), "Constructs a new landmark mapper from a file containing mappings from one set of landmark identifiers to another.", py::arg("filename"))
.def("convert", &core::LandmarkMapper::convert, "Converts the given landmark name to the mapped name.", py::arg("landmark_name"))
.def("get_mappings", &core::LandmarkMapper::get_mappings, "Returns the mappings held by this mapper.")
.def("__repr__", [](const core::LandmarkMapper& m) {
return "<eos.core.LandmarkMapper with " + std::to_string(m.num_mappings()) + " mappings.>";
});
py::class_<core::Mesh>(core_module, "Mesh", "This class represents a 3D mesh consisting of vertices, vertex colour information and texture coordinates.")
.def(py::init<>(), "Creates an empty mesh.")
.def_readwrite("vertices", &core::Mesh::vertices, "3D vertex positions")
.def_readwrite("colors", &core::Mesh::colors, "Colour information for each vertex. Expected to be in RGB order.")
.def_readwrite("texcoords", &core::Mesh::texcoords, "Texture coordinates")
.def_readwrite("tvi", &core::Mesh::tvi, "Triangle vertex indices")
.def_readwrite("tci", &core::Mesh::tci, "Triangle colour indices (usually the same as tvi)")
.def_readwrite("tti", &core::Mesh::tti, "Triangle texture indices");
core_module.def("write_obj", &core::write_obj, "Writes the given Mesh to an obj file.", py::arg("mesh"), py::arg("filename"));
core_module.def("write_textured_obj", &core::write_textured_obj, "Writes the given Mesh to an obj file, including texture coordinates, and an mtl file containing a reference to the isomap. The texture (isomap) has to be saved separately.", py::arg("mesh"), py::arg("filename"));
core_module.def("read_obj", &core::read_obj, "Reads the given Wavefront .obj file into a Mesh.", py::arg("filename"));
py::class_<core::Rect<int>>(core_module, "Rect",
"A simple type representing a rectangle with integer values.")
.def(py::init<int, int, int, int>(),
"Construct a rectangle with given top-left x and y point and given width and height.",
py::arg("x"), py::arg("y"), py::arg("width"), py::arg("height"))
.def_readwrite("x", &core::Rect<int>::x, "Top-left corner x position")
.def_readwrite("y", &core::Rect<int>::y, "Top-left corner y position")
.def_readwrite("width", &core::Rect<int>::width, "Width of the rectangle")
.def_readwrite("height", &core::Rect<int>::height, "Height of the rectangle")
.def("__repr__", [](const core::Rect<int>& r) {
return "<eos.core.Rect [x=" + std::to_string(r.x) + ", y=" + std::to_string(r.y) +
", width=" + std::to_string(r.width) + ", height=" + std::to_string(r.height) + "]>";
});
/**
* Bindings for the eos::morphablemodel namespace:
* - Blendshape
* - load_blendshapes()
*/
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::Blendshape>(morphablemodel_module, "Blendshape", "A class representing a 3D blendshape.")
.def(py::init<>(), "Creates an empty blendshape.")
.def(py::init<std::string, Eigen::VectorXf>(), "Create a blendshape with given name and deformation vector.", py::arg("name"), py::arg("deformation"))
.def_readwrite("name", &morphablemodel::Blendshape::name, "Name of the blendshape.")
.def_readwrite("deformation", &morphablemodel::Blendshape::deformation, "A 3m x 1 col-vector (xyzxyz...)', where m is the number of model-vertices. Has the same format as PcaModel::mean.");
morphablemodel_module.def("load_blendshapes", &morphablemodel::load_blendshapes, "Load a file with blendshapes from a cereal::BinaryInputArchive (.bin) from the harddisk.", py::arg("filename"));
morphablemodel_module.def("save_blendshapes", &morphablemodel::save_blendshapes, "Save a set of blendshapes to the harddisk as a cereal::BinaryOutputArchive (.bin).", py::arg("blendshapes"), py::arg("filename"));
/**
* - PcaModel
* - MorphableModel
* - MorphableModel::ExpressionModelType
* - draw_sample()
* - load_model(), save_model()
* - load_pca_model(), save_pca_model()
* - sample_to_mesh()
*/
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, orthonormal PCA basis, eigenvalues and triangle list.", py::arg("mean"), py::arg("orthonormal_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_orthonormal_pca_basis", &morphablemodel::PcaModel::get_orthonormal_pca_basis, "Returns the orthonormal PCA basis matrix, i.e. the eigenvectors. Each column of the matrix is an eigenvector.")
.def("get_rescaled_pca_basis", &morphablemodel::PcaModel::get_rescaled_pca_basis, "Returns the rescaled PCA basis matrix, i.e. the eigenvectors. Each column of the matrix is an eigenvector, and each eigenvector has been rescaled by multiplying it with the square root of its eigenvalue.")
.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> morphable_model(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.");
py::enum_<morphablemodel::MorphableModel::ExpressionModelType>(
morphable_model, "ExpressionModelType",
"The type of the expression model that a MorphableModel contains.")
.value("None", morphablemodel::MorphableModel::ExpressionModelType::None)
.value("Blendshapes", morphablemodel::MorphableModel::ExpressionModelType::Blendshapes)
.value("PcaModel", morphablemodel::MorphableModel::ExpressionModelType::PcaModel);
morphable_model
.def(py::init<morphablemodel::PcaModel, morphablemodel::PcaModel, cpp17::optional<std::unordered_map<std::string, int>>, std::vector<std::array<double, 2>>, std::vector<std::array<int, 3>>>(), "Create a Morphable Model from a shape and a colour PCA model, and optional vertex definitions and texture coordinates.", py::arg("shape_model"), py::arg("color_model"), py::arg("vertex_definitions") = cpp17::nullopt, py::arg("texture_coordinates") = std::vector<std::array<double, 2>>(), py::arg("texture_triangle_indices") = std::vector<std::array<int, 3>>())
.def(py::init<morphablemodel::PcaModel, morphablemodel::ExpressionModel, morphablemodel::PcaModel, cpp17::optional<std::unordered_map<std::string, int>>, std::vector<std::array<double, 2>>, std::vector<std::array<int, 3>>>(), "Create a Morphable Model from a shape and a colour PCA model, an expression PCA model or blendshapes, and optional vertex definitions and texture coordinates.", py::arg("shape_model"), py::arg("expression_model"), py::arg("color_model"), py::arg("vertex_definitions") = cpp17::nullopt, py::arg("texture_coordinates") = std::vector<std::array<double, 2>>(), py::arg("texture_triangle_indices") = std::vector<std::array<int, 3>>())
.def("get_shape_model", &morphablemodel::MorphableModel::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", &morphablemodel::MorphableModel::get_color_model, "Returns the PCA colour (albedo) model of this Morphable Model.")
.def("get_expression_model", &morphablemodel::MorphableModel::get_expression_model, "Returns the shape expression model or an empty optional if the Morphable Model does not have a separate expression model.")
.def("get_mean", &morphablemodel::MorphableModel::get_mean, "Returns the mean of the shape (identity, and expressions, if present) 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("draw_sample", (core::Mesh(morphablemodel::MorphableModel::*)(std::vector<float>, std::vector<float>, std::vector<float>)const)&morphablemodel::MorphableModel::draw_sample, "Returns a sample from the model with the given shape, expression, and colour PCA coefficients. The MorphableModel has to have an expression model, otherwise, this function will throw.", py::arg("shape_coefficients"), py::arg("expression_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.")
.def("has_separate_expression_model", &morphablemodel::MorphableModel::has_separate_expression_model, "Returns true if this Morphable Model contains a separate PCA or Blendshapes expression model.")
.def("get_landmark_definitions", &morphablemodel::MorphableModel::get_landmark_definitions, "Returns the landmark definitions for this Morphable Model, which might be an empty optional, if the model doesn't contain any.")
.def("get_texture_coordinates", &morphablemodel::MorphableModel::get_texture_coordinates, "Returns the texture coordinates for all the vertices in the model.")
.def("get_texture_triangle_indices", &morphablemodel::MorphableModel::get_texture_triangle_indices, "Returns the triangulation (the triangles that make up the uv mapping) for the texture coordinates.")
.def("get_expression_model_type", &morphablemodel::MorphableModel::get_expression_model_type, "Returns the type of the expression model: None, Blendshapes or PcaModel.");
morphablemodel_module.def("draw_sample", &morphablemodel::draw_sample, "Returns a sample from the model with the given expression coefficients.", py::arg("expression_model"), py::arg("expression_coefficients"));
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"));
morphablemodel_module.def("load_pca_model", &morphablemodel::load_pca_model, "Load a PCA model from a cereal::BinaryInputArchive (.bin) from the harddisk.", py::arg("filename"));
morphablemodel_module.def("save_pca_model", &morphablemodel::save_pca_model, "Save a PCA model as cereal::BinaryOutputArchive.", py::arg("model"), py::arg("filename"));
morphablemodel_module.def(
"sample_to_mesh", &morphablemodel::sample_to_mesh,
"Helper function that creates a Mesh from given shape and (optional) colour PCA instances.",
py::arg("shape_instance"), py::arg("color_instance"), py::arg("tvi"), py::arg("tci"),
py::arg("texture_coordinates") = std::vector<std::array<double, 2>>(),
py::arg("texture_triangle_indices") = std::vector<std::array<int, 3>>());
/**
* - EdgeTopology
* - load_edge_topology()
*/
py::class_<morphablemodel::EdgeTopology>(morphablemodel_module, "EdgeTopology", "A struct containing a 3D shape model's edge topology.")
.def(py::init<std::vector<std::array<int, 2>>, std::vector<std::array<int, 2>>>(), "Construct a new EdgeTopology with given adjacent_faces and adjacent_vertices.", py::arg("adjacent_faces"), py::arg("adjacent_vertices")) // py::init<> uses brace-initialisation: http://pybind11.readthedocs.io/en/stable/advanced/classes.html#brace-initialization
.def_readwrite("adjacent_faces", &morphablemodel::EdgeTopology::adjacent_faces, "A num_edges x 2 matrix storing faces adjacent to each edge.")
.def_readwrite("adjacent_vertices", &morphablemodel::EdgeTopology::adjacent_vertices, "A num_edges x 2 matrix storing vertices adjacent to each edge.");
morphablemodel_module.def("load_edge_topology", &morphablemodel::load_edge_topology, "Load a 3DMM edge topology file from a json file.", py::arg("filename"));
morphablemodel_module.def("save_edge_topology", &morphablemodel::save_edge_topology, "Save a 3DMM edge topology file to a json file.", py::arg("edge_topology"), py::arg("filename"));
/**
* Bindings for the eos::pca namespace:
* - Covariance
* - pca()
*/
py::module pca_module = eos_module.def_submodule("pca", "PCA and functionality to build statistical models.");
py::enum_<pca::Covariance>(pca_module, "Covariance", "A flag specifying how to compute the covariance matrix in the PCA.")
.value("AtA", pca::Covariance::AtA)
.value("AAt", pca::Covariance::AAt);
pca_module.def("pca", py::overload_cast<const Eigen::Ref<const Eigen::MatrixXf>, pca::Covariance>(&pca::pca), "Compute PCA on a mean-centred data matrix, and return the eigenvectors and respective eigenvalues.", py::arg("data"), py::arg("covariance_type") = pca::Covariance::AtA);
pca_module.def("pca", py::overload_cast<const Eigen::Ref<const Eigen::MatrixXf>, int, pca::Covariance>(&pca::pca), "Performs PCA and returns num_eigenvectors_to_keep eigenvectors and eigenvalues.", py::arg("data"), py::arg("num_eigenvectors_to_keep"), py::arg("covariance_type") = pca::Covariance::AtA);
pca_module.def("pca", py::overload_cast<const Eigen::Ref<const Eigen::MatrixXf>, float, pca::Covariance>(&pca::pca), "Performs PCA and returns the number of eigenvectors and eigenvalues to retain 'variance_to_keep' variance of the original data.", py::arg("data"), py::arg("variance_to_keep"), py::arg("covariance_type") = pca::Covariance::AtA);
pca_module.def("pca", py::overload_cast<const Eigen::Ref<const Eigen::MatrixXf>, std::vector<std::array<int, 3>>, pca::Covariance>(&pca::pca), "Performs PCA on the given data (including subtracting the mean) and returns the built PcaModel.", py::arg("data"), py::arg("triangle_list"), py::arg("covariance_type") = pca::Covariance::AtA);
/**
* Bindings for the eos::fitting namespace:
* - ScaledOrthoProjectionParameters
* - RenderingParameters
* - estimate_orthographic_projection_linear()
* - ContourLandmarks
* - ModelContour
* - fit_shape_and_pose()
*/
py::module fitting_module = eos_module.def_submodule("fitting", "Pose and shape fitting of a 3D Morphable Model.");
py::class_<fitting::ScaledOrthoProjectionParameters>(fitting_module, "ScaledOrthoProjectionParameters", "Parameters of an estimated scaled orthographic projection.")
.def_property_readonly("R",
[](const fitting::ScaledOrthoProjectionParameters& p) {
Eigen::Matrix3f R; // we could probably use Eigen::Map
for (int col = 0; col < 3; ++col)
for (int row = 0; row < 3; ++row)
R(row, col) = p.R[col][row];
return R;
}, "Rotation matrix") // we can easily make this writable if ever required, just need to add a lambda function with the Eigen to glm matrix conversion.
.def_readwrite("s", &fitting::ScaledOrthoProjectionParameters::s, "Scale")
.def_readwrite("tx", &fitting::ScaledOrthoProjectionParameters::tx, "x translation")
.def_readwrite("ty", &fitting::ScaledOrthoProjectionParameters::ty, "y translation");
py::class_<fitting::RenderingParameters>(fitting_module, "RenderingParameters", "Represents a set of estimated model parameters (rotation, translation) and camera parameters (viewing frustum).")
.def(py::init<fitting::ScaledOrthoProjectionParameters, int, int>(), "Create a RenderingParameters object from an instance of estimated ScaledOrthoProjectionParameters.")
.def("get_rotation",
[](const fitting::RenderingParameters& p) {
return Eigen::Vector4f(p.get_rotation().x, p.get_rotation().y, p.get_rotation().z, p.get_rotation().w);
},
"Returns the rotation quaternion [x y z w].")
.def("get_rotation_euler_angles",
[](const fitting::RenderingParameters& p) {
const glm::vec3 euler_angles = glm::eulerAngles(p.get_rotation());
return Eigen::Vector3f(euler_angles[0], euler_angles[1], euler_angles[2]);
},
"Returns the rotation's Euler angles (in radians) as [pitch, yaw, roll].")
.def("get_modelview",
[](const fitting::RenderingParameters& p) {
Eigen::Matrix4f model_view; // we could probably use Eigen::Map
for (int col = 0; col < 4; ++col)
for (int row = 0; row < 4; ++row)
model_view(row, col) = p.get_modelview()[col][row];
return model_view;
},
"Returns the 4x4 model-view matrix.")
.def("get_projection",
[](const fitting::RenderingParameters& p) {
Eigen::Matrix4f projection; // we could probably use Eigen::Map
for (int col = 0; col < 4; ++col)
for (int row = 0; row < 4; ++row)
projection(row, col) = p.get_projection()[col][row];
return projection;
}, "Returns the 4x4 projection matrix.");
fitting_module.def("estimate_orthographic_projection_linear", &fitting::estimate_orthographic_projection_linear,
"This algorithm estimates the parameters of a scaled orthographic projection, given a set of corresponding 2D-3D points.",
py::arg("image_points"), py::arg("model_points"), py::arg("is_viewport_upsidedown") = py::none(), py::arg("viewport_height") = 0);
py::class_<fitting::ContourLandmarks>(fitting_module, "ContourLandmarks", "Defines which 2D landmarks comprise the right and left face contour.")
.def_static("load", &fitting::ContourLandmarks::load, "Helper method to load contour landmarks from a text file with landmark mappings, like ibug_to_sfm.txt.", py::arg("filename"));
py::class_<fitting::ModelContour>(fitting_module, "ModelContour", "Definition of the vertex indices that define the right and left model contour.")
.def_static("load", &fitting::ModelContour::load, "Helper method to load a ModelContour from a json file from the hard drive.", py::arg("filename"));
fitting_module.def(
"fit_shape_and_pose",
[](const morphablemodel::MorphableModel& morphable_model,
const core::LandmarkCollection<Eigen::Vector2f>& landmarks,
const core::LandmarkMapper& landmark_mapper, int image_width, int image_height,
const morphablemodel::EdgeTopology& edge_topology,
const fitting::ContourLandmarks& contour_landmarks, const fitting::ModelContour& model_contour,
int num_iterations, cpp17::optional<int> num_shape_coefficients_to_fit, float lambda_identity,
cpp17::optional<int> num_expression_coefficients_to_fit,
cpp17::optional<float> lambda_expressions) {
std::vector<float> pca_coeffs;
std::vector<float> blendshape_coeffs;
std::vector<Eigen::Vector2f> fitted_image_points;
const auto result = fitting::fit_shape_and_pose(
morphable_model, landmarks, landmark_mapper, image_width, image_height, edge_topology,
contour_landmarks, model_contour, num_iterations, num_shape_coefficients_to_fit,
lambda_identity, num_expression_coefficients_to_fit, lambda_expressions, pca_coeffs,
blendshape_coeffs, fitted_image_points);
return std::make_tuple(result.first, result.second, pca_coeffs, blendshape_coeffs);
},
"Fit the pose (camera), shape model, and expression blendshapes to landmarks, in an iterative way. "
"Returns a tuple (mesh, rendering_parameters, shape_coefficients, blendshape_coefficients).",
py::arg("morphable_model"), py::arg("landmarks"), py::arg("landmark_mapper"), py::arg("image_width"),
py::arg("image_height"), py::arg("edge_topology"), py::arg("contour_landmarks"),
py::arg("model_contour"), py::arg("num_iterations") = 5,
py::arg("num_shape_coefficients_to_fit") = py::none(), py::arg("lambda_identity") = 30.0f,
py::arg("num_expression_coefficients_to_fit") = py::none(), py::arg("lambda_expressions") = 30.0f);
/**
* Bindings for the eos::render namespace:
* - extract_texture()
*/
py::module render_module = eos_module.def_submodule("render", "3D mesh and texture extraction functionality.");
render_module.def("extract_texture",
[](const core::Mesh& mesh, const fitting::RenderingParameters& rendering_params,
const core::Image3u& image, bool compute_view_angle, int isomap_resolution) {
Eigen::Matrix<float, 3, 4> affine_from_ortho = fitting::get_3x4_affine_camera_matrix(rendering_params, image.width(), image.height());
return render::extract_texture(mesh, affine_from_ortho, image, compute_view_angle, render::TextureInterpolation::NearestNeighbour, isomap_resolution);
},
"Extracts the texture of the face from the given image and stores it as isomap (a rectangular texture map).",
py::arg("mesh"), py::arg("rendering_params"), py::arg("image"), py::arg("compute_view_angle") = false, py::arg("isomap_resolution") = 512);
};