eos 1.4.0
Loading...
Searching...
No Matches
fitting.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/fitting/fitting.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_FITTING_HPP
23#define EOS_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
69inline Eigen::VectorXf fit_shape(Eigen::Matrix<float, 3, 4> affine_camera_matrix,
70 const morphablemodel::MorphableModel& morphable_model,
71 const std::vector<morphablemodel::Blendshape>& blendshapes,
72 const std::vector<Eigen::Vector2f>& image_points,
73 const std::vector<int>& vertex_indices, float lambda,
74 cpp17::optional<int> num_coefficients_to_fit,
75 std::vector<float>& pca_shape_coefficients,
76 std::vector<float>& blendshape_coefficients)
77{
78 using Eigen::MatrixXf;
79 using Eigen::VectorXf;
80
81 const MatrixXf blendshapes_as_basis = morphablemodel::to_matrix(blendshapes);
82
83 std::vector<float> last_blendshape_coeffs, current_blendshape_coeffs;
84 std::vector<float> last_pca_coeffs, current_pca_coeffs;
85 current_blendshape_coeffs.resize(blendshapes.size()); // starting values t_0, all zeros
86 // no starting values for current_pca_coeffs required, since we start with the shape fitting, and cv::norm
87 // of an empty vector is 0.
88
89 VectorXf combined_shape;
90 do // run at least once:
91 {
92 last_blendshape_coeffs = current_blendshape_coeffs;
93 last_pca_coeffs = current_pca_coeffs;
94 // Estimate the PCA shape coefficients with the current blendshape coefficients (0 in the first
95 // iteration):
96 const VectorXf mean_plus_blendshapes =
97 morphable_model.get_shape_model().get_mean() +
98 blendshapes_as_basis *
99 Eigen::Map<const VectorXf>(last_blendshape_coeffs.data(), last_blendshape_coeffs.size());
100 current_pca_coeffs = fitting::fit_shape_to_landmarks_linear(
101 morphable_model.get_shape_model(), affine_camera_matrix, image_points, vertex_indices,
102 mean_plus_blendshapes, lambda, num_coefficients_to_fit);
103
104 // Estimate the blendshape coefficients with the current PCA model estimate:
105 const VectorXf pca_model_shape = morphable_model.get_shape_model().draw_sample(current_pca_coeffs);
106 current_blendshape_coeffs = fitting::fit_blendshapes_to_landmarks_nnls(
107 blendshapes, pca_model_shape, affine_camera_matrix, image_points, vertex_indices);
108
109 // Todo/Note: Could move next line outside the loop, not needed in here actually
110 combined_shape = pca_model_shape +
111 blendshapes_as_basis * Eigen::Map<const VectorXf>(current_blendshape_coeffs.data(),
112 current_blendshape_coeffs.size());
113 } while (
114 std::abs(Eigen::Map<const VectorXf>(current_pca_coeffs.data(), current_pca_coeffs.size()).norm() -
115 Eigen::Map<const VectorXf>(last_pca_coeffs.data(), last_pca_coeffs.size()).norm()) >= 0.01 ||
116 std::abs(
117 Eigen::Map<const VectorXf>(current_blendshape_coeffs.data(), current_blendshape_coeffs.size()).norm() -
118 Eigen::Map<const VectorXf>(last_blendshape_coeffs.data(), last_blendshape_coeffs.size()).norm()) >= 0.01);
119
120 pca_shape_coefficients = current_pca_coeffs;
121 blendshape_coefficients = current_blendshape_coeffs;
122
123 return combined_shape;
124};
125
140inline Eigen::VectorXf fit_shape(Eigen::Matrix<float, 3, 4> affine_camera_matrix,
141 const morphablemodel::MorphableModel& morphable_model,
142 const std::vector<morphablemodel::Blendshape>& blendshapes,
143 const std::vector<Eigen::Vector2f>& image_points,
144 const std::vector<int>& vertex_indices, float lambda = 3.0f,
145 cpp17::optional<int> num_coefficients_to_fit = cpp17::optional<int>())
146{
147 std::vector<float> unused;
148 return fit_shape(affine_camera_matrix, morphable_model, blendshapes, image_points, vertex_indices, lambda,
149 num_coefficients_to_fit, unused, unused);
150};
151
175template <class T>
176inline auto get_corresponding_pointset(const T& landmarks, const core::LandmarkMapper& landmark_mapper,
177 const morphablemodel::MorphableModel& morphable_model)
178{
179 using Eigen::Vector2f;
180 using Eigen::Vector4f;
181 using std::vector;
182
183 // These will be the final 2D and 3D points used for the fitting:
184 vector<Vector4f> model_points; // the points in the 3D shape model
185 vector<int> vertex_indices; // their vertex indices
186 vector<Vector2f> image_points; // the corresponding 2D landmark points
187
188 // Sub-select all the landmarks which we have a mapping for (i.e. that are defined in the 3DMM):
189 for (int i = 0; i < landmarks.size(); ++i)
190 {
191 const auto converted_name = landmark_mapper.convert(landmarks[i].name);
192 if (!converted_name)
193 { // no mapping defined for the current landmark
194 continue;
195 }
196 const int vertex_idx = std::stoi(converted_name.get());
197 const auto vertex = morphable_model.get_shape_model().get_mean_at_point(vertex_idx);
198 model_points.emplace_back(vertex.homogeneous());
199 vertex_indices.emplace_back(vertex_idx);
200 image_points.emplace_back(landmarks[i].coordinates);
201 }
202 return std::make_tuple(image_points, model_points, vertex_indices);
203};
204
223inline std::vector<float>
224fit_expressions(const morphablemodel::ExpressionModel& expression_model, const Eigen::VectorXf& face_instance,
225 const Eigen::Matrix<float, 3, 4>& affine_camera_matrix,
226 const std::vector<Eigen::Vector2f>& landmarks, const std::vector<int>& vertex_ids,
227 cpp17::optional<float> lambda_expressions = cpp17::optional<float>(),
228 cpp17::optional<int> num_expression_coefficients_to_fit = cpp17::optional<int>())
229{
230 std::vector<float> expression_coefficients;
231 if (cpp17::holds_alternative<morphablemodel::PcaModel>(expression_model))
232 {
233 const auto& pca_expression_model = cpp17::get<morphablemodel::PcaModel>(expression_model);
234 // Usually, one would pass the result of the PCA identity shape fitting as a face_instance to this
235 // function. We then need to add the mean of the expression PCA model before performing the fitting,
236 // since we solve for the differences.
237 const Eigen::VectorXf face_instance_with_expression_mean =
238 face_instance + pca_expression_model.get_mean();
239 // Todo: Add lambda, num_coeffs_to_fit, ...
240 return fit_shape_to_landmarks_linear(pca_expression_model, affine_camera_matrix, landmarks,
241 vertex_ids, face_instance_with_expression_mean,
242 lambda_expressions.value_or(65.0f),
243 num_expression_coefficients_to_fit);
244 } else if (cpp17::holds_alternative<morphablemodel::Blendshapes>(expression_model))
245 {
246 const auto& expression_blendshapes = cpp17::get<morphablemodel::Blendshapes>(expression_model);
247 return fit_blendshapes_to_landmarks_nnls(expression_blendshapes, face_instance, affine_camera_matrix,
248 landmarks, vertex_ids);
249 } else
250 {
251 throw std::runtime_error("The given expression_model doesn't contain a PcaModel or Blendshapes.");
252 }
253};
254
302inline std::pair<core::Mesh, fitting::RenderingParameters> fit_shape_and_pose(
303 const morphablemodel::MorphableModel& morphable_model,
304 const core::LandmarkCollection<Eigen::Vector2f>& landmarks, const core::LandmarkMapper& landmark_mapper,
305 int image_width, int image_height, const morphablemodel::EdgeTopology& edge_topology,
306 const fitting::ContourLandmarks& contour_landmarks, const fitting::ModelContour& model_contour,
307 int num_iterations, cpp17::optional<int> num_shape_coefficients_to_fit, float lambda_identity,
308 cpp17::optional<int> num_expression_coefficients_to_fit, cpp17::optional<float> lambda_expressions,
309 std::vector<float>& pca_shape_coefficients, std::vector<float>& expression_coefficients,
310 std::vector<Eigen::Vector2f>& fitted_image_points)
311{
312 // assert(blendshapes.size() > 0);
313 assert(landmarks.size() >= 4);
314 assert(image_width > 0 && image_height > 0);
315 assert(num_iterations > 0); // Can we allow 0, for only the initial pose-fit?
316 assert(pca_shape_coefficients.size() <= morphable_model.get_shape_model().get_num_principal_components());
317 // More asserts I forgot?
318 if (num_shape_coefficients_to_fit)
319 {
320 if (num_shape_coefficients_to_fit.value() >
322 {
323 throw std::runtime_error(
324 "Specified more shape coefficients to fit than the given shape model contains.");
325 }
326 }
327
328 using Eigen::MatrixXf;
329 using Eigen::Vector2f;
330 using Eigen::Vector4f;
331 using Eigen::VectorXf;
332 using std::vector;
333
334 if (!num_shape_coefficients_to_fit)
335 {
336 num_shape_coefficients_to_fit = morphable_model.get_shape_model().get_num_principal_components();
337 }
338
339 if (pca_shape_coefficients.empty())
340 {
341 pca_shape_coefficients.resize(num_shape_coefficients_to_fit.value());
342 }
343 // Todo: This leaves the following case open: num_coeffs given is empty or defined, but the
344 // pca_shape_coefficients given is != num_coeffs or the model's max-coeffs. What to do then? Handle & document!
345
346 /*if (expression_coefficients.empty())
347 {
348 expression_coefficients.resize(blendshapes.size());
349 }*/
350
351 // Current mesh - either from the given coefficients, or the mean:
352 VectorXf current_pca_shape = morphable_model.get_shape_model().draw_sample(pca_shape_coefficients);
353 assert(morphable_model.has_separate_expression_model()); // Note: We could also just skip the expression fitting in this case.
354 // Note we don't check whether the shape and expression model dimensions match.
355 // Note: We're calling this in a loop, and morphablemodel::to_matrix(expression_blendshapes) now gets
356 // called again in every fitting iteration.
357 VectorXf current_combined_shape =
358 current_pca_shape +
359 draw_sample(morphable_model.get_expression_model().value(), expression_coefficients);
360 auto current_mesh = morphablemodel::sample_to_mesh(
361 current_combined_shape, morphable_model.get_color_model().get_mean(),
362 morphable_model.get_shape_model().get_triangle_list(),
363 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates(),
364 morphable_model.get_texture_triangle_indices());
365
366 // The 2D and 3D point correspondences used for the fitting:
367 vector<Vector4f> model_points; // the points in the 3D shape model
368 vector<int> vertex_indices; // their vertex indices
369 vector<Vector2f> image_points; // the corresponding 2D landmark points
370
371 // Sub-select all the landmarks which we have a mapping for (i.e. that are defined in the 3DMM),
372 // and get the corresponding model points (mean if given no initial coeffs, from the computed shape otherwise):
373 for (int i = 0; i < landmarks.size(); ++i)
374 {
375 const auto converted_name = landmark_mapper.convert(landmarks[i].name);
376 if (!converted_name)
377 { // no mapping defined for the current landmark
378 continue;
379 }
380 // If the MorphableModel does not contain landmark definitions, we expect the user to have given us
381 // direct mappings (e.g. directly from ibug identifiers to vertex ids). If the model does contain
382 // landmark definitions, we expect the user to use mappings from their landmark identifiers (e.g.
383 // ibug) to the landmark definitions, and not to vertex indices.
384 // Todo: This might be worth mentioning in the function documentation of fit_shape_and_pose.
385 int vertex_idx;
386 if (morphable_model.get_landmark_definitions())
387 {
388 const auto found_vertex_idx =
389 morphable_model.get_landmark_definitions().value().find(converted_name.value());
390 if (found_vertex_idx != std::end(morphable_model.get_landmark_definitions().value()))
391 {
392 vertex_idx = found_vertex_idx->second;
393 } else
394 {
395 continue;
396 }
397 } else
398 {
399 vertex_idx = std::stoi(converted_name.value());
400 }
401 model_points.emplace_back(current_mesh.vertices[vertex_idx].homogeneous());
402 vertex_indices.emplace_back(vertex_idx);
403 image_points.emplace_back(landmarks[i].coordinates);
404 }
405
406 // Need to do an initial pose fit to do the contour fitting inside the loop.
407 // We'll do an expression fit too, since face shapes vary quite a lot, depending on expressions.
409 fitting::estimate_orthographic_projection_linear(image_points, model_points, true, image_height);
410 fitting::RenderingParameters rendering_params(current_pose, image_width, image_height);
411
412 const Eigen::Matrix<float, 3, 4> affine_from_ortho =
413 fitting::get_3x4_affine_camera_matrix(rendering_params, image_width, image_height);
414 expression_coefficients =
415 fit_expressions(morphable_model.get_expression_model().value(), current_pca_shape, affine_from_ortho,
416 image_points, vertex_indices, lambda_expressions, num_expression_coefficients_to_fit);
417
418 // Mesh with same PCA coeffs as before, but new expression fit (this is relevant if no initial blendshape coeffs have been given):
419 current_combined_shape = current_pca_shape + draw_sample(morphable_model.get_expression_model().value(),
420 expression_coefficients);
421 current_mesh = morphablemodel::sample_to_mesh(
422 current_combined_shape, morphable_model.get_color_model().get_mean(),
423 morphable_model.get_shape_model().get_triangle_list(),
424 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates(),
425 morphable_model.get_texture_triangle_indices());
426
427 // The static (fixed) landmark correspondences which will stay the same throughout
428 // the fitting (the inner face landmarks):
429 const auto fixed_image_points = image_points;
430 const auto fixed_vertex_indices = vertex_indices;
431
432 for (int i = 0; i < num_iterations; ++i)
433 {
434 image_points = fixed_image_points;
435 vertex_indices = fixed_vertex_indices;
436 // Given the current pose, find 2D-3D contour correspondences of the front-facing face contour:
437 vector<Vector2f> image_points_contour;
438 vector<int> vertex_indices_contour;
439 const auto yaw_angle = rendering_params.get_yaw_pitch_roll()[0];
440 // For each 2D contour landmark, get the corresponding 3D vertex point and vertex id:
441 std::tie(image_points_contour, std::ignore, vertex_indices_contour) =
442 fitting::get_contour_correspondences(landmarks, contour_landmarks, model_contour, yaw_angle,
443 current_mesh, rendering_params.get_modelview(),
444 rendering_params.get_projection(),
445 fitting::get_opencv_viewport(image_width, image_height));
446 // Add the contour correspondences to the set of landmarks that we use for the fitting:
447 vertex_indices = fitting::concat(vertex_indices, vertex_indices_contour);
448 image_points = fitting::concat(image_points, image_points_contour);
449
450 // Fit the occluding (away-facing) contour using the detected contour LMs:
451 vector<Vector2f> occluding_contour_landmarks;
452 if (yaw_angle >= 0.0f) // positive yaw = subject looking to the left
453 { // the left contour is the occluding one we want to use ("away-facing")
454 const auto contour_landmarks_ =
455 core::filter(landmarks, contour_landmarks.left_contour); // Can do this outside of the loop
456 std::for_each(begin(contour_landmarks_), end(contour_landmarks_),
457 [&occluding_contour_landmarks](const auto& lm) {
458 occluding_contour_landmarks.push_back({lm.coordinates[0], lm.coordinates[1]});
459 });
460 } else
461 {
462 const auto contour_landmarks_ = core::filter(landmarks, contour_landmarks.right_contour);
463 std::for_each(begin(contour_landmarks_), end(contour_landmarks_),
464 [&occluding_contour_landmarks](const auto& lm) {
465 occluding_contour_landmarks.push_back({lm.coordinates[0], lm.coordinates[1]});
466 });
467 }
468 const auto edge_correspondences = fitting::find_occluding_edge_correspondences(
469 current_mesh, edge_topology, rendering_params, occluding_contour_landmarks, 180.0f);
470 image_points = fitting::concat(image_points, edge_correspondences.first);
471 vertex_indices = fitting::concat(vertex_indices, edge_correspondences.second);
472
473 // Get the model points of the current mesh, for all correspondences that we've got:
474 model_points.clear();
475 for (auto v : vertex_indices)
476 {
477 model_points.push_back({current_mesh.vertices[v][0], current_mesh.vertices[v][1],
478 current_mesh.vertices[v][2], 1.0f});
479 }
480
481 // Re-estimate the pose, using all correspondences:
482 current_pose =
483 fitting::estimate_orthographic_projection_linear(image_points, model_points, true, image_height);
484 rendering_params = fitting::RenderingParameters(current_pose, image_width, image_height);
485
486 const Eigen::Matrix<float, 3, 4> affine_from_ortho =
487 fitting::get_3x4_affine_camera_matrix(rendering_params, image_width, image_height);
488
489 // Estimate the PCA shape coefficients with the current blendshape coefficients:
490 const VectorXf mean_plus_expressions =
491 morphable_model.get_shape_model().get_mean() +
492 draw_sample(morphable_model.get_expression_model().value(), expression_coefficients);
493 pca_shape_coefficients = fitting::fit_shape_to_landmarks_linear(
494 morphable_model.get_shape_model(), affine_from_ortho, image_points, vertex_indices,
495 mean_plus_expressions, lambda_identity, num_shape_coefficients_to_fit);
496
497 // Estimate the blendshape coefficients with the current PCA model estimate:
498 current_pca_shape = morphable_model.get_shape_model().draw_sample(pca_shape_coefficients);
499 expression_coefficients = fit_expressions(
500 morphable_model.get_expression_model().value(), current_pca_shape, affine_from_ortho,
501 image_points, vertex_indices, lambda_expressions, num_expression_coefficients_to_fit);
502
503 current_combined_shape =
504 current_pca_shape +
505 draw_sample(morphable_model.get_expression_model().value(), expression_coefficients);
506 current_mesh = morphablemodel::sample_to_mesh(
507 current_combined_shape, morphable_model.get_color_model().get_mean(),
508 morphable_model.get_shape_model().get_triangle_list(),
509 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates(),
510 morphable_model.get_texture_triangle_indices());
511 }
512
513 fitted_image_points = image_points;
514 return {current_mesh, rendering_params}; // I think we could also work with a VectorXf face_instance in
515 // this function instead of a Mesh, but it would convolute the
516 // code more (i.e. more complicated to access vertices).
517};
518
554inline std::pair<core::Mesh, fitting::RenderingParameters> fit_shape_and_pose(
555 const morphablemodel::MorphableModel& morphable_model,
556 const core::LandmarkCollection<Eigen::Vector2f>& landmarks, const core::LandmarkMapper& landmark_mapper,
557 int image_width, int image_height, const morphablemodel::EdgeTopology& edge_topology,
558 const fitting::ContourLandmarks& contour_landmarks, const fitting::ModelContour& model_contour,
559 int num_iterations = 5, cpp17::optional<int> num_shape_coefficients_to_fit = cpp17::nullopt,
560 float lambda_identity = 50.0f, cpp17::optional<int> num_expression_coefficients_to_fit = cpp17::nullopt,
561 cpp17::optional<float> lambda_expressions = cpp17::nullopt)
562{
563 std::vector<float> pca_coeffs;
564 std::vector<float> blendshape_coeffs;
565 std::vector<Eigen::Vector2f> fitted_image_points;
566 return fit_shape_and_pose(morphable_model, landmarks, landmark_mapper, image_width, image_height,
567 edge_topology, contour_landmarks, model_contour, num_iterations,
568 num_shape_coefficients_to_fit, lambda_identity,
569 num_expression_coefficients_to_fit, lambda_expressions, pca_coeffs,
570 blendshape_coeffs, fitted_image_points);
571};
572
614inline std::pair<core::Mesh, fitting::RenderingParameters> fit_shape_and_pose(
615 const morphablemodel::MorphableModel& morphable_model, const std::vector<Eigen::Vector2f>& image_points,
616 const std::vector<int>& vertex_indices, int image_width, int image_height, int num_iterations,
617 cpp17::optional<int> num_shape_coefficients_to_fit, float lambda_identity,
618 cpp17::optional<int> num_expression_coefficients_to_fit, cpp17::optional<float> lambda_expressions,
619 std::vector<float>& pca_shape_coefficients, std::vector<float>& expression_coefficients,
620 std::vector<Eigen::Vector2f>& fitted_image_points)
621{
622 // assert(blendshapes.size() > 0);
623 assert(image_points.size() >= 4);
624 assert(image_points.size() == vertex_indices.size());
625 assert(image_width > 0 && image_height > 0);
626 assert(num_iterations > 0); // Can we allow 0, for only the initial pose-fit?
627 assert(pca_shape_coefficients.size() <= morphable_model.get_shape_model().get_num_principal_components());
628 // More asserts I forgot?
629
630 using Eigen::MatrixXf;
631 using Eigen::Vector2f;
632 using Eigen::Vector4f;
633 using Eigen::VectorXf;
634 using std::vector;
635
636 if (!num_shape_coefficients_to_fit)
637 {
638 num_shape_coefficients_to_fit = morphable_model.get_shape_model().get_num_principal_components();
639 }
640
641 if (pca_shape_coefficients.empty())
642 {
643 pca_shape_coefficients.resize(num_shape_coefficients_to_fit.value());
644 }
645 // Todo: This leaves the following case open: num_coeffs given is empty or defined, but the
646 // pca_shape_coefficients given is != num_coeffs or the model's max-coeffs. What to do then? Handle & document!
647
648 /*if (expression_coefficients.empty())
649 {
650 expression_coefficients.resize(blendshapes.size());
651 }*/
652
653 // Current mesh - either from the given coefficients, or the mean:
654 VectorXf current_pca_shape = morphable_model.get_shape_model().draw_sample(pca_shape_coefficients);
655 assert(morphable_model.has_separate_expression_model()); // Note: We could also just skip the expression fitting in this case.
656 // Note we don't check whether the shape and expression model dimensions match.
657 // Note: We're calling this in a loop, and morphablemodel::to_matrix(expression_blendshapes) now gets
658 // called again in every fitting iteration.
659 VectorXf current_combined_shape =
660 current_pca_shape +
661 draw_sample(morphable_model.get_expression_model().value(), expression_coefficients);
662 auto current_mesh = morphablemodel::sample_to_mesh(
663 current_combined_shape, morphable_model.get_color_model().get_mean(),
664 morphable_model.get_shape_model().get_triangle_list(),
665 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates(),
666 morphable_model.get_texture_triangle_indices());
667
668 // The 2D and 3D point correspondences used for the fitting:
669 vector<Vector4f> model_points; // the points in the 3D shape model
670 // Get the model points corresponding to the given image points (mean if given no initial coeffs, from the computed shape otherwise):
671 for (int i = 0; i < image_points.size(); ++i)
672 {
673 const int vertex_idx = vertex_indices[i];
674 Vector4f vertex(current_mesh.vertices[vertex_idx][0], current_mesh.vertices[vertex_idx][1],
675 current_mesh.vertices[vertex_idx][2], 1.0f);
676 model_points.emplace_back(vertex);
677 }
678
679 // Need to do an initial pose fit to do the contour fitting inside the loop.
680 // We'll do an expression fit too, since face shapes vary quite a lot, depending on expressions.
682 fitting::estimate_orthographic_projection_linear(image_points, model_points, true, image_height);
683 fitting::RenderingParameters rendering_params(current_pose, image_width, image_height);
684
685 const Eigen::Matrix<float, 3, 4> affine_from_ortho =
686 fitting::get_3x4_affine_camera_matrix(rendering_params, image_width, image_height);
687 expression_coefficients =
688 fit_expressions(morphable_model.get_expression_model().value(), current_pca_shape, affine_from_ortho,
689 image_points, vertex_indices, lambda_expressions, num_expression_coefficients_to_fit);
690
691 // Mesh with same PCA coeffs as before, but new expression fit (this is relevant if no initial blendshape coeffs have been given):
692 current_combined_shape = current_pca_shape + draw_sample(morphable_model.get_expression_model().value(),
693 expression_coefficients);
694 current_mesh = morphablemodel::sample_to_mesh(
695 current_combined_shape, morphable_model.get_color_model().get_mean(),
696 morphable_model.get_shape_model().get_triangle_list(),
697 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates(),
698 morphable_model.get_texture_triangle_indices());
699
700 for (int i = 0; i < num_iterations; ++i)
701 {
702 // Get the model points of the current mesh, for all correspondences that we've got:
703 model_points.clear();
704 for (auto v : vertex_indices)
705 {
706 model_points.push_back({current_mesh.vertices[v][0], current_mesh.vertices[v][1],
707 current_mesh.vertices[v][2], 1.0f});
708 }
709
710 // Re-estimate the pose, using all correspondences:
711 current_pose =
712 fitting::estimate_orthographic_projection_linear(image_points, model_points, true, image_height);
713 rendering_params = fitting::RenderingParameters(current_pose, image_width, image_height);
714
715 const Eigen::Matrix<float, 3, 4> affine_from_ortho =
716 fitting::get_3x4_affine_camera_matrix(rendering_params, image_width, image_height);
717
718 // Estimate the PCA shape coefficients with the current blendshape coefficients:
719 const VectorXf mean_plus_expressions =
720 morphable_model.get_shape_model().get_mean() +
721 draw_sample(morphable_model.get_expression_model().value(), expression_coefficients);
722 pca_shape_coefficients = fitting::fit_shape_to_landmarks_linear(
723 morphable_model.get_shape_model(), affine_from_ortho, image_points, vertex_indices,
724 mean_plus_expressions, lambda_identity, num_shape_coefficients_to_fit);
725
726 // Estimate the blendshape coefficients with the current PCA model estimate:
727 current_pca_shape = morphable_model.get_shape_model().draw_sample(pca_shape_coefficients);
728 expression_coefficients = fit_expressions(
729 morphable_model.get_expression_model().value(), current_pca_shape, affine_from_ortho,
730 image_points, vertex_indices, lambda_expressions, num_expression_coefficients_to_fit);
731
732 current_combined_shape =
733 current_pca_shape +
734 draw_sample(morphable_model.get_expression_model().value(), expression_coefficients);
735 current_mesh = morphablemodel::sample_to_mesh(
736 current_combined_shape, morphable_model.get_color_model().get_mean(),
737 morphable_model.get_shape_model().get_triangle_list(),
738 morphable_model.get_color_model().get_triangle_list(), morphable_model.get_texture_coordinates(),
739 morphable_model.get_texture_triangle_indices());
740 }
741
742 fitted_image_points = image_points;
743 return {current_mesh, rendering_params}; // I think we could also work with a VectorXf face_instance in
744 // this function instead of a Mesh, but it would convolute the
745 // code more (i.e. more complicated to access vertices).
746};
747
766inline std::pair<core::Mesh, fitting::RenderingParameters> fit_shape_and_pose(
767 const morphablemodel::MorphableModel& morphable_model, const std::vector<Eigen::Vector2f>& image_points,
768 const std::vector<int>& vertex_indices, int image_width, int image_height, int num_iterations,
769 cpp17::optional<int> num_shape_coefficients_to_fit, float lambda_identity,
770 cpp17::optional<int> num_expression_coefficients_to_fit, cpp17::optional<float> lambda_expressions)
771{
772 std::vector<float> pca_coeffs;
773 std::vector<float> blendshape_coeffs;
774 std::vector<Eigen::Vector2f> fitted_image_points;
775 return fit_shape_and_pose(morphable_model, image_points, vertex_indices, image_width, image_height,
776 num_iterations, num_shape_coefficients_to_fit, lambda_identity,
777 num_expression_coefficients_to_fit, lambda_expressions, pca_coeffs,
778 blendshape_coeffs, fitted_image_points);
779};
780
781} /* namespace fitting */
782} /* namespace eos */
783
784#endif /* EOS_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
Eigen::Matrix4f get_modelview() const
Construct a model-view matrix from the RenderingParameters' rotation and translation,...
Definition: RenderingParameters.hpp:220
Eigen::Vector3f get_yaw_pitch_roll()
Returns the intrinsic rotation angles, also called Tait-Bryan angles, in degrees. The returned Vector...
Definition: RenderingParameters.hpp:179
Eigen::Matrix4f get_projection() const
Construct an orthographic or perspective projection matrix from the RenderingParameters' frustum (ort...
Definition: RenderingParameters.hpp:245
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 auto & get_landmark_definitions() const
Definition: MorphableModel.hpp:403
const std::vector< std::array< int, 3 > > & get_texture_triangle_indices() const
Definition: MorphableModel.hpp:423
const auto & get_expression_model() const
Definition: MorphableModel.hpp:150
const PcaModel & get_shape_model() const
Definition: MorphableModel.hpp:125
bool has_separate_expression_model() const
Definition: MorphableModel.hpp:388
const PcaModel & get_color_model() const
Definition: MorphableModel.hpp:135
Eigen::Vector3f get_mean_at_point(int vertex_index) const
Definition: PcaModel.hpp:134
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
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_shape_to_landmarks_linear(const morphablemodel::PcaModel &shape_model, Eigen::Matrix< float, 3, 4 > affine_camera_matrix, const std::vector< Eigen::Vector2f > &landmarks, const std::vector< int > &vertex_ids, Eigen::VectorXf base_face=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:60
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
std::vector< float > fit_expressions(const morphablemodel::ExpressionModel &expression_model, const Eigen::VectorXf &face_instance, const Eigen::Matrix< float, 3, 4 > &affine_camera_matrix, const std::vector< Eigen::Vector2f > &landmarks, const std::vector< int > &vertex_ids, cpp17::optional< float > lambda_expressions=cpp17::optional< float >(), cpp17::optional< int > num_expression_coefficients_to_fit=cpp17::optional< int >())
Fits the given expression model to landmarks.
Definition: fitting.hpp:224
auto get_corresponding_pointset(const T &landmarks, const core::LandmarkMapper &landmark_mapper, const morphablemodel::MorphableModel &morphable_model)
Takes a LandmarkCollection of 2D landmarks and, using the landmark_mapper, finds the corresponding 3D...
Definition: fitting.hpp:176
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
Eigen::VectorXf fit_shape(Eigen::Matrix< float, 3, 4 > affine_camera_matrix, const morphablemodel::MorphableModel &morphable_model, const std::vector< morphablemodel::Blendshape > &blendshapes, const std::vector< Eigen::Vector2f > &image_points, const std::vector< int > &vertex_indices, float lambda, cpp17::optional< int > num_coefficients_to_fit, std::vector< float > &pca_shape_coefficients, std::vector< float > &blendshape_coefficients)
Definition: fitting.hpp:69
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
cpp17::variant< PcaModel, Blendshapes > ExpressionModel
Type alias to represent an expression model, which can either consist of blendshapes or a PCA model.
Definition: ExpressionModel.hpp:43
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.
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