forked from galeone/face-miner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfacepatternminer.cpp
292 lines (253 loc) · 9.46 KB
/
facepatternminer.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*
Face Miner: data mining applied to face detection
Copyright (C) 2016 Paolo Galeone <nessuno@nerdz.eu>
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Exhibit B is not attached; this software is compatible with the
licenses expressed under Section 1.12 of the MPL v2.
*/
#include "facepatternminer.h"
//#undef DEBUG
FacePatternMiner::FacePatternMiner(QString train_positive,
QString train_negative,
QString test_positive,
QString test_negative,
QString mime) {
_positiveTrainSet = train_positive;
_negativeTrainSet = train_negative;
_positiveTestSet = test_positive;
_negativeTestSet = test_negative;
_mimeFilter = mime;
_edgeDir = new QDir("edge");
if (!_edgeDir->exists()) {
QDir().mkdir(_edgeDir->path());
}
auto setPath = _edgeDir->absolutePath().append(QString("/"));
_positiveDB = new QFile(setPath + QString("positive.db"));
_negativeDB = new QFile(setPath + QString("negative.db"));
_trainImageSizeFile = new QFile(setPath + QString("image.size"));
#ifdef DEBUG
// do not use previous computed db
if (_positiveDB->exists()) {
_positiveDB->remove();
}
_positiveDB->open(QFileDevice::WriteOnly);
if (_negativeDB->exists()) {
_negativeDB->remove();
}
_negativeDB->open(QFileDevice::WriteOnly);
if (_trainImageSizeFile->exists()) {
_trainImageSizeFile->remove();
}
_trainImageSize = NULL;
_trainImageSizeFile->open(QFileDevice::WriteOnly);
#else
// use previous computed db, if exists. This ReadWrite
_positiveDB->open(QFileDevice::ReadWrite);
_negativeDB->open(QFileDevice::ReadWrite);
_trainImageSizeFile->open(QFileDevice::ReadWrite);
if (_trainImageSizeFile->size() > 0) {
// format: cols rows
QTextStream in(_trainImageSizeFile);
auto sizeLine = in.readLine();
QStringList sizes = sizeLine.split(" ");
if (sizes.count() != 2) {
_trainImageSize = NULL;
} else {
_trainImageSize = new cv::Size(std::atoi(sizes[0].toStdString().c_str()),
std::atoi(sizes[1].toStdString().c_str()));
}
_trainImageSizeFile->close();
} else {
_trainImageSize = NULL;
}
#endif
}
// _appendToSet extracts the pixel position with value bin. It appends the
// extracted pattern to the database
// file using the MAFIA database syntax
void FacePatternMiner::_addTransactionToDB(const cv::Mat1b& transaction,
uchar bin,
QFile* database) {
if (!database->isOpen()) {
throw new std::logic_error(database->fileName().toStdString() +
" is not open");
}
std::string line = "";
for (auto x = 0; x < transaction.cols; ++x) {
for (auto y = 0; y < transaction.rows; ++y) {
cv::Point point(x, y);
if (transaction.at<uchar>(point) == bin) {
// Use Cantor::pair to create a bijective association between the point
// and a number in the database
line += std::to_string(Cantor::pair(point)) + " ";
}
}
}
if (line.length() > 0) {
line.pop_back(); // remove last space after closing bracket
// add newline
line += "\n";
database->write(line.c_str());
}
}
void FacePatternMiner::_preprocess() {
// If I need to fill the databases
if (_positiveDB->size() == 0 && _negativeDB->size() == 0) {
QDirIterator* it = new QDirIterator(_positiveTrainSet);
// size_t count = 0;
while (it->hasNext()) {
auto fileName = it->next();
if (!Preprocessor::validMime(fileName)) {
continue;
}
cv::Mat image = cv::imread(fileName.toStdString());
if (_trainImageSize == NULL) {
_trainImageSize = new cv::Size(image.cols, image.rows);
QTextStream out(_trainImageSizeFile);
out << std::to_string(image.cols).c_str();
out << " ";
out << std::to_string(image.rows).c_str();
_trainImageSizeFile->close();
}
emit preprocessing(image);
cv::Mat1b res = Preprocessor::process(image);
emit preprocessed(res);
// Appending transaction (the image), to transaction database
// Creating test set for positive pattern
_addTransactionToDB(res, 255, _positiveDB);
// Creating test set for negative pattern
_addTransactionToDB(res, 0, _negativeDB);
}
}
_positiveDB->close();
_negativeDB->close();
}
// _mineMFI mines the Most Frequent Itemset in the database.
// The database must be in ASCII format, according to the MAFIA Syntax.
// Returns the MFI as a b/w image.
// minSupport is a parameter passed to MAFIA algoritm, to prune results in the
// depth first search
cv::Mat1b FacePatternMiner::_mineMFI(QFile* database,
float minSupport,
std::vector<cv::Point>& coordinates) {
std::ostringstream out;
out << std::setprecision(3) << minSupport;
std::string minSupportStr(out.str());
std::string ext(minSupportStr + ".mfi");
QString mfiPath = database->fileName() + QString(ext.c_str());
QFile mfiFile(mfiPath);
#ifndef DEBUG
if (!mfiFile.exists()) {
#endif
QString executing("./MAFIA -mfi ");
executing.append(minSupportStr.c_str());
executing.append(" -ascii ");
executing.append(database->fileName());
executing.append(" ");
executing.append(mfiPath);
std::cout << executing.toStdString() << std::endl;
QProcess::execute(executing);
#ifndef DEBUG
}
#endif
if (!mfiFile.open(QFileDevice::ReadOnly)) {
throw new std::runtime_error("Unable to open " + mfiPath.toStdString());
}
QTextStream in(&mfiFile);
auto lineCount = 1;
QSet<int> coordNumSet;
while (!in.atEnd()) {
QString line = in.readLine();
QStringList coords = line.split(" ");
if (coords.length() > 0) {
// the last element of the line (minSupp is between brackets)
coords.pop_back();
for (const QString& coord : coords) {
coordNumSet.insert(std::stoi(coord.toStdString()));
}
} else {
break;
}
++lineCount;
}
mfiFile.close();
cv::Mat1b ret = cv::Mat1b::zeros(*_trainImageSize);
for (auto coordNum : coordNumSet) {
cv::Point pos = Cantor::unpair(coordNum);
coordinates.push_back(pos);
ret.at<uchar>(pos) = 255;
}
return ret;
}
// slot
void FacePatternMiner::start() {
_preprocess();
emit preprocessing_terminated();
float positiveMinSupport = 0.7, negativeMinSupport = 0.79;
_positiveMFI =
_mineMFI(_positiveDB, positiveMinSupport, _positiveMFICoordinates);
_negativeMFI =
_mineMFI(_negativeDB, negativeMinSupport, _negativeMFICoordinates);
emit mining_terminated(_positiveMFI, _negativeMFI);
_trainClassifiers();
}
void FacePatternMiner::_trainClassifiers() {
// Classifiers
_varianceClassifier = new VarianceClassifier(*_trainImageSize);
_featureClassifier =
new FeatureClassifier(_positiveMFICoordinates, _negativeMFICoordinates);
// 5-6, 11-13
_svmClassifier =
new SVMClassifier(cv::Rect(0, 5, _trainImageSize->width, 2),
cv::Rect(0, 11, _trainImageSize->width, 3));
std::cout << "[+] Training classifiers" << std::endl;
std::cout << "\tVariance classifier: " << std::endl;
_varianceClassifier->train(_positiveTrainSet, _negativeTrainSet);
//.fist = true positive, .second = false positive
auto positivesVC =
Stats::test(_positiveTestSet, _negativeTestSet, _varianceClassifier);
std::cout << "\tFeatures classifier: " << std::endl;
_featureClassifier->train(_positiveTrainSet, _negativeTrainSet);
auto positivesFC =
Stats::test(_positiveTestSet, _negativeTestSet, _featureClassifier);
std::vector<cv::Mat1b> truePositives, falsePositives;
truePositives.reserve(positivesVC.first.size() + positivesFC.first.size());
truePositives.insert(truePositives.end(), positivesVC.first.begin(),
positivesVC.first.end());
truePositives.insert(truePositives.end(), positivesFC.first.begin(),
positivesFC.first.end());
falsePositives.reserve(positivesVC.second.size() + positivesFC.second.size());
falsePositives.insert(falsePositives.end(), positivesVC.second.begin(),
positivesVC.second.end());
falsePositives.insert(falsePositives.end(), positivesFC.second.begin(),
positivesFC.second.end());
// Training SVM on true and false positives of previous 2 classifiers
std::cout << "\tSVM classifier: " << std::endl;
QDirIterator* it = new QDirIterator(_positiveTrainSet);
while (it->hasNext()) {
auto fileName = it->next();
if (!Preprocessor::validMime(fileName)) {
continue;
}
cv::Mat image = cv::imread(fileName.toStdString());
cv::Mat gray = Preprocessor::gray(image);
truePositives.push_back(gray);
}
delete it;
it = new QDirIterator(_negativeTrainSet);
while (it->hasNext()) {
auto fileName = it->next();
if (!Preprocessor::validMime(fileName)) {
continue;
}
cv::Mat image = cv::imread(fileName.toStdString());
cv::Mat gray = Preprocessor::gray(image);
falsePositives.push_back(gray);
}
_svmClassifier->train(truePositives, falsePositives);
_faceClassifier = new FaceClassifier(_varianceClassifier, _featureClassifier,
_svmClassifier, *_trainImageSize);
emit built_classifier(_faceClassifier);
}