eos 1.4.0
Loading...
Searching...
No Matches
contour_correspondence.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/fitting/contour_correspondence.hpp
5 *
6 * Copyright 2015, 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_CONTOUR_CORRESPONDENCE_HPP
23#define EOS_CONTOUR_CORRESPONDENCE_HPP
24
25#include "eos/core/Landmark.hpp"
26#include "eos/core/Mesh.hpp"
27#include "eos/render/matrix_projection.hpp"
28
29#include "cereal/types/vector.hpp"
30#include "cereal/archives/json.hpp"
31
32#include "toml.hpp"
33
34#include "Eigen/Core"
35
36#include <vector>
37#include <string>
38#include <algorithm>
39#include <fstream>
40#include <tuple>
41
42namespace eos {
43namespace fitting {
44
45// Forward declarations of later used functions and types:
46struct ModelContour;
47struct ContourLandmarks;
48std::pair<std::vector<std::string>, std::vector<int>>
49select_contour(float yaw_angle, const ContourLandmarks& contour_landmarks, const ModelContour& model_contour,
50 float frontal_range_threshold = 7.5f);
51std::tuple<std::vector<Eigen::Vector2f>, std::vector<Eigen::Vector4f>, std::vector<int>>
52get_nearest_contour_correspondences(const core::LandmarkCollection<Eigen::Vector2f>& landmarks,
53 const std::vector<std::string>& landmark_contour_identifiers,
54 const std::vector<int>& model_contour_indices, const core::Mesh& mesh,
55 const Eigen::Matrix4f& view_model,
56 const Eigen::Matrix4f& ortho_projection, const Eigen::Vector4f& viewport);
57
71{
72 // starting from right side, eyebrow-height: (I think the order matters here)
73 std::vector<int> right_contour;
74 /* 23 = middle, below chin - not included in the contour here */
75 // starting from left side, eyebrow-height:
76 std::vector<int> left_contour;
77
78 // We store r/l separately because we currently only fit to the contour facing the camera.
79 // Also if we were to fit to the whole contour: Be careful not to just fit to the closest. The
80 // "invisible" ones behind might be closer on an e.g 90° angle. Store CNT for left/right side separately?
81
94 static ModelContour load(std::string filename)
95 {
96 ModelContour contour;
97
98 std::ifstream file(filename);
99 if (!file)
100 {
101 throw std::runtime_error("Error opening given file: " + filename);
102 }
103 cereal::JSONInputArchive input_archive(file);
104 input_archive(contour);
105
106 return contour;
107 };
108
109 friend class cereal::access;
115 template <class Archive>
116 void serialize(Archive& archive)
117 {
118 archive(cereal::make_nvp("right_contour", right_contour),
119 cereal::make_nvp("left_contour", left_contour));
120 };
121};
122
140{
141 // starting from right side, eyebrow-height.
142 std::vector<std::string> right_contour;
143 // Chin point is not included in the contour here.
144 // starting from left side, eyebrow-height. Order doesn't matter here.
145 std::vector<std::string> left_contour;
146
147 // Note: We store r/l separately because we currently only fit to the contour facing the camera.
148
158 static ContourLandmarks load(std::string filename)
159 {
160 // parse() as well as extracting the data can throw std::runtime error or toml::exception,
161 // so ideally you'd want to call this c'tor within a try-catch.
162 const auto data = toml::parse(filename);
163
164 // Todo: Handle the case when '[contour_landmarks]' is empty - now it just throws. Use
165 // std::unordered_map::count.
166 const auto& contour_table = toml::get<toml::table>(data.at("contour_landmarks"));
167
168 ContourLandmarks contour;
169 // There might be a vector<string> or a vector<int>, we need to check for that and convert to vector<string>.
170 // Todo: Again, check whether the key exists first.
171 // Read all the "right" contour landmarks:
172 const auto& right_contour = toml::get<std::vector<toml::value>>(contour_table.at("right"));
173 for (const auto& landmark : right_contour)
174 {
175 std::string value;
176 switch (landmark.type())
177 {
178 case toml::value_t::integer:
179 value = std::to_string(toml::get<int>(landmark));
180 break;
181 case toml::value_t::string:
182 value = toml::get<std::string>(landmark);
183 break;
184 default:
185 throw std::runtime_error("unexpected type : " + toml::stringize(landmark.type()));
186 }
187 contour.right_contour.push_back(value);
188 }
189 // Now the same for all the "left" contour landmarks:
190 const auto& left_contour = toml::get<std::vector<toml::value>>(contour_table.at("left"));
191 for (const auto& landmark : left_contour)
192 {
193 std::string value;
194 switch (landmark.type())
195 {
196 case toml::value_t::integer:
197 value = std::to_string(toml::get<int>(landmark));
198 break;
199 case toml::value_t::string:
200 value = toml::get<std::string>(landmark);
201 break;
202 default:
203 throw std::runtime_error("unexpected type : " + toml::stringize(landmark.type()));
204 }
205 contour.left_contour.push_back(value);
206 }
207
208 return contour;
209 };
210};
211
236inline std::tuple<std::vector<Eigen::Vector2f>, std::vector<Eigen::Vector4f>, std::vector<int>>
238 const ContourLandmarks& contour_landmarks, const ModelContour& model_contour,
239 float yaw_angle, const core::Mesh& mesh, const Eigen::Matrix4f& view_model,
240 const Eigen::Matrix4f& ortho_projection, const Eigen::Vector4f& viewport,
241 float frontal_range_threshold = 7.5f)
242{
243 // Select which side of the contour we'll use:
244 std::vector<int> model_contour_indices;
245 std::vector<std::string> landmark_contour_identifiers;
246 std::tie(landmark_contour_identifiers, model_contour_indices) =
247 select_contour(yaw_angle, contour_landmarks, model_contour, frontal_range_threshold);
248
249 // For each 2D contour landmark, get the corresponding 3D vertex point and vertex id:
250 // Note/Todo: Loop here instead of calling this function where we have no idea what it's doing? What does
251 // its documentation say?
252 return get_nearest_contour_correspondences(landmarks, landmark_contour_identifiers, model_contour_indices,
253 mesh, view_model, ortho_projection, viewport);
254};
255
272inline std::pair<std::vector<std::string>, std::vector<int>>
273select_contour(float yaw_angle, const ContourLandmarks& contour_landmarks, const ModelContour& model_contour, float frontal_range_threshold)
274{
275 using std::begin;
276 using std::end;
277 std::vector<int> model_contour_indices;
278 std::vector<std::string> contour_landmark_identifiers;
279 if (yaw_angle >= -frontal_range_threshold) // positive yaw = subject looking to the left
280 {
281 // ==> we use the right cnt-lms
282 model_contour_indices.insert(end(model_contour_indices), begin(model_contour.right_contour),
283 end(model_contour.right_contour));
284 contour_landmark_identifiers.insert(end(contour_landmark_identifiers),
285 begin(contour_landmarks.right_contour),
286 end(contour_landmarks.right_contour));
287 }
288 if (yaw_angle <= frontal_range_threshold)
289 {
290 // ==> we use the left cnt-lms
291 model_contour_indices.insert(end(model_contour_indices), begin(model_contour.left_contour),
292 end(model_contour.left_contour));
293 contour_landmark_identifiers.insert(end(contour_landmark_identifiers),
294 begin(contour_landmarks.left_contour),
295 end(contour_landmarks.left_contour));
296 }
297 // Note there's an overlap between the angles - if a subject is between +- 7.5°, both contours get added.
298 return std::make_pair(contour_landmark_identifiers, model_contour_indices);
299};
300
321inline std::tuple<std::vector<Eigen::Vector2f>, std::vector<Eigen::Vector4f>, std::vector<int>>
323 const std::vector<std::string>& landmark_contour_identifiers,
324 const std::vector<int>& model_contour_indices, const core::Mesh& mesh,
325 const Eigen::Matrix4f& view_model,
326 const Eigen::Matrix4f& ortho_projection, const Eigen::Vector4f& viewport)
327{
328 // These are the additional contour-correspondences we're going to find and then use!
329 std::vector<Eigen::Vector4f> model_points_cnt; // the points in the 3D shape model
330 std::vector<int> vertex_indices_cnt; // their vertex indices
331 std::vector<Eigen::Vector2f> image_points_cnt; // the corresponding 2D landmark points
332
333 // For each 2D-CNT-LM, find the closest 3DMM-CNT-LM and add to correspondences:
334 // Note: If we were to do this for all 3DMM vertices, then ray-casting (i.e. glm::unproject) would be
335 // quicker to find the closest vertex)
336 for (const auto& ibug_idx : landmark_contour_identifiers)
337 {
338 // Check if the contour landmark is amongst the landmarks given to us (from detector or ground truth):
339 // (Note: Alternatively, we could filter landmarks beforehand and then just loop over landmarks =>
340 // means one less function param here. Separate filtering from actual algorithm.)
341 const auto result = std::find_if(begin(landmarks), end(landmarks), [&ibug_idx](auto&& e) {
342 return e.name == ibug_idx;
343 }); // => this can go outside the loop
344 if (result == std::end(landmarks))
345 {
346 continue; // This should be okay; So it's possible that the function will not return any
347 // correspondences.
348 }
349
350 const auto screen_point_2d_contour_landmark = result->coordinates;
351
352 std::vector<float> distances_2d;
353 for (auto model_contour_vertex_idx : model_contour_indices) // we could actually pre-project them,
354 // i.e. only project them once, not for
355 // each landmark newly...
356 {
357 const Eigen::Vector3f screen_point_model_contour = render::project(
358 mesh.vertices[model_contour_vertex_idx], view_model, ortho_projection, viewport);
359 const double dist =
360 (screen_point_model_contour.head<2>() - screen_point_2d_contour_landmark).norm();
361 distances_2d.emplace_back(dist);
362 }
363 const auto min_ele = std::min_element(begin(distances_2d), end(distances_2d));
364 // Todo: Cover the case when cnt_indices_to_use.size() is 0.
365 const auto min_ele_idx = std::distance(begin(distances_2d), min_ele);
366 const auto the_3dmm_vertex_id_that_is_closest = model_contour_indices[min_ele_idx];
367
368 model_points_cnt.emplace_back(mesh.vertices[the_3dmm_vertex_id_that_is_closest].homogeneous());
369 vertex_indices_cnt.emplace_back(the_3dmm_vertex_id_that_is_closest);
370 image_points_cnt.emplace_back(screen_point_2d_contour_landmark);
371 }
372
373 return std::make_tuple(image_points_cnt, model_points_cnt, vertex_indices_cnt);
374};
375
387template <class T>
388inline auto concat(const std::vector<T>& vec_a, const std::vector<T>& vec_b)
389{
390 std::vector<T> concatenated_vec;
391 concatenated_vec.reserve(vec_a.size() + vec_b.size());
392 concatenated_vec.insert(std::end(concatenated_vec), std::begin(vec_a), std::end(vec_a));
393 concatenated_vec.insert(std::end(concatenated_vec), std::begin(vec_b), std::end(vec_b));
394 return concatenated_vec;
395};
396
397} /* namespace fitting */
398} /* namespace eos */
399
400#endif /* EOS_CONTOUR_CORRESPONDENCE_HPP */
std::vector< Landmark< LandmarkType > > LandmarkCollection
A trivial collection of landmarks that belong together.
Definition: Landmark.hpp:47
std::pair< std::vector< std::string >, std::vector< int > > select_contour(float yaw_angle, const ContourLandmarks &contour_landmarks, const ModelContour &model_contour, float frontal_range_threshold=7.5f)
Definition: contour_correspondence.hpp:273
std::tuple< std::vector< Eigen::Vector2f >, std::vector< Eigen::Vector4f >, std::vector< int > > get_nearest_contour_correspondences(const core::LandmarkCollection< Eigen::Vector2f > &landmarks, const std::vector< std::string > &landmark_contour_identifiers, const std::vector< int > &model_contour_indices, const core::Mesh &mesh, const Eigen::Matrix4f &view_model, const Eigen::Matrix4f &ortho_projection, const Eigen::Vector4f &viewport)
Definition: contour_correspondence.hpp:322
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
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
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.
This class represents a 3D mesh consisting of vertices, vertex colour information and texture coordin...
Definition: Mesh.hpp:45
std::vector< Eigen::Vector3f > vertices
3D vertex positions.
Definition: Mesh.hpp:46
Defines which 2D landmarks comprise the right and left face contour.
Definition: contour_correspondence.hpp:140
static ContourLandmarks load(std::string filename)
Definition: contour_correspondence.hpp:158
Definition of the vertex indices that define the right and left model contour.
Definition: contour_correspondence.hpp:71
void serialize(Archive &archive)
Definition: contour_correspondence.hpp:116
static ModelContour load(std::string filename)
Definition: contour_correspondence.hpp:94