eos 1.4.0
Loading...
Searching...
No Matches
blendshape_fitting.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/fitting/blendshape_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 BLENDSHAPEFITTING_HPP_
23#define BLENDSHAPEFITTING_HPP_
24
25#include "eos/morphablemodel/Blendshape.hpp"
26
27#include "Eigen/Core"
28#include "Eigen/QR"
29#include "Eigen/Sparse"
30#include "nnls.h"
31
32#include <vector>
33#include <cassert>
34
35namespace eos {
36namespace fitting {
37
58inline std::vector<float> fit_blendshapes_to_landmarks_linear(
59 const std::vector<morphablemodel::Blendshape>& blendshapes, const Eigen::VectorXf& face_instance,
60 Eigen::Matrix<float, 3, 4> affine_camera_matrix, const std::vector<Eigen::Vector2f>& landmarks,
61 const std::vector<int>& vertex_ids, float lambda = 500.0f)
62{
63 assert(landmarks.size() == vertex_ids.size());
64
65 using Eigen::MatrixXf;
66 using Eigen::VectorXf;
67
68 const auto num_blendshapes = blendshapes.size();
69 const int num_landmarks = static_cast<int>(landmarks.size());
70
71 // Copy all blendshapes into a "basis" matrix with each blendshape being a column:
72 const MatrixXf blendshapes_as_basis = morphablemodel::to_matrix(blendshapes);
73
74 // $\hat{V} \in R^{3N\times m-1}$, subselect the rows of the eigenvector matrix $V$ associated with the $N$ feature points
75 // And we insert a row of zeros after every third row, resulting in matrix $\hat{V}_h \in R^{4N\times m-1}$:
76 MatrixXf V_hat_h = MatrixXf::Zero(4 * num_landmarks, num_blendshapes);
77 int row_index = 0;
78 for (int i = 0; i < num_landmarks; ++i)
79 {
80 V_hat_h.block(row_index, 0, 3, V_hat_h.cols()) =
81 blendshapes_as_basis.block(vertex_ids[i] * 3, 0, 3, blendshapes_as_basis.cols());
82 row_index += 4; // replace 3 rows and skip the 4th one, it has all zeros
83 }
84 // Form a block diagonal matrix $P \in R^{3N\times 4N}$ in which the camera matrix C (P_Affine, affine_camera_matrix) is placed on the diagonal:
85 MatrixXf P = MatrixXf::Zero(3 * num_landmarks, 4 * num_landmarks);
86 for (int i = 0; i < num_landmarks; ++i)
87 {
88 P.block<3, 4>(3 * i, 4 * i) = affine_camera_matrix;
89 }
90
91 // The landmarks in matrix notation (in homogeneous coordinates), $3N\times 1$
92 VectorXf y = VectorXf::Ones(3 * num_landmarks);
93 for (int i = 0; i < num_landmarks; ++i)
94 {
95 y(3 * i) = landmarks[i][0];
96 y((3 * i) + 1) = landmarks[i][1];
97 // y((3 * i) + 2) = 1; // already 1, stays (homogeneous coordinate)
98 }
99 // The mean, with an added homogeneous coordinate (x_1, y_1, z_1, 1, x_2, ...)^t
100 VectorXf v_bar = VectorXf::Ones(4 * num_landmarks);
101 for (int i = 0; i < num_landmarks; ++i)
102 {
103 v_bar(4 * i) = face_instance(vertex_ids[i] * 3);
104 v_bar((4 * i) + 1) = face_instance(vertex_ids[i] * 3 + 1);
105 v_bar((4 * i) + 2) = face_instance(vertex_ids[i] * 3 + 2);
106 // v_bar((4 * i) + 3) = 1; // already 1, stays (homogeneous coordinate)
107 }
108
109 // Bring into standard regularised quadratic form:
110 const MatrixXf A = P * V_hat_h; // camera matrix times the basis
111 const MatrixXf b = P * v_bar - y; // camera matrix times the mean, minus the landmarks
112
113 const MatrixXf AtAReg =
114 A.transpose() * A + lambda * Eigen::MatrixXf::Identity(num_blendshapes, num_blendshapes);
115 const MatrixXf rhs = -A.transpose() * b;
116
117 const VectorXf coefficients = AtAReg.colPivHouseholderQr().solve(rhs);
118
119 return std::vector<float>(coefficients.data(), coefficients.data() + coefficients.size());
120};
121
138inline std::vector<float> fit_blendshapes_to_landmarks_nnls(
139 const std::vector<morphablemodel::Blendshape>& blendshapes, const Eigen::VectorXf& face_instance,
140 Eigen::Matrix<float, 3, 4> affine_camera_matrix, const std::vector<Eigen::Vector2f>& landmarks,
141 const std::vector<int>& vertex_ids)
142{
143 assert(landmarks.size() == vertex_ids.size());
144
145 using Eigen::MatrixXf;
146 using Eigen::VectorXf;
147
148 const auto num_blendshapes = blendshapes.size();
149 const int num_landmarks = static_cast<int>(landmarks.size());
150
151 // Copy all blendshapes into a "basis" matrix with each blendshape being a column:
152 const MatrixXf blendshapes_as_basis = morphablemodel::to_matrix(blendshapes);
153
154 // $\hat{V} \in R^{3N\times m-1}$, subselect the rows of the eigenvector matrix $V$ associated with the $N$ feature points
155 // And we insert a row of zeros after every third row, resulting in matrix $\hat{V}_h \in R^{4N\times m-1}$:
156 MatrixXf V_hat_h = MatrixXf::Zero(4 * num_landmarks, num_blendshapes);
157 int row_index = 0;
158 for (int i = 0; i < num_landmarks; ++i)
159 {
160 V_hat_h.block(row_index, 0, 3, V_hat_h.cols()) =
161 blendshapes_as_basis.block(vertex_ids[i] * 3, 0, 3, blendshapes_as_basis.cols());
162 row_index += 4; // replace 3 rows and skip the 4th one, it has all zeros
163 }
164
165 // Form a block diagonal matrix $P \in R^{3N\times 4N}$ in which the camera matrix C (P_Affine, affine_camera_matrix) is placed on the diagonal:
166 Eigen::SparseMatrix<float> P(3 * num_landmarks, 4 * num_landmarks);
167 std::vector<Eigen::Triplet<float>> P_coefficients; // list of non-zeros coefficients
168 for (int i = 0; i < num_landmarks; ++i)
169 { // Note: could make this the inner-most loop.
170 for (int x = 0; x < affine_camera_matrix.rows(); ++x)
171 {
172 for (int y = 0; y < affine_camera_matrix.cols(); ++y)
173 {
174 P_coefficients.push_back(
175 Eigen::Triplet<float>(3 * i + x, 4 * i + y, affine_camera_matrix(x, y)));
176 }
177 }
178 }
179 P.setFromTriplets(P_coefficients.begin(), P_coefficients.end());
180
181 // The landmarks in matrix notation (in homogeneous coordinates), $3N\times 1$
182 VectorXf y = VectorXf::Ones(3 * num_landmarks);
183 for (int i = 0; i < num_landmarks; ++i)
184 {
185 y(3 * i) = landmarks[i][0];
186 y((3 * i) + 1) = landmarks[i][1];
187 // y_((3 * i) + 2) = 1; // already 1, stays (homogeneous coordinate)
188 }
189
190 // The mean, with an added homogeneous coordinate (x_1, y_1, z_1, 1, x_2, ...)^t
191 VectorXf v_bar = VectorXf::Ones(4 * num_landmarks);
192 for (int i = 0; i < num_landmarks; ++i)
193 {
194 v_bar(4 * i) = face_instance(vertex_ids[i] * 3);
195 v_bar((4 * i) + 1) = face_instance(vertex_ids[i] * 3 + 1);
196 v_bar((4 * i) + 2) = face_instance(vertex_ids[i] * 3 + 2);
197 // v_bar((4 * i) + 3) = 1; // already 1, stays (homogeneous coordinate)
198 }
199
200 // Bring into standard least squares form:
201 const MatrixXf A = P * V_hat_h; // camera matrix times the basis
202 const MatrixXf b = P * v_bar - y; // camera matrix times the mean, minus the landmarks
203
204 // Solve using NNLS:
205 VectorXf coefficients;
206 Eigen::NNLS<MatrixXf> nnls(A, 100); // In the multi-image fitting, the solver sometimes got stuck. This limits it to 100 iterations. It normaly converges within <10 iterations. This should be fine for the single-image fitting as well.
207 bool non_singular = nnls.solve(-b);
208 coefficients.noalias() = nnls.x();
209
210 return std::vector<float>(coefficients.data(), coefficients.data() + coefficients.size());
211};
212
213} /* namespace fitting */
214} /* namespace eos */
215
216#endif /* BLENDSHAPEFITTING_HPP_ */
std::vector< float > fit_blendshapes_to_landmarks_linear(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, float lambda=500.0f)
Definition: blendshape_fitting.hpp:58
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
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
Namespace containing all of eos's 3D model fitting functionality.