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 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16  
17 17 #include <opencv2/objdetect/objdetect.hpp>
18   -//#include <opencv2/objdetect/objdetect_c.h>
19 18 #include "openbr_internal.h"
20 19 #include "openbr/core/opencvutils.h"
21 20 #include "openbr/core/resource.h"
  21 +#include "openbr/core/qtutils.h"
22 22 #include <QProcess>
  23 +#include <QTemporaryFile>
23 24  
24 25 using namespace cv;
25 26  
... ... @@ -153,7 +154,7 @@ static void trainCascade(const TrainParams &amp;params)
153 154 const QStringList cmdArgs = buildTrainingArgs(params);
154 155 QProcess::execute("opencv_traincascade", cmdArgs);
155 156 }
156   -
  157 +
157 158 namespace br
158 159 {
159 160  
... ... @@ -169,19 +170,11 @@ public:
169 170 else if (model == "Eye") file += "haarcascades/haarcascade_eye_tree_eyeglasses.xml";
170 171 else if (model == "FrontalFace") file += "haarcascades/haarcascade_frontalface_alt2.xml";
171 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 220 Q_PROPERTY(QString mode READ get_mode WRITE set_mode RESET reset_mode STORED false)
228 221 Q_PROPERTY(bool show READ get_show WRITE set_show RESET reset_show STORED false)
229 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 224 BR_PROPERTY(QString, model, "FrontalFace")
233 225 BR_PROPERTY(int, minSize, 64)
... ... @@ -251,8 +243,7 @@ class CascadeTransform : public MetaTransform
251 243 BR_PROPERTY(QString, bt, "")
252 244 BR_PROPERTY(QString, mode, "")
253 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 248 Resource<CascadeClassifier> cascadeResource;
258 249  
... ... @@ -264,33 +255,20 @@ class CascadeTransform : public MetaTransform
264 255 // Train transform
265 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 269 QTextStream posStream(&posFile);
285 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 272 TrainParams params;
295 273  
296 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 293 if (params.w < 0) params.w = minSize;
316 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 303 for (int i = 0; i < files.length(); i++) {
319 304 File f = files[i];
320 305 if (f.contains("training-set")) {
... ... @@ -322,73 +307,63 @@ class CascadeTransform : public MetaTransform
322 307  
323 308 // Negative samples
324 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 311 negCount++;
328   -
329 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 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 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 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 328 buildPos = true;
348 329 params.img = f.path() + QDir::separator() + f.fileName();
349 330  
350 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 335 if (f.contains("inv")) params.inv = f.get<bool>("inv",false);
355 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 345 posFile.close();
366 346 negFile.close();
  347 +
  348 + // Fill in remaining params conditionally
367 349 if (buildPos) {
368 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 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 365 genSamples(params);
389 366 trainCascade(params);
390   - if (posFile.exists()) posFile.remove();
391   - negFile.remove();
392 367 }
393 368  
394 369 void project(const Template &src, Template &dst) const
... ...