Commit 76e69ec607ca32f8fc19337ab591f0f58ed8545f

Authored by Scott Klum
1 parent 2ec4f684

Skip training CascadeClassifier when using pretrained OpenCV cascades

Also, minor stylistic/clarity tweaks for cascade training
Showing 1 changed file with 56 additions and 81 deletions
openbr/plugins/cascade.cpp
@@ -15,11 +15,12 @@ @@ -15,11 +15,12 @@
15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16
17 #include <opencv2/objdetect/objdetect.hpp> 17 #include <opencv2/objdetect/objdetect.hpp>
18 -//#include <opencv2/objdetect/objdetect_c.h>  
19 #include "openbr_internal.h" 18 #include "openbr_internal.h"
20 #include "openbr/core/opencvutils.h" 19 #include "openbr/core/opencvutils.h"
21 #include "openbr/core/resource.h" 20 #include "openbr/core/resource.h"
  21 +#include "openbr/core/qtutils.h"
22 #include <QProcess> 22 #include <QProcess>
  23 +#include <QTemporaryFile>
23 24
24 using namespace cv; 25 using namespace cv;
25 26
@@ -153,7 +154,7 @@ static void trainCascade(const TrainParams &amp;params) @@ -153,7 +154,7 @@ static void trainCascade(const TrainParams &amp;params)
153 const QStringList cmdArgs = buildTrainingArgs(params); 154 const QStringList cmdArgs = buildTrainingArgs(params);
154 QProcess::execute("opencv_traincascade", cmdArgs); 155 QProcess::execute("opencv_traincascade", cmdArgs);
155 } 156 }
156 - 157 +
157 namespace br 158 namespace br
158 { 159 {
159 160
@@ -169,19 +170,11 @@ public: @@ -169,19 +170,11 @@ public:
169 else if (model == "Eye") file += "haarcascades/haarcascade_eye_tree_eyeglasses.xml"; 170 else if (model == "Eye") file += "haarcascades/haarcascade_eye_tree_eyeglasses.xml";
170 else if (model == "FrontalFace") file += "haarcascades/haarcascade_frontalface_alt2.xml"; 171 else if (model == "FrontalFace") file += "haarcascades/haarcascade_frontalface_alt2.xml";
171 else if (model == "ProfileFace") file += "haarcascades/haarcascade_profileface.xml"; 172 else if (model == "ProfileFace") file += "haarcascades/haarcascade_profileface.xml";
172 - else{  
173 - // Create temp folder if does not exist  
174 - file = model+QDir::separator()+"cascade.xml";  
175 - QDir dir(model);  
176 - if (!dir.exists())  
177 - if (!QDir::current().mkdir(model)) qFatal("Cannot create model.");  
178 -  
179 - // Make sure file can be created  
180 - QFile pathTest(file);  
181 - if (pathTest.exists()) pathTest.remove();  
182 -  
183 - if (!pathTest.open(QIODevice::WriteOnly | QIODevice::Text)) qFatal("Cannot create model.");  
184 - pathTest.remove(); 173 + else {
  174 + // Create a directory for trainable cascades
  175 + file += "openbrcascades/"+model+"/cascade.xml";
  176 + QFile touchFile(file);
  177 + QtUtils::touchDir(touchFile);
185 } 178 }
186 } 179 }
187 180
@@ -227,7 +220,6 @@ class CascadeTransform : public MetaTransform @@ -227,7 +220,6 @@ class CascadeTransform : public MetaTransform
227 Q_PROPERTY(QString mode READ get_mode WRITE set_mode RESET reset_mode STORED false) 220 Q_PROPERTY(QString mode READ get_mode WRITE set_mode RESET reset_mode STORED false)
228 Q_PROPERTY(bool show READ get_show WRITE set_show RESET reset_show STORED false) 221 Q_PROPERTY(bool show READ get_show WRITE set_show RESET reset_show STORED false)
229 Q_PROPERTY(bool baseFormatSave READ get_baseFormatSave WRITE set_baseFormatSave RESET reset_baseFormatSave STORED false) 222 Q_PROPERTY(bool baseFormatSave READ get_baseFormatSave WRITE set_baseFormatSave RESET reset_baseFormatSave STORED false)
230 - Q_PROPERTY(bool overwrite READ get_overwrite WRITE set_overwrite RESET reset_overwrite STORED false)  
231 223
232 BR_PROPERTY(QString, model, "FrontalFace") 224 BR_PROPERTY(QString, model, "FrontalFace")
233 BR_PROPERTY(int, minSize, 64) 225 BR_PROPERTY(int, minSize, 64)
@@ -251,8 +243,7 @@ class CascadeTransform : public MetaTransform @@ -251,8 +243,7 @@ class CascadeTransform : public MetaTransform
251 BR_PROPERTY(QString, bt, "") 243 BR_PROPERTY(QString, bt, "")
252 BR_PROPERTY(QString, mode, "") 244 BR_PROPERTY(QString, mode, "")
253 BR_PROPERTY(bool, show, false) 245 BR_PROPERTY(bool, show, false)
254 - BR_PROPERTY(bool, baseFormatSave, false)  
255 - BR_PROPERTY(bool, overwrite, false) 246 + BR_PROPERTY(bool, baseFormatSave, false)
256 247
257 Resource<CascadeClassifier> cascadeResource; 248 Resource<CascadeClassifier> cascadeResource;
258 249
@@ -264,33 +255,20 @@ class CascadeTransform : public MetaTransform @@ -264,33 +255,20 @@ class CascadeTransform : public MetaTransform
264 // Train transform 255 // Train transform
265 void train(const TemplateList& data) 256 void train(const TemplateList& data)
266 { 257 {
267 - if (overwrite) {  
268 - QDir dataDir(model);  
269 - if (dataDir.exists()) {  
270 - dataDir.removeRecursively();  
271 - QDir::current().mkdir(model);  
272 - }  
273 - }  
274 -  
275 - const FileList files = data.files(); 258 + // Don't train if we're using OpenCV's prebuilt cascades
  259 + if (model == "Ear" || model == "Eye" || model == "FrontalFace" || model == "ProfileFace")
  260 + return;
  261 +
  262 + // Open positive and negative list temporary files
  263 + QTemporaryFile posFile;
  264 + QTemporaryFile negFile;
  265 +
  266 + posFile.open();
  267 + negFile.open();
276 268
277 - // Open positive and negative list files  
278 - const QString posFName = "pos.txt";  
279 - const QString negFName = "neg.txt";  
280 - QFile posFile(posFName);  
281 - QFile negFile(negFName);  
282 - posFile.open(QIODevice::WriteOnly | QIODevice::Text);  
283 - negFile.open(QIODevice::WriteOnly | QIODevice::Text);  
284 QTextStream posStream(&posFile); 269 QTextStream posStream(&posFile);
285 QTextStream negStream(&negFile); 270 QTextStream negStream(&negFile);
286 271
287 - const QString endln = "\r\n";  
288 -  
289 - int posCount = 0;  
290 - int negCount = 0;  
291 -  
292 - bool buildPos = false; // If true, build positive vector from single image  
293 -  
294 TrainParams params; 272 TrainParams params;
295 273
296 // Fill in from params (param defaults are same as struct defaults, so no checks are needed) 274 // Fill in from params (param defaults are same as struct defaults, so no checks are needed)
@@ -315,6 +293,13 @@ class CascadeTransform : public MetaTransform @@ -315,6 +293,13 @@ class CascadeTransform : public MetaTransform
315 if (params.w < 0) params.w = minSize; 293 if (params.w < 0) params.w = minSize;
316 if (params.h < 0) params.h = minSize; 294 if (params.h < 0) params.h = minSize;
317 295
  296 + int posCount = 0;
  297 + int negCount = 0;
  298 +
  299 + bool buildPos = false; // If true, build positive vector from single image
  300 +
  301 + const FileList files = data.files();
  302 +
318 for (int i = 0; i < files.length(); i++) { 303 for (int i = 0; i < files.length(); i++) {
319 File f = files[i]; 304 File f = files[i];
320 if (f.contains("training-set")) { 305 if (f.contains("training-set")) {
@@ -322,73 +307,63 @@ class CascadeTransform : public MetaTransform @@ -322,73 +307,63 @@ class CascadeTransform : public MetaTransform
322 307
323 // Negative samples 308 // Negative samples
324 if (tset == "neg") { 309 if (tset == "neg") {
325 - if (negCount > 0) negStream<<endln;  
326 - negStream << f.path() << QDir::separator() << f.fileName(); 310 + negStream << f.path() << QDir::separator() << f.fileName() << endl;
327 negCount++; 311 negCount++;
328 -  
329 // Positive samples for crop/rescale 312 // Positive samples for crop/rescale
330 - }else if (tset == "pos") {  
331 -  
332 - if (posCount > 0) posStream<<endln;  
333 - QString rects = ""; 313 + } else if (tset == "pos") {
  314 + QString buffer = "";
334 315
335 // Extract rectangles 316 // Extract rectangles
336 - for (int j = 0; j < f.rects().length(); j++) {  
337 - QRectF r = f.rects()[j];  
338 - rects += " " + QString::number(r.x()) + " " + QString::number(r.y()) + " " + QString::number(r.width()) + " "+ QString::number(r.height()); 317 + QList<QRectF> rects = f.rects();
  318 + for (int j = 0; j < rects.size(); j++) {
  319 + QRectF r = rects[j];
  320 + buffer += " " + QString::number(r.x()) + " " + QString::number(r.y()) + " " + QString::number(r.width()) + " "+ QString::number(r.height());
339 posCount++; 321 posCount++;
340 } 322 }
341 - if (f.rects().length() > 0)  
342 - posStream << f.path() << QDir::separator() << f.fileName() << " " << f.rects().length() << " " << rects; 323 +
  324 + posStream << f.path() << QDir::separator() << f.fileName() << " " << f.rects().length() << " " << buffer << endl;
343 325
344 // Single positive sample for background removal and overlay on negatives 326 // Single positive sample for background removal and overlay on negatives
345 - }else if (tset == "pos-base") {  
346 - 327 + } else if (tset == "pos-base") {
347 buildPos = true; 328 buildPos = true;
348 params.img = f.path() + QDir::separator() + f.fileName(); 329 params.img = f.path() + QDir::separator() + f.fileName();
349 330
350 // Parse settings (unique to this one tag) 331 // Parse settings (unique to this one tag)
351 - if (f.contains("num")) params.num = f.get<int>("num",0);  
352 - if (f.contains("bgcolor")) params.bgcolor = f.get<int>("bgcolor",0);  
353 - if (f.contains("bgthresh")) params.bgthresh =f.get<int>("bgthresh",0); 332 + if (f.contains("num")) params.num = f.get<int>("num");
  333 + if (f.contains("bgcolor")) params.bgcolor = f.get<int>("bgcolor");
  334 + if (f.contains("bgthresh")) params.bgthresh =f.get<int>("bgthresh");
354 if (f.contains("inv")) params.inv = f.get<bool>("inv",false); 335 if (f.contains("inv")) params.inv = f.get<bool>("inv",false);
355 if (f.contains("randinv")) params.randinv = f.get<bool>("randinv",false); 336 if (f.contains("randinv")) params.randinv = f.get<bool>("randinv",false);
356 - if (f.contains("maxidev")) params.maxidev = f.get<int>("maxidev",0);  
357 - if (f.contains("maxxangle")) params.maxxangle = f.get<double>("maxxangle",0);  
358 - if (f.contains("maxyangle")) params.maxyangle = f.get<double>("maxyangle",0);  
359 - if (f.contains("maxzangle")) params.maxzangle = f.get<double>("maxzangle",0); 337 + if (f.contains("maxidev")) params.maxidev = f.get<int>("maxidev");
  338 + if (f.contains("maxxangle")) params.maxxangle = f.get<double>("maxxangle");
  339 + if (f.contains("maxyangle")) params.maxyangle = f.get<double>("maxyangle");
  340 + if (f.contains("maxzangle")) params.maxzangle = f.get<double>("maxzangle");
360 } 341 }
361 } 342 }
362 } 343 }
363 344
364 - // Fill in remaining params conditionally  
365 posFile.close(); 345 posFile.close();
366 negFile.close(); 346 negFile.close();
  347 +
  348 + // Fill in remaining params conditionally
367 if (buildPos) { 349 if (buildPos) {
368 if (params.numPos < 0) { 350 if (params.numPos < 0) {
369 - if (params.num > 0) params.numPos = (int)(params.num*.95); 351 + if (params.num > 0) params.numPos = params.num*.95;
370 else params.numPos = 950; 352 else params.numPos = 950;
371 - posFile.remove();  
372 } 353 }
373 - }else{  
374 - params.info = posFName;  
375 - if (params.numPos < 0) {  
376 - params.numPos = (int)(posCount*.95);  
377 - }  
378 - }  
379 - params.bg = negFName;  
380 - params.data = model;  
381 - if (params.num < 0) {  
382 - params.num = posCount;  
383 - }  
384 - if (params.numNeg < 0) {  
385 - params.numNeg = negCount*10; 354 + } else {
  355 + params.info = posFile.fileName();
  356 + if (params.numPos < 0) params.numPos = posCount*.95;
386 } 357 }
387 - 358 +
  359 + if (params.num < 0) params.num = posCount;
  360 + if (params.numNeg < 0) params.numNeg = negCount*10;
  361 +
  362 + params.bg = negFile.fileName();
  363 + params.data = Globals->sdkPath + "/share/openbr/models/openbrcascades/" + model + "/cascade.xml";
  364 +
388 genSamples(params); 365 genSamples(params);
389 trainCascade(params); 366 trainCascade(params);
390 - if (posFile.exists()) posFile.remove();  
391 - negFile.remove();  
392 } 367 }
393 368
394 void project(const Template &src, Template &dst) const 369 void project(const Template &src, Template &dst) const