* 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>
namespaceeos{
namespacefitting{
/**
* 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).
* 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] morphable_model The Morphable Model whose shape (coefficients) are estimated.
* @param[in] affine_camera_matrix 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] vertex_ids 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] num_coefficients_to_fit How many shape-coefficients to fit (all others will stay 0). Not tested thoroughly.
* @param[in] detector_standard_deviation The standard deviation of the 2D landmarks given (e.g. of the detector used).
* @param[in] model_standard_deviation The standard deviation of the 3D vertex points in the 3D model.
* @return The estimated shape-coefficients (alphas).
MatbasisRows=morphableModel.getShapeModel().getNormalisedPcaBasis(vertexIds[i]);// In the paper, the not-normalised basis might be used? I'm not sure, check it. It's even a mess in the paper. PH 26.5.2014: I think the normalised basis is fine/better.
rowIndex+=4;// replace 3 rows and skip the 4th one, it has all zeros
Matbasis_rows=morphable_model.getShapeModel().getNormalisedPcaBasis(vertex_ids[i]);// In the paper, the not-normalised basis might be used? I'm not sure, check it. It's even a mess in the paper. PH 26.5.2014: I think the normalised basis is fine/better.
// The variances: Add the 2D and 3D standard deviations.
// If the user doesn't provide them, we choose the following:
// 2D (detector) variance: Assuming the detector has a standard deviation of 3 pixels, and the face size (IED) is around 80px. That's 3.75% of the IED. Assume that an image is on average 512x512px so 80/512 = 0.16 is the size the IED occupies inside an image.
// Now we're in clip-coords ([-1, 1]) and take 0.16 of the range [-1, 1], 0.16/2 = 0.08, and then the standard deviation of the detector is 3.75% of 0.08, i.e. 0.0375*0.08 = 0.003.
// 3D (model) variance: 0.0f. It only makes sense to set it to something when we have a different variance for different vertices.
Eigen::FullPivLU<Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> luOfAtOmegaAReg(AtOmegaAReg_Eigen); // Calculate the full-pivoting LU decomposition of the regularized AtA. Note: We could also try FullPivHouseholderQR if our system is non-minimal (i.e. there are more constraints than unknowns).
// Invert (and perform some sanity checks) using Eigen:
Eigen::FullPivLU<RowMajorMatrixXf>luOfAtOmegaAReg(AtOmegaAReg_Eigen);// Calculate the full-pivoting LU decomposition of the regularized AtA. Note: We could also try FullPivHouseholderQR if our system is non-minimal (i.e. there are more constraints than unknowns).
RowMajorMatrixXfAtARegInv_EigenFullLU=luOfAtOmegaAReg.inverse();// Note: We should use ::solve() instead
MatAtOmegaARegInvFullLU(AtARegInv_EigenFullLU.rows(),AtARegInv_EigenFullLU.cols(),CV_32FC1,AtARegInv_EigenFullLU.data());// create an OpenCV Mat header for the Eigen data
*/
MatAtOmegatb=A.t()*Omega.t()*b;
Matc_s=-AtOmegaARegInv*AtOmegatb;// Note/Todo: We get coefficients ~ N(0, sigma) I think. They are not multiplied with the eigenvalues.