diff --git a/.gitignore b/.gitignore index 2dc5649..4116733 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ data/*/img data/*/vid data/PCSO/* +data/lfpw build* scripts/results @@ -38,3 +39,4 @@ scripts/results ### autogenerated sigsets ### data/INRIAPerson/sigset data/KTH/sigset + diff --git a/openbr/core/core.cpp b/openbr/core/core.cpp index 2afb530..6b52578 100644 --- a/openbr/core/core.cpp +++ b/openbr/core/core.cpp @@ -70,7 +70,7 @@ struct AlgorithmCore if (!distance.isNull()) { qDebug("Projecting Enrollment"); - data >> *transform; + data >> *downcast; qDebug("Training Comparison"); distance->train(data); diff --git a/openbr/openbr.cpp b/openbr/openbr.cpp index f138e07..067334b 100644 --- a/openbr/openbr.cpp +++ b/openbr/openbr.cpp @@ -341,6 +341,12 @@ int br_img_channels(br_template tmpl) return t->m().channels(); } +bool br_img_is_empty(br_template tmpl) +{ + Template *t = reinterpret_cast(tmpl); + return t->m().empty(); +} + void br_set_filename(br_template tmpl, const char *filename) { Template *t = reinterpret_cast(tmpl); @@ -356,6 +362,12 @@ br_template_list br_enroll_template(br_template tmpl) return (br_template_list)tl; } +void br_enroll_template_list(br_template_list tl) +{ + TemplateList *realTL = reinterpret_cast(tl); + Enroll(*realTL); +} + br_template br_get_template(br_template_list tl, int index) { TemplateList *realTL = reinterpret_cast(tl); diff --git a/openbr/openbr.h b/openbr/openbr.h index 893588c..64d5be4 100644 --- a/openbr/openbr.h +++ b/openbr/openbr.h @@ -467,6 +467,10 @@ BR_EXPORT int br_img_cols(br_template tmpl); */ BR_EXPORT int br_img_channels(br_template tmpl); /*! + * \brief Returns if the image is empty. + */ +BR_EXPORT bool br_img_is_empty(br_template tmpl); +/*! * \brief Set the filename for a template. */ BR_EXPORT void br_set_filename(br_template tmpl, const char *filename); @@ -476,6 +480,11 @@ BR_EXPORT void br_set_filename(br_template tmpl, const char *filename); */ BR_EXPORT br_template_list br_enroll_template(br_template tmpl); /*! + * \brief Enroll a br::TemplateList from the C API! + * \param tmpl Pointer to a br::TemplateList. + */ +BR_EXPORT void br_enroll_template_list(br_template_list tl); +/*! * \brief Get a pointer to a br::Template at a specified index. * \param tl Pointer to a br::TemplateList. * \param index The index of the br::Template. diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index d2e8f39..09a9e56 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -1184,10 +1184,6 @@ Transform *Transform::make(QString str, QObject *parent) if (Globals->abbreviations.contains(str)) return make(Globals->abbreviations[str], parent); - { // Check for use of '!' as shorthand for Expand - str.replace("!","+Expand+"); - } - //! [Make a pipe] { // Check for use of '+' as shorthand for Pipe(...) QStringList words = parse(str, '+'); diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 60a13e6..609610b 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -1209,7 +1209,9 @@ public: * and copy enough of their state that projectUpdate can safely be called on the original * instance, and the copy concurrently. */ - virtual Transform * smartCopy() { return this;} + virtual Transform * smartCopy(bool & newTransform) { newTransform=false; return this;} + + virtual Transform * smartCopy() {bool junk; return smartCopy(junk);} /*! * \brief Recursively retrieve a named event, returns NULL if an event is not found. diff --git a/openbr/plugins/algorithms.cpp b/openbr/plugins/algorithms.cpp index 1b4e0f4..f6900c4 100644 --- a/openbr/plugins/algorithms.cpp +++ b/openbr/plugins/algorithms.cpp @@ -31,14 +31,14 @@ class AlgorithmsInitializer : public Initializer void initialize() const { // Face - Globals->abbreviations.insert("FaceRecognition", "FaceDetection!!++:MatchProbability(ByteL1)"); - Globals->abbreviations.insert("GenderClassification", "FaceDetection!!++Discard"); - Globals->abbreviations.insert("AgeRegression", "FaceDetection!!++Discard"); - Globals->abbreviations.insert("FaceQuality", "Open!Cascade(FrontalFace)+ASEFEyes+Affine(64,64,0.25,0.35)+ImageQuality+Cvt(Gray)+DFFS+Discard"); - Globals->abbreviations.insert("MedianFace", "Open!Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)"); + Globals->abbreviations.insert("FaceRecognition", "FaceDetection+Expand++Expand+++:MatchProbability(ByteL1)"); + Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand++Expand+++Discard"); + Globals->abbreviations.insert("AgeRegression", "FaceDetection+Expand++Expand+++Discard"); + Globals->abbreviations.insert("FaceQuality", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(64,64,0.25,0.35)+ImageQuality+Cvt(Gray)+DFFS+Discard"); + Globals->abbreviations.insert("MedianFace", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)"); Globals->abbreviations.insert("BlurredFaceDetection", "Open+LimitSize(1024)+SkinMask/(Cvt(Gray)+GradientMask)+And+Morph(Erode,16)+LargestConvexArea"); - Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)!ASEFEyes+Draw"); - Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection!Show"); + Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)+Expand+ASEFEyes+Draw"); + Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection+Expand+Show"); Globals->abbreviations.insert("OpenBR", "FaceRecognition"); Globals->abbreviations.insert("GenderEstimation", "GenderClassification"); Globals->abbreviations.insert("AgeEstimation", "AgeRegression"); @@ -60,7 +60,7 @@ class AlgorithmsInitializer : public Initializer Globals->abbreviations.insert("SURF", "Open+KeyPointDetector(SURF)+KeyPointDescriptor(SURF):KeyPointMatcher(BruteForce)"); Globals->abbreviations.insert("SmallSIFT", "Open+LimitSize(512)+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)"); Globals->abbreviations.insert("SmallSURF", "Open+LimitSize(512)+KeyPointDetector(SURF)+KeyPointDescriptor(SURF):KeyPointMatcher(BruteForce)"); - Globals->abbreviations.insert("ColorHist", "Open+LimitSize(512)!EnsureChannels(3)+SplitChannels+Hist(256,0,8)+Cat+Normalize(L1):L2"); + Globals->abbreviations.insert("ColorHist", "Open+LimitSize(512)+Expand+EnsureChannels(3)+SplitChannels+Hist(256,0,8)+Cat+Normalize(L1):L2"); Globals->abbreviations.insert("ImageClassification", "Open+CropSquare+LimitSize(256)+Cvt(Gray)+Gradient+Bin(0,360,9,true)+Merge+Integral+RecursiveIntegralSampler(4,2,8,Singleton(KMeans(256)))+Cat+CvtFloat+Hist(256)+KNN(5,Dist(L1),false,5)+Rename(KNN,Subject)"); Globals->abbreviations.insert("TanTriggs", "Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)"); diff --git a/openbr/plugins/crop.cpp b/openbr/plugins/crop.cpp index ad5382a..9751962 100644 --- a/openbr/plugins/crop.cpp +++ b/openbr/plugins/crop.cpp @@ -57,10 +57,16 @@ BR_REGISTER(Transform, CropTransform) class ROITransform : public UntrainableTransform { Q_OBJECT + Q_PROPERTY(QString propName READ get_propName WRITE set_propName RESET reset_propName STORED false) + BR_PROPERTY(QString, propName, "") void project(const Template &src, Template &dst) const { - if (src.file.rects().empty()) { + if (!propName.isEmpty()) { + QRectF rect = src.file.get(propName); + dst += src.m()(OpenCVUtils::toRect(rect)); + } + else if (src.file.rects().empty()) { dst = src; if (Globals->verbose) qWarning("No rects present in file."); } diff --git a/openbr/plugins/gui.cpp b/openbr/plugins/gui.cpp index d7bdd74..d3af3b2 100644 --- a/openbr/plugins/gui.cpp +++ b/openbr/plugins/gui.cpp @@ -176,6 +176,10 @@ public: setFixedSize(200,200); QApplication::instance()->installEventFilter(this); } + ~DisplayWindow() + { + QApplication::instance()->removeEventFilter(this); + } public slots: void showImage(const QPixmap & input) @@ -193,7 +197,6 @@ public slots: setFixedSize(temp); } - bool eventFilter(QObject * obj, QEvent * event) { if (event->type() == QEvent::KeyPress) @@ -420,7 +423,10 @@ public: ~ShowTransform() { delete displayBuffer; - delete window; + if (QThread::currentThread() == QCoreApplication::instance()->thread()) + delete window; + else + emit destroyWindow(); } void train(const TemplateList &data) { (void) data; } @@ -494,6 +500,7 @@ public: connect(this, SIGNAL(updateImage(QPixmap)), window,SLOT(showImage(QPixmap))); connect(this, SIGNAL(changeTitle(QString)), window, SLOT(setWindowTitle(QString))); connect(this, SIGNAL(hideWindow()), window, SLOT(hide())); + connect(this, SIGNAL(destroyWindow()), window, SLOT(deleteLater()), Qt::BlockingQueuedConnection); } protected: @@ -506,6 +513,7 @@ signals: void updateImage(const QPixmap & input); void changeTitle(const QString & input); void hideWindow(); + void destroyWindow(); }; BR_REGISTER(Transform, ShowTransform) diff --git a/openbr/plugins/landmarks.cpp b/openbr/plugins/landmarks.cpp index e91afdf..1cb4445 100644 --- a/openbr/plugins/landmarks.cpp +++ b/openbr/plugins/landmarks.cpp @@ -332,6 +332,9 @@ class ReadLandmarksTransform : public UntrainableTransform void init() { + if (file.isEmpty()) + return; + QFile f(file); if (!f.open(QFile::ReadOnly | QFile::Text)) qFatal("Failed to open %s for reading.", qPrintable(f.fileName())); diff --git a/openbr/plugins/meta.cpp b/openbr/plugins/meta.cpp index 19aa56b..c713c98 100644 --- a/openbr/plugins/meta.cpp +++ b/openbr/plugins/meta.cpp @@ -614,13 +614,20 @@ class DistributeTemplateTransform : public MetaTransform public: - Transform * smartCopy() + Transform * smartCopy(bool & newTransform) { - if (!transform->timeVarying()) + if (!transform->timeVarying()) { + newTransform = false; return this; + } + newTransform = true; DistributeTemplateTransform * output = new DistributeTemplateTransform; - output->transform = transform->smartCopy(); + bool newChild = false; + output->transform = transform->smartCopy(newChild); + if (newChild) + output->transform->setParent(output); + return output; } diff --git a/openbr/plugins/openbr_internal.h b/openbr/plugins/openbr_internal.h index b56daad..2680873 100644 --- a/openbr/plugins/openbr_internal.h +++ b/openbr/plugins/openbr_internal.h @@ -139,8 +139,9 @@ public: *\brief For transforms that don't do any training, this default implementation * which creates a new copy of the Transform from its description string is sufficient. */ - virtual Transform * smartCopy() + virtual Transform * smartCopy(bool & newTransform) { + newTransform = true; return this->clone(); } @@ -250,10 +251,13 @@ public: * it creates a new copy of its own class, and gives that copy the child transforms * returned by calling smartCopy on this transforms children */ - Transform * smartCopy() + Transform * smartCopy(bool & newTransform) { - if (!timeVarying()) + if (!timeVarying()) { + newTransform = false; return this; + } + newTransform = true; QString name = metaObject()->className(); name.replace("Transform",""); @@ -266,8 +270,9 @@ public: foreach(Transform* t, transforms ) { - Transform * maybe_copy = t->smartCopy(); - if (maybe_copy->parent() == NULL) + bool newItem = false; + Transform * maybe_copy = t->smartCopy(newItem); + if (newItem) maybe_copy->setParent(output); output->transforms.append(maybe_copy); } diff --git a/openbr/plugins/slidingwindow.cpp b/openbr/plugins/slidingwindow.cpp index 81468c8..8d38226 100644 --- a/openbr/plugins/slidingwindow.cpp +++ b/openbr/plugins/slidingwindow.cpp @@ -380,8 +380,10 @@ private: QList rects; QList confidences; foreach (const Template &t, src) { - rects.append(OpenCVUtils::toRect(t.file.get("Detection"))); - confidences.append(t.file.get("Confidence")); + if (t.file.contains("Detection")) { + rects.append(OpenCVUtils::toRect(t.file.get("Detection"))); + confidences.append(t.file.get("Confidence")); + } } // Compute overlap between rectangles and create discrete Laplacian matrix diff --git a/openbr/plugins/stream.cpp b/openbr/plugins/stream.cpp index 56f8b55..43673d1 100644 --- a/openbr/plugins/stream.cpp +++ b/openbr/plugins/stream.cpp @@ -1092,10 +1092,6 @@ public: // dst is set to all output received by the final stage, along // with anything output via the calls to finalize. - //dst = collectionStage->getOutput(); - - // dst is set to all output received by the final stage, along - // with anything output via the calls to finalize. foreach(const TemplateList & list, collector->sets) { dst.append(list); } @@ -1378,10 +1374,10 @@ public: basis.init(); } - Transform * smartCopy() + Transform * smartCopy(bool & newTransform) { // We just want the DirectStream to begin with, so just return a copy of that. - DirectStreamTransform * res = (DirectStreamTransform *) basis.smartCopy(); + DirectStreamTransform * res = (DirectStreamTransform *) basis.smartCopy(newTransform); res->activeFrames = this->activeFrames; return res; } diff --git a/scripts/evalFaceRecognition-LFW.sh b/scripts/evalFaceRecognition-LFW.sh new file mode 100755 index 0000000..4f0e6c4 --- /dev/null +++ b/scripts/evalFaceRecognition-LFW.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +ALGORITHM=FaceRecognition + +if [ ! -f evalFaceRecognition-LFW.sh ]; then + echo "Run this script from the scripts folder!" + exit +fi + +if ! hash br 2>/dev/null; then + echo "Can't find 'br'. Did you forget to build and install OpenBR? Here's some help: http://openbiometrics.org/doxygen/latest/installation.html" + exit +fi + +# Get the data +./downloadDatasets.sh + +if [ ! -e Algorithm_Dataset ]; then + mkdir Algorithm_Dataset +fi + +# Run the LFW test protocol +br -useGui 0 -algorithm $ALGORITHM -path ../data/LFW/img/ -crossValidate 10 -pairwiseCompare ../data/LFW/sigset/test_image_restricted_target.xml ../data/LFW/sigset/test_image_restricted_query.xml ${ALGORITHM}_LFW.mtx -convert Output ${ALGORITHM}_lfw.mtx Algorithm_Dataset/${ALGORITHM}_LFW%1.eval + +# Plot results +br -useGui 0 -plot Algorithm_Dataset/* 'lfw_results.pdf[smooth=Dataset,rocOptions[yLimits=(0,1)]]' diff --git a/scripts/evalGenderClassification-PCSO.sh b/scripts/evalGenderClassification-PCSO.sh index 9373645..d8afbbb 100755 --- a/scripts/evalGenderClassification-PCSO.sh +++ b/scripts/evalGenderClassification-PCSO.sh @@ -5,12 +5,11 @@ if [ ! -f evalGenderClassification-PCSO.sh ]; then fi export BR=../build/app/br/br -export genderAlg=GenderClassification - -export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/ +export ALGORITHM=GenderClassification +export PCSO_DIR=../data/PCSO/img # Create a file list by querying the database $BR -useGui 0 -quiet -algorithm Identity -enroll "$PCSO_DIR/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=1:8000]" terminal.txt > Input.txt # Enroll the file list and evaluate performance -$BR -useGui 0 -algorithm $genderAlg -path $PCSO_DIR/Images -enroll Input.txt Output.txt -evalClassification Output.txt Input.txt Gender \ No newline at end of file +$BR -useGui 0 -algorithm $ALGORITHM -path $PCSO_DIR -enroll Input.txt Output.txt -evalClassification Output.txt Input.txt Gender \ No newline at end of file diff --git a/share/openbr/models b/share/openbr/models index a73d510..bcbff8c 160000 --- a/share/openbr/models +++ b/share/openbr/models @@ -1 +1 @@ -Subproject commit a73d51013ea05f263e88a28539393159fff2183e +Subproject commit bcbff8c485f19daddb2e6b2abd5a505ed8c1e526