eos 1.4.0
Loading...
Searching...
No Matches
closest_edge_fitting.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/fitting/closest_edge_fitting.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_CLOSEST_EDGE_FITTING_HPP
23#define EOS_CLOSEST_EDGE_FITTING_HPP
24
25#include "eos/core/Mesh.hpp"
26#include "eos/morphablemodel/EdgeTopology.hpp"
27#include "eos/fitting/RenderingParameters.hpp"
28#include "eos/render/normals.hpp"
29#include "eos/render/vertex_visibility.hpp"
30#include "eos/render/ProjectionType.hpp"
31#include "eos/render/matrix_projection.hpp"
32
33#include "nanoflann.hpp"
34
35#include "Eigen/Core"
36
37#include <vector>
38#include <algorithm>
39#include <utility>
40#include <cstddef>
41
42namespace eos {
43namespace fitting {
44
60inline std::vector<int> occluding_boundary_vertices(const core::Mesh& mesh,
61 const morphablemodel::EdgeTopology& edge_topology,
62 Eigen::Matrix3f R, render::ProjectionType projection_type,
63 bool perform_self_occlusion_check = true)
64{
65 // Rotate the mesh:
66 std::vector<Eigen::Vector3f> rotated_vertices;
67 std::for_each(begin(mesh.vertices), end(mesh.vertices), [&rotated_vertices, &R](const auto& v) {
68 rotated_vertices.push_back(R * v);
69 });
70
71 // Compute the face normals of the rotated mesh:
72 std::vector<Eigen::Vector3f> facenormals;
73 for (const auto& f : mesh.tvi)
74 { // for each face (triangle):
75 const auto n = render::compute_face_normal(rotated_vertices[f[0]], rotated_vertices[f[1]],
76 rotated_vertices[f[2]]);
77 facenormals.push_back(n);
78 }
79
80 // Find occluding edges:
81 std::vector<int> occluding_edges_indices;
82 for (int edge_idx = 0; edge_idx < edge_topology.adjacent_faces.size();
83 ++edge_idx) // For each edge... Ef contains the indices of the two adjacent faces
84 {
85 const auto& edge = edge_topology.adjacent_faces[edge_idx];
86 if (edge[0] == 0) // ==> NOTE/Todo Need to change this if we use 0-based indexing!
87 {
88 // Edges with a zero index lie on the mesh boundary, i.e. they are only
89 // adjacent to one face.
90 continue;
91 }
92 // Compute the occluding edges as those where the two adjacent face normals
93 // differ in the sign of their z-component:
94 // Changing from 1-based indexing to 0-based!
95 if (core::sign(facenormals[edge[0] - 1].z()) != core::sign(facenormals[edge[1] - 1].z()))
96 {
97 // It's an occluding edge, store the index:
98 occluding_edges_indices.push_back(edge_idx);
99 }
100 }
101 // Select the vertices lying at the two ends of the occluding edges and remove duplicates:
102 // (This is what EdgeTopology::adjacent_vertices is needed for).
103 std::vector<int> occluding_vertices; // The model's contour vertices
104 for (auto edge_idx : occluding_edges_indices)
105 {
106 // Changing from 1-based indexing to 0-based!
107 occluding_vertices.push_back(edge_topology.adjacent_vertices[edge_idx][0] - 1);
108 occluding_vertices.push_back(edge_topology.adjacent_vertices[edge_idx][1] - 1);
109 }
110 // Remove duplicate vertex id's (std::unique works only on sorted sequences):
111 std::sort(begin(occluding_vertices), end(occluding_vertices));
112 occluding_vertices.erase(std::unique(begin(occluding_vertices), end(occluding_vertices)),
113 end(occluding_vertices));
114
115 if (perform_self_occlusion_check)
116 {
117 // Perform ray-casting to find out which vertices are not visible (i.e. self-occluded):
118 // We have to know the projection type, and then use different vector directions depending on
119 // the projection type:
120 using render::detail::RayDirection;
121 RayDirection ray_direction_type;
122 if (projection_type == render::ProjectionType::Orthographic)
123 {
124 ray_direction_type = RayDirection::Parallel;
125 } else
126 {
127 ray_direction_type = RayDirection::TowardsOrigin;
128 }
129 std::vector<bool> visibility;
130 for (auto vertex_idx : occluding_vertices)
131 {
132 const bool visible = render::is_vertex_visible(rotated_vertices[vertex_idx], rotated_vertices,
133 mesh.tvi, ray_direction_type);
134 visibility.push_back(visible);
135 }
136
137 // Remove vertices from occluding boundary list that are not visible:
138 std::vector<int> final_vertex_ids;
139 for (int i = 0; i < occluding_vertices.size(); ++i)
140 {
141 if (visibility[i] == true)
142 {
143 final_vertex_ids.push_back(occluding_vertices[i]);
144 }
145 }
146 return final_vertex_ids;
147 } else
148 {
149 return occluding_vertices;
150 }
151};
152
170template <class VectorOfVectorsType, typename num_t = double, int DIM = -1,
171 class Distance = nanoflann::metric_L2, typename IndexType = size_t>
173{
175 typedef typename Distance::template traits<num_t, self_t>::distance_t metric_t;
176 typedef nanoflann::KDTreeSingleIndexAdaptor<metric_t, self_t, DIM, IndexType> index_t;
177
178 index_t* index;
179
181 // Make sure the data is kept alive while the kd-tree is in use.
182 KDTreeVectorOfVectorsAdaptor(const int dimensionality, const VectorOfVectorsType& mat,
183 const int leaf_max_size = 10)
184 : m_data(mat)
185 {
186 assert(mat.size() != 0 && mat[0].size() != 0);
187 const size_t dims = mat[0].size();
188 if (DIM > 0 && static_cast<int>(dims) != DIM)
189 throw std::runtime_error("Data set dimensionality does not match the 'DIM' template argument");
190 index = new index_t(dims, *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size));
191 index->buildIndex();
192 }
193
194 ~KDTreeVectorOfVectorsAdaptor() { delete index; }
195
196 const VectorOfVectorsType& m_data;
197
203 inline void query(const num_t* query_point, const size_t num_closest, IndexType* out_indices,
204 num_t* out_distances_sq, const int nChecks_IGNORED = 10) const
205 {
206 nanoflann::KNNResultSet<num_t, IndexType> resultSet(num_closest);
207 resultSet.init(out_indices, out_distances_sq);
208 index->findNeighbors(resultSet, query_point, nanoflann::SearchParams());
209 }
210
214 const self_t& derived() const { return *this; }
215 self_t& derived() { return *this; }
216
217 // Must return the number of data points
218 inline size_t kdtree_get_point_count() const { return m_data.size(); }
219
220 // Returns the distance between the vector "p1[0:size-1]" and the data point with index "idx_p2" stored in
221 // the class:
222 inline num_t kdtree_distance(const num_t* p1, const size_t idx_p2, size_t size) const
223 {
224 num_t s = 0;
225 for (size_t i = 0; i < size; i++)
226 {
227 const num_t d = p1[i] - m_data[idx_p2][i];
228 s += d * d;
229 }
230 return s;
231 }
232
233 // Returns the dim'th component of the idx'th point in the class:
234 inline num_t kdtree_get_pt(const size_t idx, int dim) const
235 {
236 return m_data[idx][dim];
237 }
238
239 // Optional bounding-box computation: return false to default to a standard bbox computation loop.
240 // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again.
241 // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)
242 template <class BBOX>
243 bool kdtree_get_bbox(BBOX& /*bb*/) const
244 {
245 return false;
246 }
247
249};
250
278inline std::pair<std::vector<Eigen::Vector2f>, std::vector<int>> find_occluding_edge_correspondences(
279 const core::Mesh& mesh, const morphablemodel::EdgeTopology& edge_topology,
280 const fitting::RenderingParameters& rendering_parameters, const std::vector<Eigen::Vector2f>& image_edges,
281 float distance_threshold = 64.0f, bool perform_self_occlusion_check = true)
282{
283 assert(rendering_parameters.get_camera_type() == fitting::CameraType::Orthographic);
284 using Eigen::Vector2f;
285 using std::vector;
286
287 // If there are no image_edges given, there's no point in computing anything:
288 if (image_edges.empty())
289 {
290 return {};
291 }
292
293 // Compute vertices that lye on occluding boundaries:
294 const render::ProjectionType projection_type = [&rendering_parameters]() {
295 if (rendering_parameters.get_camera_type() == CameraType::Orthographic)
296 {
297 return render::ProjectionType::Orthographic;
298 } else
299 {
300 return render::ProjectionType::Perspective;
301 }
302 }();
303 const auto occluding_vertices = occluding_boundary_vertices(
304 mesh, edge_topology, rendering_parameters.get_rotation().normalized().toRotationMatrix(),
305 projection_type, perform_self_occlusion_check);
306
307 // Project these occluding boundary vertices from 3D to 2D:
308 vector<Vector2f> model_edges_projected;
309 for (const auto& v : occluding_vertices)
310 {
311 const auto p = render::project(
312 mesh.vertices[v], rendering_parameters.get_modelview(), rendering_parameters.get_projection(),
313 fitting::get_opencv_viewport(rendering_parameters.get_screen_width(),
314 rendering_parameters.get_screen_height()));
315 model_edges_projected.push_back({p.x(), p.y()});
316 }
317
318 // Find edge correspondences:
319 // Build a kd-tree and use nearest neighbour search:
320 using kd_tree_t = KDTreeVectorOfVectorsAdaptor<vector<Vector2f>, float, 2>;
321 kd_tree_t tree(2, image_edges); // dim, samples, max_leaf
322 tree.index->buildIndex();
323
324 vector<std::pair<std::size_t, double>>
325 idx_d; // will contain [ index to the 2D edge in 'image_edges', distance (L2^2) ]
326 for (const auto& e : model_edges_projected)
327 {
328 std::size_t idx; // contains the indices into the original 'image_edges' vector
329 double dist_sq; // squared distances
330 nanoflann::KNNResultSet<double> resultSet(1);
331 resultSet.init(&idx, &dist_sq);
332 tree.index->findNeighbors(resultSet, e.data(), nanoflann::SearchParams(10));
333 idx_d.push_back({idx, dist_sq});
334 }
335 // Filter edge matches:
336 // We filter below by discarding all correspondence that are a certain distance apart.
337 // We could also (or in addition to) discard the worst 5% of the distances or something like that.
338
339 // Filter and store the image (edge) points with their corresponding vertex id:
340 vector<int> vertex_indices;
341 vector<Vector2f> image_points;
342 assert(occluding_vertices.size() == idx_d.size());
343 for (int i = 0; i < occluding_vertices.size(); ++i)
344 {
345 const auto ortho_scale =
346 rendering_parameters.get_screen_width() /
347 rendering_parameters.get_frustum().r; // This might be a bit of a hack - we recover the "real"
348 // scaling from the SOP estimate
349 if (idx_d[i].second <= distance_threshold * ortho_scale) // I think multiplying by the scale is good
350 // here and gives us invariance w.r.t. the
351 // image resolution and face size.
352 {
353 const auto edge_point = image_edges[idx_d[i].first];
354 // Store the found 2D edge point, and the associated vertex id:
355 vertex_indices.push_back(occluding_vertices[i]);
356 image_points.push_back(edge_point);
357 }
358 }
359 return {image_points, vertex_indices};
360};
361
362} /* namespace fitting */
363} /* namespace eos */
364
365#endif /* EOS_CLOSEST_EDGE_FITTING_HPP */
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::Matrix4f get_projection() const
Construct an orthographic or perspective projection matrix from the RenderingParameters' frustum (ort...
Definition: RenderingParameters.hpp:245
std::enable_if< std::is_unsigned< T >::value, int >::type constexpr sign(T const x)
Returns the sign of the given number (-1, 0 or +1).
Definition: math.hpp:66
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< int > occluding_boundary_vertices(const core::Mesh &mesh, const morphablemodel::EdgeTopology &edge_topology, Eigen::Matrix3f R, render::ProjectionType projection_type, bool perform_self_occlusion_check=true)
Computes the vertices that lie on occluding boundaries, given a particular pose.
Definition: closest_edge_fitting.hpp:60
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
bool is_vertex_visible(const Eigen::Vector3f &probe_vertex, const std::vector< Eigen::Vector3f > &mesh_vertices, const std::vector< std::array< int, 3 > > &mesh_triangle_vertex_indices, detail::RayDirection ray_direction_type)
Definition: vertex_visibility.hpp:52
Eigen::Vector3f compute_face_normal(const Eigen::Vector3f &v0, const Eigen::Vector3f &v1, const Eigen::Vector3f &v2)
Definition: normals.hpp:43
ProjectionType
Definition: ProjectionType.hpp:31
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
std::vector< std::array< int, 3 > > tvi
Triangle vertex indices.
Definition: Mesh.hpp:50
Definition: closest_edge_fitting.hpp:173
void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED=10) const
Definition: closest_edge_fitting.hpp:203
KDTreeVectorOfVectorsAdaptor(const int dimensionality, const VectorOfVectorsType &mat, const int leaf_max_size=10)
The kd-tree index for the user to call its methods as usual with any other FLANN index.
Definition: closest_edge_fitting.hpp:182
A struct containing a 3D shape model's edge topology.
Definition: EdgeTopology.hpp:54
std::vector< std::array< int, 2 > > adjacent_faces
num_edges x 2 matrix storing faces adjacent to each edge
Definition: EdgeTopology.hpp:56
std::vector< std::array< int, 2 > > adjacent_vertices
num_edges x 2 matrix storing vertices adjacent to each edge
Definition: EdgeTopology.hpp:58