diff --git a/openbr/core/eval.cpp b/openbr/core/eval.cpp index 158097a..0097086 100644 --- a/openbr/core/eval.cpp +++ b/openbr/core/eval.cpp @@ -130,6 +130,12 @@ float Evaluate(const Mat &simmat, const Mat &mask, const QString &csv) qFatal("Similarity matrix (%ix%i) differs in size from mask matrix (%ix%i).", simmat.rows, simmat.cols, mask.rows, mask.cols); + if (simmat.type() != CV_32FC1) + qFatal("Invalid simmat format"); + + if (mask.type() != CV_8UC1) + qFatal("Invalid mask format"); + float result = -1; // Make comparisons @@ -427,84 +433,106 @@ static QStringList computeDetectionResults(const QList &detec return lines; } -QString getDetectKey(const TemplateList &templates) +struct DetectionKey : public QString +{ + enum Type { + Invalid, + Rect, + RectList, + XYWidthHeight + } type; + + DetectionKey(const QString &key = "", Type type = Invalid) + : QString(key), type(type) {} +}; + +static DetectionKey getDetectKey(const FileList &files) { - const File &f = templates.first().file; - foreach (const QString &key, f.localKeys()) { - // first check for single detections + if (files.empty()) + return DetectionKey(); + + const File &f = files.first(); + const QStringList localKeys = f.localKeys(); + + // first check for single detections + foreach (const QString &key, localKeys) if (!f.get(key, QRectF()).isNull()) - return key; - } + return DetectionKey(key, DetectionKey::Rect); + // and then multiple if (!f.rects().empty()) - return "Rects"; - return ""; -} + return DetectionKey("Rects", DetectionKey::RectList); -bool detectKeyIsList(QString key, const TemplateList &templates) -{ - return templates.first().file.get(key, QRectF()).isNull(); + // check for _X, _Y, _Width, _Height + foreach (const QString &localKey, localKeys) { + if (!localKey.endsWith("_X")) + continue; + const QString key = localKey.mid(0, localKey.size()-2); + if (localKeys.contains(key+"_Y") && + localKeys.contains(key+"_Width") && + localKeys.contains(key+"_Height")) + return DetectionKey(key, DetectionKey::XYWidthHeight); + } + + return DetectionKey(); } -// return a list of detections whether the template holds -// multiple detections or a single detection -QList getDetections(QString key, const Template &t, bool isList, bool isTruth) +// return a list of detections independent of the detection key format +static QList getDetections(const DetectionKey &key, const File &f, bool isTruth) { - File f = t.file; QList dets; - if (isList) { + if (key.type == DetectionKey::RectList) { QList rects = f.rects(); QList confidences = f.getList("Confidences", QList()); if (!isTruth && rects.size() != confidences.size()) qFatal("You don't have enough confidence. I mean, your detections don't all have confidence measures."); for (int i=0; i(key))); - else - dets.append(Detection(f.get(key), f.get("Confidence", -1))); + } else if (key.type == DetectionKey::Rect) { + dets.append(Detection(f.get(key), isTruth ? -1 : f.get("Confidence", -1))); + } else if (key.type == DetectionKey::XYWidthHeight) { + const QRectF rect(f.get(key+"_X"), f.get(key+"_Y"), f.get(key+"_Width"), f.get(key+"_Height")); + dets.append(Detection(rect, isTruth ? -1 : f.get("Confidence", -1))); } return dets; } -QMap getDetections(const TemplateList &predicted, const TemplateList &truth) +static QMap getDetections(const File &predictedGallery, const File &truthGallery) { + const FileList predicted = TemplateList::fromGallery(predictedGallery).files(); + const FileList truth = TemplateList::fromGallery(truthGallery).files(); + // Figure out which metadata field contains a bounding box - QString truthDetectKey = getDetectKey(truth); - if (truthDetectKey.isEmpty()) qFatal("No suitable ground truth metadata key found."); - QString predictedDetectKey = getDetectKey(predicted); - if (predictedDetectKey.isEmpty()) qFatal("No suitable predicted metadata key found."); + DetectionKey truthDetectKey = getDetectKey(truth); + if (truthDetectKey.isEmpty()) + qFatal("No suitable ground truth metadata key found."); + + DetectionKey predictedDetectKey = getDetectKey(predicted); + if (predictedDetectKey.isEmpty()) + qFatal("No suitable predicted metadata key found."); + qDebug("Using metadata key: %s%s", qPrintable(predictedDetectKey), qPrintable(predictedDetectKey == truthDetectKey ? QString() : "/"+truthDetectKey)); QMap allDetections; - bool predKeyIsList = detectKeyIsList(predictedDetectKey, predicted); - bool truthKeyIsList = detectKeyIsList(truthDetectKey, truth); - foreach (const Template &t, predicted) { - QList dets = getDetections(predictedDetectKey, t, predKeyIsList, false); - allDetections[t.file.baseName()].predicted.append(dets); - } - foreach (const Template &t, truth) { - QList dets = getDetections(truthDetectKey, t, truthKeyIsList, true); - allDetections[t.file.baseName()].truth.append(dets); - } + foreach (const File &f, predicted) + allDetections[f.baseName()].predicted.append(getDetections(predictedDetectKey, f, false)); + foreach (const File &f, truth) + allDetections[f.baseName()].truth.append(getDetections(truthDetectKey, f, true)); return allDetections; } float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv) { qDebug("Evaluating detection of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); - const TemplateList predicted(TemplateList::fromGallery(predictedGallery)); - const TemplateList truth(TemplateList::fromGallery(truthGallery)); // Organized by file, QMap used to preserve order - QMap allDetections = getDetections(predicted, truth); + QMap allDetections = getDetections(predictedGallery, truthGallery); QList resolvedDetections, falseNegativeDetections; int totalTrueDetections = 0; diff --git a/openbr/janus b/openbr/janus index 6706011..9b87929 160000 --- a/openbr/janus +++ b/openbr/janus @@ -1 +1 @@ -Subproject commit 670601106366ca802289ca1916a47b6f2ed4976e +Subproject commit 9b8792922eaee3f34ea13a71874885433b61adda diff --git a/openbr/janus.cpp b/openbr/janus.cpp index cff4547..f427e59 100644 --- a/openbr/janus.cpp +++ b/openbr/janus.cpp @@ -77,15 +77,23 @@ janus_error janus_finalize_template(janus_template template_, janus_flat_templat { *bytes = 0; foreach (const cv::Mat &m, *template_) { - assert(m.isContinuous()); + if (!m.data) + continue; + + if (!m.isContinuous()) + return JANUS_UNKNOWN_ERROR; + const size_t templateBytes = m.rows * m.cols * m.elemSize(); if (*bytes + sizeof(size_t) + templateBytes > janus_max_template_size()) break; + memcpy(flat_template, &templateBytes, sizeof(templateBytes)); flat_template += sizeof(templateBytes); + *bytes += sizeof(templateBytes); + memcpy(flat_template, m.data, templateBytes); flat_template += templateBytes; - *bytes += sizeof(size_t) + templateBytes; + *bytes += templateBytes; } delete template_; diff --git a/openbr/plugins/cascade.cpp b/openbr/plugins/cascade.cpp index cc928b5..eed4b3c 100644 --- a/openbr/plugins/cascade.cpp +++ b/openbr/plugins/cascade.cpp @@ -19,12 +19,144 @@ #include "openbr_internal.h" #include "openbr/core/opencvutils.h" #include "openbr/core/resource.h" +#include using namespace cv; + +struct TrainParams +{ + QString data; // REQUIRED: Filepath to store trained classifier + QString vec; // REQUIRED: Filepath to store vector of positive samples, default "vector" + QString img; // Filepath to source object image. Either this or info is REQUIRED + QString info; // Description file of source images. Either this or img is REQUIRED + QString bg; // REQUIRED: Filepath to background list file + int num; // Number of samples to generate + int bgcolor; // Background color supplied image (via img) + int bgthresh; // Threshold to determine bgcolor match + bool inv; // Invert colors + bool randinv; // Randomly invert colors + int maxidev; // Max intensity deviation of foreground pixels + double maxxangle; // Maximum rotation angle (X) + double maxyangle; // Maximum rotation angle (Y) + double maxzangle; // Maximum rotation angle (Z) + bool show; // Show generated samples + int w; // REQUIRED: Sample width + int h; // REQUIRED: Sample height + int numPos; // Number of positive samples + int numNeg; // Number of negative samples + int numStages; // Number of stages + int precalcValBufSize; // Precalculated val buffer size in Mb + int precalcIdxBufSize; // Precalculated index buffer size in Mb + bool baseFormatSave; // Save in old format + QString stageType; // Stage type (BOOST) + QString featureType; // Feature type (HAAR, LBP) + QString bt; // Boosted classifier type (DAB, RAB, LB, GAB) + double minHitRate; // Minimal hit rate per stage + double maxFalseAlarmRate; // Max false alarm rate per stage + double weightTrimRate; // Weight for trimming + int maxDepth; // Max weak tree depth + int maxWeakCount; // Max weak tree count per stage + QString mode; // Haar feature mode (BASIC, CORE, ALL) -namespace br + TrainParams() + { + num = -1; + maxidev = -1; + maxxangle = -1; + maxyangle = -1; + maxzangle = -1; + w = -1; + h = -1; + numPos = -1; + numNeg = -1; + numStages = -1; + precalcValBufSize = -1; + precalcIdxBufSize = -1; + minHitRate = -1; + maxFalseAlarmRate = -1; + weightTrimRate = -1; + maxDepth = -1; + maxWeakCount = -1; + inv = false; + randinv = false; + show = false; + baseFormatSave = false; + vec = "vector.vec"; + bgcolor = -1; + bgthresh = -1; + } +}; + +static QStringList buildTrainingArgs(const TrainParams ¶ms) +{ + QStringList args; + if (params.data != "") args << "-data" << params.data; + else qFatal("Must specify storage location for cascade"); + if (params.vec != "") args << "-vec" << params.vec; + else qFatal("Must specify location of positive vector"); + if (params.bg != "") args << "-bg" << params.bg; + else qFatal("Must specify negative images"); + if (params.numPos >= 0) args << "-numPos" << QString::number(params.numPos); + if (params.numNeg >= 0) args << "-numNeg" << QString::number(params.numNeg); + if (params.numStages >= 0) args << "-numStages" << QString::number(params.numStages); + if (params.precalcValBufSize >= 0) args << "-precalcValBufSize" << QString::number(params.precalcValBufSize); + if (params.precalcIdxBufSize >= 0) args << "-precalcIdxBufSize" << QString::number(params.precalcIdxBufSize); + if (params.baseFormatSave) args << "-baseFormatSave"; + if (params.stageType != "") args << "-stageType" << params.stageType; + if (params.featureType != "") args << "-featureType" << params.featureType; + if (params.w >= 0) args << "-w" << QString::number(params.w); + else qFatal("Must specify width"); + if (params.h >= 0) args << "-h" << QString::number(params.h); + else qFatal("Must specify height"); + if (params.bt != "") args << "-bt" << params.bt; + if (params.minHitRate >= 0) args << "-minHitRate" << QString::number(params.minHitRate); + if (params.maxFalseAlarmRate >= 0) args << "-maxFalseAlarmRate" << QString::number(params.maxFalseAlarmRate); + if (params.weightTrimRate >= 0) args << "-weightTrimRate" << QString::number(params.weightTrimRate); + if (params.maxDepth >= 0) args << "-maxDepth" << QString::number(params.maxDepth); + if (params.maxWeakCount >= 0) args << "-maxWeakCount" << QString::number(params.maxWeakCount); + if (params.mode != "") args << "-mode" << params.mode; + return args; +} + +static QStringList buildSampleArgs(const TrainParams ¶ms) +{ + QStringList args; + if (params.vec != "") args << "-vec" << params.vec; + else qFatal("Must specify location of positive vector"); + if (params.img != "") args << "-img" << params.img; + else if (params.info != "") args << "-info" << params.info; + else qFatal("Must specify positive images"); + if (params.bg != "") args << "-bg" << params.bg; + if (params.num > 0) args << "-num" << QString::number(params.num); + if (params.bgcolor >=0 ) args << "-bgcolor" << QString::number(params.bgcolor); + if (params.bgthresh >= 0) args << "-bgthresh" << QString::number(params.bgthresh); + if (params.maxidev >= 0) args << "-maxidev" << QString::number(params.maxidev); + if (params.maxxangle >= 0) args << "-maxxangle" << QString::number(params.maxxangle); + if (params.maxyangle >= 0) args << "-maxyangle" << QString::number(params.maxyangle); + if (params.maxzangle >= 0) args << "-maxzangle" << QString::number(params.maxzangle); + if (params.w >= 0) args << "-w" << QString::number(params.w); + if (params.h >= 0) args << "-h" << QString::number(params.h); + if (params.show) args << "-show"; + if (params.inv) args << "-inv"; + if (params.randinv) args << "-randinv"; + return args; +} + +static void genSamples(const TrainParams ¶ms) { + const QStringList cmdArgs = buildSampleArgs(params); + QProcess::execute("opencv_createsamples",cmdArgs); +} +static void trainCascade(const TrainParams ¶ms) +{ + const QStringList cmdArgs = buildTrainingArgs(params); + QProcess::execute("opencv_traincascade", cmdArgs); +} + +namespace br +{ + class CascadeResourceMaker : public ResourceMaker { QString file; @@ -37,7 +169,20 @@ public: else if (model == "Eye") file += "haarcascades/haarcascade_eye_tree_eyeglasses.xml"; else if (model == "FrontalFace") file += "haarcascades/haarcascade_frontalface_alt2.xml"; else if (model == "ProfileFace") file += "haarcascades/haarcascade_profileface.xml"; - else qFatal("Invalid model."); + else{ + // Create temp folder if does not exist + file = model+QDir::separator()+"cascade.xml"; + QDir dir(model); + if (!dir.exists()) + if (!QDir::current().mkdir(model)) qFatal("Cannot create model."); + + // Make sure file can be created + QFile pathTest(file); + if (pathTest.exists()) pathTest.remove(); + + if (!pathTest.open(QIODevice::WriteOnly | QIODevice::Text)) qFatal("Cannot create model."); + pathTest.remove(); + } } private: @@ -54,16 +199,60 @@ private: * \ingroup transforms * \brief Wraps OpenCV cascade classifier * \author Josh Klontz \cite jklontz + * \author David Crouse \cite dgcrouse */ -class CascadeTransform : public UntrainableMetaTransform +class CascadeTransform : public MetaTransform { Q_OBJECT Q_PROPERTY(QString model READ get_model WRITE set_model RESET reset_model STORED false) Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) Q_PROPERTY(bool ROCMode READ get_ROCMode WRITE set_ROCMode RESET reset_ROCMode STORED false) + + // Training parameters + Q_PROPERTY(int numStages READ get_numStages WRITE set_numStages RESET reset_numStages STORED false) + Q_PROPERTY(int w READ get_w WRITE set_w RESET reset_w STORED false) + Q_PROPERTY(int h READ get_h WRITE set_h RESET reset_h STORED false) + Q_PROPERTY(int numPos READ get_numPos WRITE set_numPos RESET reset_numPos STORED false) + Q_PROPERTY(int numNeg READ get_numNeg WRITE set_numNeg RESET reset_numNeg STORED false) + Q_PROPERTY(int precalcValBufSize READ get_precalcValBufSize WRITE set_precalcValBufSize RESET reset_precalcValBufSize STORED false) + Q_PROPERTY(int precalcIdxBufSize READ get_precalcIdxBufSize WRITE set_precalcIdxBufSize RESET reset_precalcIdxBufSize STORED false) + Q_PROPERTY(double minHitRate READ get_minHitRate WRITE set_minHitRate RESET reset_minHitRate STORED false) + Q_PROPERTY(double maxFalseAlarmRate READ get_maxFalseAlarmRate WRITE set_maxFalseAlarmRate RESET reset_maxFalseAlarmRate STORED false) + Q_PROPERTY(double weightTrimRate READ get_weightTrimRate WRITE set_weightTrimRate RESET reset_weightTrimRate STORED false) + Q_PROPERTY(int maxDepth READ get_maxDepth WRITE set_maxDepth RESET reset_maxDepth STORED false) + Q_PROPERTY(int maxWeakCount READ get_maxWeakCount WRITE set_maxWeakCount RESET reset_maxWeakCount STORED false) + Q_PROPERTY(QString stageType READ get_stageType WRITE set_stageType RESET reset_stageType STORED false) + Q_PROPERTY(QString featureType READ get_featureType WRITE set_featureType RESET reset_featureType STORED false) + Q_PROPERTY(QString bt READ get_bt WRITE set_bt RESET reset_bt STORED false) + Q_PROPERTY(QString mode READ get_mode WRITE set_mode RESET reset_mode STORED false) + Q_PROPERTY(bool show READ get_show WRITE set_show RESET reset_show STORED false) + Q_PROPERTY(bool baseFormatSave READ get_baseFormatSave WRITE set_baseFormatSave RESET reset_baseFormatSave STORED false) + Q_PROPERTY(bool overwrite READ get_overwrite WRITE set_overwrite RESET reset_overwrite STORED false) + BR_PROPERTY(QString, model, "FrontalFace") BR_PROPERTY(int, minSize, 64) BR_PROPERTY(bool, ROCMode, false) + + // Training parameters - Default values provided trigger OpenCV defaults + BR_PROPERTY(int, numStages, -1) + BR_PROPERTY(int, w, -1) + BR_PROPERTY(int, h, -1) + BR_PROPERTY(int, numPos, -1) + BR_PROPERTY(int, numNeg, -1) + BR_PROPERTY(int, precalcValBufSize, -1) + BR_PROPERTY(int, precalcIdxBufSize, -1) + BR_PROPERTY(double, minHitRate, -1) + BR_PROPERTY(double, maxFalseAlarmRate, -1) + BR_PROPERTY(double, weightTrimRate, -1) + BR_PROPERTY(int, maxDepth, -1) + BR_PROPERTY(int, maxWeakCount, -1) + BR_PROPERTY(QString, stageType, "") + BR_PROPERTY(QString, featureType, "") + BR_PROPERTY(QString, bt, "") + BR_PROPERTY(QString, mode, "") + BR_PROPERTY(bool, show, false) + BR_PROPERTY(bool, baseFormatSave, false) + BR_PROPERTY(bool, overwrite, false) Resource cascadeResource; @@ -71,6 +260,136 @@ class CascadeTransform : public UntrainableMetaTransform { cascadeResource.setResourceMaker(new CascadeResourceMaker(model)); } + + // Train transform + void train(const TemplateList& data) + { + if (overwrite) { + QDir dataDir(model); + if (dataDir.exists()) { + dataDir.removeRecursively(); + QDir::current().mkdir(model); + } + } + + const FileList files = data.files(); + + // Open positive and negative list files + const QString posFName = "pos.txt"; + const QString negFName = "neg.txt"; + QFile posFile(posFName); + QFile negFile(negFName); + posFile.open(QIODevice::WriteOnly | QIODevice::Text); + negFile.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream posStream(&posFile); + QTextStream negStream(&negFile); + + const QString endln = "\r\n"; + + int posCount = 0; + int negCount = 0; + + bool buildPos = false; // If true, build positive vector from single image + + TrainParams params; + + // Fill in from params (param defaults are same as struct defaults, so no checks are needed) + params.numStages = numStages; + params.w = w; + params.h = h; + params.numPos = numPos; + params.numNeg = numNeg; + params.precalcValBufSize = precalcValBufSize; + params.precalcIdxBufSize = precalcIdxBufSize; + params.minHitRate = minHitRate; + params.maxFalseAlarmRate = maxFalseAlarmRate; + params.weightTrimRate = weightTrimRate; + params.maxDepth = maxDepth; + params.maxWeakCount = maxWeakCount; + params.stageType = stageType; + params.featureType = featureType; + params.bt = bt; + params.mode = mode; + params.show = show; + params.baseFormatSave = baseFormatSave; + if (params.w < 0) params.w = minSize; + if (params.h < 0) params.h = minSize; + + for (int i = 0; i < files.length(); i++) { + File f = files[i]; + if (f.contains("training-set")) { + QString tset = f.get("training-set",QString()).toLower(); + + // Negative samples + if (tset == "neg") { + if (negCount > 0) negStream< 0) posStream< 0) + posStream << f.path() << QDir::separator() << f.fileName() << " " << f.rects().length() << " " << rects; + + // Single positive sample for background removal and overlay on negatives + }else if (tset == "pos-base") { + + buildPos = true; + params.img = f.path() + QDir::separator() + f.fileName(); + + // Parse settings (unique to this one tag) + if (f.contains("num")) params.num = f.get("num",0); + if (f.contains("bgcolor")) params.bgcolor = f.get("bgcolor",0); + if (f.contains("bgthresh")) params.bgthresh =f.get("bgthresh",0); + if (f.contains("inv")) params.inv = f.get("inv",false); + if (f.contains("randinv")) params.randinv = f.get("randinv",false); + if (f.contains("maxidev")) params.maxidev = f.get("maxidev",0); + if (f.contains("maxxangle")) params.maxxangle = f.get("maxxangle",0); + if (f.contains("maxyangle")) params.maxyangle = f.get("maxyangle",0); + if (f.contains("maxzangle")) params.maxzangle = f.get("maxzangle",0); + } + } + } + + // Fill in remaining params conditionally + posFile.close(); + negFile.close(); + if (buildPos) { + if (params.numPos < 0) { + if (params.num > 0) params.numPos = (int)(params.num*.95); + else params.numPos = 950; + posFile.remove(); + } + }else{ + params.info = posFName; + if (params.numPos < 0) { + params.numPos = (int)(posCount*.95); + } + } + params.bg = negFName; + params.data = model; + if (params.num < 0) { + params.num = posCount; + } + if (params.numNeg < 0) { + params.numNeg = negCount*10; + } + + genSamples(params); + trainCascade(params); + if (posFile.exists()) posFile.remove(); + negFile.remove(); + } void project(const Template &src, Template &dst) const { diff --git a/openbr/plugins/pp5.cpp b/openbr/plugins/pp5.cpp index 1cbe76e..2015a57 100644 --- a/openbr/plugins/pp5.cpp +++ b/openbr/plugins/pp5.cpp @@ -322,6 +322,11 @@ class PP5CompareDistance : public Distance ppr_free_gallery(gallery.gallery); } + float compare(const cv::Mat &target, const cv::Mat &query) const + { + return compare(Template(target), Template(query)); + } + float compare(const Template &target, const Template &query) const { TemplateList targetList; diff --git a/scripts/downloadDatasets.sh b/scripts/downloadDatasets.sh index 0cb08f6..2a342c9 100755 --- a/scripts/downloadDatasets.sh +++ b/scripts/downloadDatasets.sh @@ -117,20 +117,7 @@ if [ ! -d ../data/LFW/img ]; then rm lfw.tgz fi -# MEDS -if [ ! -d ../data/MEDS/img ]; then - echo "Downloading MEDS..." - if hash curl 2>/dev/null; then - curl -OL http://nigos.nist.gov:8080/nist/sd/32/NIST_SD32_MEDS-II_face.zip - else - wget http://nigos.nist.gov:8080/nist/sd/32/NIST_SD32_MEDS-II_face.zip - fi - - unzip NIST_SD32_MEDS-II_face.zip - mkdir ../data/MEDS/img - mv data/*/*.jpg ../data/MEDS/img - rm -r data NIST_SD32_MEDS-II_face.zip -fi +./downloadMeds.sh #LFPW if [ ! -d ../data/lfpw/trainset ]; then diff --git a/scripts/downloadMEDS.sh b/scripts/downloadMEDS.sh new file mode 100644 index 0000000..b536db6 --- /dev/null +++ b/scripts/downloadMEDS.sh @@ -0,0 +1,17 @@ +#!/bin/bash + + +# MEDS +if [ ! -d ../data/MEDS/img ]; then + echo "Downloading MEDS..." + if hash curl 2>/dev/null; then + curl -OL http://nigos.nist.gov:8080/nist/sd/32/NIST_SD32_MEDS-II_face.zip + else + wget http://nigos.nist.gov:8080/nist/sd/32/NIST_SD32_MEDS-II_face.zip + fi + + unzip NIST_SD32_MEDS-II_face.zip + mkdir ../data/MEDS/img + mv data/*/*.jpg ../data/MEDS/img + rm -r data NIST_SD32_MEDS-II_face.zip +fi diff --git a/scripts/evalFaceRecognition-MEDS.sh b/scripts/evalFaceRecognition-MEDS.sh index 231370f..2a7a307 100755 --- a/scripts/evalFaceRecognition-MEDS.sh +++ b/scripts/evalFaceRecognition-MEDS.sh @@ -13,7 +13,7 @@ if ! hash br 2>/dev/null; then fi # Get the data -./downloadDatasets.sh +./downloadMEDS.sh if [ ! -e Algorithm_Dataset ]; then mkdir Algorithm_Dataset diff --git a/share/openbr/openbr.bib b/share/openbr/openbr.bib index 111cd1b..fd5ec21 100644 --- a/share/openbr/openbr.bib +++ b/share/openbr/openbr.bib @@ -38,6 +38,11 @@ Author = {Austin Van Blanton}, Howpublished = {https://github.com/imaus10}, Title = {imaus10 at gmail.com}} + + @misc{dgcrouse, + Author = {David G. Crouse}, + Howpublished = {https://github.com/dgcrouse}, + Title = {dgcrouse at gmail.com}} % Software @misc{libface,