Commit 047d7252 authored by Patrik Huber's avatar Patrik Huber

Moved PcaModel and Mesh stuff to headers

parent 5521b88f
......@@ -45,10 +45,8 @@ endif()
# Source and header files:
set(SOURCES
src/eos/core/LandmarkMapper.cpp
src/eos/morphablemodel/PcaModel.cpp
src/eos/morphablemodel/MorphableModel.cpp
src/eos/morphablemodel/io/cvssp.cpp
src/eos/render/Mesh.cpp
src/eos/render/utils.cpp
)
......
......@@ -24,12 +24,6 @@
#include "opencv2/core/core.hpp"
#ifdef WIN32
#define BOOST_ALL_DYN_LINK // Link against the dynamic boost lib. Seems to be necessary because we use /MD, i.e. link to the dynamic CRT.
#define BOOST_ALL_NO_LIB // Don't use the automatic library linking by boost with VS2010 (#pragma ...). Instead, we specify everything in cmake.
#endif
#include "boost/filesystem/path.hpp"
#include <string>
#include <vector>
#include <array>
......@@ -38,6 +32,12 @@
namespace eos {
namespace morphablemodel {
/**
* Forward declarations
*/
cv::Mat normalisePcaBasis(cv::Mat unnormalisedBasis, cv::Mat eigenvalues);
cv::Mat unnormalisePcaBasis(cv::Mat normalisedBasis, cv::Mat eigenvalues);
/**
* This class represents a PCA-model that consists of:
* - a mean vector (y x z)
......@@ -64,14 +64,23 @@ public:
* @param[in] eigenvalues The eigenvalues used to build the PCA model.
* @param[in] triangleList An index list of how to assemble the mesh.
*/
PcaModel(cv::Mat mean, cv::Mat pcaBasis, cv::Mat eigenvalues, std::vector<std::array<int, 3>> triangleList);
PcaModel(cv::Mat mean, cv::Mat pcaBasis, cv::Mat eigenvalues, std::vector<std::array<int, 3>> triangleList)
{
const auto seed = std::random_device()();
engine.seed(seed);
unnormalisedPcaBasis = unnormalisePcaBasis(normalisedPcaBasis, eigenvalues);
};
/**
* Returns the number of principal components in the model.
*
* @return The number of principal components in the model.
*/
int getNumberOfPrincipalComponents() const;
int getNumberOfPrincipalComponents() const
{
// Note: we could assert(normalisedPcaBasis.cols==unnormalisedPcaBasis.cols)
return normalisedPcaBasis.cols;
};
/**
* Returns the dimension of the data, i.e. the number of shape dimensions.
......@@ -81,21 +90,31 @@ public:
*
* @return The dimension of the data.
*/
int getDataDimension() const;
int getDataDimension() const
{
// Note: we could assert(normalisedPcaBasis.rows==unnormalisedPcaBasis.rows)
return normalisedPcaBasis.rows;
};
/**
* Returns a list of triangles on how to assemble the vertices into a mesh.
*
* @return The list of triangles to build a mesh.
*/
std::vector<std::array<int, 3>> getTriangleList() const;
std::vector<std::array<int, 3>> getTriangleList() const
{
return triangleList;
};
/**
* Returns the mean of the model.
*
* @return The mean of the model.
*/
cv::Mat getMean() const;
cv::Mat getMean() const
{
return mean;
};
/**
* Return the value of the mean at a given vertex id.
......@@ -103,7 +122,14 @@ public:
* @param[in] vertexIndex A vertex id.
* @return A homogeneous vector containing the values at the given vertex id.
*/
cv::Vec4f getMeanAtPoint(int vertexIndex) const;
cv::Vec4f getMeanAtPoint(int vertexIndex) const
{
vertexIndex *= 3;
if (vertexIndex >= mean.rows) {
throw std::out_of_range("The given vertex id is larger than the dimension of the mean.");
}
return cv::Vec4f(mean.at<float>(vertexIndex), mean.at<float>(vertexIndex + 1), mean.at<float>(vertexIndex + 2), 1.0f);
};
/**
* Draws a random sample from the model, where the coefficients are drawn
......@@ -112,7 +138,18 @@ public:
* @param[in] sigma The standard deviation.
* @return A random sample from the model.
*/
cv::Mat drawSample(float sigma = 1.0f);
cv::Mat drawSample(float sigma = 1.0f)
{
std::normal_distribution<float> distribution(0.0f, sigma); // this constructor takes the stddev
std::vector<float> alphas(getNumberOfPrincipalComponents());
for (auto&& a : alphas) {
a = distribution(engine);
}
return drawSample(alphas);
};
/**
* Returns a sample from the model with the given PCA coefficients.
......@@ -122,7 +159,18 @@ public:
* @param[in] coefficients The PCA coefficients used to generate the sample.
* @return A model instance with given coefficients.
*/
cv::Mat drawSample(std::vector<float> coefficients);
cv::Mat drawSample(std::vector<float> coefficients)
{
// Fill the rest with zeros if not all coefficients are given:
if (coefficients.size() < getNumberOfPrincipalComponents()) {
coefficients.resize(getNumberOfPrincipalComponents());
}
cv::Mat alphas(coefficients);
cv::Mat modelSample = mean + normalisedPcaBasis * alphas;
return modelSample;
};
/**
* Returns the PCA basis matrix, i.e. the eigenvectors.
......@@ -135,7 +183,10 @@ public:
*
* @return Returns the normalised PCA basis matrix.
*/
cv::Mat getNormalisedPcaBasis() const;
cv::Mat getNormalisedPcaBasis() const
{
return normalisedPcaBasis.clone();
};
/**
* Returns the PCA basis for a particular vertex.
......@@ -145,7 +196,11 @@ public:
* @param[in] vertexId A vertex index. Make sure it is valid.
* @return A Mat that points to the rows in the original basis.
*/
cv::Mat getNormalisedPcaBasis(int vertexId) const;
cv::Mat getNormalisedPcaBasis(int vertexId) const
{
vertexId *= 3; // the basis is stored in the format [x y z x y z ...]
return normalisedPcaBasis.rowRange(vertexId, vertexId + 3);
};
/**
* Returns the PCA basis matrix, i.e. the eigenvectors.
......@@ -157,7 +212,10 @@ public:
*
* @return Returns the unnormalised PCA basis matrix.
*/
cv::Mat getUnnormalisedPcaBasis() const;
cv::Mat getUnnormalisedPcaBasis() const
{
return unnormalisedPcaBasis.clone();
};
/**
* Returns the PCA basis for a particular vertex.
......@@ -166,7 +224,11 @@ public:
* @param[in] vertexId A vertex index. Make sure it is valid.
* @return A Mat that points to the rows in the original basis.
*/
cv::Mat getUnnormalisedPcaBasis(int vertexId) const;
cv::Mat getUnnormalisedPcaBasis(int vertexId) const
{
vertexId *= 3; // the basis is stored in the format [x y z x y z ...]
return unnormalisedPcaBasis.rowRange(vertexId, vertexId + 3);
};
/**
* Returns an eigenvalue.
......@@ -174,7 +236,10 @@ public:
* @param[in] index The index of the eigenvalue to return.
* @return The eigenvalue.
*/
float getEigenvalue(int index) const;
float getEigenvalue(int index) const
{
return eigenvalues.at<float>(index);
};
private:
std::mt19937 engine; ///< Random number engine used to draw random coefficients.
......@@ -187,6 +252,7 @@ private:
std::vector<std::array<int, 3>> triangleList; ///< List of triangles that make up the mesh of the model.
};
/**
* Takes an unnormalised PCA basis matrix (a matrix consisting
* of the eigenvectors and normalises it, i.e. multiplies each
......@@ -197,11 +263,26 @@ private:
* @param[in] eigenvalues A row or column vector of eigenvalues.
* @return The normalised PCA basis matrix.
*/
cv::Mat normalisePcaBasis(cv::Mat unnormalisedBasis, cv::Mat eigenvalues);
inline cv::Mat normalisePcaBasis(cv::Mat unnormalisedBasis, cv::Mat eigenvalues)
{
using cv::Mat;
Mat normalisedPcaBasis(unnormalisedBasis.size(), unnormalisedBasis.type()); // empty matrix with the same dimensions
Mat sqrtOfEigenvalues = eigenvalues.clone();
for (int i = 0; i < eigenvalues.rows; ++i) {
sqrtOfEigenvalues.at<float>(i) = std::sqrt(eigenvalues.at<float>(i));
}
// Normalise the basis: We multiply each eigenvector (i.e. each column) with the square root of its corresponding eigenvalue
for (int basis = 0; basis < unnormalisedBasis.cols; ++basis) {
Mat normalisedEigenvector = unnormalisedBasis.col(basis).mul(sqrtOfEigenvalues.at<float>(basis));
normalisedEigenvector.copyTo(normalisedPcaBasis.col(basis));
}
return normalisedPcaBasis;
};
/**
* Takes a normalised PCA basis matrix (a matrix consisting
* of the eigenvectors and denormalizes it, i.e. multiplies each
* of the eigenvectors and denormalises it, i.e. multiplies each
* eigenvector by 1 over the square root of its corresponding
* eigenvalue.
*
......@@ -209,7 +290,22 @@ cv::Mat normalisePcaBasis(cv::Mat unnormalisedBasis, cv::Mat eigenvalues);
* @param[in] eigenvalues A row or column vector of eigenvalues.
* @return The unnormalised PCA basis matrix.
*/
cv::Mat unnormalisePcaBasis(cv::Mat normalisedBasis, cv::Mat eigenvalues);
inline cv::Mat unnormalisePcaBasis(cv::Mat normalisedBasis, cv::Mat eigenvalues)
{
using cv::Mat;
Mat unnormalisedBasis(normalisedBasis.size(), normalisedBasis.type()); // empty matrix with the same dimensions
Mat oneOverSqrtOfEigenvalues = eigenvalues.clone();
for (int i = 0; i < eigenvalues.rows; ++i) {
oneOverSqrtOfEigenvalues.at<float>(i) = 1.0f / std::sqrt(eigenvalues.at<float>(i));
}
// De-normalise the basis: We multiply each eigenvector (i.e. each column) with 1 over the square root of its corresponding eigenvalue
for (int basis = 0; basis < normalisedBasis.cols; ++basis) {
Mat unnormalisedEigenvector = normalisedBasis.col(basis).mul(oneOverSqrtOfEigenvalues.at<float>(basis));
unnormalisedEigenvector.copyTo(unnormalisedBasis.col(basis));
}
return unnormalisedBasis;
};
} /* namespace morphablemodel */
} /* namespace eos */
......
......@@ -24,9 +24,17 @@
#include "opencv2/core/core.hpp"
#ifdef WIN32
#define BOOST_ALL_DYN_LINK // Link against the dynamic boost lib. Seems to be necessary because we use /MD, i.e. link to the dynamic CRT.
#define BOOST_ALL_NO_LIB // Don't use the automatic library linking by boost with VS2010 (#pragma ...). Instead, we specify everything in cmake.
#endif
#include "boost/filesystem/path.hpp"
#include <vector>
#include <array>
#include <string>
#include <cassert>
#include <fstream>
namespace eos {
namespace render {
......@@ -36,9 +44,8 @@ namespace eos {
* information. Additionally it stores the indices that specify which vertices
* to use to generate the triangle mesh out of the vertices.
*/
class Mesh
struct Mesh
{
public:
std::vector<cv::Vec4f> vertices; ///< 3D vertex positions.
std::vector<cv::Vec3f> colors; ///< Color information for each vertex. Expected to be in RGB order.
std::vector<cv::Vec2f> texcoords; ///< Texture coordinates for each vertex.
......@@ -53,7 +60,23 @@ public:
* @param[in] mesh The mesh to save as obj.
* @param[in] filename Output filename.
*/
void write_obj(Mesh mesh, std::string filename);
inline void write_obj(Mesh mesh, std::string filename)
{
assert(mesh.vertices.size() == mesh.colors.size());
std::ofstream obj_file(filename);
for (std::size_t i = 0; i < mesh.vertices.size(); ++i) {
obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " << mesh.vertices[i][2] << " " << mesh.colors[i][0] << " " << mesh.colors[i][1] << " " << mesh.colors[i][2] << " " << std::endl;
}
for (auto&& v : mesh.tvi) {
// Add one because obj starts counting triangle indices at 1
obj_file << "f " << v[0] + 1 << " " << v[1] + 1 << " " << v[2] + 1 << std::endl;
}
return;
}
/**
* Writes an obj file of the given Mesh that can be read by e.g. Meshlab.
......@@ -62,7 +85,43 @@ void write_obj(Mesh mesh, std::string filename);
* @param[in] mesh The mesh to save as obj.
* @param[in] filename Output filename.
*/
void write_textured_obj(Mesh mesh, std::string filename);
inline void write_textured_obj(Mesh mesh, std::string filename)
{
assert(mesh.vertices.size() == mesh.colors.size() && !mesh.texcoords.empty());
std::ofstream obj_file(filename);
boost::filesystem::path mtl_filename(filename);
mtl_filename.replace_extension(".mtl");
obj_file << "mtllib " << mtl_filename.string() << std::endl; // first line of the obj file
for (std::size_t i = 0; i < mesh.vertices.size(); ++i) { // same as in write_obj()
obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " << mesh.vertices[i][2] << " " << mesh.colors[i][0] << " " << mesh.colors[i][1] << " " << mesh.colors[i][2] << " " << std::endl;
}
for (std::size_t i = 0; i < mesh.texcoords.size(); ++i) {
obj_file << "vt " << mesh.texcoords[i][0] << " " << 1.0f - mesh.texcoords[i][1] << std::endl;
// We invert y because Meshlab's uv origin (0, 0) is on the bottom-left
}
obj_file << "usemtl FaceTexture" << std::endl; // the name of our texture (material) will be 'FaceTexture'
for (auto&& v : mesh.tvi) {
// This assumes mesh.texcoords.size() == mesh.vertices.size(). The texture indices could theoretically be different (for example in the cube-mapped 3D scan)
// Add one because obj starts counting triangle indices at 1
obj_file << "f " << v[0] + 1 << "/" << v[0] + 1 << " " << v[1] + 1 << "/" << v[1] + 1 << " " << v[2] + 1 << "/" << v[2] + 1 << std::endl;
}
std::ofstream mtl_file(mtl_filename.string());
boost::filesystem::path texture_filename(filename);
texture_filename.replace_extension(".isomap.png");
mtl_file << "newmtl FaceTexture" << std::endl;
mtl_file << "map_Kd " << texture_filename.string() << std::endl;
return;
}
} /* namespace render */
} /* namespace eos */
......
/*
* Eos - A 3D Morphable Model fitting library written in modern C++11/14.
*
* File: include/eos/morphablemodel/PcaModel.cpp
*
* 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.
*/
#include "eos/morphablemodel/PcaModel.hpp"
#include "boost/lexical_cast.hpp"
#include "boost/algorithm/string.hpp"
#include <fstream>
using cv::Mat;
using cv::Vec4f;
using boost::lexical_cast;
using boost::filesystem::path;
using std::string;
using std::vector;
using std::array;
namespace eos {
namespace morphablemodel {
PcaModel::PcaModel(Mat mean, Mat pcaBasis, Mat eigenvalues, vector<array<int, 3>> triangleList) : mean(mean), normalisedPcaBasis(pcaBasis), eigenvalues(eigenvalues), triangleList(triangleList)
{
const auto seed = std::random_device()();
engine.seed(seed);
unnormalisedPcaBasis = unnormalisePcaBasis(normalisedPcaBasis, eigenvalues);
}
int PcaModel::getNumberOfPrincipalComponents() const
{
// Note: we could assert(normalisedPcaBasis.cols==unnormalisedPcaBasis.cols)
return normalisedPcaBasis.cols;
}
int PcaModel::getDataDimension() const
{
// Note: we could assert(normalisedPcaBasis.rows==unnormalisedPcaBasis.rows)
return normalisedPcaBasis.rows;
}
std::vector<std::array<int, 3>> PcaModel::getTriangleList() const
{
return triangleList;
}
Mat PcaModel::getMean() const
{
return mean;
}
Vec4f PcaModel::getMeanAtPoint(int vertexIndex) const
{
vertexIndex *= 3;
if (vertexIndex >= mean.rows) {
throw std::out_of_range("The given vertex id is larger than the dimension of the mean.");
}
return Vec4f(mean.at<float>(vertexIndex), mean.at<float>(vertexIndex+1), mean.at<float>(vertexIndex+2), 1.0f);
}
Mat PcaModel::drawSample(float sigma/*=1.0f*/)
{
std::normal_distribution<float> distribution(0.0f, sigma); // this constructor takes the stddev
vector<float> alphas(getNumberOfPrincipalComponents());
for (auto&& a : alphas) {
a = distribution(engine);
}
return drawSample(alphas);
}
Mat PcaModel::drawSample(vector<float> coefficients)
{
// Fill the rest with zeros if not all coefficients are given:
if (coefficients.size() < getNumberOfPrincipalComponents()) {
coefficients.resize(getNumberOfPrincipalComponents());
}
Mat alphas(coefficients);
Mat modelSample = mean + normalisedPcaBasis * alphas;
return modelSample;
}
cv::Mat PcaModel::getNormalisedPcaBasis() const
{
return normalisedPcaBasis.clone();
}
cv::Mat PcaModel::getNormalisedPcaBasis(int vertexId) const
{
vertexId *= 3; // the basis is stored in the format [x y z x y z ...]
return normalisedPcaBasis.rowRange(vertexId, vertexId + 3);
}
cv::Mat PcaModel::getUnnormalisedPcaBasis() const
{
return unnormalisedPcaBasis.clone();
}
cv::Mat PcaModel::getUnnormalisedPcaBasis(int vertexId) const
{
vertexId *= 3; // the basis is stored in the format [x y z x y z ...]
return unnormalisedPcaBasis.rowRange(vertexId, vertexId + 3);
}
float PcaModel::getEigenvalue(int index) const
{
return eigenvalues.at<float>(index);
}
cv::Mat normalisePcaBasis(cv::Mat unnormalisedBasis, cv::Mat eigenvalues)
{
Mat normalisedPcaBasis(unnormalisedBasis.size(), unnormalisedBasis.type()); // empty matrix with the same dimensions
Mat sqrtOfEigenvalues = eigenvalues.clone();
for (int i = 0; i < eigenvalues.rows; ++i) {
sqrtOfEigenvalues.at<float>(i) = std::sqrt(eigenvalues.at<float>(i));
}
// Normalise the basis: We multiply each eigenvector (i.e. each column) with the square root of its corresponding eigenvalue
for (int basis = 0; basis < unnormalisedBasis.cols; ++basis) {
Mat normalisedEigenvector = unnormalisedBasis.col(basis).mul(sqrtOfEigenvalues.at<float>(basis));
normalisedEigenvector.copyTo(normalisedPcaBasis.col(basis));
}
return normalisedPcaBasis;
}
cv::Mat unnormalisePcaBasis(cv::Mat normalisedBasis, cv::Mat eigenvalues)
{
Mat unnormalisedBasis(normalisedBasis.size(), normalisedBasis.type()); // empty matrix with the same dimensions
Mat oneOverSqrtOfEigenvalues = eigenvalues.clone();
for (int i = 0; i < eigenvalues.rows; ++i) {
oneOverSqrtOfEigenvalues.at<float>(i) = 1.0f / std::sqrt(eigenvalues.at<float>(i));
}
// De-normalise the basis: We multiply each eigenvector (i.e. each column) with 1 over the square root of its corresponding eigenvalue
for (int basis = 0; basis < normalisedBasis.cols; ++basis) {
Mat unnormalisedEigenvector = normalisedBasis.col(basis).mul(oneOverSqrtOfEigenvalues.at<float>(basis));
unnormalisedEigenvector.copyTo(unnormalisedBasis.col(basis));
}
return unnormalisedBasis;
}
} /* namespace morphablemodel */
} /* namespace eos */
......@@ -17,75 +17,3 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "eos/render/Mesh.hpp"
#ifdef WIN32
#define BOOST_ALL_DYN_LINK // Link against the dynamic boost lib. Seems to be necessary because we use /MD, i.e. link to the dynamic CRT.
#define BOOST_ALL_NO_LIB // Don't use the automatic library linking by boost with VS2010 (#pragma ...). Instead, we specify everything in cmake.
#endif
#include "boost/filesystem/path.hpp"
#include <cassert>
#include <fstream>
namespace eos {
namespace render {
void write_obj(Mesh mesh, std::string filename)
{
assert(mesh.vertices.size() == mesh.colors.size());
std::ofstream obj_file(filename);
for (std::size_t i = 0; i < mesh.vertices.size(); ++i) {
obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " << mesh.vertices[i][2] << " " << mesh.colors[i][0] << " " << mesh.colors[i][1] << " " << mesh.colors[i][2] << " " << std::endl;
}
for (auto&& v : mesh.tvi) {
// Add one because obj starts counting triangle indices at 1
obj_file << "f " << v[0] + 1 << " " << v[1] + 1 << " " << v[2] + 1 << std::endl;
}
return;
}
void write_textured_obj(Mesh mesh, std::string filename)
{
assert(mesh.vertices.size() == mesh.colors.size() && !mesh.texcoords.empty());
std::ofstream obj_file(filename);
boost::filesystem::path mtl_filename(filename);
mtl_filename.replace_extension(".mtl");
obj_file << "mtllib " << mtl_filename.string() << std::endl; // first line of the obj file
for (std::size_t i = 0; i < mesh.vertices.size(); ++i) { // same as in write_obj()
obj_file << "v " << mesh.vertices[i][0] << " " << mesh.vertices[i][1] << " " << mesh.vertices[i][2] << " " << mesh.colors[i][0] << " " << mesh.colors[i][1] << " " << mesh.colors[i][2] << " " << std::endl;
}
for (std::size_t i = 0; i < mesh.texcoords.size(); ++i) {
obj_file << "vt " << mesh.texcoords[i][0] << " " << 1.0f - mesh.texcoords[i][1] << std::endl;
// We invert y because Meshlab's uv origin (0, 0) is on the bottom-left
}
obj_file << "usemtl FaceTexture" << std::endl; // the name of our texture (material) will be 'FaceTexture'
for (auto&& v : mesh.tvi) {
// This assumes mesh.texcoords.size() == mesh.vertices.size(). The texture indices could theoretically be different (for example in the cube-mapped 3D scan)
// Add one because obj starts counting triangle indices at 1
obj_file << "f " << v[0] + 1 << "/" << v[0] + 1 << " " << v[1] + 1 << "/" << v[1] + 1 << " " << v[2] + 1 << "/" << v[2] + 1 << std::endl;
}
std::ofstream mtl_file(mtl_filename.string());
boost::filesystem::path texture_filename(filename);
texture_filename.replace_extension(".isomap.png");
mtl_file << "newmtl FaceTexture" << std::endl;
mtl_file << "map_Kd " << texture_filename.string() << std::endl;
return;
}
} /* namespace render */
} /* namespace eos */
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