1 /*
2 * Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>.
3 * Released to public domain under terms of the BSD Simplified license.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the organization nor the names of its contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * See <http://www.opensource.org/licenses/bsd-license>
17 */
18
19 #include "opencv2/core.hpp"
20 #include "opencv2/face.hpp"
21 #include "opencv2/highgui.hpp"
22 #include "opencv2/imgproc.hpp"
23
24 #include <iostream>
25 #include <fstream>
26 #include <sstream>
27
28 using namespace cv;
29 using namespace cv::face;
30 using namespace std;
31
norm_0_255(InputArray _src)32 static Mat norm_0_255(InputArray _src) {
33 Mat src = _src.getMat();
34 // Create and return normalized image:
35 Mat dst;
36 switch(src.channels()) {
37 case 1:
38 cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
39 break;
40 case 3:
41 cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
42 break;
43 default:
44 src.copyTo(dst);
45 break;
46 }
47 return dst;
48 }
49
read_csv(const string & filename,vector<Mat> & images,vector<int> & labels,char separator=';')50 static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
51 std::ifstream file(filename.c_str(), ifstream::in);
52 if (!file) {
53 string error_message = "No valid input file was given, please check the given filename.";
54 CV_Error(Error::StsBadArg, error_message);
55 }
56 string line, path, classlabel;
57 while (getline(file, line)) {
58 stringstream liness(line);
59 getline(liness, path, separator);
60 getline(liness, classlabel);
61 if(!path.empty() && !classlabel.empty()) {
62 images.push_back(imread(path, 0));
63 labels.push_back(atoi(classlabel.c_str()));
64 }
65 }
66 }
67
main(int argc,const char * argv[])68 int main(int argc, const char *argv[]) {
69 // Check for valid command line arguments, print usage
70 // if no arguments were given.
71 if (argc < 2) {
72 cout << "usage: " << argv[0] << " <csv.ext> <output_folder> " << endl;
73 exit(1);
74 }
75 string output_folder = ".";
76 if (argc == 3) {
77 output_folder = string(argv[2]);
78 }
79 // Get the path to your CSV.
80 string fn_csv = string(argv[1]);
81 // These vectors hold the images and corresponding labels.
82 vector<Mat> images;
83 vector<int> labels;
84 // Read in the data. This can fail if no valid
85 // input filename is given.
86 try {
87 read_csv(fn_csv, images, labels);
88 } catch (const cv::Exception& e) {
89 cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
90 // nothing more we can do
91 exit(1);
92 }
93 // Quit if there are not enough images for this demo.
94 if(images.size() <= 1) {
95 string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
96 CV_Error(Error::StsError, error_message);
97 }
98 // Get the height from the first image. We'll need this
99 // later in code to reshape the images to their original
100 // size:
101 int height = images[0].rows;
102 // The following lines simply get the last images from
103 // your dataset and remove it from the vector. This is
104 // done, so that the training data (which we learn the
105 // cv::BasicFaceRecognizer on) and the test data we test
106 // the model with, do not overlap.
107 Mat testSample = images[images.size() - 1];
108 int testLabel = labels[labels.size() - 1];
109 images.pop_back();
110 labels.pop_back();
111 // The following lines create an Eigenfaces model for
112 // face recognition and train it with the images and
113 // labels read from the given CSV file.
114 // This here is a full PCA, if you just want to keep
115 // 10 principal components (read Eigenfaces), then call
116 // the factory method like this:
117 //
118 // EigenFaceRecognizer::create(10);
119 //
120 // If you want to create a FaceRecognizer with a
121 // confidence threshold (e.g. 123.0), call it with:
122 //
123 // EigenFaceRecognizer::create(10, 123.0);
124 //
125 // If you want to use _all_ Eigenfaces and have a threshold,
126 // then call the method like this:
127 //
128 // EigenFaceRecognizer::create(0, 123.0);
129 //
130 Ptr<EigenFaceRecognizer> model = EigenFaceRecognizer::create();
131 model->train(images, labels);
132 // The following line predicts the label of a given
133 // test image:
134 int predictedLabel = model->predict(testSample);
135 //
136 // To get the confidence of a prediction call the model with:
137 //
138 // int predictedLabel = -1;
139 // double confidence = 0.0;
140 // model->predict(testSample, predictedLabel, confidence);
141 //
142 string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
143 cout << result_message << endl;
144 // Here is how to get the eigenvalues of this Eigenfaces model:
145 Mat eigenvalues = model->getEigenValues();
146 // And we can do the same to display the Eigenvectors (read Eigenfaces):
147 Mat W = model->getEigenVectors();
148 // Get the sample mean from the training data
149 Mat mean = model->getMean();
150 // Display or save:
151 if(argc == 2) {
152 imshow("mean", norm_0_255(mean.reshape(1, images[0].rows)));
153 } else {
154 imwrite(format("%s/mean.png", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows)));
155 }
156 // Display or save the Eigenfaces:
157 for (int i = 0; i < min(10, W.cols); i++) {
158 string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i));
159 cout << msg << endl;
160 // get eigenvector #i
161 Mat ev = W.col(i).clone();
162 // Reshape to original size & normalize to [0...255] for imshow.
163 Mat grayscale = norm_0_255(ev.reshape(1, height));
164 // Show the image & apply a Jet colormap for better sensing.
165 Mat cgrayscale;
166 applyColorMap(grayscale, cgrayscale, COLORMAP_JET);
167 // Display or save:
168 if(argc == 2) {
169 imshow(format("eigenface_%d", i), cgrayscale);
170 } else {
171 imwrite(format("%s/eigenface_%d.png", output_folder.c_str(), i), norm_0_255(cgrayscale));
172 }
173 }
174
175 // Display or save the image reconstruction at some predefined steps:
176 for(int num_components = min(W.cols, 10); num_components < min(W.cols, 300); num_components+=15) {
177 // slice the eigenvectors from the model
178 Mat evs = Mat(W, Range::all(), Range(0, num_components));
179 Mat projection = LDA::subspaceProject(evs, mean, images[0].reshape(1,1));
180 Mat reconstruction = LDA::subspaceReconstruct(evs, mean, projection);
181 // Normalize the result:
182 reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));
183 // Display or save:
184 if(argc == 2) {
185 imshow(format("eigenface_reconstruction_%d", num_components), reconstruction);
186 } else {
187 imwrite(format("%s/eigenface_reconstruction_%d.png", output_folder.c_str(), num_components), reconstruction);
188 }
189 }
190 // Display if we are not writing to an output folder:
191 if(argc == 2) {
192 waitKey(0);
193 }
194 return 0;
195 }
196