Commit e3778811bb6ecd169ff3fc8bb677bf0a3fe6335b
Merge pull request #117 from biometrics/streamlining
Leverage stream to do enrollment more efficiently
Showing
10 changed files
with
612 additions
and
485 deletions
openbr/core/core.cpp
| ... | ... | @@ -19,6 +19,7 @@ |
| 19 | 19 | #include "bee.h" |
| 20 | 20 | #include "common.h" |
| 21 | 21 | #include "qtutils.h" |
| 22 | +#include "../plugins/openbr_internal.h" | |
| 22 | 23 | |
| 23 | 24 | using namespace br; |
| 24 | 25 | |
| ... | ... | @@ -44,6 +45,14 @@ struct AlgorithmCore |
| 44 | 45 | qDebug("Training on %s%s", qPrintable(input.flat()), |
| 45 | 46 | model.isEmpty() ? "" : qPrintable(" to " + model)); |
| 46 | 47 | |
| 48 | + QScopedPointer<Transform> trainingWrapper(Transform::make("DirectStream([Identity])", NULL)); | |
| 49 | + CompositeTransform * downcast = dynamic_cast<CompositeTransform *>(trainingWrapper.data()); | |
| 50 | + if (downcast == NULL) | |
| 51 | + qFatal("downcast failed?"); | |
| 52 | + downcast->transforms[0] = this->transform.data(); | |
| 53 | + | |
| 54 | + downcast->init(); | |
| 55 | + | |
| 47 | 56 | TemplateList data(TemplateList::fromGallery(input)); |
| 48 | 57 | |
| 49 | 58 | // set the Train bool metadata, in case a Transform's project |
| ... | ... | @@ -56,7 +65,7 @@ struct AlgorithmCore |
| 56 | 65 | |
| 57 | 66 | QTime time; time.start(); |
| 58 | 67 | qDebug("Training Enrollment"); |
| 59 | - transform->train(data); | |
| 68 | + downcast->train(data); | |
| 60 | 69 | |
| 61 | 70 | if (!distance.isNull()) { |
| 62 | 71 | qDebug("Projecting Enrollment"); |
| ... | ... | @@ -114,74 +123,64 @@ struct AlgorithmCore |
| 114 | 123 | |
| 115 | 124 | FileList enroll(File input, File gallery = File()) |
| 116 | 125 | { |
| 126 | + FileList files; | |
| 127 | + | |
| 117 | 128 | qDebug("Enrolling %s%s", qPrintable(input.flat()), |
| 118 | 129 | gallery.isNull() ? "" : qPrintable(" to " + gallery.flat())); |
| 119 | 130 | |
| 120 | - FileList fileList; | |
| 121 | 131 | if (gallery.name.isEmpty()) { |
| 122 | 132 | if (input.name.isEmpty()) return FileList(); |
| 123 | 133 | else gallery = getMemoryGallery(input); |
| 124 | 134 | } |
| 135 | + TemplateList data(TemplateList::fromGallery(input)); | |
| 125 | 136 | |
| 126 | - QScopedPointer<Gallery> g(Gallery::make(gallery)); | |
| 127 | - if (g.isNull()) qFatal("Null gallery!"); | |
| 128 | - | |
| 129 | - do { | |
| 130 | - fileList.clear(); | |
| 131 | - | |
| 132 | - if (gallery.contains("read") || gallery.contains("cache")) | |
| 133 | - fileList = g->files(); | |
| 134 | - | |
| 135 | - if (!fileList.isEmpty() && gallery.contains("cache")) | |
| 136 | - return fileList; | |
| 137 | - | |
| 138 | - const TemplateList i(TemplateList::fromGallery(input)); | |
| 139 | - if (i.isEmpty()) return fileList; // Nothing to enroll | |
| 140 | - | |
| 141 | - if (transform.isNull()) qFatal("Null transform."); | |
| 142 | - const int blocks = Globals->blocks(i.size()); | |
| 143 | - Globals->currentStep = 0; | |
| 144 | - Globals->totalSteps = i.size(); | |
| 145 | - Globals->startTime.start(); | |
| 146 | - | |
| 147 | - const bool noDuplicates = gallery.contains("noDuplicates"); | |
| 148 | - QStringList fileNames = noDuplicates ? fileList.names() : QStringList(); | |
| 149 | - const int subBlockSize = 4*std::max(1, Globals->parallelism); | |
| 150 | - const int numSubBlocks = ceil(1.0*Globals->blockSize/subBlockSize); | |
| 151 | - int totalCount = 0, failureCount = 0; | |
| 152 | - double totalBytes = 0; | |
| 153 | - for (int block=0; block<blocks; block++) { | |
| 154 | - for (int subBlock = 0; subBlock<numSubBlocks; subBlock++) { | |
| 155 | - TemplateList data = i.mid(block*Globals->blockSize + subBlock*subBlockSize, subBlockSize); | |
| 156 | - if (data.isEmpty()) break; | |
| 157 | - if (noDuplicates) | |
| 158 | - for (int i=data.size()-1; i>=0; i--) | |
| 159 | - if (fileNames.contains(data[i].file.name)) | |
| 160 | - data.removeAt(i); | |
| 161 | - const int numFiles = data.size(); | |
| 162 | - | |
| 163 | - data >> *transform; | |
| 164 | - | |
| 165 | - g->writeBlock(data); | |
| 166 | - const FileList newFiles = data.files(); | |
| 167 | - fileList.append(newFiles); | |
| 168 | - | |
| 169 | - totalCount += newFiles.size(); | |
| 170 | - failureCount += newFiles.failures(); | |
| 171 | - totalBytes += data.bytes<double>(); | |
| 172 | - Globals->currentStep += numFiles; | |
| 173 | - Globals->printStatus(); | |
| 137 | + if (gallery.contains("append")) | |
| 138 | + { | |
| 139 | + // Remove any templates which are already in the gallery | |
| 140 | + QScopedPointer<Gallery> g(Gallery::make(gallery)); | |
| 141 | + files = g->files(); | |
| 142 | + QSet<QString> nameSet = QSet<QString>::fromList(files.names()); | |
| 143 | + for (int i = data.size() - 1; i>=0; i--) { | |
| 144 | + if (nameSet.contains(data[i].file.name)) | |
| 145 | + { | |
| 146 | + data.removeAt(i); | |
| 174 | 147 | } |
| 175 | 148 | } |
| 149 | + } | |
| 150 | + | |
| 151 | + if (data.empty()) | |
| 152 | + return files; | |
| 153 | + | |
| 154 | + // Trust me, this makes complete sense. | |
| 155 | + // We're just going to make a pipe with a placeholder first transform | |
| 156 | + QString pipeDesc = "Identity+GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(data.length())+")+Discard"; | |
| 157 | + QScopedPointer<Transform> basePipe(Transform::make(pipeDesc,NULL)); | |
| 158 | + | |
| 159 | + CompositeTransform * downcast = dynamic_cast<CompositeTransform *>(basePipe.data()); | |
| 160 | + if (downcast == NULL) | |
| 161 | + qFatal("downcast failed?"); | |
| 162 | + | |
| 163 | + // replace that placeholder with the current algorithm | |
| 164 | + downcast->transforms[0] = this->transform.data(); | |
| 165 | + | |
| 166 | + // call init on the pipe to collapse the algorithm (if its top level is a pipe) | |
| 167 | + downcast->init(); | |
| 168 | + | |
| 169 | + // Next, we make a Stream (with placeholder transform) | |
| 170 | + QString streamDesc = "Stream(Identity, readMode=DistributeFrames)"; | |
| 171 | + QScopedPointer<Transform> baseStream(Transform::make(streamDesc, NULL)); | |
| 172 | + WrapperTransform * wrapper = dynamic_cast<WrapperTransform *> (baseStream.data()); | |
| 173 | + | |
| 174 | + // replace that placeholder with the pipe we built | |
| 175 | + wrapper->transform = downcast; | |
| 176 | + | |
| 177 | + // and get the final stream's stages by reinterpreting the pipe. Perfectly straightforward. | |
| 178 | + wrapper->init(); | |
| 176 | 179 | |
| 177 | - const float speed = 1000 * Globals->totalSteps / Globals->startTime.elapsed() / std::max(1, abs(Globals->parallelism)); | |
| 178 | - if (!Globals->quiet && (Globals->totalSteps > 1)) | |
| 179 | - fprintf(stderr, "\rTIME ELAPSED (MINS) %f SPEED=%.1e SIZE=%.4g FAILURES=%d/%d \n", | |
| 180 | - Globals->startTime.elapsed()/1000./60.,speed, totalBytes/totalCount, failureCount, totalCount); | |
| 181 | - Globals->totalSteps = 0; | |
| 182 | - } while (input.getBool("infinite")); | |
| 180 | + wrapper->projectUpdate(data,data); | |
| 183 | 181 | |
| 184 | - return fileList; | |
| 182 | + files.append(data.files()); | |
| 183 | + return files; | |
| 185 | 184 | } |
| 186 | 185 | |
| 187 | 186 | void enroll(TemplateList &data) |
| ... | ... | @@ -312,8 +311,6 @@ private: |
| 312 | 311 | if ((words.size() < 1) || (words.size() > 2)) qFatal("Invalid algorithm format."); |
| 313 | 312 | //! [Parsing the algorithm description] |
| 314 | 313 | |
| 315 | - if (description.getBool("distribute", true)) | |
| 316 | - words[0] = "DistributeTemplate(" + words[0] + ")"; | |
| 317 | 314 | |
| 318 | 315 | //! [Creating the template generation and comparison methods] |
| 319 | 316 | transform = QSharedPointer<Transform>(Transform::make(words[0], NULL)); | ... | ... |
openbr/core/qtutils.cpp
| ... | ... | @@ -427,6 +427,17 @@ QString toString(const QVariantList &variantList) |
| 427 | 427 | return QString(); |
| 428 | 428 | } |
| 429 | 429 | |
| 430 | +QString toTime(int s) | |
| 431 | +{ | |
| 432 | + int h = s / (60*60); | |
| 433 | + int m = (s - h*60*60) / 60; | |
| 434 | + s = (s - h*60*60 - m*60); | |
| 435 | + | |
| 436 | + const QChar fillChar = QLatin1Char('0'); | |
| 437 | + | |
| 438 | + return QString("%1:%2:%3").arg(h,2,10,fillChar).arg(m,2,10,fillChar).arg(s,2,10,fillChar); | |
| 439 | +} | |
| 440 | + | |
| 430 | 441 | float euclideanLength(const QPointF &point) |
| 431 | 442 | { |
| 432 | 443 | return sqrt(pow(point.x(), 2) + pow(point.y(), 2)); | ... | ... |
openbr/core/qtutils.h
| ... | ... | @@ -65,6 +65,7 @@ namespace QtUtils |
| 65 | 65 | QPointF toPoint(const QString &string, bool *ok = NULL); |
| 66 | 66 | QRectF toRect(const QString &string, bool *ok = NULL); |
| 67 | 67 | QStringList naturalSort(const QStringList &strings); |
| 68 | + QString toTime(int s); | |
| 68 | 69 | |
| 69 | 70 | /**** Process Utilities ****/ |
| 70 | 71 | bool runRScript(const QString &file); | ... | ... |
openbr/openbr_plugin.cpp
| ... | ... | @@ -839,10 +839,7 @@ void br::Context::printStatus() |
| 839 | 839 | const float p = progress(); |
| 840 | 840 | if (p < 1) { |
| 841 | 841 | int s = timeRemaining(); |
| 842 | - int h = s / (60*60); | |
| 843 | - int m = (s - h*60*60) / 60; | |
| 844 | - s = (s - h*60*60 - m*60); | |
| 845 | - fprintf(stderr, "%05.2f%% REMAINING=%02d:%02d:%02d COUNT=%g \r", 100 * p, h, m, s, totalSteps); | |
| 842 | + fprintf(stderr, "%05.2f%% REMAINING=%s COUNT=%g \r", 100 * p, QtUtils::toTime(s/1000.0f).toStdString().c_str(), totalSteps); | |
| 846 | 843 | } |
| 847 | 844 | } |
| 848 | 845 | |
| ... | ... | @@ -1156,6 +1153,17 @@ Gallery *Gallery::make(const File &file) |
| 1156 | 1153 | return gallery; |
| 1157 | 1154 | } |
| 1158 | 1155 | |
| 1156 | +// Default init -- if the file contains "append", read the existing | |
| 1157 | +// data and immediately write it | |
| 1158 | +void Gallery::init() | |
| 1159 | +{ | |
| 1160 | + if (file.exists() && file.contains("append")) | |
| 1161 | + { | |
| 1162 | + TemplateList data = this->read(); | |
| 1163 | + this->writeBlock(data); | |
| 1164 | + } | |
| 1165 | +} | |
| 1166 | + | |
| 1159 | 1167 | /* Transform - public methods */ |
| 1160 | 1168 | Transform::Transform(bool _independent, bool _trainable) |
| 1161 | 1169 | { | ... | ... |
openbr/openbr_plugin.h
| ... | ... | @@ -1060,6 +1060,7 @@ public: |
| 1060 | 1060 | void writeBlock(const TemplateList &templates); /*!< \brief Serialize a template list. */ |
| 1061 | 1061 | virtual void write(const Template &t) = 0; /*!< \brief Serialize a template. */ |
| 1062 | 1062 | static Gallery *make(const File &file); /*!< \brief Make a gallery to/from a file on disk. */ |
| 1063 | + void init(); | |
| 1063 | 1064 | |
| 1064 | 1065 | private: |
| 1065 | 1066 | QSharedPointer<Gallery> next; | ... | ... |
openbr/plugins/algorithms.cpp
| ... | ... | @@ -38,7 +38,7 @@ class AlgorithmsInitializer : public Initializer |
| 38 | 38 | Globals->abbreviations.insert("MedianFace", "Open!Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)"); |
| 39 | 39 | Globals->abbreviations.insert("BlurredFaceDetection", "Open+LimitSize(1024)+SkinMask/(Cvt(Gray)+GradientMask)+And+Morph(Erode,16)+LargestConvexArea"); |
| 40 | 40 | Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)!ASEFEyes+Draw"); |
| 41 | - Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection!Show[distribute=false]"); | |
| 41 | + Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection!Show"); | |
| 42 | 42 | Globals->abbreviations.insert("OpenBR", "FaceRecognition"); |
| 43 | 43 | Globals->abbreviations.insert("GenderEstimation", "GenderClassification"); |
| 44 | 44 | Globals->abbreviations.insert("AgeEstimation", "AgeRegression"); | ... | ... |
openbr/plugins/gallery.cpp
| ... | ... | @@ -73,6 +73,11 @@ class arffGallery : public Gallery |
| 73 | 73 | arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(','))); |
| 74 | 74 | arffFile.write(qPrintable(",'" + t.file.get<QString>("Label") + "'\n")); |
| 75 | 75 | } |
| 76 | + | |
| 77 | + void init() | |
| 78 | + { | |
| 79 | + // | |
| 80 | + } | |
| 76 | 81 | }; |
| 77 | 82 | |
| 78 | 83 | BR_REGISTER(Gallery, arffGallery) |
| ... | ... | @@ -94,7 +99,12 @@ class galGallery : public Gallery |
| 94 | 99 | if (file.get<bool>("remove", false)) |
| 95 | 100 | gallery.remove(); |
| 96 | 101 | QtUtils::touchDir(gallery); |
| 97 | - if (!gallery.open(QFile::ReadWrite | QFile::Append)) | |
| 102 | + QFile::OpenMode mode = QFile::ReadWrite; | |
| 103 | + | |
| 104 | + if (file.contains("append")) | |
| 105 | + mode |= QFile::Append; | |
| 106 | + | |
| 107 | + if (!gallery.open(mode)) | |
| 98 | 108 | qFatal("Can't open gallery: %s", qPrintable(gallery.fileName())); |
| 99 | 109 | stream.setDevice(&gallery); |
| 100 | 110 | } |
| ... | ... | @@ -579,6 +589,11 @@ class templateGallery : public Gallery |
| 579 | 589 | (void) t; |
| 580 | 590 | qFatal("No supported."); |
| 581 | 591 | } |
| 592 | + | |
| 593 | + void init() | |
| 594 | + { | |
| 595 | + // | |
| 596 | + } | |
| 582 | 597 | }; |
| 583 | 598 | |
| 584 | 599 | BR_REGISTER(Gallery, templateGallery) |
| ... | ... | @@ -737,6 +752,11 @@ class dbGallery : public Gallery |
| 737 | 752 | (void) t; |
| 738 | 753 | qFatal("Not supported."); |
| 739 | 754 | } |
| 755 | + | |
| 756 | + void init() | |
| 757 | + { | |
| 758 | + // | |
| 759 | + } | |
| 740 | 760 | }; |
| 741 | 761 | |
| 742 | 762 | BR_REGISTER(Gallery, dbGallery) |
| ... | ... | @@ -790,6 +810,11 @@ class googleGallery : public Gallery |
| 790 | 810 | (void) t; |
| 791 | 811 | qFatal("Not supported."); |
| 792 | 812 | } |
| 813 | + | |
| 814 | + void init() | |
| 815 | + { | |
| 816 | + // | |
| 817 | + } | |
| 793 | 818 | }; |
| 794 | 819 | |
| 795 | 820 | BR_REGISTER(Gallery, googleGallery) |
| ... | ... | @@ -883,6 +908,11 @@ class FDDBGallery : public Gallery |
| 883 | 908 | (void) t; |
| 884 | 909 | qFatal("Not implemented."); |
| 885 | 910 | } |
| 911 | + | |
| 912 | + void init() | |
| 913 | + { | |
| 914 | + // | |
| 915 | + } | |
| 886 | 916 | }; |
| 887 | 917 | |
| 888 | 918 | BR_REGISTER(Gallery, FDDBGallery) |
| ... | ... | @@ -927,6 +957,11 @@ class landmarksGallery : public Gallery |
| 927 | 957 | (void) t; |
| 928 | 958 | qFatal("Not implemented."); |
| 929 | 959 | } |
| 960 | + | |
| 961 | + void init() | |
| 962 | + { | |
| 963 | + // | |
| 964 | + } | |
| 930 | 965 | }; |
| 931 | 966 | |
| 932 | 967 | BR_REGISTER(Gallery, landmarksGallery) | ... | ... |
openbr/plugins/misc.cpp
| ... | ... | @@ -14,10 +14,12 @@ |
| 14 | 14 | * limitations under the License. * |
| 15 | 15 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 16 | 16 | |
| 17 | +#include <QElapsedTimer> | |
| 17 | 18 | #include <QRegularExpression> |
| 18 | 19 | #include <opencv2/highgui/highgui.hpp> |
| 19 | 20 | #include "openbr_internal.h" |
| 20 | 21 | #include "openbr/core/opencvutils.h" |
| 22 | +#include "openbr/core/qtutils.h" | |
| 21 | 23 | |
| 22 | 24 | using namespace cv; |
| 23 | 25 | |
| ... | ... | @@ -517,6 +519,106 @@ class EventTransform : public UntrainableMetaTransform |
| 517 | 519 | }; |
| 518 | 520 | BR_REGISTER(Transform, EventTransform) |
| 519 | 521 | |
| 522 | + | |
| 523 | +class GalleryOutputTransform : public TimeVaryingTransform | |
| 524 | +{ | |
| 525 | + Q_OBJECT | |
| 526 | + | |
| 527 | + Q_PROPERTY(QString outputString READ get_outputString WRITE set_outputString RESET reset_outputString STORED false) | |
| 528 | + BR_PROPERTY(QString, outputString, "") | |
| 529 | + | |
| 530 | + void projectUpdate(const TemplateList &src, TemplateList &dst) | |
| 531 | + { | |
| 532 | + if (src.empty()) | |
| 533 | + return; | |
| 534 | + dst = src; | |
| 535 | + writer->writeBlock(dst); | |
| 536 | + } | |
| 537 | + | |
| 538 | + void train(const TemplateList& data) | |
| 539 | + { | |
| 540 | + (void) data; | |
| 541 | + } | |
| 542 | + ; | |
| 543 | + void init() | |
| 544 | + { | |
| 545 | + writer = QSharedPointer<Gallery>(Gallery::make(outputString)); | |
| 546 | + } | |
| 547 | + | |
| 548 | + QSharedPointer<Gallery> writer; | |
| 549 | +public: | |
| 550 | + GalleryOutputTransform() : TimeVaryingTransform(false,false) {} | |
| 551 | +}; | |
| 552 | + | |
| 553 | +BR_REGISTER(Transform, GalleryOutputTransform) | |
| 554 | + | |
| 555 | +class ProgressCounterTransform : public TimeVaryingTransform | |
| 556 | +{ | |
| 557 | + Q_OBJECT | |
| 558 | + | |
| 559 | + Q_PROPERTY(int totalTemplates READ get_totalTemplates WRITE set_totalTemplates RESET reset_totalTemplates STORED false) | |
| 560 | + BR_PROPERTY(int, totalTemplates, 1) | |
| 561 | + | |
| 562 | + void projectUpdate(const TemplateList &src, TemplateList &dst) | |
| 563 | + { | |
| 564 | + dst = src; | |
| 565 | + qint64 elapsed = timer.elapsed(); | |
| 566 | + calls++; | |
| 567 | + set_calls++; | |
| 568 | + // updated every 10 seconds | |
| 569 | + if (elapsed > 5 * 1000) { | |
| 570 | + float f_elapsed = elapsed / 1000.0f; | |
| 571 | + // remaining calls (according to our input variable) | |
| 572 | + int remaining = totalTemplates - calls; | |
| 573 | + // calls / second | |
| 574 | + float speed = set_calls / f_elapsed; | |
| 575 | + | |
| 576 | + float p = 100 * float(calls) / totalTemplates; | |
| 577 | + | |
| 578 | + // seconds remaining | |
| 579 | + int s = float(remaining) / speed; | |
| 580 | + | |
| 581 | + fprintf(stderr, "%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(s).toStdString().c_str(), float(calls)); | |
| 582 | + | |
| 583 | + timer.start(); | |
| 584 | + set_calls = 0; | |
| 585 | + } | |
| 586 | + | |
| 587 | + | |
| 588 | + return; | |
| 589 | + } | |
| 590 | + | |
| 591 | + void train(const TemplateList& data) | |
| 592 | + { | |
| 593 | + (void) data; | |
| 594 | + } | |
| 595 | + | |
| 596 | + void finalize(TemplateList & data) | |
| 597 | + { | |
| 598 | + (void) data; | |
| 599 | + float p = 100 * float(calls) / totalTemplates; | |
| 600 | + qDebug("%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), float(calls)); | |
| 601 | + } | |
| 602 | + | |
| 603 | + void init() | |
| 604 | + { | |
| 605 | + calls = 0; | |
| 606 | + set_calls = 0; | |
| 607 | + timer.start(); | |
| 608 | + Globals->startTime.start(); | |
| 609 | + } | |
| 610 | + | |
| 611 | +public: | |
| 612 | + ProgressCounterTransform() : TimeVaryingTransform(false,false) {} | |
| 613 | + bool initialized; | |
| 614 | + QElapsedTimer timer; | |
| 615 | + qint64 calls; | |
| 616 | + qint64 set_calls; | |
| 617 | + | |
| 618 | +}; | |
| 619 | + | |
| 620 | +BR_REGISTER(Transform, ProgressCounterTransform) | |
| 621 | + | |
| 520 | 622 | } |
| 521 | 623 | |
| 522 | 624 | #include "misc.moc" | ... | ... |
openbr/plugins/openbr_internal.h
| ... | ... | @@ -154,6 +154,53 @@ protected: |
| 154 | 154 | } |
| 155 | 155 | }; |
| 156 | 156 | |
| 157 | +/*! | |
| 158 | + * \brief Interface for transforms that act as decorators of another transform | |
| 159 | + */ | |
| 160 | +class BR_EXPORT WrapperTransform : public TimeVaryingTransform | |
| 161 | +{ | |
| 162 | + Q_OBJECT | |
| 163 | +public: | |
| 164 | + WrapperTransform(bool independent = true) : TimeVaryingTransform(independent) | |
| 165 | + { | |
| 166 | + } | |
| 167 | + | |
| 168 | + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) | |
| 169 | + BR_PROPERTY(br::Transform *, transform, NULL) | |
| 170 | + | |
| 171 | + bool timeVarying() const { return transform->timeVarying(); } | |
| 172 | + | |
| 173 | + void project(const Template &src, Template &dst) const | |
| 174 | + { | |
| 175 | + transform->project(src,dst); | |
| 176 | + } | |
| 177 | + | |
| 178 | + void projectUpdate(const Template &src, Template &dst) | |
| 179 | + { | |
| 180 | + transform->projectUpdate(src,dst); | |
| 181 | + } | |
| 182 | + void projectUpdate(const TemplateList & src, TemplateList & dst) | |
| 183 | + { | |
| 184 | + transform->projectUpdate(src,dst); | |
| 185 | + } | |
| 186 | + | |
| 187 | + void train(const QList<TemplateList> & data) | |
| 188 | + { | |
| 189 | + transform->train(data); | |
| 190 | + } | |
| 191 | + | |
| 192 | + virtual void finalize(TemplateList & output) | |
| 193 | + { | |
| 194 | + transform->finalize(output); | |
| 195 | + } | |
| 196 | + | |
| 197 | + void init() | |
| 198 | + { | |
| 199 | + if (transform) | |
| 200 | + this->trainable = transform->trainable; | |
| 201 | + } | |
| 202 | + | |
| 203 | +}; | |
| 157 | 204 | |
| 158 | 205 | /*! |
| 159 | 206 | * \brief A MetaTransform that aggregates some sub-transforms | ... | ... |
openbr/plugins/stream.cpp
| ... | ... | @@ -16,6 +16,17 @@ using namespace cv; |
| 16 | 16 | namespace br |
| 17 | 17 | { |
| 18 | 18 | |
| 19 | +class Idiocy : public QObject | |
| 20 | +{ | |
| 21 | + Q_OBJECT | |
| 22 | +public: | |
| 23 | + enum StreamModes { StreamVideo, | |
| 24 | + DistributeFrames, | |
| 25 | + Auto}; | |
| 26 | + | |
| 27 | + Q_ENUMS(StreamModes) | |
| 28 | +}; | |
| 29 | + | |
| 19 | 30 | class FrameData |
| 20 | 31 | { |
| 21 | 32 | public: |
| ... | ... | @@ -180,198 +191,29 @@ private: |
| 180 | 191 | QList<FrameData *> buffer2; |
| 181 | 192 | }; |
| 182 | 193 | |
| 183 | - | |
| 184 | -// Interface for sequentially getting data from some data source. | |
| 185 | -// Initialized off of a template, can represent a video file (stored in the template's filename) | |
| 186 | -// or a set of images already loaded into memory stored as multiple matrices in an input template. | |
| 187 | -class DataSource | |
| 194 | +// Given a template as input, return N templates as output, one at a time on subsequent | |
| 195 | +// calls to getNext | |
| 196 | +class TemplateProcessor | |
| 188 | 197 | { |
| 189 | 198 | public: |
| 190 | - DataSource(int maxFrames=500) | |
| 191 | - { | |
| 192 | - // The sequence number of the last frame | |
| 193 | - final_frame = -1; | |
| 194 | - for (int i=0; i < maxFrames;i++) | |
| 195 | - { | |
| 196 | - allFrames.addItem(new FrameData()); | |
| 197 | - } | |
| 198 | - } | |
| 199 | - | |
| 200 | - virtual ~DataSource() | |
| 201 | - { | |
| 202 | - while (true) | |
| 203 | - { | |
| 204 | - FrameData * frame = allFrames.tryGetItem(); | |
| 205 | - if (frame == NULL) | |
| 206 | - break; | |
| 207 | - delete frame; | |
| 208 | - } | |
| 209 | - } | |
| 210 | - | |
| 211 | - // non-blocking version of getFrame | |
| 212 | - // Returns a NULL FrameData if too many frames are out, or the | |
| 213 | - // data source is broken. Sets last_frame to true iff the FrameData | |
| 214 | - // returned is the last valid frame, and the data source is now broken. | |
| 215 | - FrameData * tryGetFrame(bool & last_frame) | |
| 216 | - { | |
| 217 | - last_frame = false; | |
| 218 | - | |
| 219 | - if (is_broken) { | |
| 220 | - return NULL; | |
| 221 | - } | |
| 222 | - | |
| 223 | - // Try to get a FrameData from the pool, if we can't it means too many | |
| 224 | - // frames are already out, and we will return NULL to indicate failure | |
| 225 | - FrameData * aFrame = allFrames.tryGetItem(); | |
| 226 | - if (aFrame == NULL) | |
| 227 | - return NULL; | |
| 228 | - | |
| 229 | - // Try to actually read a frame, if this returns false the data source is broken | |
| 230 | - bool res = getNext(*aFrame); | |
| 231 | - | |
| 232 | - // The datasource broke, update final_frame | |
| 233 | - if (!res) | |
| 234 | - { | |
| 235 | - QMutexLocker lock(&last_frame_update); | |
| 236 | - final_frame = lookAhead.back()->sequenceNumber; | |
| 237 | - allFrames.addItem(aFrame); | |
| 238 | - } | |
| 239 | - else { | |
| 240 | - lookAhead.push_back(aFrame); | |
| 241 | - } | |
| 242 | - | |
| 243 | - // we will return the first frame on the lookAhead buffer | |
| 244 | - FrameData * rVal = lookAhead.first(); | |
| 245 | - lookAhead.pop_front(); | |
| 246 | - | |
| 247 | - // If this is the last frame, say so | |
| 248 | - if (rVal->sequenceNumber == final_frame) { | |
| 249 | - last_frame = true; | |
| 250 | - is_broken = true; | |
| 251 | - } | |
| 252 | - | |
| 253 | - return rVal; | |
| 254 | - } | |
| 255 | - | |
| 256 | - // Return a frame to the pool, returns true if the frame returned was the last | |
| 257 | - // frame issued, false otherwise | |
| 258 | - bool returnFrame(FrameData * inputFrame) | |
| 259 | - { | |
| 260 | - int frameNumber = inputFrame->sequenceNumber; | |
| 261 | - | |
| 262 | - inputFrame->data.clear(); | |
| 263 | - inputFrame->sequenceNumber = -1; | |
| 264 | - allFrames.addItem(inputFrame); | |
| 265 | - | |
| 266 | - bool rval = false; | |
| 267 | - | |
| 268 | - QMutexLocker lock(&last_frame_update); | |
| 269 | - | |
| 270 | - if (frameNumber == final_frame) { | |
| 271 | - // We just received the last frame, better pulse | |
| 272 | - allReturned = true; | |
| 273 | - lastReturned.wakeAll(); | |
| 274 | - rval = true; | |
| 275 | - } | |
| 276 | - | |
| 277 | - return rval; | |
| 278 | - } | |
| 279 | - | |
| 280 | - bool waitLast() | |
| 281 | - { | |
| 282 | - QMutexLocker lock(&last_frame_update); | |
| 283 | - | |
| 284 | - while (!allReturned) | |
| 285 | - { | |
| 286 | - // This would be a safer wait if we used a timeout, but | |
| 287 | - // theoretically that should never matter. | |
| 288 | - lastReturned.wait(&last_frame_update); | |
| 289 | - } | |
| 290 | - return true; | |
| 291 | - } | |
| 292 | - | |
| 293 | - bool open(Template & output, int start_index = 0) | |
| 294 | - { | |
| 295 | - is_broken = false; | |
| 296 | - allReturned = false; | |
| 297 | - | |
| 298 | - // The last frame isn't initialized yet | |
| 299 | - final_frame = -1; | |
| 300 | - // Start our sequence numbers from the input index | |
| 301 | - next_sequence_number = start_index; | |
| 302 | - | |
| 303 | - // Actually open the data source | |
| 304 | - bool open_res = concreteOpen(output); | |
| 305 | - | |
| 306 | - // We couldn't open the data source | |
| 307 | - if (!open_res) { | |
| 308 | - is_broken = true; | |
| 309 | - return false; | |
| 310 | - } | |
| 311 | - | |
| 312 | - // Try to get a frame from the global pool | |
| 313 | - FrameData * firstFrame = allFrames.tryGetItem(); | |
| 314 | - | |
| 315 | - // If this fails, things have gone pretty badly. | |
| 316 | - if (firstFrame == NULL) { | |
| 317 | - is_broken = true; | |
| 318 | - return false; | |
| 319 | - } | |
| 320 | - | |
| 321 | - // Read a frame from the video source | |
| 322 | - bool res = getNext(*firstFrame); | |
| 323 | - | |
| 324 | - // the data source broke already, we couldn't even get one frame | |
| 325 | - // from it even though it claimed to have opened successfully. | |
| 326 | - if (!res) { | |
| 327 | - is_broken = true; | |
| 328 | - return false; | |
| 329 | - } | |
| 330 | - | |
| 331 | - // We read one frame ahead of the last one returned, this allows | |
| 332 | - // us to know which frame is the final frame when we return it. | |
| 333 | - lookAhead.append(firstFrame); | |
| 334 | - return true; | |
| 335 | - } | |
| 336 | - | |
| 337 | - /* | |
| 338 | - * Pure virtual methods | |
| 339 | - */ | |
| 340 | - | |
| 341 | - // isOpen doesn't appear to particularly work when used on opencv | |
| 342 | - // VideoCaptures, so we don't use it for anything important. | |
| 199 | + virtual ~TemplateProcessor() {} | |
| 200 | + virtual bool open(Template & input)=0; | |
| 343 | 201 | virtual bool isOpen()=0; |
| 344 | - // Called from open, open the data source specified by the input | |
| 345 | - // template, don't worry about setting any of the state variables | |
| 346 | - // set in open. | |
| 347 | - virtual bool concreteOpen(Template & output) = 0; | |
| 348 | - // Get the next frame from the data source, store the results in | |
| 349 | - // FrameData (including the actual frame and appropriate sequence | |
| 350 | - // number). | |
| 351 | - virtual bool getNext(FrameData & input) = 0; | |
| 352 | - // close the currently open data source. | |
| 353 | - virtual void close() = 0; | |
| 354 | - | |
| 355 | - int next_sequence_number; | |
| 202 | + virtual void close()=0; | |
| 203 | + virtual bool getNextTemplate(Template & output)=0; | |
| 356 | 204 | protected: |
| 357 | - DoubleBuffer allFrames; | |
| 358 | - int final_frame; | |
| 359 | - bool is_broken; | |
| 360 | - bool allReturned; | |
| 361 | - QList<FrameData *> lookAhead; | |
| 362 | - | |
| 363 | - QWaitCondition lastReturned; | |
| 364 | - QMutex last_frame_update; | |
| 205 | + Template basis; | |
| 365 | 206 | }; |
| 366 | 207 | |
| 367 | 208 | static QMutex openLock; |
| 209 | + | |
| 368 | 210 | // Read a video frame by frame using cv::VideoCapture |
| 369 | -class VideoDataSource : public DataSource | |
| 211 | +class VideoReader : public TemplateProcessor | |
| 370 | 212 | { |
| 371 | 213 | public: |
| 372 | - VideoDataSource(int maxFrames) : DataSource(maxFrames) {} | |
| 214 | + VideoReader() {} | |
| 373 | 215 | |
| 374 | - bool concreteOpen(Template &input) | |
| 216 | + bool open(Template &input) | |
| 375 | 217 | { |
| 376 | 218 | basis = input; |
| 377 | 219 | |
| ... | ... | @@ -407,30 +249,23 @@ public: |
| 407 | 249 | |
| 408 | 250 | bool isOpen() { return video.isOpened(); } |
| 409 | 251 | |
| 410 | - void close() { | |
| 411 | - video.release(); | |
| 412 | - } | |
| 252 | + void close() { video.release(); } | |
| 413 | 253 | |
| 414 | -private: | |
| 415 | - bool getNext(FrameData & output) | |
| 254 | + bool getNextTemplate(Template & output) | |
| 416 | 255 | { |
| 417 | 256 | if (!isOpen()) { |
| 418 | 257 | qDebug("video source is not open"); |
| 419 | 258 | return false; |
| 420 | 259 | } |
| 421 | - | |
| 422 | - output.data.append(Template(basis.file)); | |
| 423 | - output.data.last().m() = cv::Mat(); | |
| 424 | - | |
| 425 | - output.sequenceNumber = next_sequence_number; | |
| 426 | - next_sequence_number++; | |
| 260 | + output.file = basis.file; | |
| 261 | + output.m() = cv::Mat(); | |
| 427 | 262 | |
| 428 | 263 | cv::Mat temp; |
| 429 | 264 | bool res = video.read(temp); |
| 430 | 265 | |
| 431 | 266 | if (!res) { |
| 432 | 267 | // The video capture broke, return false. |
| 433 | - output.data.last().m() = cv::Mat(); | |
| 268 | + output.m() = cv::Mat(); | |
| 434 | 269 | close(); |
| 435 | 270 | return false; |
| 436 | 271 | } |
| ... | ... | @@ -438,198 +273,330 @@ private: |
| 438 | 273 | // This clone is critical, if we don't do it then the matrix will |
| 439 | 274 | // be an alias of an internal buffer of the video source, leading |
| 440 | 275 | // to various problems later. |
| 441 | - output.data.last().m() = temp.clone(); | |
| 442 | - | |
| 443 | - output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 276 | + output.m() = temp.clone(); | |
| 444 | 277 | return true; |
| 445 | 278 | } |
| 446 | - | |
| 279 | +protected: | |
| 447 | 280 | cv::VideoCapture video; |
| 448 | - Template basis; | |
| 449 | 281 | }; |
| 450 | 282 | |
| 451 | -// Given a template as input, return its matrices one by one on subsequent calls | |
| 452 | -// to getNext | |
| 453 | -class TemplateDataSource : public DataSource | |
| 283 | + | |
| 284 | +class DirectReturn : public TemplateProcessor | |
| 454 | 285 | { |
| 455 | 286 | public: |
| 456 | - TemplateDataSource(int maxFrames) : DataSource(maxFrames) | |
| 287 | + DirectReturn() | |
| 457 | 288 | { |
| 458 | - current_matrix_idx = INT_MAX; | |
| 459 | 289 | data_ok = false; |
| 460 | 290 | } |
| 461 | 291 | |
| 462 | - // To "open" it we just set appropriate indices, we assume that if this | |
| 463 | - // is an image, it is already loaded into memory. | |
| 464 | - bool concreteOpen(Template &input) | |
| 292 | + // We don't do anything, just prepare to return input when getNext is called. | |
| 293 | + bool open(Template &input) | |
| 465 | 294 | { |
| 466 | 295 | basis = input; |
| 467 | - current_matrix_idx = 0; | |
| 468 | - | |
| 469 | - data_ok = current_matrix_idx < basis.size(); | |
| 296 | + data_ok =true; | |
| 470 | 297 | return data_ok; |
| 471 | 298 | } |
| 472 | 299 | |
| 473 | - bool isOpen() { | |
| 474 | - return data_ok; | |
| 475 | - } | |
| 300 | + bool isOpen() { return data_ok; } | |
| 476 | 301 | |
| 477 | 302 | void close() |
| 478 | 303 | { |
| 479 | - current_matrix_idx = INT_MAX; | |
| 304 | + data_ok = false; | |
| 480 | 305 | basis.clear(); |
| 481 | 306 | } |
| 482 | 307 | |
| 483 | -private: | |
| 484 | - bool getNext(FrameData & output) | |
| 308 | + bool getNextTemplate(Template & output) | |
| 485 | 309 | { |
| 486 | - data_ok = current_matrix_idx < basis.size(); | |
| 487 | 310 | if (!data_ok) |
| 488 | 311 | return false; |
| 489 | - | |
| 490 | - output.data.append(basis[current_matrix_idx]); | |
| 491 | - current_matrix_idx++; | |
| 492 | - | |
| 493 | - output.sequenceNumber = next_sequence_number; | |
| 494 | - next_sequence_number++; | |
| 495 | - | |
| 496 | - output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 312 | + output = basis; | |
| 313 | + data_ok = false; | |
| 497 | 314 | return true; |
| 498 | 315 | } |
| 499 | 316 | |
| 500 | - Template basis; | |
| 501 | - // Index of the next matrix to output from the template | |
| 502 | - int current_matrix_idx; | |
| 503 | - | |
| 504 | - // is current_matrix_idx in bounds? | |
| 317 | +protected: | |
| 318 | + // Have we sent our template yet? | |
| 505 | 319 | bool data_ok; |
| 506 | 320 | }; |
| 507 | 321 | |
| 508 | -// Given a templatelist as input, create appropriate data source for each | |
| 509 | -// individual template | |
| 510 | -class DataSourceManager : public DataSource | |
| 322 | + | |
| 323 | +// Interface for sequentially getting data from some data source. | |
| 324 | +// Given a TemplateList, return single template frames sequentially by applying a TemplateProcessor | |
| 325 | +// to each individual template. | |
| 326 | +class DataSource | |
| 511 | 327 | { |
| 512 | 328 | public: |
| 513 | - DataSourceManager(int activeFrames=100) : DataSource(activeFrames) | |
| 329 | + DataSource(int maxFrames=500) | |
| 514 | 330 | { |
| 515 | - actualSource = NULL; | |
| 331 | + // The sequence number of the last frame | |
| 332 | + final_frame = -1; | |
| 333 | + for (int i=0; i < maxFrames;i++) | |
| 334 | + { | |
| 335 | + allFrames.addItem(new FrameData()); | |
| 336 | + } | |
| 337 | + frameSource = NULL; | |
| 516 | 338 | } |
| 517 | 339 | |
| 518 | - ~DataSourceManager() | |
| 340 | + virtual ~DataSource() | |
| 519 | 341 | { |
| 520 | - close(); | |
| 342 | + while (true) | |
| 343 | + { | |
| 344 | + FrameData * frame = allFrames.tryGetItem(); | |
| 345 | + if (frame == NULL) | |
| 346 | + break; | |
| 347 | + delete frame; | |
| 348 | + } | |
| 521 | 349 | } |
| 522 | 350 | |
| 523 | - int size() | |
| 351 | + void close() | |
| 524 | 352 | { |
| 525 | - return this->allFrames.size(); | |
| 353 | + if (this->frameSource) | |
| 354 | + { | |
| 355 | + frameSource->close(); | |
| 356 | + delete frameSource; | |
| 357 | + frameSource = NULL; | |
| 358 | + } | |
| 526 | 359 | } |
| 527 | 360 | |
| 528 | - void close() | |
| 361 | + int size() | |
| 529 | 362 | { |
| 530 | - if (actualSource) { | |
| 531 | - actualSource->close(); | |
| 532 | - delete actualSource; | |
| 533 | - actualSource = NULL; | |
| 534 | - } | |
| 363 | + return this->templates.size(); | |
| 535 | 364 | } |
| 536 | 365 | |
| 537 | - // We are used through a call to open(TemplateList) | |
| 538 | - bool open(TemplateList & input) | |
| 366 | + bool open(TemplateList & input, br::Idiocy::StreamModes _mode) | |
| 539 | 367 | { |
| 540 | 368 | // Set up variables specific to us |
| 541 | 369 | current_template_idx = 0; |
| 542 | 370 | templates = input; |
| 371 | + mode = _mode; | |
| 372 | + | |
| 373 | + is_broken = false; | |
| 374 | + allReturned = false; | |
| 543 | 375 | |
| 544 | - // Call datasourece::open on the first template to set up | |
| 545 | - // state variables | |
| 546 | - return DataSource::open(templates[current_template_idx]); | |
| 376 | + // The last frame isn't initialized yet | |
| 377 | + final_frame = -1; | |
| 378 | + // Start our sequence numbers from the input index | |
| 379 | + next_sequence_number = 0; | |
| 380 | + | |
| 381 | + // Actually open the data source | |
| 382 | + bool open_res = openNextTemplate(); | |
| 383 | + | |
| 384 | + // We couldn't open the data source | |
| 385 | + if (!open_res) { | |
| 386 | + is_broken = true; | |
| 387 | + return false; | |
| 388 | + } | |
| 389 | + | |
| 390 | + // Try to get a frame from the global pool | |
| 391 | + FrameData * firstFrame = allFrames.tryGetItem(); | |
| 392 | + | |
| 393 | + // If this fails, things have gone pretty badly. | |
| 394 | + if (firstFrame == NULL) { | |
| 395 | + is_broken = true; | |
| 396 | + return false; | |
| 397 | + } | |
| 398 | + | |
| 399 | + // Read a frame from the video source | |
| 400 | + bool res = getNextFrame(*firstFrame); | |
| 401 | + | |
| 402 | + // the data source broke already, we couldn't even get one frame | |
| 403 | + // from it even though it claimed to have opened successfully. | |
| 404 | + if (!res) { | |
| 405 | + is_broken = true; | |
| 406 | + return false; | |
| 407 | + } | |
| 408 | + | |
| 409 | + // We read one frame ahead of the last one returned, this allows | |
| 410 | + // us to know which frame is the final frame when we return it. | |
| 411 | + lookAhead.append(firstFrame); | |
| 412 | + return true; | |
| 547 | 413 | } |
| 548 | 414 | |
| 549 | - // Create an actual data source of appropriate type for this template | |
| 550 | - // (initially called via the call to DataSource::open, called later | |
| 551 | - // as we run out of frames on our templates). | |
| 552 | - bool concreteOpen(Template & input) | |
| 415 | + | |
| 416 | + // non-blocking version of getFrame | |
| 417 | + // Returns a NULL FrameData if too many frames are out, or the | |
| 418 | + // data source is broken. Sets last_frame to true iff the FrameData | |
| 419 | + // returned is the last valid frame, and the data source is now broken. | |
| 420 | + FrameData * tryGetFrame(bool & last_frame) | |
| 553 | 421 | { |
| 554 | - close(); | |
| 422 | + last_frame = false; | |
| 555 | 423 | |
| 556 | - bool open_res = false; | |
| 557 | - // Input has no matrices? Its probably a video that hasn't been loaded yet | |
| 558 | - if (input.empty()) { | |
| 559 | - actualSource = new VideoDataSource(0); | |
| 560 | - open_res = actualSource->concreteOpen(input); | |
| 424 | + if (is_broken) { | |
| 425 | + return NULL; | |
| 426 | + } | |
| 427 | + | |
| 428 | + // Try to get a FrameData from the pool, if we can't it means too many | |
| 429 | + // frames are already out, and we will return NULL to indicate failure | |
| 430 | + FrameData * aFrame = allFrames.tryGetItem(); | |
| 431 | + if (aFrame == NULL) | |
| 432 | + return NULL; | |
| 433 | + | |
| 434 | + // Try to actually read a frame, if this returns false the data source is broken | |
| 435 | + bool res = getNextFrame(*aFrame); | |
| 436 | + | |
| 437 | + // The datasource broke, update final_frame | |
| 438 | + if (!res) | |
| 439 | + { | |
| 440 | + QMutexLocker lock(&last_frame_update); | |
| 441 | + final_frame = lookAhead.back()->sequenceNumber; | |
| 442 | + allFrames.addItem(aFrame); | |
| 561 | 443 | } |
| 562 | - // If the input is not empty, we assume it is a set of frames already | |
| 563 | - // in memory. | |
| 564 | 444 | else { |
| 565 | - actualSource = new TemplateDataSource(0); | |
| 566 | - open_res = actualSource->concreteOpen(input); | |
| 445 | + lookAhead.push_back(aFrame); | |
| 567 | 446 | } |
| 568 | 447 | |
| 569 | - // The data source failed to open | |
| 570 | - if (!open_res) { | |
| 571 | - delete actualSource; | |
| 572 | - actualSource = NULL; | |
| 573 | - return false; | |
| 448 | + // we will return the first frame on the lookAhead buffer | |
| 449 | + FrameData * rVal = lookAhead.first(); | |
| 450 | + lookAhead.pop_front(); | |
| 451 | + if (rVal->data.empty()) | |
| 452 | + qDebug("returning empty frame from look ahead!"); | |
| 453 | + | |
| 454 | + // If this is the last frame, say so | |
| 455 | + if (rVal->sequenceNumber == final_frame) { | |
| 456 | + last_frame = true; | |
| 457 | + is_broken = true; | |
| 574 | 458 | } |
| 575 | - return true; | |
| 459 | + | |
| 460 | + return rVal; | |
| 576 | 461 | } |
| 577 | 462 | |
| 578 | - bool isOpen() { return !actualSource ? false : actualSource->isOpen(); } | |
| 463 | + // Return a frame to the pool, returns true if the frame returned was the last | |
| 464 | + // frame issued, false otherwise | |
| 465 | + bool returnFrame(FrameData * inputFrame) | |
| 466 | + { | |
| 467 | + int frameNumber = inputFrame->sequenceNumber; | |
| 579 | 468 | |
| 580 | -protected: | |
| 581 | - // Index of the template in the templatelist we are currently reading from | |
| 582 | - int current_template_idx; | |
| 469 | + inputFrame->data.clear(); | |
| 470 | + inputFrame->sequenceNumber = -1; | |
| 471 | + allFrames.addItem(inputFrame); | |
| 583 | 472 | |
| 584 | - TemplateList templates; | |
| 585 | - DataSource * actualSource; | |
| 586 | - // Get the next frame, if we run out of frames on the current template | |
| 587 | - // move on to the next one. | |
| 588 | - bool getNext(FrameData & output) | |
| 473 | + bool rval = false; | |
| 474 | + | |
| 475 | + QMutexLocker lock(&last_frame_update); | |
| 476 | + | |
| 477 | + if (frameNumber == final_frame) { | |
| 478 | + // We just received the last frame, better pulse | |
| 479 | + allReturned = true; | |
| 480 | + lastReturned.wakeAll(); | |
| 481 | + rval = true; | |
| 482 | + } | |
| 483 | + | |
| 484 | + return rval; | |
| 485 | + } | |
| 486 | + | |
| 487 | + bool waitLast() | |
| 589 | 488 | { |
| 590 | - bool res = actualSource->getNext(output); | |
| 591 | - output.sequenceNumber = next_sequence_number; | |
| 592 | - | |
| 593 | - // OK we got a frame | |
| 594 | - if (res) { | |
| 595 | - // Override the sequence number set by actualSource | |
| 596 | - output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 597 | - next_sequence_number++; | |
| 598 | - if (output.data.last().last().empty()) | |
| 599 | - qDebug("broken matrix"); | |
| 600 | - return true; | |
| 489 | + QMutexLocker lock(&last_frame_update); | |
| 490 | + | |
| 491 | + while (!allReturned) | |
| 492 | + { | |
| 493 | + // This would be a safer wait if we used a timeout, but | |
| 494 | + // theoretically that should never matter. | |
| 495 | + lastReturned.wait(&last_frame_update); | |
| 601 | 496 | } |
| 497 | + return true; | |
| 498 | + } | |
| 602 | 499 | |
| 603 | - // We didn't get a frame, try to move on to the next template. | |
| 604 | - while(!res) { | |
| 605 | - output.data.clear(); | |
| 606 | - current_template_idx++; | |
| 500 | +protected: | |
| 607 | 501 | |
| 608 | - // No more templates? We're done | |
| 609 | - if (current_template_idx >= templates.size()) | |
| 610 | - return false; | |
| 502 | + bool openNextTemplate() | |
| 503 | + { | |
| 504 | + if (this->current_template_idx >= this->templates.size()) | |
| 505 | + return false; | |
| 611 | 506 | |
| 612 | - // open the next data source | |
| 613 | - bool open_res = concreteOpen(templates[current_template_idx]); | |
| 614 | - // We couldn't open it, give up? We could maybe continue here | |
| 615 | - // but don't currently. | |
| 616 | - if (!open_res) | |
| 617 | - return false; | |
| 507 | + bool open_res = false; | |
| 508 | + while (!open_res) | |
| 509 | + { | |
| 510 | + if (frameSource) | |
| 511 | + frameSource->close(); | |
| 618 | 512 | |
| 619 | - // get a frame from the newly opened data source, if that fails | |
| 620 | - // we continue to open the next one. | |
| 621 | - res = actualSource->getNext(output); | |
| 513 | + if (mode == br::Idiocy::Auto) | |
| 514 | + { | |
| 515 | + delete frameSource; | |
| 516 | + if (this->templates[this->current_template_idx].empty()) | |
| 517 | + frameSource = new VideoReader(); | |
| 518 | + else | |
| 519 | + frameSource = new DirectReturn(); | |
| 520 | + } | |
| 521 | + else if (mode == br::Idiocy::DistributeFrames) | |
| 522 | + { | |
| 523 | + if (!frameSource) | |
| 524 | + frameSource = new DirectReturn(); | |
| 525 | + } | |
| 526 | + else if (mode == br::Idiocy::StreamVideo) | |
| 527 | + { | |
| 528 | + if (!frameSource) | |
| 529 | + frameSource = new VideoReader(); | |
| 530 | + } | |
| 531 | + | |
| 532 | + open_res = frameSource->open(this->templates[current_template_idx]); | |
| 533 | + if (!open_res) | |
| 534 | + { | |
| 535 | + current_template_idx++; | |
| 536 | + if (current_template_idx >= this->templates.size()) | |
| 537 | + return false; | |
| 538 | + } | |
| 622 | 539 | } |
| 623 | - // Finally, set the sequence number for the frame we actually return. | |
| 624 | - output.sequenceNumber = next_sequence_number++; | |
| 625 | - output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 540 | + return true; | |
| 541 | + } | |
| 626 | 542 | |
| 627 | - if (output.data.last().last().empty()) | |
| 628 | - qDebug("broken matrix"); | |
| 543 | + bool getNextFrame(FrameData & output) | |
| 544 | + { | |
| 545 | + bool got_frame = false; | |
| 629 | 546 | |
| 630 | - return res; | |
| 547 | + Template aTemplate; | |
| 548 | + | |
| 549 | + while (!got_frame) | |
| 550 | + { | |
| 551 | + got_frame = frameSource->getNextTemplate(aTemplate); | |
| 552 | + | |
| 553 | + // OK we got a frame | |
| 554 | + if (got_frame) { | |
| 555 | + // set the sequence number and tempalte of this frame | |
| 556 | + output.sequenceNumber = next_sequence_number; | |
| 557 | + output.data.append(aTemplate); | |
| 558 | + // set the frame number in the template's metadata | |
| 559 | + output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 560 | + next_sequence_number++; | |
| 561 | + return true; | |
| 562 | + } | |
| 563 | + | |
| 564 | + // advance to the next tempalte in our list | |
| 565 | + this->current_template_idx++; | |
| 566 | + bool open_res = this->openNextTemplate(); | |
| 567 | + | |
| 568 | + // couldn't get the next template? nothing to do, otherwise we try to read | |
| 569 | + // a frame at the top of this loop. | |
| 570 | + if (!open_res) { | |
| 571 | + return false; | |
| 572 | + } | |
| 573 | + } | |
| 574 | + | |
| 575 | + return false; | |
| 631 | 576 | } |
| 632 | 577 | |
| 578 | + // Index of the template in the templatelist we are currently reading from | |
| 579 | + int current_template_idx; | |
| 580 | + | |
| 581 | + // What do we do to each template | |
| 582 | + br::Idiocy::StreamModes mode; | |
| 583 | + | |
| 584 | + // list of templates we are workign from | |
| 585 | + TemplateList templates; | |
| 586 | + | |
| 587 | + // processor for the current template | |
| 588 | + TemplateProcessor * frameSource; | |
| 589 | + | |
| 590 | + int next_sequence_number; | |
| 591 | + int final_frame; | |
| 592 | + bool is_broken; | |
| 593 | + bool allReturned; | |
| 594 | + | |
| 595 | + DoubleBuffer allFrames; | |
| 596 | + QList<FrameData *> lookAhead; | |
| 597 | + | |
| 598 | + QWaitCondition lastReturned; | |
| 599 | + QMutex last_frame_update; | |
| 633 | 600 | }; |
| 634 | 601 | |
| 635 | 602 | class ProcessingStage; |
| ... | ... | @@ -710,6 +677,7 @@ public: |
| 710 | 677 | if (input == NULL) { |
| 711 | 678 | qFatal("null input to multi-thread stage"); |
| 712 | 679 | } |
| 680 | + | |
| 713 | 681 | input->data >> *transform; |
| 714 | 682 | |
| 715 | 683 | should_continue = nextStage->tryAcquireNextStage(input); |
| ... | ... | @@ -861,13 +829,35 @@ public: |
| 861 | 829 | |
| 862 | 830 | }; |
| 863 | 831 | |
| 832 | +// Semi-functional, doesn't do anything productive outside of stream::train | |
| 833 | +class CollectSets : public TimeVaryingTransform | |
| 834 | +{ | |
| 835 | + Q_OBJECT | |
| 836 | +public: | |
| 837 | + CollectSets() : TimeVaryingTransform(false, false) {} | |
| 838 | + | |
| 839 | + QList<TemplateList> sets; | |
| 840 | + | |
| 841 | + void projectUpdate(const TemplateList &src, TemplateList &dst) | |
| 842 | + { | |
| 843 | + (void) dst; | |
| 844 | + sets.append(src); | |
| 845 | + } | |
| 846 | + | |
| 847 | + void train(const TemplateList & data) | |
| 848 | + { | |
| 849 | + (void) data; | |
| 850 | + } | |
| 851 | + | |
| 852 | +}; | |
| 853 | + | |
| 864 | 854 | // This stage reads new frames from the data source. |
| 865 | -class FirstStage : public SingleThreadStage | |
| 855 | +class ReadStage : public SingleThreadStage | |
| 866 | 856 | { |
| 867 | 857 | public: |
| 868 | - FirstStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ } | |
| 858 | + ReadStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ } | |
| 869 | 859 | |
| 870 | - DataSourceManager dataSource; | |
| 860 | + DataSource dataSource; | |
| 871 | 861 | |
| 872 | 862 | void reset() |
| 873 | 863 | { |
| ... | ... | @@ -951,99 +941,17 @@ public: |
| 951 | 941 | void status(){ |
| 952 | 942 | qDebug("Read stage %d, status starting? %d, next frame %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->dataSource.size()); |
| 953 | 943 | } |
| 954 | - | |
| 955 | - | |
| 956 | -}; | |
| 957 | - | |
| 958 | -// Appened to the end of a Stream's transform sequence. Collects the output | |
| 959 | -// from each frame on a single templatelist | |
| 960 | -class LastStage : public SingleThreadStage | |
| 961 | -{ | |
| 962 | -public: | |
| 963 | - LastStage(bool _prev_stage_variance) : SingleThreadStage(_prev_stage_variance) {} | |
| 964 | - TemplateList getOutput() | |
| 965 | - { | |
| 966 | - return collectedOutput; | |
| 967 | - } | |
| 968 | - | |
| 969 | -private: | |
| 970 | - TemplateList collectedOutput; | |
| 971 | -public: | |
| 972 | - | |
| 973 | - void reset() | |
| 974 | - { | |
| 975 | - collectedOutput.clear(); | |
| 976 | - SingleThreadStage::reset(); | |
| 977 | - } | |
| 978 | - | |
| 979 | - FrameData * run(FrameData * input, bool & should_continue) | |
| 980 | - { | |
| 981 | - if (input == NULL) { | |
| 982 | - qFatal("NULL input to stage %d", this->stage_id); | |
| 983 | - } | |
| 984 | - | |
| 985 | - if (input->sequenceNumber != next_target) | |
| 986 | - { | |
| 987 | - qFatal("out of order frames for stage %d, got %d expected %d", this->stage_id, input->sequenceNumber, this->next_target); | |
| 988 | - } | |
| 989 | - next_target = input->sequenceNumber + 1; | |
| 990 | - | |
| 991 | - // add the item to our output buffer | |
| 992 | - collectedOutput.append(input->data); | |
| 993 | - | |
| 994 | - // Can we enter the read stage? | |
| 995 | - should_continue = nextStage->tryAcquireNextStage(input); | |
| 996 | - | |
| 997 | - // Is there anything on our input buffer? If so we should start a thread | |
| 998 | - // in this stage to process that frame. | |
| 999 | - QWriteLocker lock(&statusLock); | |
| 1000 | - FrameData * newItem = inputBuffer->tryGetItem(); | |
| 1001 | - if (!newItem) | |
| 1002 | - { | |
| 1003 | - this->currentStatus = STOPPING; | |
| 1004 | - } | |
| 1005 | - lock.unlock(); | |
| 1006 | - | |
| 1007 | - if (newItem) | |
| 1008 | - startThread(newItem); | |
| 1009 | - | |
| 1010 | - return input; | |
| 1011 | - } | |
| 1012 | - | |
| 1013 | - void status(){ | |
| 1014 | - qDebug("Collection stage %d, status starting? %d, next %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->inputBuffer->size()); | |
| 1015 | - } | |
| 1016 | - | |
| 1017 | -}; | |
| 1018 | - | |
| 1019 | -// Semi-functional, doesn't do anything productive outside of stream::train | |
| 1020 | -class CollectSets : public TimeVaryingTransform | |
| 1021 | -{ | |
| 1022 | - Q_OBJECT | |
| 1023 | -public: | |
| 1024 | - CollectSets() : TimeVaryingTransform(false, false) {} | |
| 1025 | - | |
| 1026 | - QList<TemplateList> sets; | |
| 1027 | - | |
| 1028 | - void projectUpdate(const TemplateList &src, TemplateList &dst) | |
| 1029 | - { | |
| 1030 | - (void) dst; | |
| 1031 | - sets.append(src); | |
| 1032 | - } | |
| 1033 | - | |
| 1034 | - void train(const TemplateList & data) | |
| 1035 | - { | |
| 1036 | - (void) data; | |
| 1037 | - } | |
| 1038 | - | |
| 1039 | 944 | }; |
| 1040 | 945 | |
| 1041 | 946 | class DirectStreamTransform : public CompositeTransform |
| 1042 | 947 | { |
| 1043 | 948 | Q_OBJECT |
| 1044 | 949 | public: |
| 950 | + | |
| 1045 | 951 | Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames) |
| 952 | + Q_PROPERTY(br::Idiocy::StreamModes readMode READ get_readMode WRITE set_readMode RESET reset_readMode) | |
| 1046 | 953 | BR_PROPERTY(int, activeFrames, 100) |
| 954 | + BR_PROPERTY(br::Idiocy::StreamModes, readMode, br::Idiocy::Auto) | |
| 1047 | 955 | |
| 1048 | 956 | friend class StreamTransfrom; |
| 1049 | 957 | |
| ... | ... | @@ -1109,13 +1017,21 @@ public: |
| 1109 | 1017 | qFatal("whatever"); |
| 1110 | 1018 | } |
| 1111 | 1019 | |
| 1020 | + | |
| 1021 | + virtual void finalize(TemplateList & output) | |
| 1022 | + { | |
| 1023 | + (void) output; | |
| 1024 | + // Nothing in particular to do here, stream calls finalize | |
| 1025 | + // on all child transforms as part of projectUpdate | |
| 1026 | + } | |
| 1027 | + | |
| 1112 | 1028 | // start processing, consider all templates in src a continuous |
| 1113 | 1029 | // 'video' |
| 1114 | 1030 | void projectUpdate(const TemplateList & src, TemplateList & dst) |
| 1115 | 1031 | { |
| 1116 | 1032 | dst = src; |
| 1117 | 1033 | |
| 1118 | - bool res = readStage->dataSource.open(dst); | |
| 1034 | + bool res = readStage->dataSource.open(dst,readMode); | |
| 1119 | 1035 | if (!res) { |
| 1120 | 1036 | qDebug("stream failed to open %s", qPrintable(dst[0].file.name)); |
| 1121 | 1037 | return; |
| ... | ... | @@ -1149,6 +1065,8 @@ public: |
| 1149 | 1065 | { |
| 1150 | 1066 | TemplateList output_set; |
| 1151 | 1067 | transforms[i]->finalize(output_set); |
| 1068 | + if (output_set.empty()) | |
| 1069 | + continue; | |
| 1152 | 1070 | |
| 1153 | 1071 | for (int j=i+1; j < transforms.size();j++) |
| 1154 | 1072 | { |
| ... | ... | @@ -1159,7 +1077,12 @@ public: |
| 1159 | 1077 | |
| 1160 | 1078 | // dst is set to all output received by the final stage, along |
| 1161 | 1079 | // with anything output via the calls to finalize. |
| 1162 | - dst = collectionStage->getOutput(); | |
| 1080 | + //dst = collectionStage->getOutput(); | |
| 1081 | + foreach(const TemplateList & list, collector->sets) { | |
| 1082 | + dst.append(list); | |
| 1083 | + } | |
| 1084 | + collector->sets.clear(); | |
| 1085 | + | |
| 1163 | 1086 | dst.append(final_output); |
| 1164 | 1087 | |
| 1165 | 1088 | foreach(ProcessingStage * stage, processingStages) { |
| ... | ... | @@ -1167,12 +1090,6 @@ public: |
| 1167 | 1090 | } |
| 1168 | 1091 | } |
| 1169 | 1092 | |
| 1170 | - virtual void finalize(TemplateList & output) | |
| 1171 | - { | |
| 1172 | - (void) output; | |
| 1173 | - // Nothing in particular to do here, stream calls finalize | |
| 1174 | - // on all child transforms as part of projectUpdate | |
| 1175 | - } | |
| 1176 | 1093 | |
| 1177 | 1094 | // Create and link stages |
| 1178 | 1095 | void init() |
| ... | ... | @@ -1193,7 +1110,7 @@ public: |
| 1193 | 1110 | QMutexLocker poolLock(&poolsAccess); |
| 1194 | 1111 | QHash<QObject *, QThreadPool *>::Iterator it; |
| 1195 | 1112 | if (!pools.contains(this->parent())) { |
| 1196 | - it = pools.insert(this->parent(), new QThreadPool(this)); | |
| 1113 | + it = pools.insert(this->parent(), new QThreadPool(this->parent())); | |
| 1197 | 1114 | it.value()->setMaxThreadCount(Globals->parallelism); |
| 1198 | 1115 | } |
| 1199 | 1116 | else it = pools.find(this->parent()); |
| ... | ... | @@ -1202,6 +1119,7 @@ public: |
| 1202 | 1119 | |
| 1203 | 1120 | // Are our children time varying or not? This decides whether |
| 1204 | 1121 | // we run them in single threaded or multi threaded stages |
| 1122 | + stage_variance.clear(); | |
| 1205 | 1123 | stage_variance.reserve(transforms.size()); |
| 1206 | 1124 | foreach (const br::Transform *transform, transforms) { |
| 1207 | 1125 | stage_variance.append(transform->timeVarying()); |
| ... | ... | @@ -1209,7 +1127,7 @@ public: |
| 1209 | 1127 | |
| 1210 | 1128 | // Additionally, we have a separate stage responsible for reading |
| 1211 | 1129 | // frames from the data source |
| 1212 | - readStage = new FirstStage(activeFrames); | |
| 1130 | + readStage = new ReadStage(activeFrames); | |
| 1213 | 1131 | |
| 1214 | 1132 | processingStages.push_back(readStage); |
| 1215 | 1133 | readStage->stage_id = 0; |
| ... | ... | @@ -1244,7 +1162,10 @@ public: |
| 1244 | 1162 | |
| 1245 | 1163 | // We also have the last stage, which just puts the output of the |
| 1246 | 1164 | // previous stages on a template list. |
| 1247 | - collectionStage = new LastStage(prev_stage_variance); | |
| 1165 | + collectionStage = new SingleThreadStage(prev_stage_variance); | |
| 1166 | + collectionStage->transform = this->collector.data(); | |
| 1167 | + | |
| 1168 | + | |
| 1248 | 1169 | processingStages.append(collectionStage); |
| 1249 | 1170 | collectionStage->stage_id = next_stage_id; |
| 1250 | 1171 | collectionStage->stages = &this->processingStages; |
| ... | ... | @@ -1258,6 +1179,11 @@ public: |
| 1258 | 1179 | collectionStage->nextStage = readStage; |
| 1259 | 1180 | } |
| 1260 | 1181 | |
| 1182 | + DirectStreamTransform() | |
| 1183 | + { | |
| 1184 | + this->collector = QSharedPointer<CollectSets>(new CollectSets()); | |
| 1185 | + } | |
| 1186 | + | |
| 1261 | 1187 | ~DirectStreamTransform() |
| 1262 | 1188 | { |
| 1263 | 1189 | // Delete all the stages |
| ... | ... | @@ -1270,8 +1196,9 @@ public: |
| 1270 | 1196 | protected: |
| 1271 | 1197 | QList<bool> stage_variance; |
| 1272 | 1198 | |
| 1273 | - FirstStage * readStage; | |
| 1274 | - LastStage * collectionStage; | |
| 1199 | + ReadStage * readStage; | |
| 1200 | + SingleThreadStage * collectionStage; | |
| 1201 | + QSharedPointer<CollectSets> collector; | |
| 1275 | 1202 | |
| 1276 | 1203 | QList<ProcessingStage *> processingStages; |
| 1277 | 1204 | |
| ... | ... | @@ -1311,21 +1238,20 @@ QMutex DirectStreamTransform::poolsAccess; |
| 1311 | 1238 | |
| 1312 | 1239 | BR_REGISTER(Transform, DirectStreamTransform) |
| 1313 | 1240 | |
| 1314 | -; | |
| 1315 | - | |
| 1316 | -class StreamTransform : public TimeVaryingTransform | |
| 1241 | +class StreamTransform : public WrapperTransform | |
| 1317 | 1242 | { |
| 1318 | 1243 | Q_OBJECT |
| 1319 | 1244 | |
| 1320 | 1245 | public: |
| 1321 | - StreamTransform() : TimeVaryingTransform(false) | |
| 1246 | + StreamTransform() : WrapperTransform(false) | |
| 1322 | 1247 | { |
| 1323 | 1248 | } |
| 1324 | 1249 | |
| 1325 | - Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) | |
| 1326 | 1250 | Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames) |
| 1327 | - BR_PROPERTY(br::Transform *, transform, NULL) | |
| 1251 | + Q_PROPERTY(br::Idiocy::StreamModes readMode READ get_readMode WRITE set_readMode RESET reset_readMode) | |
| 1252 | + | |
| 1328 | 1253 | BR_PROPERTY(int, activeFrames, 100) |
| 1254 | + BR_PROPERTY(br::Idiocy::StreamModes, readMode, br::Idiocy::Auto) | |
| 1329 | 1255 | |
| 1330 | 1256 | bool timeVarying() const { return true; } |
| 1331 | 1257 | |
| ... | ... | @@ -1366,6 +1292,7 @@ public: |
| 1366 | 1292 | basis.setParent(this->parent()); |
| 1367 | 1293 | basis.transforms.clear(); |
| 1368 | 1294 | basis.activeFrames = this->activeFrames; |
| 1295 | + basis.readMode = this->readMode; | |
| 1369 | 1296 | |
| 1370 | 1297 | // We need at least a CompositeTransform * to acess transform's children. |
| 1371 | 1298 | CompositeTransform * downcast = dynamic_cast<CompositeTransform *> (transform); |
| ... | ... | @@ -1448,8 +1375,6 @@ private: |
| 1448 | 1375 | |
| 1449 | 1376 | BR_REGISTER(Transform, StreamTransform) |
| 1450 | 1377 | |
| 1451 | - | |
| 1452 | - | |
| 1453 | 1378 | } // namespace br |
| 1454 | 1379 | |
| 1455 | 1380 | #include "stream.moc" | ... | ... |