eos  0.7.1
cvssp.hpp
1 /*
2  * Eos - A 3D Morphable Model fitting library written in modern C++11/14.
3  *
4  * File: include/eos/morphablemodel/io/cvssp.hpp
5  *
6  * Copyright 2014, 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 #pragma once
21 
22 #ifndef IO_CVSSP_HPP_
23 #define IO_CVSSP_HPP_
24 
25 #include "eos/morphablemodel/MorphableModel.hpp"
26 
27 #include "opencv2/core/core.hpp"
28 
29 #include "boost/filesystem/path.hpp"
30 
31 #include <vector>
32 #include <iostream>
33 
34 namespace eos {
35  namespace morphablemodel {
36 
37 // Forward declaration
38 std::vector<cv::Vec2f> load_isomap(boost::filesystem::path isomap_file);
39 
59 MorphableModel load_scm_model(boost::filesystem::path model_filename, boost::filesystem::path isomap_file = boost::filesystem::path())
60 {
61  using cv::Mat;
62  if (sizeof(unsigned int) != 4) { // note/todo: maybe use uint32 or similar instead? Yep, but still we could encounter endianness-trouble.
63  std::cout << "Warning: We're reading 4 Bytes from the file but sizeof(unsigned int) != 4. Check the code/behaviour." << std::endl;
64  }
65  if (sizeof(double) != 8) {
66  std::cout << "Warning: We're reading 8 Bytes from the file but sizeof(double) != 8. Check the code/behaviour." << std::endl;
67  }
68 
69  std::ifstream modelFile(model_filename.string(), std::ios::binary);
70  if (!modelFile.is_open()) {
71  std::string msg("Unable to open model file: " + model_filename.string());
72  std::cout << msg << std::endl;
73  throw std::runtime_error(msg);
74  }
75 
76  // Reading the shape model
77  // Read (reference?) num triangles and vertices
78  unsigned int numVertices = 0;
79  unsigned int numTriangles = 0;
80  modelFile.read(reinterpret_cast<char*>(&numVertices), 4); // 1 char = 1 byte. uint32=4bytes. float64=8bytes.
81  modelFile.read(reinterpret_cast<char*>(&numTriangles), 4);
82 
83  // Read triangles
84  std::vector<std::array<int, 3>> triangleList;
85 
86  triangleList.resize(numTriangles);
87  unsigned int v0, v1, v2;
88  for (unsigned int i = 0; i < numTriangles; ++i) {
89  v0 = v1 = v2 = 0;
90  modelFile.read(reinterpret_cast<char*>(&v0), 4); // would be nice to pass a &vector and do it in one
91  modelFile.read(reinterpret_cast<char*>(&v1), 4); // go, but didn't work. Maybe a cv::Mat would work?
92  modelFile.read(reinterpret_cast<char*>(&v2), 4);
93  triangleList[i][0] = v0;
94  triangleList[i][1] = v1;
95  triangleList[i][2] = v2;
96  }
97 
98  // Read number of rows and columns of the shape projection matrix (pcaBasis)
99  unsigned int numShapePcaCoeffs = 0;
100  unsigned int numShapeDims = 0; // dimension of the shape vector (3*numVertices)
101  modelFile.read(reinterpret_cast<char*>(&numShapePcaCoeffs), 4);
102  modelFile.read(reinterpret_cast<char*>(&numShapeDims), 4);
103 
104  if (3 * numVertices != numShapeDims) {
105  std::cout << "Warning: Number of shape dimensions is not equal to three times the number of vertices. Something will probably go wrong during the loading." << std::endl;
106  }
107 
108  // Read shape projection matrix
109  Mat unnormalisedPcaBasisShape(numShapeDims, numShapePcaCoeffs, CV_32FC1); // m x n (rows x cols) = numShapeDims x numShapePcaCoeffs
110  std::cout << "Loading shape PCA basis matrix with " << unnormalisedPcaBasisShape.rows << " rows and " << unnormalisedPcaBasisShape.cols << " cols." << std::endl;
111  for (unsigned int col = 0; col < numShapePcaCoeffs; ++col) {
112  for (unsigned int row = 0; row < numShapeDims; ++row) {
113  double var = 0.0;
114  modelFile.read(reinterpret_cast<char*>(&var), 8);
115  unnormalisedPcaBasisShape.at<float>(row, col) = static_cast<float>(var);
116  }
117  }
118 
119  // Read mean shape vector
120  unsigned int numMean = 0; // dimension of the mean (3*numVertices)
121  modelFile.read(reinterpret_cast<char*>(&numMean), 4);
122  if (numMean != numShapeDims) {
123  std::cout << "Warning: Number of shape dimensions is not equal to the number of dimensions of the mean. Something will probably go wrong during the loading." << std::endl;
124  }
125  Mat meanShape(numMean, 1, CV_32FC1);
126  unsigned int counter = 0;
127  double vd0, vd1, vd2;
128  for (unsigned int i = 0; i < numMean / 3; ++i) {
129  vd0 = vd1 = vd2 = 0.0;
130  modelFile.read(reinterpret_cast<char*>(&vd0), 8);
131  modelFile.read(reinterpret_cast<char*>(&vd1), 8);
132  modelFile.read(reinterpret_cast<char*>(&vd2), 8);
133  meanShape.at<float>(counter, 0) = static_cast<float>(vd0);
134  ++counter;
135  meanShape.at<float>(counter, 0) = static_cast<float>(vd1);
136  ++counter;
137  meanShape.at<float>(counter, 0) = static_cast<float>(vd2);
138  ++counter;
139  }
140 
141  // Read shape eigenvalues
142  unsigned int numEigenValsShape = 0;
143  modelFile.read(reinterpret_cast<char*>(&numEigenValsShape), 4);
144  if (numEigenValsShape != numShapePcaCoeffs) {
145  std::cout << "Warning: Number of coefficients in the PCA basis matrix is not equal to the number of eigenvalues. Something will probably go wrong during the loading." << std::endl;
146  }
147  Mat eigenvaluesShape(numEigenValsShape, 1, CV_32FC1);
148  for (unsigned int i = 0; i < numEigenValsShape; ++i) {
149  double var = 0.0;
150  modelFile.read(reinterpret_cast<char*>(&var), 8);
151  eigenvaluesShape.at<float>(i, 0) = static_cast<float>(var);
152  }
153 
154  // We read the unnormalised basis from the file. Now let's normalise it and store the normalised basis separately.
155  Mat normalisedPcaBasisShape = normalise_pca_basis(unnormalisedPcaBasisShape, eigenvaluesShape);
156  PcaModel shapeModel(meanShape, normalisedPcaBasisShape, eigenvaluesShape, triangleList);
157 
158  // Reading the color model
159  // Read number of rows and columns of projection matrix
160  unsigned int numTexturePcaCoeffs = 0;
161  unsigned int numTextureDims = 0;
162  modelFile.read(reinterpret_cast<char*>(&numTexturePcaCoeffs), 4);
163  modelFile.read(reinterpret_cast<char*>(&numTextureDims), 4);
164  // Read color projection matrix
165  Mat unnormalisedPcaBasisColor(numTextureDims, numTexturePcaCoeffs, CV_32FC1);
166  std::cout << "Loading color PCA basis matrix with " << unnormalisedPcaBasisColor.rows << " rows and " << unnormalisedPcaBasisColor.cols << " cols." << std::endl;
167  for (unsigned int col = 0; col < numTexturePcaCoeffs; ++col) {
168  for (unsigned int row = 0; row < numTextureDims; ++row) {
169  double var = 0.0;
170  modelFile.read(reinterpret_cast<char*>(&var), 8);
171  unnormalisedPcaBasisColor.at<float>(row, col) = static_cast<float>(var);
172  }
173  }
174 
175  // Read mean color vector
176  unsigned int numMeanColor = 0; // dimension of the mean (3*numVertices)
177  modelFile.read(reinterpret_cast<char*>(&numMeanColor), 4);
178  Mat meanColor(numMeanColor, 1, CV_32FC1);
179  counter = 0;
180  for (unsigned int i = 0; i < numMeanColor / 3; ++i) {
181  vd0 = vd1 = vd2 = 0.0;
182  modelFile.read(reinterpret_cast<char*>(&vd0), 8); // order in hdf5: RGB. Order in OCV: BGR. But order in vertex.color: RGB
183  modelFile.read(reinterpret_cast<char*>(&vd1), 8);
184  modelFile.read(reinterpret_cast<char*>(&vd2), 8);
185  meanColor.at<float>(counter, 0) = static_cast<float>(vd0);
186  ++counter;
187  meanColor.at<float>(counter, 0) = static_cast<float>(vd1);
188  ++counter;
189  meanColor.at<float>(counter, 0) = static_cast<float>(vd2);
190  ++counter;
191  }
192 
193  // Read color eigenvalues
194  unsigned int numEigenValsColor = 0;
195  modelFile.read(reinterpret_cast<char*>(&numEigenValsColor), 4);
196  Mat eigenvaluesColor(numEigenValsColor, 1, CV_32FC1);
197  for (unsigned int i = 0; i < numEigenValsColor; ++i) {
198  double var = 0.0;
199  modelFile.read(reinterpret_cast<char*>(&var), 8);
200  eigenvaluesColor.at<float>(i, 0) = static_cast<float>(var);
201  }
202 
203  // We read the unnormalised basis from the file. Now let's normalise it and store the normalised basis separately.
204  Mat normalisedPcaBasisColor = normalise_pca_basis(unnormalisedPcaBasisColor, eigenvaluesColor);
205  PcaModel colorModel(meanColor, normalisedPcaBasisColor, eigenvaluesColor, triangleList);
206 
207  modelFile.close();
208 
209  // Load the isomap with texture coordinates if a filename has been given:
210  std::vector<cv::Vec2f> texCoords;
211  if (!isomap_file.empty()) {
212  texCoords = load_isomap(isomap_file);
213  if (shapeModel.get_data_dimension() / 3.0f != texCoords.size()) {
214  std::string errorMessage("Error, wrong number of texture coordinates. Don't have the same number of texcoords than the shape model has vertices.");
215  std::cout << errorMessage << std::endl;
216  throw std::runtime_error(errorMessage);
217  }
218  }
219 
220  return MorphableModel(shapeModel, colorModel, texCoords);
221 };
222 
231 std::vector<cv::Vec2f> load_isomap(boost::filesystem::path isomapFile)
232 {
233  using std::string;
234  std::vector<float> xCoords, yCoords;
235  string line;
236  std::ifstream myfile(isomapFile.string());
237  if (!myfile.is_open()) {
238  string logMessage("The isomap file could not be opened. Did you specify a correct filename? " + isomapFile.string());
239  throw std::runtime_error(logMessage);
240  }
241  else {
242  while (getline(myfile, line))
243  {
244  std::istringstream iss(line);
245  string x, y;
246  iss >> x >> y;
247  xCoords.push_back(std::stof(x));
248  yCoords.push_back(std::stof(y));
249  }
250  myfile.close();
251  }
252  // Process the coordinates: Find the min/max and rescale to [0, 1] x [0, 1]
253  auto minMaxX = std::minmax_element(begin(xCoords), end(xCoords)); // minMaxX is a pair, first=min, second=max
254  auto minMaxY = std::minmax_element(begin(yCoords), end(yCoords));
255 
256  std::vector<cv::Vec2f> texCoords;
257  float divisorX = *minMaxX.second - *minMaxX.first;
258  float divisorY = *minMaxY.second - *minMaxY.first;
259  for (int i = 0; i < xCoords.size(); ++i) {
260  texCoords.push_back(cv::Vec2f((xCoords[i] - *minMaxX.first) / divisorX, 1.0f - (yCoords[i] - *minMaxY.first) / divisorY)); // We rescale to [0, 1] and at the same time flip the y-coords (because in the isomap, the coordinates are stored upside-down).
261  }
262 
263  return texCoords;
264 };
265 
266  } /* namespace morphablemodel */
267 } /* namespace eos */
268 
269 #endif /* IO_CVSSP_HPP_ */
This class represents a PCA-model that consists of:
Definition: PcaModel.hpp:55
int get_data_dimension() const
Definition: PcaModel.hpp:98
Namespace containing all of eos&#39;s 3D model fitting functionality.
MorphableModel load_scm_model(boost::filesystem::path model_filename, boost::filesystem::path isomap_file=boost::filesystem::path())
Definition: cvssp.hpp:59
cv::Mat normalise_pca_basis(cv::Mat unnormalised_basis, cv::Mat eigenvalues)
Definition: PcaModel.hpp:285
std::vector< cv::Vec2f > load_isomap(boost::filesystem::path isomap_file)
Definition: cvssp.hpp:231
A class representing a 3D Morphable Model, consisting of a shape- and colour (albedo) PCA model...
Definition: MorphableModel.hpp:54