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 | 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 &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 | ... | ... |