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 cea16f1..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); @@ -490,9 +490,18 @@ void br::Cat(const QStringList &inputGalleries, const QString &outputGallery) } } -QSharedPointer br::Transform::fromAlgorithm(const QString &algorithm) +QSharedPointer br::Transform::fromAlgorithm(const QString &algorithm, bool preprocess) { - return AlgorithmManager::getAlgorithm(algorithm)->transform; + if (!preprocess) + return AlgorithmManager::getAlgorithm(algorithm)->transform; + else { + QSharedPointer orig_tform = AlgorithmManager::getAlgorithm(algorithm)->transform; + QSharedPointer newRoot = QSharedPointer(Transform::make("Stream(Identity)", NULL)); + WrapperTransform * downcast = dynamic_cast (newRoot.data()); + downcast->transform = orig_tform.data(); + downcast->init(); + return newRoot; + } } QSharedPointer br::Distance::fromAlgorithm(const QString &algorithm) 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 f9acbf8..609610b 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -1105,7 +1105,7 @@ public: virtual ~Transform() {} static Transform *make(QString str, QObject *parent); /*!< \brief Make a transform from a string. */ - static QSharedPointer fromAlgorithm(const QString &algorithm); /*!< \brief Retrieve an algorithm's transform. */ + static QSharedPointer fromAlgorithm(const QString &algorithm, bool preprocess=true); /*!< \brief Retrieve an algorithm's transform. If preprocess is true, attaches a stream transform as the root of the algorithm*/ virtual Transform *clone() const; /*!< \brief Copy the transform. */ @@ -1124,6 +1124,7 @@ public: /*!< \brief Apply the transform to a single template. Typically used by independent transforms */ virtual void project(const Template &src, Template &dst) const = 0; + /*!< \brief Apply the transform, taking the full template list as input. * A TemplateList is what is typically passed from transform to transform. Transforms that just * need to operatoe on a single template at a time (and want to output exactly 1 template) can implement @@ -1208,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/draw.cpp b/openbr/plugins/draw.cpp index ea34e8b..3057b8a 100644 --- a/openbr/plugins/draw.cpp +++ b/openbr/plugins/draw.cpp @@ -56,7 +56,7 @@ class DrawTransform : public UntrainableTransform for (int i=0; i(br::Transform::make("Cache(Open)", NULL)); } }; 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 79732cb..7885408 100644 --- a/openbr/plugins/landmarks.cpp +++ b/openbr/plugins/landmarks.cpp @@ -305,6 +305,116 @@ class DrawDelaunayTransform : public UntrainableTransform BR_REGISTER(Transform, DrawDelaunayTransform) +/*! + * \ingroup transforms + * \brief Read landmarks from a file and associate them with the correct templates. + * \author Scott Klum \cite sklum + * + * Example of the format: + * \code + * image_001.jpg:146.000000,190.000000,227.000000,186.000000,202.000000,256.000000 + * image_002.jpg:75.000000,235.000000,140.000000,225.000000,91.000000,300.000000 + * image_003.jpg:158.000000,186.000000,246.000000,188.000000,208.000000,233.000000 + * \endcode + */ +class ReadLandmarksTransform : public UntrainableTransform +{ + Q_OBJECT + + Q_PROPERTY(QString file READ get_file WRITE set_file RESET reset_file STORED false) + Q_PROPERTY(QString imageDelimiter READ get_imageDelimiter WRITE set_imageDelimiter RESET reset_imageDelimiter STORED false) + Q_PROPERTY(QString landmarkDelimiter READ get_landmarkDelimiter WRITE set_landmarkDelimiter RESET reset_landmarkDelimiter STORED false) + BR_PROPERTY(QString, file, QString()) + BR_PROPERTY(QString, imageDelimiter, ":") + BR_PROPERTY(QString, landmarkDelimiter, ",") + + QHash > landmarks; + + 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())); + + while (!f.atEnd()) { + const QStringList words = QString(f.readLine()).split(imageDelimiter); + const QStringList lm = words[1].split(landmarkDelimiter); + + QList points; + bool ok; + for (int i=0; i indices READ get_indices WRITE set_indices RESET reset_indices STORED false) + Q_PROPERTY(QStringList names READ get_names WRITE set_names RESET reset_names STORED false) + BR_PROPERTY(QList, indices, QList()) + BR_PROPERTY(QStringList, names, QStringList()) + + void project(const Template &src, Template &dst) const + { + if (indices.size() != names.size()) qFatal("Point/name size mismatch"); + + dst = src; + + QList points = src.file.points(); + + for (int i=0; i(name)); + } +}; + +BR_REGISTER(Transform, AnonymizePointsTransform) + } // namespace br #include "landmarks.moc" diff --git a/openbr/plugins/meta.cpp b/openbr/plugins/meta.cpp index 7d61341..c713c98 100644 --- a/openbr/plugins/meta.cpp +++ b/openbr/plugins/meta.cpp @@ -248,8 +248,8 @@ class ExpandTransform : public UntrainableMetaTransform virtual void project(const Template & src, Template & dst) const { - qFatal("this has gone bad"); - (void) src; (void) dst; + dst = src; + qDebug("Called Expand project(Template,Template), nothing will happen"); } }; @@ -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 65c3501..43673d1 100644 --- a/openbr/plugins/stream.cpp +++ b/openbr/plugins/stream.cpp @@ -1007,14 +1007,24 @@ public: void project(const Template &src, Template &dst) const { - (void) src; (void) dst; - qFatal("nope"); + TemplateList in; + in.append(src); + TemplateList out; + CompositeTransform::project(in,out); + dst = out.first(); + if (out.size() > 1) + qDebug("Returning first output template only"); } void projectUpdate(const Template &src, Template &dst) { - (void) src; (void) dst; - qFatal("whatever"); + TemplateList in; + in.append(src); + TemplateList out; + projectUpdate(in,out); + dst = out.first(); + if (out.size() > 1) + qDebug("Returning first output template only"); } @@ -1082,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); } @@ -1368,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/openbr/plugins/template.cpp b/openbr/plugins/template.cpp index 7c10c9f..5d92eba 100644 --- a/openbr/plugins/template.cpp +++ b/openbr/plugins/template.cpp @@ -50,58 +50,6 @@ class RemoveTemplatesTransform : public UntrainableMetaTransform BR_REGISTER(Transform, RemoveTemplatesTransform) -/*! - * \ingroup transforms - * \brief Name a point - * \author Scott Klum \cite sklum - */ -class NamePointsTransform : public UntrainableMetaTransform -{ - Q_OBJECT - Q_PROPERTY(QList indices READ get_indices WRITE set_indices RESET reset_indices STORED false) - Q_PROPERTY(QStringList names READ get_names WRITE set_names RESET reset_names STORED false) - BR_PROPERTY(QList, indices, QList()) - BR_PROPERTY(QStringList, names, QStringList()) - - void project(const Template &src, Template &dst) const - { - if (indices.size() != names.size()) qFatal("Point/name size mismatch"); - - dst = src; - - QList points = src.file.points(); - - for (int i=0; i(name)); - } -}; - -BR_REGISTER(Transform, AnonymizePointsTransform) - } // namespace br #include "template.moc" 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