Skip to content

Commit 5fe5a73

Browse files
committed
Merge branch 'devel'
# Resolved conflicts: # include/eos/fitting/RenderingParameters.hpp # include/eos/fitting/contour_correspondence.hpp
2 parents e00cfa0 + 5d745ff commit 5fe5a73

19 files changed

+82803
-91
lines changed

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
[submodule "3rdparty/pybind11"]
22
path = 3rdparty/pybind11
33
url = https://github.com/pybind/pybind11.git
4+
[submodule "3rdparty/nanoflann"]
5+
path = 3rdparty/nanoflann
6+
url = https://github.com/jlblancoc/nanoflann.git
47
[submodule "3rdparty/glm"]
58
path = 3rdparty/glm
69
url = https://github.com/g-truc/glm.git
10+
[submodule "3rdparty/eigen3-nnls"]
11+
path = 3rdparty/eigen3-nnls
12+
url = https://github.com/hmatuschek/eigen3-nnls.git

3rdparty/eigen3-nnls

Submodule eigen3-nnls added at d20add3

3rdparty/nanoflann

Submodule nanoflann added at 206d65a

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ message(STATUS "Eigen3 version: ${EIGEN3_VERSION}")
9696

9797
set(CEREAL_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/3rdparty/cereal-1.1.1/include")
9898
set(glm_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/3rdparty/glm")
99+
set(nanoflann_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/3rdparty/nanoflann/include")
100+
set(eigen3_nnls_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/3rdparty/eigen3-nnls/src")
99101

100102
# Header files:
101103
set(HEADERS
@@ -105,6 +107,7 @@ set(HEADERS
105107
include/eos/morphablemodel/MorphableModel.hpp
106108
include/eos/morphablemodel/Blendshape.hpp
107109
include/eos/morphablemodel/coefficients.hpp
110+
include/eos/morphablemodel/EdgeTopology.hpp
108111
include/eos/morphablemodel/io/cvssp.hpp
109112
include/eos/morphablemodel/io/mat_cerealisation.hpp
110113
include/eos/fitting/affine_camera_estimation.hpp
@@ -116,6 +119,7 @@ set(HEADERS
116119
include/eos/fitting/linear_shape_fitting.hpp
117120
include/eos/fitting/contour_correspondence.hpp
118121
include/eos/fitting/blendshape_fitting.hpp
122+
include/eos/fitting/closest_edge_fitting.hpp
119123
include/eos/fitting/fitting.hpp
120124
include/eos/fitting/ceres_nonlinear.hpp
121125
include/eos/fitting/RenderingParameters.hpp
@@ -136,6 +140,8 @@ include_directories(${Boost_INCLUDE_DIRS})
136140
include_directories(${OpenCV_INCLUDE_DIRS})
137141
include_directories(${EIGEN3_INCLUDE_DIR})
138142
include_directories(${glm_INCLUDE_DIR})
143+
include_directories(${nanoflann_INCLUDE_DIR})
144+
include_directories(${eigen3_nnls_INCLUDE_DIR})
139145

140146
# Custom target for the library, to make the headers show up in IDEs:
141147
add_custom_target(eos SOURCES ${HEADERS})

examples/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ if(MSVC)
3333
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
3434
endif()
3535

36-
# Model fitting (affine cam & shape to landmarks) example:
36+
# Simple model fitting (orthographic camera & shape to landmarks) example:
37+
add_executable(fit-model-simple fit-model-simple.cpp)
38+
target_link_libraries(fit-model-simple ${OpenCV_LIBS} ${Boost_LIBRARIES})
39+
40+
# Model fitting example that fits orthographic camera, shape, blendshapes, and contours:
3741
add_executable(fit-model fit-model.cpp)
3842
target_link_libraries(fit-model ${OpenCV_LIBS} ${Boost_LIBRARIES})
3943

@@ -55,6 +59,7 @@ target_link_libraries(generate-obj ${OpenCV_LIBS} ${Boost_LIBRARIES})
5559

5660

5761
# install target:
62+
install(TARGETS fit-model-simple DESTINATION bin)
5863
install(TARGETS fit-model DESTINATION bin)
5964
install(TARGETS generate-obj DESTINATION bin)
6065
install(DIRECTORY ${CMAKE_SOURCE_DIR}/examples/data DESTINATION bin)

examples/fit-model-ceres.cpp

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,9 @@ int main(int argc, char *argv[])
212212

213213
constexpr bool use_perspective = false;
214214

215-
// These will be the final 2D and 3D points used for the fitting:
216-
vector<Vec4f> model_points; // the points in the 3D shape model
217-
vector<int> vertex_indices; // their vertex indices
218-
vector<Vec2f> image_points; // the corresponding 2D landmark points
215+
// These will be the 2D image points and their corresponding 3D vertex id's used for the fitting:
216+
vector<Vec2f> image_points; // the 2D landmark points
217+
vector<int> vertex_indices; // their corresponding vertex indices
219218

220219
// Sub-select all the landmarks which we have a mapping for (i.e. that are defined in the 3DMM):
221220
for (int i = 0; i < landmarks.size(); ++i) {
@@ -225,7 +224,6 @@ int main(int argc, char *argv[])
225224
}
226225
int vertex_idx = std::stoi(converted_name.get());
227226
Vec4f vertex = morphable_model.get_shape_model().get_mean_at_point(vertex_idx);
228-
model_points.emplace_back(vertex);
229227
vertex_indices.emplace_back(vertex_idx);
230228
image_points.emplace_back(landmarks[i].coordinates);
231229
}
@@ -259,7 +257,7 @@ int main(int argc, char *argv[])
259257
blendshape_coefficients.resize(6);
260258

261259
Problem camera_costfunction;
262-
for (int i = 0; i < model_points.size(); ++i)
260+
for (int i = 0; i < image_points.size(); ++i)
263261
{
264262
CostFunction* cost_function = new AutoDiffCostFunction<fitting::LandmarkCost, 2 /* num residuals */, 4 /* camera rotation (quaternion) */, num_cam_trans_intr_params /* camera translation & fov/frustum_scale */, 10 /* shape-coeffs */, 6 /* bs-coeffs */>(new fitting::LandmarkCost(morphable_model.get_shape_model(), blendshapes, image_points[i], vertex_indices[i], image.cols, image.rows, use_perspective));
265263
camera_costfunction.AddResidualBlock(cost_function, NULL, &camera_rotation[0], &camera_translation_and_intrinsics[0], &shape_coefficients[0], &blendshape_coefficients[0]);
@@ -333,21 +331,19 @@ int main(int argc, char *argv[])
333331

334332
// Contour fitting:
335333
// These are the additional contour-correspondences we're going to find and then use:
336-
vector<Vec4f> model_points_contour; // the points in the 3D shape model
337-
vector<int> vertex_indices_contour; // their vertex indices
338-
vector<Vec2f> image_points_contour; // the corresponding 2D landmark points
334+
vector<Vec2f> image_points_contour; // the 2D landmark points
335+
vector<int> vertex_indices_contour; // their corresponding 3D vertex indices
339336
// For each 2D contour landmark, get the corresponding 3D vertex point and vertex id:
340-
std::tie(image_points_contour, model_points_contour, vertex_indices_contour) = fitting::get_contour_correspondences(landmarks, ibug_contour, model_contour, glm::degrees(euler_angles[1]), morphable_model, t_mtx * rot_mtx, projection_mtx, viewport);
337+
std::tie(image_points_contour, std::ignore, vertex_indices_contour) = fitting::get_contour_correspondences(landmarks, ibug_contour, model_contour, glm::degrees(euler_angles[1]), morphable_model.get_mean(), t_mtx * rot_mtx, projection_mtx, viewport);
341338
using eos::fitting::concat;
342-
model_points = concat(model_points, model_points_contour);
343339
vertex_indices = concat(vertex_indices, vertex_indices_contour);
344340
image_points = concat(image_points, image_points_contour);
345341

346342
// Full fitting - Estimate shape and pose, given the previous pose estimate:
347343
start = std::chrono::steady_clock::now();
348344
Problem fitting_costfunction;
349345
// Landmark constraint:
350-
for (int i = 0; i < model_points.size(); ++i)
346+
for (int i = 0; i < image_points.size(); ++i)
351347
{
352348
CostFunction* cost_function = new AutoDiffCostFunction<fitting::LandmarkCost, 2 /* num residuals */, 4 /* camera rotation (quaternion) */, num_cam_trans_intr_params /* camera translation & focal length */, 10 /* shape-coeffs */, 6 /* bs-coeffs */>(new fitting::LandmarkCost(morphable_model.get_shape_model(), blendshapes, image_points[i], vertex_indices[i], image.cols, image.rows, use_perspective));
353349
fitting_costfunction.AddResidualBlock(cost_function, NULL, &camera_rotation[0], &camera_translation_and_intrinsics[0], &shape_coefficients[0], &blendshape_coefficients[0]);

examples/fit-model-simple.cpp

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* eos - A 3D Morphable Model fitting library written in modern C++11/14.
3+
*
4+
* File: examples/fit-model-simple.cpp
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+
#include "eos/core/Landmark.hpp"
21+
#include "eos/core/LandmarkMapper.hpp"
22+
#include "eos/fitting/orthographic_camera_estimation_linear.hpp"
23+
#include "eos/fitting/RenderingParameters.hpp"
24+
#include "eos/fitting/linear_shape_fitting.hpp"
25+
#include "eos/render/utils.hpp"
26+
#include "eos/render/texture_extraction.hpp"
27+
28+
#include "opencv2/core/core.hpp"
29+
#include "opencv2/highgui/highgui.hpp"
30+
31+
#include "boost/program_options.hpp"
32+
#include "boost/filesystem.hpp"
33+
34+
#include <vector>
35+
#include <iostream>
36+
#include <fstream>
37+
38+
using namespace eos;
39+
namespace po = boost::program_options;
40+
namespace fs = boost::filesystem;
41+
using eos::core::Landmark;
42+
using eos::core::LandmarkCollection;
43+
using cv::Mat;
44+
using cv::Vec2f;
45+
using cv::Vec3f;
46+
using cv::Vec4f;
47+
using std::cout;
48+
using std::endl;
49+
using std::vector;
50+
using std::string;
51+
52+
/**
53+
* Reads an ibug .pts landmark file and returns an ordered vector with
54+
* the 68 2D landmark coordinates.
55+
*
56+
* @param[in] filename Path to a .pts file.
57+
* @return An ordered vector with the 68 ibug landmarks.
58+
*/
59+
LandmarkCollection<cv::Vec2f> read_pts_landmarks(std::string filename)
60+
{
61+
using std::getline;
62+
using cv::Vec2f;
63+
using std::string;
64+
LandmarkCollection<Vec2f> landmarks;
65+
landmarks.reserve(68);
66+
67+
std::ifstream file(filename);
68+
if (!file.is_open()) {
69+
throw std::runtime_error(string("Could not open landmark file: " + filename));
70+
}
71+
72+
string line;
73+
// Skip the first 3 lines, they're header lines:
74+
getline(file, line); // 'version: 1'
75+
getline(file, line); // 'n_points : 68'
76+
getline(file, line); // '{'
77+
78+
int ibugId = 1;
79+
while (getline(file, line))
80+
{
81+
if (line == "}") { // end of the file
82+
break;
83+
}
84+
std::stringstream lineStream(line);
85+
86+
Landmark<Vec2f> landmark;
87+
landmark.name = std::to_string(ibugId);
88+
if (!(lineStream >> landmark.coordinates[0] >> landmark.coordinates[1])) {
89+
throw std::runtime_error(string("Landmark format error while parsing the line: " + line));
90+
}
91+
// From the iBug website:
92+
// "Please note that the re-annotated data for this challenge are saved in the Matlab convention of 1 being
93+
// the first index, i.e. the coordinates of the top left pixel in an image are x=1, y=1."
94+
// ==> So we shift every point by 1:
95+
landmark.coordinates[0] -= 1.0f;
96+
landmark.coordinates[1] -= 1.0f;
97+
landmarks.emplace_back(landmark);
98+
++ibugId;
99+
}
100+
return landmarks;
101+
};
102+
103+
/**
104+
* This app demonstrates estimation of the camera and fitting of the shape
105+
* model of a 3D Morphable Model from an ibug LFPW image with its landmarks.
106+
*
107+
* First, the 68 ibug landmarks are loaded from the .pts file and converted
108+
* to vertex indices using the LandmarkMapper. Then, an orthographic camera
109+
* is estimated, and then, using this camera matrix, the shape is fitted
110+
* to the landmarks.
111+
*/
112+
int main(int argc, char *argv[])
113+
{
114+
fs::path modelfile, isomapfile, imagefile, landmarksfile, mappingsfile, outputfile;
115+
try {
116+
po::options_description desc("Allowed options");
117+
desc.add_options()
118+
("help,h",
119+
"display the help message")
120+
("model,m", po::value<fs::path>(&modelfile)->required()->default_value("../share/sfm_shape_3448.bin"),
121+
"a Morphable Model stored as cereal BinaryArchive")
122+
("image,i", po::value<fs::path>(&imagefile)->required()->default_value("data/image_0010.png"),
123+
"an input image")
124+
("landmarks,l", po::value<fs::path>(&landmarksfile)->required()->default_value("data/image_0010.pts"),
125+
"2D landmarks for the image, in ibug .pts format")
126+
("mapping,p", po::value<fs::path>(&mappingsfile)->required()->default_value("../share/ibug2did.txt"),
127+
"landmark identifier to model vertex number mapping")
128+
("output,o", po::value<fs::path>(&outputfile)->required()->default_value("out"),
129+
"basename for the output rendering and obj files")
130+
;
131+
po::variables_map vm;
132+
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
133+
if (vm.count("help")) {
134+
cout << "Usage: fit-model-simple [options]" << endl;
135+
cout << desc;
136+
return EXIT_SUCCESS;
137+
}
138+
po::notify(vm);
139+
}
140+
catch (const po::error& e) {
141+
cout << "Error while parsing command-line arguments: " << e.what() << endl;
142+
cout << "Use --help to display a list of options." << endl;
143+
return EXIT_SUCCESS;
144+
}
145+
146+
// Load the image, landmarks, LandmarkMapper and the Morphable Model:
147+
Mat image = cv::imread(imagefile.string());
148+
LandmarkCollection<cv::Vec2f> landmarks;
149+
try {
150+
landmarks = read_pts_landmarks(landmarksfile.string());
151+
}
152+
catch (const std::runtime_error& e) {
153+
cout << "Error reading the landmarks: " << e.what() << endl;
154+
return EXIT_FAILURE;
155+
}
156+
morphablemodel::MorphableModel morphable_model;
157+
try {
158+
morphable_model = morphablemodel::load_model(modelfile.string());
159+
}
160+
catch (const std::runtime_error& e) {
161+
cout << "Error loading the Morphable Model: " << e.what() << endl;
162+
return EXIT_FAILURE;
163+
}
164+
core::LandmarkMapper landmark_mapper = mappingsfile.empty() ? core::LandmarkMapper() : core::LandmarkMapper(mappingsfile);
165+
166+
// Draw the loaded landmarks:
167+
Mat outimg = image.clone();
168+
for (auto&& lm : landmarks) {
169+
cv::rectangle(outimg, cv::Point2f(lm.coordinates[0] - 2.0f, lm.coordinates[1] - 2.0f), cv::Point2f(lm.coordinates[0] + 2.0f, lm.coordinates[1] + 2.0f), { 255, 0, 0 });
170+
}
171+
172+
// These will be the final 2D and 3D points used for the fitting:
173+
vector<Vec4f> model_points; // the points in the 3D shape model
174+
vector<int> vertex_indices; // their vertex indices
175+
vector<Vec2f> image_points; // the corresponding 2D landmark points
176+
177+
// Sub-select all the landmarks which we have a mapping for (i.e. that are defined in the 3DMM):
178+
for (int i = 0; i < landmarks.size(); ++i) {
179+
auto converted_name = landmark_mapper.convert(landmarks[i].name);
180+
if (!converted_name) { // no mapping defined for the current landmark
181+
continue;
182+
}
183+
int vertex_idx = std::stoi(converted_name.get());
184+
Vec4f vertex = morphable_model.get_shape_model().get_mean_at_point(vertex_idx);
185+
model_points.emplace_back(vertex);
186+
vertex_indices.emplace_back(vertex_idx);
187+
image_points.emplace_back(landmarks[i].coordinates);
188+
}
189+
190+
// Estimate the camera (pose) from the 2D - 3D point correspondences
191+
fitting::ScaledOrthoProjectionParameters pose = fitting::estimate_orthographic_projection_linear(image_points, model_points, true, image.rows);
192+
fitting::RenderingParameters rendering_params(pose, image.cols, image.rows);
193+
194+
// The 3D head pose can be recovered as follows:
195+
float yaw_angle = glm::degrees(glm::yaw(rendering_params.get_rotation()));
196+
// and similarly for pitch and roll.
197+
198+
// Estimate the shape coefficients by fitting the shape to the landmarks:
199+
Mat affine_from_ortho = fitting::get_3x4_affine_camera_matrix(rendering_params, image.cols, image.rows);
200+
vector<float> fitted_coeffs = fitting::fit_shape_to_landmarks_linear(morphable_model, affine_from_ortho, image_points, vertex_indices);
201+
202+
// Obtain the full mesh with the estimated coefficients:
203+
render::Mesh mesh = morphable_model.draw_sample(fitted_coeffs, vector<float>());
204+
205+
// Extract the texture from the image using given mesh and camera parameters:
206+
Mat isomap = render::extract_texture(mesh, affine_from_ortho, image);
207+
208+
// Save the mesh as textured obj:
209+
outputfile += fs::path(".obj");
210+
render::write_textured_obj(mesh, outputfile.string());
211+
212+
// And save the isomap:
213+
outputfile.replace_extension(".isomap.png");
214+
cv::imwrite(outputfile.string(), isomap);
215+
216+
cout << "Finished fitting and wrote result mesh and isomap to files with basename " << outputfile.stem().stem() << "." << endl;
217+
218+
return EXIT_SUCCESS;
219+
}

0 commit comments

Comments
 (0)