Commit 76e69ec607ca32f8fc19337ab591f0f58ed8545f
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 &params) | @@ -153,7 +154,7 @@ static void trainCascade(const TrainParams &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 |