eos 1.4.0
Loading...
Searching...
No Matches
ceres_nonlinear.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/fitting/ceres_nonlinear.hpp
5 *
6 * Copyright 2016-2023 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_CERES_NONLINEAR_HPP
23#define EOS_CERES_NONLINEAR_HPP
24
25#include "eos/core/Image.hpp"
26#include "eos/morphablemodel/MorphableModel.hpp"
27#include "eos/morphablemodel/Blendshape.hpp"
28#include "eos/render/matrix_projection.hpp"
29
30#include "Eigen/Core"
31
32#include "ceres/ceres.h"
33#include "ceres/cubic_interpolation.h"
34
35#include <vector>
36
37namespace eos {
38namespace fitting {
39
40// Forward declarations:
41template <typename T>
42Eigen::Vector3<T> get_shape_at_point(const eos::morphablemodel::PcaModel& shape_model,
43 const eos::morphablemodel::Blendshapes& blendshapes, int vertex_id,
44 Eigen::Map<const Eigen::VectorX<T>> shape_coeffs,
45 Eigen::Map<const Eigen::VectorX<T>> blendshape_coeffs);
46
47template <typename T>
48Eigen::Vector3<T> get_vertex_color_at_point(const eos::morphablemodel::PcaModel& color_model, int vertex_id,
49 Eigen::Map<const Eigen::VectorX<T>> color_coeffs);
50
58{
59
65 NormCost(int num_parameters) : num_parameters(num_parameters){};
66
74 template <typename T>
75 bool operator()(const T* const x, T* residual) const
76 {
77 for (int i = 0; i < num_parameters; ++i)
78 {
79 residual[i] = x[i];
80 }
81 return true;
82 };
83
90 template <int num_parameters>
91 static ceres::CostFunction* Create()
92 {
93 return (new ceres::AutoDiffCostFunction<NormCost, num_parameters /* num residuals */,
94 num_parameters /* num parameters */>(
95 new NormCost(num_parameters)));
96 }
97
98private:
99 int num_parameters;
100};
101
109{
110
128 const std::vector<morphablemodel::Blendshape>& blendshapes,
129 int num_shape_coeffs, int num_blendshape_coeffs,
130 Eigen::Vector2f observed_landmark, int vertex_id, int image_width,
131 int image_height)
132 : shape_model(shape_model), blendshapes(blendshapes), num_shape_coeffs(num_shape_coeffs),
133 num_blendshape_coeffs(num_blendshape_coeffs), observed_landmark(observed_landmark),
134 vertex_id(vertex_id), image_width(image_width), image_height(image_height),
135 aspect_ratio(static_cast<double>(image_width) / image_height){};
136
151 template <typename T>
152 bool operator()(const T* const camera_rotation, const T* const camera_translation,
153 const T* const camera_intrinsics, const T* const shape_coeffs,
154 const T* const blendshape_coeffs, T* residual) const
155 {
156 // Generate shape instance (of only one vertex id) using current coefficients:
157 Eigen::Map<const Eigen::VectorX<T>> shape_coeffs_mapped(shape_coeffs, num_shape_coeffs);
158 Eigen::Map<const Eigen::VectorX<T>> blendshape_coeffs_mapped(blendshape_coeffs,
159 num_blendshape_coeffs);
160
161 const Eigen::Vector3<T> point_3d = get_shape_at_point(shape_model, blendshapes, vertex_id,
162 shape_coeffs_mapped, blendshape_coeffs_mapped);
163 // Project the point to 2D:
164 // I think the quaternion is always normalised because we run Ceres with QuaternionParameterization
165 // Previously, we used glm::tquat, which expects w, x, y, z, and called it with [0, 1, 2, 3].
166 // Eigen stores it as x, y, z, w. So... are we sure this is right?
167 Eigen::Map<const Eigen::Quaternion<T>> rotation(camera_rotation);
168 Eigen::Map<const Eigen::Vector3<T>> translation(camera_translation);
169
170 Eigen::Matrix4<T> model_view_mtx = Eigen::Matrix4<T>::Identity();
171 model_view_mtx.template block<3, 3>(0, 0) = rotation.toRotationMatrix(); // dependent name
172 model_view_mtx.col(3).template head<3>() = translation; // dependent name
173
174 // Todo: use get_opencv_viewport() from nonlin_cam_esti.hpp.
175 const Eigen::Vector4<T> viewport(T(0), T(image_height), T(image_width),
176 T(-image_height)); // OpenCV convention
177
178 const T& fov = camera_intrinsics[0];
179 const auto projection_mtx = render::perspective(fov, T(aspect_ratio), T(0.1), T(1000.0));
180
181 Eigen::Vector3<T> projected_point =
182 render::project(point_3d, model_view_mtx, projection_mtx, viewport);
183
184 // Residual: Projected point minus the observed 2D landmark point
185 residual[0] = projected_point.x() - T(observed_landmark[0]);
186 residual[1] = projected_point.y() - T(observed_landmark[1]);
187 return true;
188 };
189
196 template <int num_shape_coeffs, int num_blendshape_coeffs>
197 static ceres::CostFunction* Create(const eos::morphablemodel::PcaModel& shape_model,
198 const eos::morphablemodel::Blendshapes& blendshapes,
199 Eigen::Vector2f observed_landmark, int vertex_id, int image_width,
200 int image_height)
201 {
202 // Template arguments: 2 residuals, 4 (camera rotation), 3 (camera translation), 1 (camera
203 // intrinsics), n (shape coeffs), m (blendshape coeffs)
204 return (new ceres::AutoDiffCostFunction<PerspectiveProjectionLandmarkCost, 2, 4, 3, 1,
205 num_shape_coeffs, num_blendshape_coeffs>(
206 new PerspectiveProjectionLandmarkCost(shape_model, blendshapes, num_shape_coeffs,
207 num_blendshape_coeffs, observed_landmark, vertex_id,
208 image_width, image_height)));
209 };
210
211private:
213 shape_model; // Or store as pointer (non-owning) or std::reference_wrapper?
214 const std::vector<morphablemodel::Blendshape>& blendshapes;
215 const int num_shape_coeffs;
216 const int num_blendshape_coeffs;
217 const Eigen::Vector2f observed_landmark;
218 const int vertex_id;
219 const int image_width;
220 const int image_height;
221 const double aspect_ratio;
222};
223
234{
252 const eos::morphablemodel::Blendshapes& blendshapes,
253 const eos::morphablemodel::PcaModel& color_model, int num_shape_coeffs,
254 int num_blendshape_coeffs, int num_color_coeffs, int vertex_id,
255 const eos::core::Image3u& image)
256 : shape_model(shape_model), blendshapes(blendshapes), color_model(color_model),
257 num_shape_coeffs(num_shape_coeffs), num_blendshape_coeffs(num_blendshape_coeffs),
258 num_color_coeffs(num_color_coeffs), vertex_id(vertex_id), image(image){};
259
277 template <typename T>
278 bool operator()(const T* const camera_rotation, const T* const camera_translation,
279 const T* const camera_intrinsics, const T* const shape_coeffs,
280 const T* const blendshape_coeffs, const T* const color_coeffs, T* residual) const
281 {
282 // The following code blocks are identical to PerspectiveProjectionLandmarkCost:
283
284 // Generate shape instance (of only one vertex id) using current coefficients:
285 Eigen::Map<const Eigen::VectorX<T>> shape_coeffs_mapped(shape_coeffs, num_shape_coeffs);
286 Eigen::Map<const Eigen::VectorX<T>> blendshape_coeffs_mapped(blendshape_coeffs,
287 num_blendshape_coeffs);
288
289 const Eigen::Vector3<T> point_3d = get_shape_at_point(shape_model, blendshapes, vertex_id,
290 shape_coeffs_mapped, blendshape_coeffs_mapped);
291 // Project the point to 2D:
292 // I think the quaternion is always normalised because we run Ceres with QuaternionParameterization
293 // Previously, we used glm::tquat, which expects w, x, y, z, and called it with [0, 1, 2, 3].
294 // Eigen stores it as x, y, z, w. So... are we sure this is right?
295 Eigen::Map<const Eigen::Quaternion<T>> rotation(camera_rotation);
296 Eigen::Map<const Eigen::Vector3<T>> translation(camera_translation);
297
298 Eigen::Matrix4<T> model_view_mtx = Eigen::Matrix4<T>::Identity();
299 model_view_mtx.template block<3, 3>(0, 0) = rotation.toRotationMatrix(); // dependent name
300 model_view_mtx.col(3).template head<3>() = translation; // dependent name
301
302 // Todo: use get_opencv_viewport() from nonlin_cam_esti.hpp.
303 const Eigen::Vector4<T> viewport(T(0), T(image.height()), T(image.width()),
304 T(-image.height())); // OpenCV convention
305
306 const T& fov = camera_intrinsics[0];
307 const double aspect_ratio = static_cast<double>(image.width()) / image.height();
308 const auto projection_mtx = render::perspective(fov, T(aspect_ratio), T(0.1), T(1000.0));
309
310 Eigen::Vector3<T> projected_point =
311 render::project(point_3d, model_view_mtx, projection_mtx, viewport);
312 // End of identical block with PerspectiveProjectionLandmarkCost.
313
314 // Access the image colour value at the projected pixel location, if inside the image - otherwise
315 // Ceres uses the value from the grid edge.
316 // Note: We could store the BiCubicInterpolator as member variable.
317 // The default template arguments for Grid2D are <T, kDataDim=1, kRowMajor=true,
318 // kInterleaved=true> and (except for the dimension), they're the right ones for us.
319 ceres::Grid2D<unsigned char, 3> grid(&image(0, 0).data()[0], 0, image.height(), 0, image.width());
320 ceres::BiCubicInterpolator<ceres::Grid2D<unsigned char, 3>> interpolator(grid);
321 T observed_colour[3];
322 interpolator.Evaluate(projected_point.y(), projected_point.x(), &observed_colour[0]);
323
324 // Get the vertex's colour value:
325 Eigen::Map<const Eigen::VectorX<T>> color_coeffs_mapped(color_coeffs, num_color_coeffs);
326 const auto model_color = get_vertex_color_at_point(color_model, vertex_id, color_coeffs_mapped);
327 // Returns RGB, between [0, 1].
328
329 // Residual: Vertex colour of model point minus the observed colour in the 2D image
330 // observed_colour is RGB, model_colour is RGB. Residual will be RGB.
331 residual[0] = model_color[0] * 255.0 - T(observed_colour[0]); // r
332 residual[1] = model_color[1] * 255.0 - T(observed_colour[1]); // g
333 residual[2] = model_color[2] * 255.0 - T(observed_colour[2]); // b
334
335 return true;
336 };
337
344 template <int num_shape_coeffs, int num_blendshape_coeffs, int num_color_coeffs>
345 static ceres::CostFunction* Create(const eos::morphablemodel::PcaModel& shape_model,
346 const eos::morphablemodel::Blendshapes& blendshapes,
347 const eos::morphablemodel::PcaModel& color_model, int vertex_id,
348 const eos::core::Image3u& image)
349 {
350 // Template arguments: 3 residuals (RGB), 4 (camera rotation), 3 (camera translation), 1 (camera
351 // intrinsics), x (shape coeffs), x (expr coeffs), x (colour coeffs)
352 return (new ceres::AutoDiffCostFunction<VertexColorCost, 3, 4, 3, 1, num_shape_coeffs,
353 num_blendshape_coeffs, num_color_coeffs>(
354 new VertexColorCost(shape_model, blendshapes, color_model, num_shape_coeffs,
355 num_blendshape_coeffs, num_color_coeffs, vertex_id, image)));
356 };
357
358private:
360 shape_model; // Or store as pointer (non-owning) or std::reference_wrapper?
361 const eos::morphablemodel::Blendshapes& blendshapes;
362 const eos::morphablemodel::PcaModel& color_model;
363 const int num_shape_coeffs;
364 const int num_blendshape_coeffs;
365 const int num_color_coeffs;
366 const int vertex_id;
367 const eos::core::Image3u& image; // the observed image
368};
369
380template <typename T>
381Eigen::Vector3<T> get_shape_at_point(const eos::morphablemodel::PcaModel& shape_model,
382 const eos::morphablemodel::Blendshapes& blendshapes, int vertex_id,
383 Eigen::Map<const Eigen::VectorX<T>> shape_coeffs,
384 Eigen::Map<const Eigen::VectorX<T>> blendshape_coeffs)
385{
386 // Computing Shape = mean + shape_basis*shape_coeffs + blendshapes*blendshape_coeffs:
387 const Eigen::Vector3f mean = shape_model.get_mean_at_point(vertex_id);
388 // Note: We seem to have a dependent name here, so we need 'template', to help the compiler that 'cast<T>'
389 // is a template.
390 const Eigen::Vector3<T> shape_vector = shape_model.get_rescaled_pca_basis_at_point(vertex_id)
391 .leftCols(shape_coeffs.size())
392 .template cast<T>() *
393 shape_coeffs;
394 Eigen::Vector3<T> expression_vector(T(0.0), T(0.0), T(0.0));
395 for (std::size_t i = 0; i < blendshape_coeffs.size(); i++)
396 {
397 expression_vector.x() += T(blendshapes[i].deformation(vertex_id * 3 + 0)) * blendshape_coeffs(i);
398 expression_vector.y() += T(blendshapes[i].deformation(vertex_id * 3 + 1)) * blendshape_coeffs(i);
399 expression_vector.z() += T(blendshapes[i].deformation(vertex_id * 3 + 2)) * blendshape_coeffs(i);
400 }
401
402 return Eigen::Vector3<T>(mean.cast<T>() + shape_vector + expression_vector);
403};
404
413template <typename T>
414Eigen::Vector3<T> get_vertex_color_at_point(const eos::morphablemodel::PcaModel& color_model, int vertex_id,
415 Eigen::Map<const Eigen::VectorX<T>> color_coeffs)
416{
417 const Eigen::Vector3f mean = color_model.get_mean_at_point(vertex_id);
418 // Note: We seem to have a dependent name here, so we need 'template', to help the compiler that 'cast<T>'
419 // is a template.
420 const Eigen::Vector3<T> color_vector = color_model.get_rescaled_pca_basis_at_point(vertex_id)
421 .leftCols(color_coeffs.size())
422 .template cast<T>() *
423 color_coeffs;
424
425 return Eigen::Vector3<T>(mean.cast<T>() + color_vector);
426};
427
428} /* namespace fitting */
429} /* namespace eos */
430
431#endif /* EOS_CERES_NONLINEAR_HPP */
Class to represent images.
Definition: Image.hpp:44
This class represents a PCA-model that consists of:
Definition: PcaModel.hpp:59
Eigen::Vector3f get_mean_at_point(int vertex_index) const
Definition: PcaModel.hpp:134
Eigen::MatrixXf get_rescaled_pca_basis_at_point(int vertex_id) const
Definition: PcaModel.hpp:223
Eigen::Vector3< T > get_shape_at_point(const eos::morphablemodel::PcaModel &shape_model, const eos::morphablemodel::Blendshapes &blendshapes, int vertex_id, Eigen::Map< const Eigen::VectorX< T > > shape_coeffs, Eigen::Map< const Eigen::VectorX< T > > blendshape_coeffs)
Definition: ceres_nonlinear.hpp:381
Eigen::Vector3< T > get_vertex_color_at_point(const eos::morphablemodel::PcaModel &color_model, int vertex_id, Eigen::Map< const Eigen::VectorX< T > > color_coeffs)
Definition: ceres_nonlinear.hpp:414
std::vector< Blendshape > Blendshapes
Definition: Blendshape.hpp:68
Eigen::Matrix4< T > perspective(T fov_y, T aspect, T z_near, T z_far)
Definition: matrix_projection.hpp:52
Eigen::Vector3< T > project(const Eigen::Vector3< T > &point_3d, const Eigen::Matrix4< T > &modelview_matrix, const Eigen::Matrix4< T > &projection_matrix, const Eigen::Vector4< T > &viewport)
Definition: matrix_projection.hpp:148
Namespace containing all of eos's 3D model fitting functionality.
Definition: ceres_nonlinear.hpp:58
static ceres::CostFunction * Create()
Definition: ceres_nonlinear.hpp:91
NormCost(int num_parameters)
Definition: ceres_nonlinear.hpp:65
bool operator()(const T *const x, T *residual) const
Definition: ceres_nonlinear.hpp:75
Definition: ceres_nonlinear.hpp:109
bool operator()(const T *const camera_rotation, const T *const camera_translation, const T *const camera_intrinsics, const T *const shape_coeffs, const T *const blendshape_coeffs, T *residual) const
Definition: ceres_nonlinear.hpp:152
PerspectiveProjectionLandmarkCost(const morphablemodel::PcaModel &shape_model, const std::vector< morphablemodel::Blendshape > &blendshapes, int num_shape_coeffs, int num_blendshape_coeffs, Eigen::Vector2f observed_landmark, int vertex_id, int image_width, int image_height)
Definition: ceres_nonlinear.hpp:127
static ceres::CostFunction * Create(const eos::morphablemodel::PcaModel &shape_model, const eos::morphablemodel::Blendshapes &blendshapes, Eigen::Vector2f observed_landmark, int vertex_id, int image_width, int image_height)
Definition: ceres_nonlinear.hpp:197
Definition: ceres_nonlinear.hpp:234
static ceres::CostFunction * Create(const eos::morphablemodel::PcaModel &shape_model, const eos::morphablemodel::Blendshapes &blendshapes, const eos::morphablemodel::PcaModel &color_model, int vertex_id, const eos::core::Image3u &image)
Definition: ceres_nonlinear.hpp:345
VertexColorCost(const eos::morphablemodel::PcaModel &shape_model, const eos::morphablemodel::Blendshapes &blendshapes, const eos::morphablemodel::PcaModel &color_model, int num_shape_coeffs, int num_blendshape_coeffs, int num_color_coeffs, int vertex_id, const eos::core::Image3u &image)
Definition: ceres_nonlinear.hpp:251
bool operator()(const T *const camera_rotation, const T *const camera_translation, const T *const camera_intrinsics, const T *const shape_coeffs, const T *const blendshape_coeffs, const T *const color_coeffs, T *residual) const
Definition: ceres_nonlinear.hpp:278