-
Notifications
You must be signed in to change notification settings - Fork 35
/
main.cpp
203 lines (168 loc) · 7.93 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/* Copyright (C) 2016 Kristian Sloth Lauszus. All rights reserved.
This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").
Contact information
-------------------
Kristian Sloth Lauszus
Web : http://www.lauszus.com
e-mail : [email protected]
*/
#include <iostream>
#include <sys/stat.h>
#include <algorithm>
#include <Eigen/Dense> // http://eigen.tuxfamily.org
#include "Eigenfaces.h"
#include "Facebase.h"
#include "Fisherfaces.h"
#include "pgm.h"
#include "Tools.h"
Eigenfaces eigenfaces;
Fisherfaces fisherfaces;
using namespace std;
using namespace Eigen;
// Based on: http://jmcspot.com/Eigenface
// Images are from the AT&T Facedatabase: http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html
// See: http://eigen.tuxfamily.org/dox/structEigen_1_1IOFormat.html
static IOFormat OctaveFmt(StreamPrecision, 0, ", ", ";\n", "", "", "[", "]");
#if 1 // AT&T Facedatabase
static const char *facePath = "../orl_faces";
static const uint8_t N = 92, M = 112;
static const uint16_t n_pixels = M*N;
static const uint8_t n_person = 40;
static const uint8_t n_img_pr_person = 10;
#else // Yale Face Database
static const char *facePath = "../yalefaces";
static const uint16_t N = 320, M = 243;
static const uint32_t n_pixels = M*N;
static const uint8_t n_person = 15;
static const uint8_t n_img_pr_person = 11;
#endif
static const uint16_t n_images = n_img_pr_person*n_person;
static MatrixXi readPgmAsMatrix(const char *filename) {
PGMImage imgRaw;
getPGMfile(filename, &imgRaw);
MatrixXi img(imgRaw.height, imgRaw.width);
for (int y = 0; y < imgRaw.height; y++) {
for (int x = 0; x < imgRaw.width; x++) {
img(y, x) = imgRaw.data[imgRaw.height - y - 1][x].red; // Map data to Eigen Matrix
}
}
//cout << img.format(OctaveFmt) << endl;
return img;
}
static void saveMatrixAsPgm(const char *filename, const MatrixXf &A) {
const float min = A.minCoeff();
const float max = A.maxCoeff();
PGMImage img;
img.height = A.rows();
img.width = A.cols();
img.maxVal = 255;
for (int y = 0; y < img.height; y++) {
for (int x = 0; x < img.width; x++)
img.data[img.height - y - 1][x].red = img.maxVal * (A(y, x) - min) / (max - min); // Scale data to 0-255
}
savePGMfile(filename, &img);
}
static void trainFaces(MatrixXi &images) {
cout << "Loading images" << endl;
images = MatrixXi(n_pixels, n_images); // Pre-allocate storage for images
VectorXi classes = VectorXi(n_images); // Create class vector
cout << "images: " << images.rows() << " x " << images.cols() << endl << endl;
// Load all images into the matrix
for (int i = 0; i < n_person; i++) {
for (int j = 0; j < n_img_pr_person; j++) {
char filename[50];
sprintf(filename, "%s/s%u/%u.pgm", facePath, i + 1, j + 1);
MatrixXi img = readPgmAsMatrix(filename);
const size_t index = i*n_img_pr_person + j;
images.block<n_pixels, 1>(0, index) = Map<VectorXi>(img.data(), img.size()); // Flatten image
classes(index) = i + 1; // Generate class number for each person
}
}
eigenfaces.train(images);
cout << "eigenfaces: " << eigenfaces.V.rows() << " x " << eigenfaces.V.cols() << endl;
mkdir("eigenfaces", 0755);
for (int i = 0; i < eigenfaces.numComponents; i++) { // Save Eigenfaces as PGM images
Map<MatrixXf> eigenface(eigenfaces.V.block<n_pixels, 1>(0, i).data(), M, N); // Extract Eigenface
//cout << "Eigenface: " << eigenface.rows() << " x " << eigenface.cols() << endl;
char filename[50];
sprintf(filename, "eigenfaces/eigenface%u.pgm", i);
saveMatrixAsPgm(filename, eigenface); // Save Eigenface
}
cout << "Done training Eigenfaces" << endl << endl;
fisherfaces.train(images, classes);
cout << "fisherfaces: " << fisherfaces.V.rows() << " x " << fisherfaces.V.cols() << endl;
mkdir("fisherfaces", 0755);
for (int i = 0; i < fisherfaces.numComponents; i++) { // Save Fisherfaces as PGM images
Map<MatrixXf> fisherface(fisherfaces.V.block<n_pixels, 1>(0, i).data(), M, N); // Extract Fisherface
//cout << "Fisherface: " << fisherface.rows() << " x " << fisherface.cols() << endl;
char filename[50];
sprintf(filename, "fisherfaces/fisherface%u.pgm", i);
saveMatrixAsPgm(filename, fisherface); // Save Fisherface
}
cout << "Done training Fisherfaces" << endl << endl;
}
void calculateMatches(MatrixXi &target, const MatrixXi &images, Facebase &facebase, const char *dirName) {
cout << "target: " << target.rows() << " x " << target.cols() << endl;
cout << "Reconstructing Faces" << endl;
VectorXi target_flat = Map<VectorXi>(target.data(), target.size()); // Flatten image
VectorXf W = facebase.project(target_flat); // Project onto subspace
//cout << W.format(OctaveFmt) << endl;
VectorXf face = facebase.reconstructFace(W);
cout << "Calculate normalized Euclidean distance" << endl;
float dist_face = facebase.euclideanDistFace(target_flat, face);
cout << "Face distance: " << dist_face << endl;
VectorXf dist = facebase.euclideanDist(W);
//cout << "dist: " << dist.rows() << " x " << dist.cols() << endl;
vector<size_t> idx = sortIndexes(dist);
//for (auto i : idx) cout << "dist[" << i << "]: " << dist(i) << endl;
char filename[50];
mkdir(dirName, 0755);
for (int i = 0; i < min(dist.size(), 9L); i++) { // Save first nine matches
cout << "dist[" << idx[i] << "]: " << dist(idx[i]) << endl;
sprintf(filename, "%s/match%u.pgm", dirName, i);
MatrixXi img = images.block<n_pixels, 1>(0, idx[i]);
//cout << "img: " << img.rows() << " x " << img.cols() << endl;
saveMatrixAsPgm(filename, Map<MatrixXi>(img.data(), M, N).cast<float>()); // Save matched image
}
sprintf(filename, "%s/face.pgm", dirName);
saveMatrixAsPgm(filename, Map<MatrixXf>(face.data(), M, N)); // Save face image
}
int main(void) {
MatrixXi images;
trainFaces(images);
MatrixXi target = readPgmAsMatrix("../orl_faces/s3/8.pgm"); // Load a random image from the database
//MatrixXi target = readPgmAsMatrix("img/bart.pgm"); // Used to test face distance
//MatrixXi target = readPgmAsMatrix("../yalefaces/s1/4.pgm"); // Load image with light coming from the left side
cout << "Calculating matches using Eigenfaces" << endl;
calculateMatches(target, images, eigenfaces, "matches_eigenfaces"); // Calculate matches based on Eigenfaces
cout << endl << "Calculating matches using Fisherfaces" << endl;
calculateMatches(target, images, fisherfaces, "matches_fisherfaces"); // Calculate matches based on Fisherfaces
#if 0
cout << endl << "Testing all faces against database" << endl;
bool allMatched = true;
for (int i = 0; i < n_person; i++) {
for (int j = 0; j < n_img_pr_person; j++) {
char filename[50];
sprintf(filename, "%s/s%u/%u.pgm", facePath, i + 1, j + 1);
target = readPgmAsMatrix(filename);
VectorXf W = eigenfaces.project(Map<VectorXi>(target.data(), target.size())); // Flatten image and project onto Eigenfaces
VectorXf dist = eigenfaces.euclideanDist(W);
vector<size_t> idx = sortIndexes(dist);
//cout << "dist[" << idx[0] << "]: " << dist(idx[0]) << endl;
const size_t index = i*n_img_pr_person + j;
if (idx[0] != index) {
cout << "Wrong match: " << index << " should be: " << idx[0] << endl;
allMatched = false;
}
}
}
if (allMatched)
cout << "All faces matched!" << endl;
#endif
return 0;
}