diff --git a/data b/data index 50452ec..e8e79d3 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 50452eccc85ed3092fd6b915f4dc52498e825c02 +Subproject commit e8e79d30eb6bce6e295837179dbf4544c78739b5 diff --git a/openbr/core/classify.cpp b/openbr/core/classify.cpp index fd4cfae..ad2937e 100644 --- a/openbr/core/classify.cpp +++ b/openbr/core/classify.cpp @@ -14,8 +14,6 @@ * limitations under the License. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include -#include #include #include "classify.h" @@ -24,7 +22,7 @@ // Helper struct for statistics accumulation struct Counter { - int truePositive, falsePositive, falseNegative; + float truePositive, falsePositive, falseNegative; Counter() { truePositive = 0; @@ -41,35 +39,44 @@ void br::EvalClassification(const QString &predictedInput, const QString &truthI TemplateList truth(TemplateList::fromGallery(truthInput)); if (predicted.size() != truth.size()) qFatal("Input size mismatch."); - QHash counters; + QHash counters; for (int i=0; i("Subject"); + QStringList trueSubjects = truth[i].file.get("Subject"); + foreach (const QString &subject, trueSubjects.toVector() /* Hack to copy the list. */) { + if (predictedSubjects.contains(subject)) { + counters[subject].truePositive++; + trueSubjects.removeOne(subject); + predictedSubjects.removeOne(subject); + } else { + counters[subject].falseNegative++; + } } + + for (int i=0; i output(Output::make("", FileList() << "Subject" << "Count" << "Precision" << "Recall" << "F-score", FileList(counters.size()))); int tpc = 0; int fnc = 0; + const QStringList keys = counters.keys(); for (int i=0; isetRelative(trueLabel, i, 0); + output->setRelative(File("", subject).label(), i, 0); output->setRelative(count, i, 1); output->setRelative(precision, i, 2); output->setRelative(recall, i, 3); diff --git a/openbr/core/common.h b/openbr/core/common.h index 4bb9620..5154c7c 100644 --- a/openbr/core/common.h +++ b/openbr/core/common.h @@ -102,7 +102,7 @@ void MinMax(const QList &vals, T *min, T *max) template T Min(const QList &vals) { - int min, max; + T min, max; MinMax(vals, &min, &max); return min; } @@ -110,7 +110,7 @@ T Min(const QList &vals) template T Max(const QList &vals) { - int min, max; + T min, max; MinMax(vals, &min, &max); return max; } diff --git a/openbr/core/opencvutils.cpp b/openbr/core/opencvutils.cpp index 2630724..fb5622c 100644 --- a/openbr/core/opencvutils.cpp +++ b/openbr/core/opencvutils.cpp @@ -157,6 +157,26 @@ Mat OpenCVUtils::toMatByRow(const QList &src) return dst; } +QString OpenCVUtils::depthToString(const Mat &m) +{ + switch (m.depth()) { + case CV_8U: return "8U"; + case CV_8S: return "8S"; + case CV_16U: return "16U"; + case CV_16S: return "16S"; + case CV_32S: return "32S"; + case CV_32F: return "32F"; + case CV_64F: return "64F"; + default: qFatal("Unknown matrix depth!"); + } + return "?"; +} + +QString OpenCVUtils::typeToString(const cv::Mat &m) +{ + return depthToString(m) + "C" + QString::number(m.channels()); +} + QString OpenCVUtils::elemToString(const Mat &m, int r, int c) { assert(m.channels() == 1); diff --git a/openbr/core/opencvutils.h b/openbr/core/opencvutils.h index 72ac9c7..ecf6067 100644 --- a/openbr/core/opencvutils.h +++ b/openbr/core/opencvutils.h @@ -39,6 +39,8 @@ namespace OpenCVUtils cv::Mat toMatByRow(const QList &src); // Data organized one row per row // From image + QString depthToString(const cv::Mat &m); + QString typeToString(const cv::Mat &m); QString elemToString(const cv::Mat &m, int r, int c); QString matrixToString(const cv::Mat &m); QStringList matrixToStringList(const cv::Mat &m); diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index b7f0058..d36560c 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -923,24 +923,20 @@ void br::Context::messageHandler(QtMsgType type, const QMessageLogContext &conte { // Something about this method is not thread safe, and will lead to crashes if qDebug // statements are called from multiple threads. Unless we lock the whole thing... - static QMutex generalLock(QMutex::Recursive); + static QMutex generalLock; QMutexLocker locker(&generalLock); QString txt; - switch (type) { - case QtDebugMsg: + if (type == QtDebugMsg) { if (Globals->quiet) return; txt = QString("%1\n").arg(msg); - break; - case QtWarningMsg: - txt = QString("Warning: %1\n").arg(msg); - break; - case QtCriticalMsg: - txt = QString("Critical: %1\n").arg(msg); - break; - case QtFatalMsg: - txt = QString("Fatal: %1\n").arg(msg); - break; + } else { + switch (type) { + case QtWarningMsg: txt = QString("Warning: %1\n" ).arg(msg); break; + case QtCriticalMsg: txt = QString("Critical: %1\n").arg(msg); break; + default: txt = QString("Fatal: %1\n" ).arg(msg); + } + txt += " File: " + QString(context.file) + "\n Function: " + QString(context.function) + "\n Line: " + QString::number(context.line) + "\n"; } std::cerr << txt.toStdString(); @@ -951,11 +947,8 @@ void br::Context::messageHandler(QtMsgType type, const QMessageLogContext &conte Globals->logFile.flush(); } - if (type == QtFatalMsg) { - // Write debug output then close - qDebug(" File: %s\n Function: %s\n Line: %d", qPrintable(context.file), qPrintable(context.function), context.line); - Globals->finalize(); - } + if (type == QtFatalMsg) + abort(); // We abort so we can get a stack trace back to the code that triggered the message. } Context *br::Globals = NULL; @@ -1027,7 +1020,7 @@ MatrixOutput *MatrixOutput::make(const FileList &targetFiles, const FileList &qu /* MatrixOutput - protected methods */ QString MatrixOutput::toString(int row, int column) const { - if (targetFiles[column] == "Label") { + if (targetFiles[column] == "Subject") { const int label = data.at(row,column); return Globals->subjects.key(label, QString::number(label)); } diff --git a/openbr/plugins/algorithms.cpp b/openbr/plugins/algorithms.cpp index c11f534..3219849 100644 --- a/openbr/plugins/algorithms.cpp +++ b/openbr/plugins/algorithms.cpp @@ -50,6 +50,7 @@ class AlgorithmsInitializer : public Initializer 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("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)"); // Hash Globals->abbreviations.insert("FileName", "Name+Identity:Identical"); diff --git a/openbr/plugins/cluster.cpp b/openbr/plugins/cluster.cpp index 23a4bfd..a298f24 100644 --- a/openbr/plugins/cluster.cpp +++ b/openbr/plugins/cluster.cpp @@ -17,6 +17,7 @@ #include #include "openbr_internal.h" +#include "openbr/core/common.h" #include "openbr/core/opencvutils.h" using namespace cv; @@ -76,6 +77,68 @@ class KMeansTransform : public Transform BR_REGISTER(Transform, KMeansTransform) -} +/*! + * \ingroup transforms + * \brief K nearest neighbors classifier. + * \author Josh Klontz \cite jklontz + */ +class KNNTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(int k READ get_k WRITE set_k RESET reset_k STORED false) + Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED false) + Q_PROPERTY(bool weighted READ get_weighted WRITE set_weighted RESET reset_weighted STORED false) + Q_PROPERTY(int numSubjects READ get_numSubjects WRITE set_numSubjects RESET reset_numSubjects STORED false) + BR_PROPERTY(int, k, 1) + BR_PROPERTY(br::Distance*, distance, NULL) + BR_PROPERTY(bool, weighted, false) + BR_PROPERTY(int, numSubjects, 1) + + TemplateList gallery; + + void train(const TemplateList &data) + { + distance->train(data); + gallery = data; + } + + void project(const Template &src, Template &dst) const + { + QList< QPair > sortedScores = Common::Sort(distance->compare(gallery, src), true); + + QStringList subjects; + for (int i=0; i votes; + const int max = (k < 1) ? sortedScores.size() : std::min(k, sortedScores.size()); + for (int j=0; j=0; j--) + if (gallery[sortedScores[j].second].file.subject() == subjects.last()) + sortedScores.removeAt(j); + } + + dst.file.set("KNN", subjects.size() > 1 ? "[" + subjects.join(",") + "]" : subjects.first()); + } + + void store(QDataStream &stream) const + { + stream << gallery; + } + + void load(QDataStream &stream) + { + stream >> gallery; + } +}; + +BR_REGISTER(Transform, KNNTransform) + + + +} // namespace br #include "cluster.moc" diff --git a/openbr/plugins/crop.cpp b/openbr/plugins/crop.cpp index f0e14ea..2389ebc 100644 --- a/openbr/plugins/crop.cpp +++ b/openbr/plugins/crop.cpp @@ -151,6 +151,48 @@ class CropBlackTransform : public UntrainableTransform BR_REGISTER(Transform, CropBlackTransform) +/*! + * \ingroup transforms + * \brief Divide the matrix into 4 smaller matricies of equal size. + * \author Josh Klontz \cite jklontz + */ +class SubdivideTransform : public UntrainableTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + const Mat &m = src; + const int subrows = m.rows/2; + const int subcolumns = m.cols/2; + dst.append(Mat(m,Rect(0, 0, subcolumns, subrows)).clone()); + dst.append(Mat(m,Rect(subcolumns, 0, subcolumns, subrows)).clone()); + dst.append(Mat(m,Rect(0, subrows, subcolumns, subrows)).clone()); + dst.append(Mat(m,Rect(subcolumns, subrows, subcolumns, subrows)).clone()); + } +}; + +BR_REGISTER(Transform, SubdivideTransform) + +/*! + * \ingroup transforms + * \brief Trim the image so the width and the height are the same size. + * \author Josh Klontz \cite jklontz + */ +class CropSquareTransform : public UntrainableTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + const Mat &m = src; + const int newSize = min(m.rows, m.cols); + dst = Mat(m, Rect((m.cols-newSize)/2, (m.rows-newSize)/2, newSize, newSize)); + } +}; + +BR_REGISTER(Transform, CropSquareTransform) + } // namespace br #include "crop.moc" diff --git a/openbr/plugins/gallery.cpp b/openbr/plugins/gallery.cpp index f0bb614..e753123 100644 --- a/openbr/plugins/gallery.cpp +++ b/openbr/plugins/gallery.cpp @@ -212,10 +212,52 @@ class DefaultGallery : public Gallery format->write(t); } }; + BR_REGISTER(Gallery, DefaultGallery) /*! * \ingroup galleries + * \brief Combine all templates into one large matrix and process it as a br::Format + * \author Josh Klontz \cite jklontz + */ +class matrixGallery : public Gallery +{ + Q_OBJECT + Q_PROPERTY(const QString extension READ get_extension WRITE set_extension RESET reset_extension STORED false) + BR_PROPERTY(QString, extension, "mtx") + + TemplateList templates; + + ~matrixGallery() + { + if (templates.isEmpty()) + return; + + QScopedPointer format(Factory::make(getFormat())); + format->write(Template(file, OpenCVUtils::toMat(templates.data()))); + } + + File getFormat() const + { + return file.name.left(file.name.size() - file.suffix().size()) + extension; + } + + TemplateList readBlock(bool *done) + { + *done = true; + return TemplateList() << getFormat(); + } + + void write(const Template &t) + { + templates.append(t); + } +}; + +BR_REGISTER(Gallery, matrixGallery) + +/*! + * \ingroup galleries * \brief Treat a video as a gallery, making a single template from each frame * \author Charles Otto \cite caotto */ @@ -499,6 +541,8 @@ BR_REGISTER(Gallery, csvGallery) class txtGallery : public Gallery { Q_OBJECT + Q_PROPERTY(QString metadataKey READ get_metadataKey WRITE set_metadataKey RESET reset_metadataKey STORED false) + BR_PROPERTY(QString, metadataKey, "") QStringList lines; @@ -521,7 +565,7 @@ class txtGallery : public Gallery void write(const Template &t) { - lines.append(t.file.flat()); + lines.append(metadataKey.isEmpty() ? t.file.flat() : t.file.get(metadataKey)); } }; @@ -762,6 +806,41 @@ class googleGallery : public Gallery BR_REGISTER(Gallery, googleGallery) +/*! + * \ingroup galleries + * \brief Count the number of templates. + * \author Josh Klontz \cite jklontz + */ +class TemplateCountGallery : public Gallery +{ + Q_OBJECT + int count; + + ~TemplateCountGallery() + { + printf("%d\n", count); + } + + void init() + { + count = 0; + } + + TemplateList readBlock(bool *done) + { + *done = true; + return TemplateList() << file; + } + + void write(const Template &t) + { + (void) t; + count++; + } +}; + +BR_REGISTER(Gallery, TemplateCountGallery) + } // namespace br #include "gallery.moc" diff --git a/openbr/plugins/independent.cpp b/openbr/plugins/independent.cpp index b0153fe..0fe335c 100644 --- a/openbr/plugins/independent.cpp +++ b/openbr/plugins/independent.cpp @@ -162,6 +162,71 @@ class IndependentTransform : public MetaTransform BR_REGISTER(Transform, IndependentTransform) +/*! + * \ingroup transforms + * \brief A globally shared transform. + * \author Josh Klontz \cite jklontz + */ +class SingletonTransform : public MetaTransform +{ + Q_OBJECT + Q_PROPERTY(QString description READ get_description WRITE set_description RESET reset_description STORED false) + BR_PROPERTY(QString, description, "Identity") + + static QMutex mutex; + static QHash transforms; + static QHash trainingReferenceCounts; + static QHash trainingData; + + Transform *transform; + + void init() + { + QMutexLocker locker(&mutex); + if (!transforms.contains(description)) { + transforms.insert(description, make(description)); + trainingReferenceCounts.insert(description, 0); + } + + transform = transforms[description]; + trainingReferenceCounts[description]++; + } + + void train(const TemplateList &data) + { + QMutexLocker locker(&mutex); + trainingData[description].append(data); + trainingReferenceCounts[description]--; + if (trainingReferenceCounts[description] > 0) return; + transform->train(trainingData[description]); + trainingData[description].clear(); + } + + void project(const Template &src, Template &dst) const + { + transform->project(src, dst); + } + + void store(QDataStream &stream) const + { + if (transform->parent() == this) + transform->store(stream); + } + + void load(QDataStream &stream) + { + if (transform->parent() == this) + transform->load(stream); + } +}; + +QMutex SingletonTransform::mutex; +QHash SingletonTransform::transforms; +QHash SingletonTransform::trainingReferenceCounts; +QHash SingletonTransform::trainingData; + +BR_REGISTER(Transform, SingletonTransform) + } // namespace br #include "independent.moc" diff --git a/openbr/plugins/integral.cpp b/openbr/plugins/integral.cpp index 0880dfd..1ebdca5 100644 --- a/openbr/plugins/integral.cpp +++ b/openbr/plugins/integral.cpp @@ -154,14 +154,14 @@ class RecursiveIntegralSamplerTransform : public Transform } } - static void integralHistogram(const Mat &src, const int x, const int y, const int rows, const int columns, Mat &dst, int index) + static void integralHistogram(const Mat &src, const int x, const int y, const int width, const int height, Mat &dst, int index) { const int channels = src.channels(); OutputDescriptor(dst.ptr(index), channels, 1) = - ( InputDescriptor(src.ptr(y+rows, x+columns), channels, 1) - - InputDescriptor(src.ptr(y, x+columns), channels, 1) - - InputDescriptor(src.ptr(y+rows, x), channels, 1) - + InputDescriptor(src.ptr(y, x), channels, 1)).cast()/(rows*columns); + ( InputDescriptor(src.ptr(y+height, x+width), channels, 1) + - InputDescriptor(src.ptr(y, x+width), channels, 1) + - InputDescriptor(src.ptr(y+height, x), channels, 1) + + InputDescriptor(src.ptr(y, x), channels, 1)).cast()/(height*width); } void computeDescriptor(const Mat &src, Mat &dst) const @@ -171,11 +171,11 @@ class RecursiveIntegralSamplerTransform : public Transform const int columns = src.cols-1; Mat tmp(5, channels, CV_32FC1); - integralHistogram(src, 0, 0, rows/2, columns/2, tmp, 0); - integralHistogram(src, 0, columns/2, rows/2, columns/2, tmp, 1); - integralHistogram(src, rows/2, 0, rows/2, columns/2, tmp, 2); - integralHistogram(src, rows/2, columns/2, rows/2, columns/2, tmp, 3); - integralHistogram(src, rows/4, columns/4, rows/2, columns/2, tmp, 4); + integralHistogram(src, 0, 0, columns/2, rows/2, tmp, 0); + integralHistogram(src, columns/2, 0, columns/2, rows/2, tmp, 1); + integralHistogram(src, 0, rows/2, columns/2, rows/2, tmp, 2); + integralHistogram(src, columns/2, rows/2, columns/2, rows/2, tmp, 3); + integralHistogram(src, columns/4, rows/4, columns/2, rows/2, tmp, 4); const SecondOrderInputDescriptor a(tmp.ptr(0), channels, 1); const SecondOrderInputDescriptor b(tmp.ptr(1), channels, 1); const SecondOrderInputDescriptor c(tmp.ptr(2), channels, 1); @@ -188,6 +188,7 @@ class RecursiveIntegralSamplerTransform : public Transform OutputDescriptor(dst.ptr(2), channels, 1) = ((a+b)-(c+d))/2.f; OutputDescriptor(dst.ptr(3), channels, 1) = ((a+c)-(b+d))/2.f; OutputDescriptor(dst.ptr(4), channels, 1) = ((a+d)-(b+c))/2.f; + dst = dst.reshape(1, 1); } Template subdivide(const Template &src) const diff --git a/openbr/plugins/misc.cpp b/openbr/plugins/misc.cpp index 7b9225f..765568d 100644 --- a/openbr/plugins/misc.cpp +++ b/openbr/plugins/misc.cpp @@ -60,20 +60,18 @@ class PrintTransform : public UntrainableMetaTransform Q_OBJECT Q_PROPERTY(bool error READ get_error WRITE set_error RESET reset_error) Q_PROPERTY(bool data READ get_data WRITE set_data RESET reset_data) - Q_PROPERTY(bool nTemplates READ get_data WRITE set_data RESET reset_data) BR_PROPERTY(bool, error, true) BR_PROPERTY(bool, data, false) - BR_PROPERTY(bool, size, false) void project(const Template &src, Template &dst) const { dst = src; const QString nameString = src.file.flat(); const QString dataString = data ? OpenCVUtils::matrixToString(src)+"\n" : QString(); - const QString nTemplates = size ? QString::number(src.size()) : QString(); - qDebug() << "Dimensionality: " << src.first().cols; - if (error) qDebug("%s\n%s\n%s", qPrintable(nameString), qPrintable(dataString), qPrintable(nTemplates)); - else printf("%s\n%s\n%s", qPrintable(nameString), qPrintable(dataString), qPrintable(nTemplates)); + QStringList matricies; + foreach (const Mat &m, src) + matricies.append(QString::number(m.rows) + "x" + QString::number(m.cols) + "_" + OpenCVUtils::typeToString(m)); + fprintf(error ? stderr : stdout, "%s\n %s\n%s", qPrintable(nameString), qPrintable(matricies.join(",")), qPrintable(dataString)); } }; @@ -402,13 +400,14 @@ BR_REGISTER(Transform, AsTransform) /*! * \ingroup transforms - * \brief Change the template label using a regular expresion matched to the file's base name. + * \brief Change the template subject using a regular expresion matched to the file's base name. + * \author Josh Klontz \cite jklontz */ -class RelabelTransform : public UntrainableMetaTransform +class SubjectTransform : public UntrainableMetaTransform { Q_OBJECT Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) - BR_PROPERTY(QString, regexp, "") + BR_PROPERTY(QString, regexp, "(.*)") void project(const Template &src, Template &dst) const { @@ -417,11 +416,11 @@ class RelabelTransform : public UntrainableMetaTransform QRegularExpressionMatch match = re.match(dst.file.baseName()); if (!match.hasMatch()) qFatal("Unable to match regular expression \"%s\" to base name \"%s\"!", qPrintable(regexp), qPrintable(dst.file.baseName())); - dst.file.set("Label", match.captured(match.lastCapturedIndex())); + dst.file.set("Subject", match.captured(match.lastCapturedIndex())); } }; -BR_REGISTER(Transform, RelabelTransform) +BR_REGISTER(Transform, SubjectTransform) /*! * \ingroup transforms diff --git a/openbr/plugins/regions.cpp b/openbr/plugins/regions.cpp index c3feafb..61e6a36 100644 --- a/openbr/plugins/regions.cpp +++ b/openbr/plugins/regions.cpp @@ -92,15 +92,15 @@ class CatTransform : public UntrainableMetaTransform qFatal("%d partitions does not evenly divide %d matrices.", partitions, src.size()); QVector sizes(partitions, 0); for (int i=0; i offsets(partitions, 0); for (int i=0; i #include #include -#include "openbr_internal.h" +#include "openbr_internal.h" #include "openbr/core/opencvutils.h" +using namespace cv; + namespace br { +static void storeSVM(float a, float b, const SVM &svm, QDataStream &stream) +{ + stream << a << b; + + // Create local file + QTemporaryFile tempFile; + tempFile.open(); + tempFile.close(); + + // Save SVM to local file + svm.save(qPrintable(tempFile.fileName())); + + // Copy local file contents to stream + tempFile.open(); + QByteArray data = tempFile.readAll(); + tempFile.close(); + stream << data; +} + +static void loadSVM(float &a, float &b, SVM &svm, QDataStream &stream) +{ + stream >> a >> b; + + // Copy local file contents from stream + QByteArray data; + stream >> data; + + // Create local file + QTemporaryFile tempFile(QDir::tempPath()+"/SVM"); + tempFile.open(); + tempFile.write(data); + tempFile.close(); + + // Load SVM from local file + svm.load(qPrintable(tempFile.fileName())); +} + +static void trainSVM(float &a, float &b, SVM &svm, Mat data, Mat lab, int kernel, int type, float C, float gamma) +{ + if ((type == CvSVM::EPS_SVR) || (type == CvSVM::NU_SVR)) { + // Scale labels to [-1,1] + double min, max; + minMaxLoc(lab, &min, &max); + if (max > min) { + a = 2.0/(max-min); + b = -(min*a+1); + lab = (lab * a) + b; + } + } else { + a = 1; + b = 0; + } + + if (data.type() != CV_32FC1) + qFatal("Expected single channel floating point training data."); + + CvSVMParams params; + params.kernel_type = kernel; + params.svm_type = type; + params.p = 0.1; + params.nu = 0.5; + if ((C == -1) || ((gamma == -1) && (kernel == CvSVM::RBF))) { + try { + svm.train_auto(data, lab, Mat(), Mat(), params, 5); + } catch (...) { + qWarning("Some classes do not contain sufficient examples or are not discriminative enough for accurate SVM classification."); + svm.train(data, lab); + } + } else { + params.C = C; + params.gamma = gamma; + svm.train(data, lab, Mat(), Mat(), params); + } + + CvSVMParams p = svm.get_params(); + qDebug("SVM C = %f Gamma = %f Support Vectors = %d", p.C, p.gamma, svm.get_support_vector_count()); +} + /*! * \ingroup transforms * \brief C. Burges. "A tutorial on support vector machines for pattern recognition," - * Knowledge Discovery and Data Mining 2(2), 1998. * \author Josh Klontz \cite jklontz + * Knowledge Discovery and Data Mining 2(2), 1998. */ class SVMTransform : public Transform { @@ -41,16 +121,11 @@ class SVMTransform : public Transform Q_PROPERTY(float gamma READ get_gamma WRITE set_gamma RESET reset_gamma STORED false) public: - /*! - * \brief The Kernel enum - */ enum Kernel { Linear = CvSVM::LINEAR, Poly = CvSVM::POLY, RBF = CvSVM::RBF, Sigmoid = CvSVM::SIGMOID }; - /*! - * \brief The Type enum - */ + enum Type { C_SVC = CvSVM::C_SVC, NU_SVC = CvSVM::NU_SVC, ONE_CLASS = CvSVM::ONE_CLASS, @@ -63,99 +138,110 @@ private: BR_PROPERTY(float, C, -1) BR_PROPERTY(float, gamma, -1) - cv::SVM svm; + SVM svm; float a, b; -public: - SVMTransform() : a(1), b(0) {} - -private: void train(const TemplateList &_data) { - cv::Mat data = OpenCVUtils::toMat(_data.data()); - cv::Mat lab = OpenCVUtils::toMat(_data.labels()); - - if ((type == EPS_SVR) || (type == NU_SVR)) { - // Scale labels to [-1,1] - double min, max; - cv::minMaxLoc(lab, &min, &max); - if (max > min) { - a = 2.0/(max-min); - b = -(min*a+1); - lab = (lab * a) + b; - } - } - - if (data.type() != CV_32FC1) - qFatal("Expected single channel floating point training data."); - - CvSVMParams params; - params.kernel_type = kernel; - params.svm_type = type; - params.p = 0.1; - params.nu = 0.5; - if ((C == -1) || ((gamma == -1) && (int(kernel) != int(CvSVM::LINEAR)))) { - try { - svm.train_auto(data, lab, cv::Mat(), cv::Mat(), params, 5); - } catch (...) { - qWarning("Some classes do not contain sufficient examples or are not discriminative enough for accurate SVM classification."); - svm.train(data, lab); - } - } else { - params.C = C; - params.gamma = gamma; - svm.train(data, lab, cv::Mat(), cv::Mat(), params); - } - - CvSVMParams p = svm.get_params(); - qDebug("SVM C = %f Gamma = %f Support Vectors = %d", p.C, p.gamma, svm.get_support_vector_count()); + Mat data = OpenCVUtils::toMat(_data.data()); + Mat lab = OpenCVUtils::toMat(_data.labels()); + trainSVM(a, b, svm, data, lab, kernel, type, C, gamma); } void project(const Template &src, Template &dst) const { dst = src; - dst.file.set("Label", ((svm.predict(src.m().reshape(0, 1)) - b)/a)); + dst.file.set("Label", ((svm.predict(src.m().reshape(1, 1)) - b)/a)); } void store(QDataStream &stream) const { - stream << a << b; + storeSVM(a, b, svm, stream); + } - // Create local file - QTemporaryFile tempFile; - tempFile.open(); - tempFile.close(); + void load(QDataStream &stream) + { + loadSVM(a, b, svm, stream); + } +}; - // Save SVM to local file - svm.save(qPrintable(tempFile.fileName())); +BR_REGISTER(Transform, SVMTransform) - // Copy local file contents to stream - tempFile.open(); - QByteArray data = tempFile.readAll(); - tempFile.close(); - stream << data; - } +/*! + * \ingroup Distances + * \brief SVM Regression on template absolute differences. + * \author Josh Klontz + */ +class SVMDistance : public Distance +{ + Q_OBJECT + Q_ENUMS(Kernel) + Q_ENUMS(Type) + Q_PROPERTY(Kernel kernel READ get_kernel WRITE set_kernel RESET reset_kernel STORED false) + Q_PROPERTY(Type type READ get_type WRITE set_type RESET reset_type STORED false) - void load(QDataStream &stream) +public: + enum Kernel { Linear = CvSVM::LINEAR, + Poly = CvSVM::POLY, + RBF = CvSVM::RBF, + Sigmoid = CvSVM::SIGMOID }; + + enum Type { C_SVC = CvSVM::C_SVC, + NU_SVC = CvSVM::NU_SVC, + ONE_CLASS = CvSVM::ONE_CLASS, + EPS_SVR = CvSVM::EPS_SVR, + NU_SVR = CvSVM::NU_SVR}; + +private: + BR_PROPERTY(Kernel, kernel, Linear) + BR_PROPERTY(Type, type, EPS_SVR) + + SVM svm; + float a, b; + + void train(const TemplateList &src) { - stream >> a >> b; + const Mat data = OpenCVUtils::toMat(src.data()); + const QList lab = src.labels(); + + const int instances = data.rows * (data.rows+1) / 2; + Mat deltaData(instances, data.cols, data.type()); + Mat deltaLab(instances, 1, CV_32FC1); + int index = 0; + for (int i=0; i(index, 0) = (match ? 1 : 0); + index++; + } + deltaData = deltaData.rowRange(0, index); + deltaLab = deltaLab.rowRange(0, index); - // Copy local file contents from stream - QByteArray data; - stream >> data; + trainSVM(a, b, svm, deltaData, deltaLab, kernel, type, -1, -1); + } - // Create local file - QTemporaryFile tempFile(QDir::tempPath()+"/SVM"); - tempFile.open(); - tempFile.write(data); - tempFile.close(); + float compare(const Template &ta, const Template &tb) const + { + Mat delta; + absdiff(ta, tb, delta); + return (svm.predict(delta.reshape(1, 1)) - b)/a; + } - // Load SVM from local file - svm.load(qPrintable(tempFile.fileName())); + void store(QDataStream &stream) const + { + storeSVM(a, b, svm, stream); + } + + void load(QDataStream &stream) + { + loadSVM(a, b, svm, stream); } }; -BR_REGISTER(Transform, SVMTransform) +BR_REGISTER(Distance, SVMDistance) } // namespace br diff --git a/share/openbr/abstraction.svg b/share/openbr/abstraction.svg new file mode 100644 index 0000000..a077111 --- /dev/null +++ b/share/openbr/abstraction.svg @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + br + OpenBR + + OpenCVQt + + + + + + PCA + LDA + LBP + + ... + CommercialWrapper + CommercialApplication + + + Source Code + Shared Library + Application + Plugin + + + + CommercialLibrary + + Open SourceApplication + + + •Algorithm evaluation•Parallel training & execution•Image processing grammar + www.openbiometrics.org + + diff --git a/share/openbr/openbr.bib b/share/openbr/openbr.bib index 5d9a23d..6e504e7 100644 --- a/share/openbr/openbr.bib +++ b/share/openbr/openbr.bib @@ -70,21 +70,6 @@ Title = {PittPatt {SDK} 5.2.2}, Year = {2011}} - -% Datasets -@misc{GBU, - Author = {NIST}, - Howpublished = {www.nist.gov/itl/iad/ig/focs.cfm}, - Title = {Face and Ocular Challenge Series ({FOCS})}, - Year = {2010}} - -@misc{MEDS, - Author = {NIST}, - Howpublished = {www.nist.gov/itl/iad/ig/sd32.cfm}, - Title = {{NIST} Special Database 32 - Multiple Encounter Dataset ({MEDS})}, - Year = {2011}} - - % Papers @inproceedings{arandjelovic12, Author={Arandjelovic, R. and Zisserman, A.}, @@ -117,16 +102,22 @@ Title = {Average of Synthetic Exact Filters}, Year = {2009}} +@misc{founds11, + Author = {Founds, A.P. and Orlans, N. and Whiddon, G. and Watson, C.}, + Howpublished = {www.nist.gov/itl/iad/ig/sd32.cfm}, + Title = {{NIST Special Database 32 - Multiple Encounter Dataset II (MEDS-II)}}, + Year = {2011}} + @misc{grother12, Author = {Grother, P. and Quinn, G.W. and Ngan, M.}, - Title = {Face Recognition Vendor Test (FRVT) 2012}, + Title = {{Face Recognition Vendor Test (FRVT)} 2012}, Month = {mar}, Year = {2013}, Url = {http://www.nist.gov/itl/iad/ig/frvt-2012.cfm}} @misc{grother13, Author = {Grother, P.}, - Title = {{MITRE} {FRVT} {Submission} {Question}}, + Title = {{MITRE FRVT Submission Question}}, HowPublished = {Personal communication}, Month = {jan}, Year = {2013}} @@ -158,7 +149,7 @@ @article{martinez98, Author={Martinez, A.M.}, Journal={CVC Technical Report}, - Title={The AR face database}, + Title={The {AR} face database}, Volume={24}, Year={1998}} @@ -183,7 +174,7 @@ Booktitle = {Second international conference on audio and video-based biometric person authentication}, Organization = {Citeseer}, Pages = {965-966}, - Title = {XM2VTSDB: The extended M2VTS database}, + Title = {{XM2VTSDB}: The extended {M2VTS} database}, Volume = {964}, Year = {1999}} @@ -224,7 +215,7 @@ Author = {Li, S.Z. and Lei, Z. and Ao, M.}, Booktitle = {6th IEEE Workshop on Object Tracking and Classification Beyond and in the Visible Spectrum (OTCBVS, in conjunction with CVPR 2009)}, Month = {jun}, - Title = {The HFB Face Database for Heterogeneous Face Biometrics Research}, + Title = {{The HFB Face Database} for {Heterogeneous Face Biometrics} Research}, Year = {2009}} @article{wang09,