eos 1.4.0
Loading...
Searching...
No Matches
multi_image_fitting.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/fitting/multi_image_fitting.hpp
5 *
6 * Copyright 2017, 2018 Patrik Huber, Philipp Kopp
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 MULTI_IMAGE_FITTING_HPP_
23#define MULTI_IMAGE_FITTING_HPP_
24
25#include "eos/core/Landmark.hpp"
26#include "eos/core/LandmarkMapper.hpp"
27#include "eos/core/Mesh.hpp"
28#include "eos/morphablemodel/MorphableModel.hpp"
29#include "eos/morphablemodel/Blendshape.hpp"
30#include "eos/morphablemodel/EdgeTopology.hpp"
31#include "eos/fitting/orthographic_camera_estimation_linear.hpp"
32#include "eos/fitting/linear_shape_fitting.hpp"
33#include "eos/fitting/blendshape_fitting.hpp"
34#include "eos/fitting/contour_correspondence.hpp"
35#include "eos/fitting/closest_edge_fitting.hpp"
36#include "eos/fitting/RenderingParameters.hpp"
37#include "eos/cpp17/optional.hpp"
38
39#include "Eigen/Core"
40
41#include <algorithm>
42#include <cassert>
43#include <vector>
44
45namespace eos {
46namespace fitting {
47
89inline std::pair<std::vector<core::Mesh>, std::vector<fitting::RenderingParameters>> fit_shape_and_pose(
90 const morphablemodel::MorphableModel& morphable_model,
91 const std::vector<morphablemodel::Blendshape>& blendshapes,
92 const std::vector<core::LandmarkCollection<Eigen::Vector2f>>& landmarks,
93 const core::LandmarkMapper& landmark_mapper, std::vector<int> image_width, std::vector<int> image_height,
94 const morphablemodel::EdgeTopology& edge_topology, const fitting::ContourLandmarks& contour_landmarks,
95 const fitting::ModelContour& model_contour, int num_iterations,
96 cpp17::optional<int> num_shape_coefficients_to_fit, float lambda,
97 cpp17::optional<fitting::RenderingParameters> initial_rendering_params,
98 std::vector<float>& pca_shape_coefficients, std::vector<std::vector<float>>& blendshape_coefficients,
99 std::vector<std::vector<Eigen::Vector2f>>& fitted_image_points)
100{
101 assert(blendshapes.size() > 0);
102 assert(landmarks.size() > 0 && landmarks.size() == image_width.size() &&
103 image_width.size() == image_height.size());
104 assert(num_iterations > 0); // Can we allow 0, for only the initial pose-fit?
105 assert(pca_shape_coefficients.size() <= morphable_model.get_shape_model().get_num_principal_components());
106 int num_images = static_cast<int>(landmarks.size());
107 for (int j = 0; j < num_images; ++j)
108 {
109 assert(landmarks[j].size() >= 4);
110 assert(image_width[j] > 0 && image_height[j] > 0);
111 }
112 // More asserts I forgot?
113
114 using Eigen::MatrixXf;
115 using Eigen::Vector2f;
116 using Eigen::Vector4f;
117 using Eigen::VectorXf;
118 using std::vector;
119
120 if (!num_shape_coefficients_to_fit)
121 {
122 num_shape_coefficients_to_fit = morphable_model.get_shape_model().get_num_principal_components();
123 }
124
125 if (pca_shape_coefficients.empty())
126 {
127 pca_shape_coefficients.resize(num_shape_coefficients_to_fit.value());
128 }
129 // Todo: This leaves the following case open: num_coeffs given is empty or defined, but the
130 // pca_shape_coefficients given is != num_coeffs or the model's max-coeffs. What to do then? Handle &
131 // document!
132
133 if (blendshape_coefficients.empty())
134 {
135 for (int j = 0; j < num_images; ++j)
136 {
137 std::vector<float> current_blendshape_coefficients;
138 current_blendshape_coefficients.resize(blendshapes.size());
139 blendshape_coefficients.push_back(current_blendshape_coefficients);
140 }
141 }
142
143 MatrixXf blendshapes_as_basis = morphablemodel::to_matrix(blendshapes);
144
145 // Current mesh - either from the given coefficients, or the mean:
146 VectorXf current_pca_shape = morphable_model.get_shape_model().draw_sample(pca_shape_coefficients);
147 vector<VectorXf> current_combined_shapes;
148 vector<core::Mesh> current_meshes;
149 for (int j = 0; j < num_images; ++j)
150 {
151 VectorXf current_combined_shape =
152 current_pca_shape +
153 blendshapes_as_basis * Eigen::Map<const Eigen::VectorXf>(blendshape_coefficients[j].data(),
154 blendshape_coefficients[j].size());
155 current_combined_shapes.push_back(current_combined_shape);
156
158 current_combined_shape, morphable_model.get_color_model().get_mean(),
159 morphable_model.get_shape_model().get_triangle_list(),
160 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates());
161 current_meshes.push_back(current_mesh);
162 }
163
164 // The 2D and 3D point correspondences used for the fitting:
165 vector<vector<Vector4f>> model_points; // the points in the 3D shape model of all frames
166 vector<vector<int>> vertex_indices; // their vertex indices of all frames
167 vector<vector<Vector2f>> image_points; // the corresponding 2D landmark points of all frames
168
169 for (int j = 0; j < num_images; ++j)
170 {
171 vector<Vector4f> current_model_points;
172 vector<int> current_vertex_indices;
173 vector<Vector2f> current_image_points;
174
175 // Sub-select all the landmarks which we have a mapping for (i.e. that are defined in the 3DMM),
176 // and get the corresponding model points (mean if given no initial coeffs, from the computed shape
177 // otherwise):
178 for (int i = 0; i < landmarks[j].size(); ++i)
179 {
180 const auto converted_name = landmark_mapper.convert(landmarks[j][i].name);
181 if (!converted_name)
182 { // no mapping defined for the current landmark
183 continue;
184 }
185 const int vertex_idx = std::stoi(converted_name.value());
186 current_model_points.emplace_back(current_meshes[j].vertices[vertex_idx].homogeneous());
187 current_vertex_indices.emplace_back(vertex_idx);
188 current_image_points.emplace_back(landmarks[j][i].coordinates);
189 }
190
191 model_points.push_back(current_model_points);
192 vertex_indices.push_back(current_vertex_indices);
193 image_points.push_back(current_image_points);
194 }
195
196 // Need to do an initial pose fit to do the contour fitting inside the loop.
197 // We'll do an expression fit too, since face shapes vary quite a lot, depending on expressions.
198 vector<fitting::RenderingParameters> rendering_params;
199 for (int j = 0; j < num_images; ++j)
200 {
202 fitting::estimate_orthographic_projection_linear(image_points[j], model_points[j], true,
203 image_height[j]);
204 fitting::RenderingParameters current_rendering_params(current_pose, image_width[j], image_height[j]);
205 rendering_params.push_back(current_rendering_params);
206
207 const Eigen::Matrix<float, 3, 4> affine_from_ortho =
208 fitting::get_3x4_affine_camera_matrix(current_rendering_params, image_width[j], image_height[j]);
209 blendshape_coefficients[j] = fitting::fit_blendshapes_to_landmarks_nnls(
210 blendshapes, current_pca_shape, affine_from_ortho, image_points[j], vertex_indices[j]);
211
212 // Mesh with same PCA coeffs as before, but new expression fit (this is relevant if no initial
213 // blendshape coeffs have been given):
214 current_combined_shapes[j] =
215 current_pca_shape + morphablemodel::to_matrix(blendshapes) *
216 Eigen::Map<const Eigen::VectorXf>(blendshape_coefficients[j].data(),
217 blendshape_coefficients[j].size());
218 current_meshes[j] = morphablemodel::sample_to_mesh(
219 current_combined_shapes[j], morphable_model.get_color_model().get_mean(),
220 morphable_model.get_shape_model().get_triangle_list(),
221 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates());
222 }
223
224 // The static (fixed) landmark correspondences which will stay the same throughout
225 // the fitting (the inner face landmarks):
226 vector<vector<int>> fixed_vertex_indices(vertex_indices);
227 vector<vector<Vector2f>> fixed_image_points(image_points);
228
229 for (int i = 0; i < num_iterations; ++i)
230 {
231 vector<Eigen::Matrix<float, 3, 4>> affine_from_orthos;
232 vector<VectorXf> mean_plus_blendshapes;
233
234 image_points = fixed_image_points;
235 vertex_indices = fixed_vertex_indices;
236
237 for (int j = 0; j < num_images; ++j)
238 {
239 // Given the current pose, find 2D-3D contour correspondences of the front-facing face contour:
240 vector<Vector2f> image_points_contour;
241 vector<int> vertex_indices_contour;
242 const auto yaw_angle = rendering_params[j].get_yaw_pitch_roll()[0];
243 // For each 2D contour landmark, get the corresponding 3D vertex point and vertex id:
244 std::tie(image_points_contour, std::ignore, vertex_indices_contour) =
246 landmarks[j], contour_landmarks, model_contour, yaw_angle, current_meshes[j],
247 rendering_params[j].get_modelview(), rendering_params[j].get_projection(),
248 fitting::get_opencv_viewport(image_width[j], image_height[j]));
249 // Add the contour correspondences to the set of landmarks that we use for the fitting:
250 vertex_indices[j] = fitting::concat(vertex_indices[j], vertex_indices_contour);
251 image_points[j] = fitting::concat(image_points[j], image_points_contour);
252
253 // Fit the occluding (away-facing) contour using the detected contour LMs:
254 vector<Vector2f> occluding_contour_landmarks;
255 if (yaw_angle >= 0.0f) // positive yaw = subject looking to the left
256 { // the left contour is the occluding one we want to use ("away-facing")
257 auto contour_landmarks_ = core::filter(
258 landmarks[j], contour_landmarks.left_contour); // Can do this outside of the loop
259 std::for_each(
260 begin(contour_landmarks_), end(contour_landmarks_),
261 [&occluding_contour_landmarks](auto&& lm) {
262 occluding_contour_landmarks.push_back({lm.coordinates[0], lm.coordinates[1]});
263 });
264 } else
265 {
266 auto contour_landmarks_ = core::filter(landmarks[j], contour_landmarks.right_contour);
267 std::for_each(
268 begin(contour_landmarks_), end(contour_landmarks_),
269 [&occluding_contour_landmarks](auto&& lm) {
270 occluding_contour_landmarks.push_back({lm.coordinates[0], lm.coordinates[1]});
271 });
272 }
273 auto edge_correspondences = fitting::find_occluding_edge_correspondences(
274 current_meshes[j], edge_topology, rendering_params[j], occluding_contour_landmarks, 180.0f);
275 image_points[j] = fitting::concat(image_points[j], edge_correspondences.first);
276 vertex_indices[j] = fitting::concat(vertex_indices[j], edge_correspondences.second);
277
278 // Get the model points of the current mesh, for all correspondences that we've got:
279 model_points[j].clear();
280 for (auto v : vertex_indices[j])
281 {
282 model_points[j].push_back({current_meshes[j].vertices[v][0], current_meshes[j].vertices[v][1],
283 current_meshes[j].vertices[v][2], 1.0f});
284 }
285
286 // Re-estimate the pose, using all correspondences:
288 fitting::estimate_orthographic_projection_linear(image_points[j], model_points[j], true,
289 image_height[j]);
290 rendering_params[j] = fitting::RenderingParameters(current_pose, image_width[j], image_height[j]);
291
292 const Eigen::Matrix<float, 3, 4> affine_from_ortho =
293 fitting::get_3x4_affine_camera_matrix(rendering_params[j], image_width[j], image_height[j]);
294 affine_from_orthos.push_back(affine_from_ortho);
295
296 // Estimate the PCA shape coefficients with the current blendshape coefficients:
297 VectorXf current_mean_plus_blendshapes =
298 morphable_model.get_shape_model().get_mean() +
299 blendshapes_as_basis * Eigen::Map<const Eigen::VectorXf>(blendshape_coefficients[j].data(),
300 blendshape_coefficients[j].size());
301 mean_plus_blendshapes.push_back(current_mean_plus_blendshapes);
302 }
303 pca_shape_coefficients = fitting::fit_shape_to_landmarks_linear_multi(
304 morphable_model.get_shape_model(), affine_from_orthos, image_points, vertex_indices,
305 mean_plus_blendshapes, lambda, num_shape_coefficients_to_fit);
306
307 // Estimate the blendshape coefficients with the current PCA model estimate:
308 current_pca_shape = morphable_model.get_shape_model().draw_sample(pca_shape_coefficients);
309
310 for (int j = 0; j < num_images; ++j)
311 {
312 blendshape_coefficients[j] = fitting::fit_blendshapes_to_landmarks_nnls(
313 blendshapes, current_pca_shape, affine_from_orthos[j], image_points[j], vertex_indices[j]);
314
315 current_combined_shapes[j] =
316 current_pca_shape +
317 blendshapes_as_basis * Eigen::Map<const Eigen::VectorXf>(blendshape_coefficients[j].data(),
318 blendshape_coefficients[j].size());
319 current_meshes[j] = morphablemodel::sample_to_mesh(
320 current_combined_shapes[j], morphable_model.get_color_model().get_mean(),
321 morphable_model.get_shape_model().get_triangle_list(),
322 morphable_model.get_color_model().get_triangle_list(),
323 morphable_model.get_texture_coordinates());
324 }
325 }
326
327 fitted_image_points = image_points;
328 return {current_meshes, rendering_params}; // I think we could also work with a Mat face_instance in this
329 // function instead of a Mesh, but it would convolute the code
330 // more (i.e. more complicated to access vertices).
331};
332
369inline std::pair<std::vector<core::Mesh>, std::vector<fitting::RenderingParameters>>
371 const std::vector<morphablemodel::Blendshape>& blendshapes,
372 const std::vector<core::LandmarkCollection<Eigen::Vector2f>>& landmarks,
373 const core::LandmarkMapper& landmark_mapper, std::vector<int> image_width,
374 std::vector<int> image_height, const morphablemodel::EdgeTopology& edge_topology,
375 const fitting::ContourLandmarks& contour_landmarks,
376 const fitting::ModelContour& model_contour, int num_iterations = 5,
377 cpp17::optional<int> num_shape_coefficients_to_fit = cpp17::nullopt, float lambda = 30.0f)
378{
379 using std::vector;
380 vector<float> pca_shape_coefficients;
381 vector<vector<float>> blendshape_coefficients;
382 vector<vector<Eigen::Vector2f>> fitted_image_points;
383
384 return fit_shape_and_pose(morphable_model, blendshapes, landmarks, landmark_mapper, image_width,
385 image_height, edge_topology, contour_landmarks, model_contour, num_iterations,
386 num_shape_coefficients_to_fit, lambda, cpp17::nullopt, pca_shape_coefficients,
387 blendshape_coefficients, fitted_image_points);
388};
389
390} /* namespace fitting */
391} /* namespace eos */
392
393#endif /* MULTI_IMAGE_FITTING_HPP_ */
Represents a mapping from one kind of landmarks to a different format (e.g. model vertices).
Definition: LandmarkMapper.hpp:53
cpp17::optional< std::string > convert(std::string landmark_name) const
Converts the given landmark name to the mapped name.
Definition: LandmarkMapper.hpp:116
Represents a set of estimated model parameters (rotation, translation) and camera parameters (viewing...
Definition: RenderingParameters.hpp:102
A class representing a 3D Morphable Model, consisting of a shape- and colour (albedo) PCA model.
Definition: MorphableModel.hpp:73
const std::vector< std::array< double, 2 > > & get_texture_coordinates() const
Definition: MorphableModel.hpp:413
const PcaModel & get_shape_model() const
Definition: MorphableModel.hpp:125
const PcaModel & get_color_model() const
Definition: MorphableModel.hpp:135
int get_num_principal_components() const
Definition: PcaModel.hpp:88
const std::vector< std::array< int, 3 > > & get_triangle_list() const
Definition: PcaModel.hpp:113
const Eigen::VectorXf & get_mean() const
Definition: PcaModel.hpp:123
Eigen::VectorXf draw_sample(RNG &engine, float sigma=1.0f) const
Definition: PcaModel.hpp:150
std::vector< Landmark< LandmarkType > > LandmarkCollection
A trivial collection of landmarks that belong together.
Definition: Landmark.hpp:47
LandmarkCollection< T > filter(const LandmarkCollection< T > &landmarks, const std::vector< std::string > &filter)
Shorthand for a 2D floating point landmark type.
Definition: Landmark.hpp:69
ScaledOrthoProjectionParameters estimate_orthographic_projection_linear(std::vector< Eigen::Vector2f > image_points, std::vector< Eigen::Vector4f > model_points, bool is_viewport_upsidedown, cpp17::optional< int > viewport_height=cpp17::nullopt)
Definition: orthographic_camera_estimation_linear.hpp:72
std::vector< float > fit_shape_to_landmarks_linear_multi(const morphablemodel::PcaModel &shape_model, const std::vector< Eigen::Matrix< float, 3, 4 > > &affine_camera_matrices, const std::vector< std::vector< Eigen::Vector2f > > &landmarks, const std::vector< std::vector< int > > &vertex_ids, std::vector< Eigen::VectorXf > base_faces=std::vector< Eigen::VectorXf >(), float lambda=3.0f, cpp17::optional< int > num_coefficients_to_fit=cpp17::optional< int >(), cpp17::optional< float > detector_standard_deviation=cpp17::optional< float >(), cpp17::optional< float > model_standard_deviation=cpp17::optional< float >())
Definition: linear_shape_fitting.hpp:178
Eigen::Vector4f get_opencv_viewport(int width, int height)
Returns a glm/OpenGL compatible viewport vector that flips y and has the origin on the top-left,...
Definition: RenderingParameters.hpp:325
std::vector< float > fit_blendshapes_to_landmarks_nnls(const std::vector< morphablemodel::Blendshape > &blendshapes, const Eigen::VectorXf &face_instance, Eigen::Matrix< float, 3, 4 > affine_camera_matrix, const std::vector< Eigen::Vector2f > &landmarks, const std::vector< int > &vertex_ids)
Definition: blendshape_fitting.hpp:138
auto concat(const std::vector< T > &vec_a, const std::vector< T > &vec_b)
Concatenates two std::vector's of the same type and returns the concatenated vector....
Definition: contour_correspondence.hpp:388
Eigen::Matrix< float, 3, 4 > get_3x4_affine_camera_matrix(RenderingParameters params, int width, int height)
Creates a 3x4 affine camera matrix from given fitting parameters. The matrix transforms points direct...
Definition: RenderingParameters.hpp:337
std::pair< std::vector< Eigen::Vector2f >, std::vector< int > > find_occluding_edge_correspondences(const core::Mesh &mesh, const morphablemodel::EdgeTopology &edge_topology, const fitting::RenderingParameters &rendering_parameters, const std::vector< Eigen::Vector2f > &image_edges, float distance_threshold=64.0f, bool perform_self_occlusion_check=true)
For a given list of 2D edge points, find corresponding 3D vertex IDs.
Definition: closest_edge_fitting.hpp:278
std::tuple< std::vector< Eigen::Vector2f >, std::vector< Eigen::Vector4f >, std::vector< int > > get_contour_correspondences(const core::LandmarkCollection< Eigen::Vector2f > &landmarks, const ContourLandmarks &contour_landmarks, const ModelContour &model_contour, float yaw_angle, const core::Mesh &mesh, const Eigen::Matrix4f &view_model, const Eigen::Matrix4f &ortho_projection, const Eigen::Vector4f &viewport, float frontal_range_threshold=7.5f)
Definition: contour_correspondence.hpp:237
std::pair< core::Mesh, fitting::RenderingParameters > 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_shape_coefficients, std::vector< float > &expression_coefficients, std::vector< Eigen::Vector2f > &fitted_image_points)
Fit the pose (camera), shape model, and expression blendshapes to landmarks, in an iterative way.
Definition: fitting.hpp:302
Eigen::MatrixXf to_matrix(const std::vector< Blendshape > &blendshapes)
Copies the blendshapes into a matrix, with each column being a blendshape.
Definition: Blendshape.hpp:118
core::Mesh sample_to_mesh(const Eigen::VectorXf &shape_instance, const Eigen::VectorXf &color_instance, const std::vector< std::array< int, 3 > > &tvi, const std::vector< std::array< int, 3 > > &tci, const std::vector< std::array< double, 2 > > &texture_coordinates=std::vector< std::array< double, 2 > >(), const std::vector< std::array< int, 3 > > &texture_triangle_indices=std::vector< std::array< int, 3 > >())
Definition: MorphableModel.hpp:584
Namespace containing all of eos's 3D model fitting functionality.
This class represents a 3D mesh consisting of vertices, vertex colour information and texture coordin...
Definition: Mesh.hpp:45
Defines which 2D landmarks comprise the right and left face contour.
Definition: contour_correspondence.hpp:140
Definition of the vertex indices that define the right and left model contour.
Definition: contour_correspondence.hpp:71
Definition: orthographic_camera_estimation_linear.hpp:42
A struct containing a 3D shape model's edge topology.
Definition: EdgeTopology.hpp:54