Commit bbc82d0d authored by Patrik Huber's avatar Patrik Huber

Moved shape and camera fitting to headers, renamed to snake_case

parent 77f271f1
...@@ -48,8 +48,6 @@ set(SOURCES ...@@ -48,8 +48,6 @@ set(SOURCES
src/eos/morphablemodel/PcaModel.cpp src/eos/morphablemodel/PcaModel.cpp
src/eos/morphablemodel/MorphableModel.cpp src/eos/morphablemodel/MorphableModel.cpp
src/eos/morphablemodel/io/cvssp.cpp src/eos/morphablemodel/io/cvssp.cpp
src/eos/fitting/AffineCameraEstimation.cpp
src/eos/fitting/LinearShapeFitting.cpp
src/eos/render/Mesh.cpp src/eos/render/Mesh.cpp
src/eos/render/utils.cpp src/eos/render/utils.cpp
) )
......
...@@ -174,7 +174,7 @@ int main(int argc, char *argv[]) ...@@ -174,7 +174,7 @@ int main(int argc, char *argv[])
} }
// Estimate the camera from the 2D - 3D point correspondences // Estimate the camera from the 2D - 3D point correspondences
Mat affineCam = fitting::estimateAffineCamera(imagePoints, modelPoints); Mat affineCam = fitting::estimate_affine_camera(imagePoints, modelPoints);
// Draw the mean-face landmarks projected using the estimated camera: // Draw the mean-face landmarks projected using the estimated camera:
for (auto&& vertex : modelPoints) { for (auto&& vertex : modelPoints) {
...@@ -184,7 +184,7 @@ int main(int argc, char *argv[]) ...@@ -184,7 +184,7 @@ int main(int argc, char *argv[])
// Estimate the shape coefficients by fitting the shape to the landmarks: // Estimate the shape coefficients by fitting the shape to the landmarks:
float lambda = 5.0f; ///< the regularisation parameter float lambda = 5.0f; ///< the regularisation parameter
vector<float> fittedCoeffs = fitting::fitShapeToLandmarksLinear(morphableModel, affineCam, imagePoints, vertexIndices, lambda); vector<float> fittedCoeffs = fitting::fit_shape_to_landmarks_linear(morphableModel, affineCam, imagePoints, vertexIndices, lambda);
// Obtain the full mesh and draw it using the estimated camera: // Obtain the full mesh and draw it using the estimated camera:
render::Mesh mesh = morphableModel.drawSample(fittedCoeffs, vector<float>()); render::Mesh mesh = morphableModel.drawSample(fittedCoeffs, vector<float>());
......
/*
* Eos - A 3D Morphable Model fitting library written in modern C++11/14.
*
* File: include/eos/fitting/AffineCameraEstimation.hpp
*
* Copyright 2014, 2015 Patrik Huber
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef AFFINECAMERAESTIMATION_HPP_
#define AFFINECAMERAESTIMATION_HPP_
#include "eos/render/utils.hpp"
#include "opencv2/core/core.hpp"
#include <vector>
namespace eos {
namespace fitting {
/**
* The Gold Standard Algorithm for estimating an affine
* camera matrix from world to image correspondences.
* See Algorithm 7.2 in Multiple View Geometry, Hartley &
* Zisserman, 2nd Edition, 2003.
*
* Requires >= 4 corresponding points.
*
* @param[in] imagePoints A list of 2D image points.
* @param[in] modelPoints Corresponding points of a 3D model.
* @return A 3x4 affine camera matrix (the third row is [0, 0, 0, 1]).
*/
cv::Mat estimateAffineCamera(std::vector<cv::Vec2f> imagePoints, std::vector<cv::Vec4f> modelPoints);
/**
* Projects a point from world coordinates to screen coordinates.
* First, an estimated affine camera matrix is used to transform
* the point to clip space. Second, the point is transformed to
* screen coordinates using the window transform. The window transform
* also flips the y-axis (the image origin is top-left, while in
* clip space top is +1 and bottom is -1).
*
* Note: Assumes the affine camera matrix only projects from world
* to clip space, because a subsequent window transform is applied.
*
* @param[in] vertex A vertex in 3D space. vertex[3] = 1.0f.
* @param[in] affine_camera_matrix A 3x4 affine camera matrix.
* @param[in] screen_width Width of the screen or window used for projection.
* @param[in] screen_height Height of the screen or window used for projection.
* @return A vector with x and y coordinates transformed to screen coordinates.
*/
inline cv::Vec2f project_affine(cv::Vec4f vertex, cv::Mat affine_camera_matrix, int screen_width, int screen_height)
{
// Transform to clip space:
cv::Mat clip_coords = affine_camera_matrix * cv::Mat(vertex);
// Take the x and y coordinates in clip space and apply the window transform:
cv::Vec2f screen_coords = render::clip_to_screen_space(cv::Vec2f(clip_coords.rowRange(0, 2)), screen_width, screen_height);
return screen_coords;
};
} /* namespace fitting */
} /* namespace eos */
#endif /* AFFINECAMERAESTIMATION_HPP_ */
/*
* Eos - A 3D Morphable Model fitting library written in modern C++11/14.
*
* File: include/eos/fitting/LinearShapeFitting.hpp
*
* Copyright 2014, 2015 Patrik Huber
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef LINEARSHAPEFITTING_HPP_
#define LINEARSHAPEFITTING_HPP_
#include "eos/morphablemodel/MorphableModel.hpp"
#include "opencv2/core/core.hpp"
#include "boost/optional.hpp"
#include <vector>
namespace eos {
namespace fitting {
/**
* Fits the shape of a Morphable Model to given 2D landmarks (i.e. estimates the maximum likelihood solution of the shape coefficients) as proposed in [1].
* It's a linear, closed-form solution fitting of the shape, with regularisation (prior towards the mean).
*
* [1] O. Aldrian & W. Smith, Inverse Rendering of Faces with a 3D Morphable Model, PAMI 2013.
*
* Note: Using less than the maximum number of coefficients to fit is not thoroughly tested yet and may contain an error.
* Note: Returns coefficients following standard normal distribution (i.e. all have similar magnitude). Why? Because we fit using the normalised basis?
* Note: The standard deviations given should be a vector, i.e. different for each landmark. This is not implemented yet.
*
* @param[in] morphableModel The Morphable Model whose shape (coefficients) are estimated.
* @param[in] affineCameraMatrix A 3x4 affine camera matrix from world to clip-space (should probably be of type CV_32FC1 as all our calculations are done with float).
* @param[in] landmarks 2D landmarks from an image, given in clip-coordinates.
* @param[in] vertexIds The vertex ids in the model that correspond to the 2D points.
* @param[in] lambda The regularisation parameter (weight of the prior towards the mean).
* @param[in] numCoefficientsToFit How many shape-coefficients to fit (all others will stay 0). Not tested thoroughly.
* @param[in] detectorStandardDeviation The standard deviation of the 2D landmarks given (e.g. of the detector used).
* @param[in] modelStandardDeviation The standard deviation of the 3D vertex points in the 3D model.
* @return The estimated shape-coefficients (alphas).
*/
std::vector<float> fitShapeToLandmarksLinear(morphablemodel::MorphableModel morphableModel, cv::Mat affineCameraMatrix, std::vector<cv::Vec2f> landmarks, std::vector<int> vertexIds, float lambda=20.0f, boost::optional<int> numCoefficientsToFit=boost::optional<int>(), boost::optional<float> detectorStandardDeviation=boost::optional<float>(), boost::optional<float> modelStandardDeviation=boost::optional<float>());
} /* namespace fitting */
} /* namespace eos */
#endif /* LINEARSHAPEFITTING_HPP_ */
/* /*
* Eos - A 3D Morphable Model fitting library written in modern C++11/14. * Eos - A 3D Morphable Model fitting library written in modern C++11/14.
* *
* File: src/eos/fitting/AffineCameraEstimation.cpp * File: include/eos/fitting/AffineCameraEstimation.hpp
* *
* Copyright 2014, 2015 Patrik Huber * Copyright 2014, 2015 Patrik Huber
* *
...@@ -17,49 +17,55 @@ ...@@ -17,49 +17,55 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include "eos/fitting/AffineCameraEstimation.hpp" #pragma once
#ifndef AFFINECAMERAESTIMATION_HPP_
#define AFFINECAMERAESTIMATION_HPP_
#include "eos/render/utils.hpp" #include "eos/render/utils.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/core/core_c.h" // for CV_REDUCE_AVG #include "opencv2/core/core_c.h" // for CV_REDUCE_AVG
#include <iostream> #include <vector>
#include <exception>
#include <cassert> #include <cassert>
using cv::Mat;
using cv::Vec2f;
using cv::Vec3f;
using cv::Vec4f;
using std::vector;
namespace eos { namespace eos {
namespace fitting { namespace fitting {
Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints) /**
* The Gold Standard Algorithm for estimating an affine
* camera matrix from world to image correspondences.
* See Algorithm 7.2 in Multiple View Geometry, Hartley &
* Zisserman, 2nd Edition, 2003.
*
* Requires >= 4 corresponding points.
*
* @param[in] image_points A list of 2D image points.
* @param[in] model_points Corresponding points of a 3D model.
* @return A 3x4 affine camera matrix (the third row is [0, 0, 0, 1]).
*/
cv::Mat estimate_affine_camera(std::vector<cv::Vec2f> image_points, std::vector<cv::Vec4f> model_points)
{ {
assert(imagePoints.size() == modelPoints.size()); using cv::Mat;
assert(image_points.size() == model_points.size());
assert(image_points.size() >= 4); // Number of correspondence points given needs to be equal to or larger than 4
const auto numCorrespondences = imagePoints.size(); const auto numCorrespondences = image_points.size();
if (numCorrespondences < 4) {
std::string errorMsg("AffineCameraEstimation: Number of points given needs to be equal to or larger than 4.");
std::cout << errorMsg << std::endl;
throw std::runtime_error(errorMsg);
}
Mat matImagePoints; // will be numCorrespondences x 2, CV_32FC1 Mat matImagePoints; // will be numCorrespondences x 2, CV_32FC1
Mat matModelPoints; // will be numCorrespondences x 3, CV_32FC1 Mat matModelPoints; // will be numCorrespondences x 3, CV_32FC1
for (int i = 0; i < imagePoints.size(); ++i) { for (int i = 0; i < image_points.size(); ++i) {
Mat imgPoint(1, 2, CV_32FC1); Mat imgPoint(1, 2, CV_32FC1);
imgPoint.at<float>(0, 0) = imagePoints[i][0]; imgPoint.at<float>(0, 0) = image_points[i][0];
imgPoint.at<float>(0, 1) = imagePoints[i][1]; imgPoint.at<float>(0, 1) = image_points[i][1];
matImagePoints.push_back(imgPoint); matImagePoints.push_back(imgPoint);
Mat mdlPoint(1, 3, CV_32FC1); Mat mdlPoint(1, 3, CV_32FC1);
mdlPoint.at<float>(0, 0) = modelPoints[i][0]; mdlPoint.at<float>(0, 0) = model_points[i][0];
mdlPoint.at<float>(0, 1) = modelPoints[i][1]; mdlPoint.at<float>(0, 1) = model_points[i][1];
mdlPoint.at<float>(0, 2) = modelPoints[i][2]; mdlPoint.at<float>(0, 2) = model_points[i][2];
matModelPoints.push_back(mdlPoint); matModelPoints.push_back(mdlPoint);
} }
...@@ -76,20 +82,20 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints) ...@@ -76,20 +82,20 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints)
} }
averageNorm /= matImagePoints.rows; averageNorm /= matImagePoints.rows;
// 2) multiply every vectors coordinate by sqrt(2)/avgnorm // 2) multiply every vectors coordinate by sqrt(2)/avgnorm
float scaleFactor = std::sqrt(2)/averageNorm; float scaleFactor = std::sqrt(2) / averageNorm;
matImagePoints *= scaleFactor; // add unit homogeneous component here matImagePoints *= scaleFactor; // add unit homogeneous component here
// The points in matImagePoints now have a RMS distance from the origin of sqrt(2). // The points in matImagePoints now have a RMS distance from the origin of sqrt(2).
// The normalisation matrix so that the 2D points are mean-free and their norm is as described above. // The normalisation matrix so that the 2D points are mean-free and their norm is as described above.
Mat T = Mat::zeros(3, 3, CV_32FC1); Mat T = Mat::zeros(3, 3, CV_32FC1);
T.at<float>(0, 0) = scaleFactor; // s_x T.at<float>(0, 0) = scaleFactor; // s_x
T.at<float>(1, 1) = scaleFactor; // s_y T.at<float>(1, 1) = scaleFactor; // s_y
T.at<float>(0, 2) = -imagePointsMean.at<float>(0, 0) * scaleFactor; // t_x T.at<float>(0, 2) = -imagePointsMean.at<float>(0, 0) * scaleFactor; // t_x
T.at<float>(1, 2) = -imagePointsMean.at<float>(0, 1) * scaleFactor; // t_y T.at<float>(1, 2) = -imagePointsMean.at<float>(0, 1) * scaleFactor; // t_y
T.at<float>(2, 2) = 1; T.at<float>(2, 2) = 1;
// center the model points to the origin: // center the model points to the origin:
Mat tmpOrigMdlPoints = matModelPoints.clone(); // Temp for testing: Save the original coordinates. Mat tmpOrigMdlPoints = matModelPoints.clone(); // Temp for testing: Save the original coordinates.
// translate the centroid of the model points to the origin: // translate the centroid of the model points to the origin:
Mat modelPointsMean; // use non-homogeneous coords for the next few steps? (less submatrices etc overhead) Mat modelPointsMean; // use non-homogeneous coords for the next few steps? (less submatrices etc overhead)
cv::reduce(matModelPoints, modelPointsMean, 0, CV_REDUCE_AVG); cv::reduce(matModelPoints, modelPointsMean, 0, CV_REDUCE_AVG);
modelPointsMean = cv::repeat(modelPointsMean, matModelPoints.rows, 1); modelPointsMean = cv::repeat(modelPointsMean, matModelPoints.rows, 1);
...@@ -104,8 +110,8 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints) ...@@ -104,8 +110,8 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints)
// 2) multiply every vectors coordinate by sqrt(3)/avgnorm // 2) multiply every vectors coordinate by sqrt(3)/avgnorm
scaleFactor = std::sqrt(3) / averageNorm; scaleFactor = std::sqrt(3) / averageNorm;
matModelPoints *= scaleFactor; // add unit homogeneous component here matModelPoints *= scaleFactor; // add unit homogeneous component here
// The points in matModelPoints now have a RMS distance from the origin of sqrt(3). // The points in matModelPoints now have a RMS distance from the origin of sqrt(3).
// The normalisation matrix so that the 3D points are mean-free and their norm is as described above. // The normalisation matrix so that the 3D points are mean-free and their norm is as described above.
Mat U = Mat::zeros(4, 4, CV_32FC1); Mat U = Mat::zeros(4, 4, CV_32FC1);
U.at<float>(0, 0) = scaleFactor; // s_x U.at<float>(0, 0) = scaleFactor; // s_x
U.at<float>(1, 1) = scaleFactor; // s_y U.at<float>(1, 1) = scaleFactor; // s_y
...@@ -123,16 +129,16 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints) ...@@ -123,16 +129,16 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints)
//Mat p_8(); // p_8 is 8 x 1. We are solving for it. //Mat p_8(); // p_8 is 8 x 1. We are solving for it.
Mat b(numCorrespondences * 2, 1, CV_32FC1); Mat b(numCorrespondences * 2, 1, CV_32FC1);
for (int i = 0; i < numCorrespondences; ++i) { for (int i = 0; i < numCorrespondences; ++i) {
A_8.at<float>(2*i, 0) = matModelPoints.at<float>(i, 0); // could maybe made faster by assigning the whole row/col-range if possible? A_8.at<float>(2 * i, 0) = matModelPoints.at<float>(i, 0); // could maybe made faster by assigning the whole row/col-range if possible?
A_8.at<float>(2*i, 1) = matModelPoints.at<float>(i, 1); A_8.at<float>(2 * i, 1) = matModelPoints.at<float>(i, 1);
A_8.at<float>(2*i, 2) = matModelPoints.at<float>(i, 2); A_8.at<float>(2 * i, 2) = matModelPoints.at<float>(i, 2);
A_8.at<float>(2*i, 3) = 1; A_8.at<float>(2 * i, 3) = 1;
A_8.at<float>((2*i)+1, 4) = matModelPoints.at<float>(i, 0); A_8.at<float>((2 * i) + 1, 4) = matModelPoints.at<float>(i, 0);
A_8.at<float>((2*i)+1, 5) = matModelPoints.at<float>(i, 1); A_8.at<float>((2 * i) + 1, 5) = matModelPoints.at<float>(i, 1);
A_8.at<float>((2*i)+1, 6) = matModelPoints.at<float>(i, 2); A_8.at<float>((2 * i) + 1, 6) = matModelPoints.at<float>(i, 2);
A_8.at<float>((2*i)+1, 7) = 1; A_8.at<float>((2 * i) + 1, 7) = 1;
b.at<float>(2*i, 0) = matImagePoints.at<float>(i, 0); b.at<float>(2 * i, 0) = matImagePoints.at<float>(i, 0);
b.at<float>((2*i)+1, 0) = matImagePoints.at<float>(i, 1); b.at<float>((2 * i) + 1, 0) = matImagePoints.at<float>(i, 1);
} }
Mat p_8 = A_8.inv(cv::DECOMP_SVD) * b; Mat p_8 = A_8.inv(cv::DECOMP_SVD) * b;
Mat C_tilde = Mat::zeros(3, 4, CV_32FC1); Mat C_tilde = Mat::zeros(3, 4, CV_32FC1);
...@@ -142,7 +148,35 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints) ...@@ -142,7 +148,35 @@ Mat estimateAffineCamera(vector<Vec2f> imagePoints, vector<Vec4f> modelPoints)
Mat P_Affine = T.inv() * C_tilde * U; Mat P_Affine = T.inv() * C_tilde * U;
return P_Affine; return P_Affine;
} };
/**
* Projects a point from world coordinates to screen coordinates.
* First, an estimated affine camera matrix is used to transform
* the point to clip space. Second, the point is transformed to
* screen coordinates using the window transform. The window transform
* also flips the y-axis (the image origin is top-left, while in
* clip space top is +1 and bottom is -1).
*
* Note: Assumes the affine camera matrix only projects from world
* to clip space, because a subsequent window transform is applied.
*
* @param[in] vertex A vertex in 3D space. vertex[3] = 1.0f.
* @param[in] affine_camera_matrix A 3x4 affine camera matrix.
* @param[in] screen_width Width of the screen or window used for projection.
* @param[in] screen_height Height of the screen or window used for projection.
* @return A vector with x and y coordinates transformed to screen coordinates.
*/
inline cv::Vec2f project_affine(cv::Vec4f vertex, cv::Mat affine_camera_matrix, int screen_width, int screen_height)
{
// Transform to clip space:
cv::Mat clip_coords = affine_camera_matrix * cv::Mat(vertex);
// Take the x and y coordinates in clip space and apply the window transform:
cv::Vec2f screen_coords = render::clip_to_screen_space(cv::Vec2f(clip_coords.rowRange(0, 2)), screen_width, screen_height);
return screen_coords;
};
} /* namespace fitting */ } /* namespace fitting */
} /* namespace eos */ } /* namespace eos */
#endif /* AFFINECAMERAESTIMATION_HPP_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment