diff --git a/3rdparty/stasm4.0.0/CMakeLists.txt b/3rdparty/stasm4.0.0/CMakeLists.txt index 1675828..e79d55a 100644 --- a/3rdparty/stasm4.0.0/CMakeLists.txt +++ b/3rdparty/stasm4.0.0/CMakeLists.txt @@ -13,7 +13,7 @@ set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSIO set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules/") # Find Qt 5.0.2 -set(QT_DEPENDENCIES Concurrent Core Gui Network Sql Svg Widgets Xml) +set(QT_DEPENDENCIES Concurrent Core Gui Network Sql Widgets Xml) foreach(QT_DEPENDENCY ${QT_DEPENDENCIES}) find_package(Qt5${QT_DEPENDENCY}) endforeach() diff --git a/CMakeLists.txt b/CMakeLists.txt index eda7b49..0deea75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ endif() set(QT_DEPENDENCIES Concurrent Core) option(BR_EMBEDDED "Limit software dependencies") if(NOT ${BR_EMBEDDED}) - set(QT_DEPENDENCIES ${QT_DEPENDENCIES} Gui Network Sql Svg Widgets Xml) + set(QT_DEPENDENCIES ${QT_DEPENDENCIES} Gui Network Sql Widgets Xml) endif() foreach(QT_DEPENDENCY ${QT_DEPENDENCIES}) find_package(Qt5${QT_DEPENDENCY}) @@ -89,7 +89,7 @@ set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${Qt5Core_QTMAIN_LIBRARIES}) # Find OpenCV find_package(OpenCV 2.4.5 REQUIRED) -set(OPENCV_DEPENDENCIES opencv_calib3d opencv_core opencv_features2d opencv_flann opencv_gpu opencv_highgui opencv_imgproc opencv_ml opencv_nonfree opencv_objdetect opencv_photo opencv_video) +set(OPENCV_DEPENDENCIES opencv_calib3d opencv_core opencv_features2d opencv_flann opencv_gpu opencv_highgui opencv_imgproc opencv_ml opencv_nonfree opencv_objdetect opencv_photo opencv_video opencv_videostab opencv_superres opencv_stitching opencv_ocl opencv_legacy opencv_contrib) set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${OpenCV_LIBS}) # Find Alphanum diff --git a/openbr/core/cluster.cpp b/openbr/core/cluster.cpp index 44f196a..a16a3e0 100644 --- a/openbr/core/cluster.cpp +++ b/openbr/core/cluster.cpp @@ -82,7 +82,7 @@ float normalizedROD(const Neighborhood &neighborhood, int a, int b) return 1.f * (distanceA + distanceB) / std::min(indexA+1, indexB+1); } -Neighborhood getNeighborhood(const QStringList &simmats) +Neighborhood br::knnFromSimmat(const QList &simmats, int k) { Neighborhood neighborhood; @@ -99,9 +99,7 @@ Neighborhood getNeighborhood(const QStringList &simmats) int currentRows = -1; int columnOffset = 0; for (int j=0; j format(br::Factory::make(simmats[i*numGalleries+j])); - br::Template t = format->read(); - cv::Mat m = t.m(); + cv::Mat m = simmats[i * numGalleries + j]; if (j==0) { currentRows = m.rows; allNeighbors.resize(currentRows); @@ -132,37 +130,164 @@ Neighborhood getNeighborhood(const QStringList &simmats) // Keep the top matches for (int j=0; j::infinity()) - neighbor.second = 0; - else if (neighbor.second == std::numeric_limits::infinity()) - neighbor.second = 1; - else - neighbor.second = (neighbor.second - globalMin) / (globalMax - globalMin); - } + return neighborhood; +} + +// generate k-NN graph from pre-computed similarity matrices +Neighborhood br::knnFromSimmat(const QStringList &simmats, int k) +{ + QList mats; + foreach (const QString &simmat, simmats) { + QScopedPointer format(br::Factory::make(simmat)); + br::Template t = format->read(); + mats.append(t); + } + return knnFromSimmat(mats, k); +} + +TemplateList knnFromGallery(const QString & galleryName, bool inMemory, const QString & outFile, int k) +{ + QSharedPointer comparison = Transform::fromComparison(Globals->algorithm); + + Gallery *tempG = Gallery::make(galleryName); + qint64 total = tempG->totalSize(); + delete tempG; + comparison->setPropertyRecursive("galleryName", galleryName+"[dropMetadata=true]"); + + bool multiProcess = Globals->file.getBool("multiProcess", false); + if (multiProcess) + comparison = QSharedPointer (br::wrapTransform(comparison.data(), "ProcessWrapper")); + + QScopedPointer collect(Transform::make("CollectNN+ProgressCounter+Discard", NULL)); + collect->setPropertyRecursive("totalProgress", total); + collect->setPropertyRecursive("keep", k); + + QList tforms; + tforms.append(comparison.data()); + tforms.append(collect.data()); + + QScopedPointer compareCollect(br::pipeTransforms(tforms)); + + QSharedPointer projector; + if (inMemory) + projector = QSharedPointer (br::wrapTransform(compareCollect.data(), "Stream(readMode=StreamGallery, endPoint=Discard")); + else + projector = QSharedPointer (br::wrapTransform(compareCollect.data(), "Stream(readMode=StreamGallery, endPoint=LogNN("+outFile+")+DiscardTemplates)")); + + TemplateList input; + input.append(Template(galleryName)); + TemplateList output; + + projector->init(); + projector->projectUpdate(input, output); + + return output; +} + +// Generate k-NN graph from a gallery, using the current algorithm for comparison. +// Direct serialization to file system, k-NN graph is not retained in memory +void br::knnFromGallery(const QString &galleryName, const QString &outFile, int k) +{ + knnFromGallery(galleryName, false, outFile, k); +} + +// In-memory graph construction +Neighborhood br::knnFromGallery(const QString &gallery, int k) +{ + // Nearest neighbor data current stored as template metadata, so retrieve it + TemplateList res = knnFromGallery(gallery, true, "", k); + + Neighborhood neighborhood; + foreach (const Template &t, res) { + Neighbors neighbors = t.file.get("neighbors"); + neighbors.append(neighbors); } return neighborhood; } -// Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011 -br::Clusters br::ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv) +Neighborhood br::loadkNN(const QString &infile) +{ + Neighborhood neighborhood; + QFile file(infile); + bool success = file.open(QFile::ReadOnly); + if (!success) qFatal("Failed to open %s for reading.", qPrintable(infile)); + QStringList lines = QString(file.readAll()).split("\n"); + file.close(); + int min_idx = INT_MAX; + int max_idx = -1; + int count = 0; + + foreach (const QString &line, lines) { + Neighbors neighbors; + count++; + if (line.trimmed().isEmpty()) { + neighborhood.append(neighbors); + continue; + } + bool off = false; + QStringList list = line.trimmed().split(",", QString::SkipEmptyParts); + foreach (const QString &item, list) { + QStringList parts = item.trimmed().split(":", QString::SkipEmptyParts); + bool intOK = true; + bool floatOK = true; + int idx = parts[0].toInt(&intOK); + float score = parts[1].toFloat(&floatOK); + + if (idx > max_idx) + max_idx = idx; + if (idx = lines.size()) { + off = true; + continue; + } + neighbors.append(qMakePair(idx, score)); + + + if (!intOK && floatOK) + qFatal("Failed to parse word: %s", qPrintable(item)); + } + neighborhood.append(neighbors); + } + return neighborhood; +} + +bool br::savekNN(const Neighborhood &neighborhood, const QString &outfile) +{ + QFile file(outfile); + bool success = file.open(QFile::WriteOnly); + if (!success) qFatal("Failed to open %s for writing.", qPrintable(outfile)); + + foreach (Neighbors neighbors, neighborhood) { + QString aLine; + if (!neighbors.empty()) + { + aLine.append(QString::number(neighbors[0].first)+":"+QString::number(neighbors[0].second)); + for (int i=1; i < neighbors.size();i++) { + aLine.append(","+QString::number(neighbors[i].first)+":"+QString::number(neighbors[i].second)); + } + } + aLine += "\n"; + file.write(qPrintable(aLine)); + } + file.close(); + return true; +} + + +// Rank-order clustering on a pre-computed k-NN graph +Clusters br::ClusterGraph(Neighborhood neighborhood, float aggressiveness, const QString &csv) { - qDebug("Clustering %d simmat(s), aggressiveness %f", simmats.size(), aggressiveness); - // Read in gallery parts, keeping top neighbors of each template - Neighborhood neighborhood = getNeighborhood(simmats); const int cutoff = neighborhood.first().size(); const float threshold = 3*cutoff/4 * aggressiveness/5; @@ -239,9 +364,39 @@ br::Clusters br::ClusterGallery(const QStringList &simmats, float aggressiveness neighborhood = newNeighborhood; } - // Save clusters if (!csv.isEmpty()) WriteClusters(clusters, csv); + + return clusters; +} + +Clusters br::ClusterGraph(const QString & knnName, float aggressiveness, const QString &csv) +{ + Neighborhood neighbors = loadkNN(knnName); + return ClusterGraph(neighbors, aggressiveness, csv); +} + +// Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011 +br::Clusters br::ClusterSimmat(const QList &simmats, float aggressiveness, const QString &csv) +{ + qDebug("Clustering %d simmat(s), aggressiveness %f", simmats.size(), aggressiveness); + + // Read in gallery parts, keeping top neighbors of each template + Neighborhood neighborhood = knnFromSimmat(simmats); + + return ClusterGraph(neighborhood, aggressiveness, csv); +} + +br::Clusters br::ClusterSimmat(const QStringList &simmats, float aggressiveness, const QString &csv) +{ + QList mats; + foreach (const QString &simmat, simmats) { + QScopedPointer format(br::Factory::make(simmat)); + br::Template t = format->read(); + mats.append(t); + } + + Clusters clusters = ClusterSimmat(mats, aggressiveness, csv); return clusters; } diff --git a/openbr/core/cluster.h b/openbr/core/cluster.h index 84bb9ba..3def28d 100644 --- a/openbr/core/cluster.h +++ b/openbr/core/cluster.h @@ -21,15 +21,46 @@ #include #include #include +#include +#include namespace br { typedef QList Cluster; // List of indices into galleries typedef QVector Clusters; - Clusters ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv); + // generate k-NN graph from pre-computed similarity matrices + Neighborhood knnFromSimmat(const QStringList &simmats, int k = 20); + Neighborhood knnFromSimmat(const QList &simmats, int k = 20); + + // Generate k-NN graph from a gallery, using the current algorithm for comparison. + // direct serialization to file system. + void knnFromGallery(const QString &galleryName, const QString & outFile, int k = 20); + // in memory graph computation + Neighborhood knnFromGallery(const QString &gallery, int k = 20); + + // Load k-NN graph from a file with the following ascii format: + // One line per sample, each line lists the top k neighbors for the sample as follows: + // index1:score1,index2:score2,...,indexk:scorek + Neighborhood loadkNN(const QString &fname); + + // Save k-NN graph to file + bool savekNN(const Neighborhood &neighborhood, const QString &outfile); + + // Rank-order clustering on a pre-computed k-NN graph + Clusters ClusterGraph(Neighborhood neighbors, float aggresssiveness, const QString &csv = ""); + Clusters ClusterGraph(const QString & knnName, float aggressiveness, const QString &csv = ""); + + // Given a similarity matrix, compute the k-NN graph, then perform rank-order clustering. + Clusters ClusterSimmat(const QList &simmats, float aggressiveness, const QString &csv = ""); + Clusters ClusterSimmat(const QStringList &simmats, float aggressiveness, const QString &csv = ""); + + // evaluate clustering results in csv, reading ground truth data from gallery input, using truth_property + // as the key for ground truth labels. void EvalClustering(const QString &csv, const QString &input, QString truth_property); + // Read/write clusters from a text format, 1 line = 1 cluster, each line contains comma separated list + // of assigned indices. Clusters ReadClusters(const QString &csv); void WriteClusters(const Clusters &clusters, const QString &csv); } diff --git a/openbr/core/core.cpp b/openbr/core/core.cpp index a6f68bf..070cab4 100644 --- a/openbr/core/core.cpp +++ b/openbr/core/core.cpp @@ -138,6 +138,13 @@ struct AlgorithmCore void load(const QString &model) { + // since we are loading an existing model, add its path to the search list for submodels + // assuming it is not already present. + QFileInfo finfo(model); + QString path = finfo.absolutePath(); + if (!Globals->modelSearch.contains(path)) + Globals->modelSearch.append(path); + QtUtils::BlockCompression compressedRead; QFile inFile(model); compressedRead.setBasis(&inFile); @@ -762,5 +769,15 @@ QSharedPointer br::Distance::fromAlgorithm(const QString &algorith return AlgorithmManager::getAlgorithm(algorithm)->distance; } +class pathInitializer : public Initializer +{ + Q_OBJECT + void initialize() const + { + Globals->modelSearch.append(Globals->sdkPath + "/share/openbr/models/transforms/"); + } + +}; +BR_REGISTER(Initializer, pathInitializer) #include "core.moc" diff --git a/openbr/core/qtutils.cpp b/openbr/core/qtutils.cpp index fdfd004..9203c71 100644 --- a/openbr/core/qtutils.cpp +++ b/openbr/core/qtutils.cpp @@ -500,14 +500,15 @@ QString getAbsolutePath(const QString &filename) return QFileInfo(filename).absoluteFilePath(); } +const int base_block = 100000000; + BlockCompression::BlockCompression(QIODevice *_basis) { - blockSize = 100000000; + blockSize = base_block; setBasis(_basis); } -BlockCompression::BlockCompression() { blockSize = 100000000; }; - +BlockCompression::BlockCompression() { blockSize = base_block;}; bool BlockCompression::open(QIODevice::OpenMode mode) { @@ -521,14 +522,22 @@ bool BlockCompression::open(QIODevice::OpenMode mode) blockWriter.setDevice(basis); if (mode & QIODevice::WriteOnly) { - precompressedBlockWriter = new QBuffer; - precompressedBlockWriter->open(QIODevice::ReadWrite); + precompressedBlockWriter.open(QIODevice::WriteOnly); } else if (mode & QIODevice::ReadOnly) { + + // Read an initial compressed block from the underlying QIODevice, + // decompress, and set up a reader on it QByteArray compressedBlock; - blockReader >> compressedBlock; + quint32 block_size; + blockReader >> block_size; + compressedBlock.resize(block_size); + int read_count = blockReader.readRawData(compressedBlock.data(), block_size); + if (read_count != block_size) + qFatal("Failed to read initial block"); decompressedBlock = qUncompress(compressedBlock); + decompressedBlockReader.setBuffer(&decompressedBlock); decompressedBlockReader.open(QIODevice::ReadOnly); } @@ -538,11 +547,17 @@ bool BlockCompression::open(QIODevice::OpenMode mode) void BlockCompression::close() { - // flush output buffer - if ((openMode() & QIODevice::WriteOnly) && precompressedBlockWriter) { - QByteArray compressedBlock = qCompress(precompressedBlockWriter->buffer(), -1); - blockWriter << compressedBlock; + // flush output buffer, since we may have a partial block which hasn't been + // written to disk yet. + if ((openMode() & QIODevice::WriteOnly) && precompressedBlockWriter.isOpen()) { + QByteArray compressedBlock = qCompress(precompressedBlockWriter.buffer()); + precompressedBlockWriter.close(); + + quint32 bsize= compressedBlock.size(); + blockWriter << bsize; + blockWriter.writeRawData(compressedBlock.data(), compressedBlock.size()); } + // close the underlying device. basis->close(); } @@ -557,8 +572,10 @@ void BlockCompression::setBasis(QIODevice *_basis) // block from basis qint64 BlockCompression::readData(char *data, qint64 remaining) { + qint64 initial = remaining; qint64 read = 0; while (remaining > 0) { + // attempt to read the target amount of data qint64 single_read = decompressedBlockReader.read(data, remaining); if (single_read == -1) qFatal("miss read"); @@ -567,13 +584,21 @@ qint64 BlockCompression::readData(char *data, qint64 remaining) read += single_read; data += single_read; - // need a new block + // need a new block if we didn't get enough bytes from the previous read if (remaining > 0) { QByteArray compressedBlock; - blockReader >> compressedBlock; - if (compressedBlock.size() == 0) { - return read; - } + + // read the size of the next block + quint32 block_size; + blockReader >> block_size; + if (block_size == 0) + break; + + compressedBlock.resize(block_size); + int actualRead = blockReader.readRawData(compressedBlock.data(), block_size); + if (actualRead != block_size) + qFatal("Bad read on nominal block size: %d, only got %d", block_size, remaining); + decompressedBlock = qUncompress(compressedBlock); decompressedBlockReader.close(); @@ -581,7 +606,12 @@ qint64 BlockCompression::readData(char *data, qint64 remaining) decompressedBlockReader.open(QIODevice::ReadOnly); } } - return blockReader.atEnd() && !basis->isReadable() ? -1 : read; + + bool condition = blockReader.atEnd() && !basis->isReadable() ; + if (condition) + qWarning("Returning -1 from read"); + + return condition ? -1 : read; } bool BlockCompression::isSequential() const @@ -591,36 +621,57 @@ bool BlockCompression::isSequential() const qint64 BlockCompression::writeData(const char *data, qint64 remaining) { + const char * endPoint = data + remaining; + qint64 initial = remaining; + qint64 written = 0; while (remaining > 0) { // how much more can be put in this buffer? - qint64 capacity = blockSize - precompressedBlockWriter->pos(); + qint64 capacity = blockSize - precompressedBlockWriter.pos(); + if (capacity < 0) + qFatal("Negative capacity!!!"); // don't try to write beyond capacity qint64 write_size = qMin(capacity, remaining); - qint64 singleWrite = precompressedBlockWriter->write(data, write_size); - // ignore the error case here, we consdier basis's failure mode the real - // end case + qint64 singleWrite = precompressedBlockWriter.write(data, write_size); + if (singleWrite == -1) - singleWrite = 0; + qFatal("matrix write failure?"); remaining -= singleWrite; data += singleWrite; written += singleWrite; + if (data > endPoint) + qFatal("Wrote past the end"); if (remaining > 0) { - QByteArray compressedBlock = qCompress(precompressedBlockWriter->buffer(), -1); + QByteArray compressedBlock = qCompress(precompressedBlockWriter.buffer(), -1); - if (compressedBlock.size() != 0) - blockWriter << compressedBlock; + if (precompressedBlockWriter.buffer().size() != 0) { + quint32 block_size = compressedBlock.size(); + blockWriter << block_size; - delete precompressedBlockWriter; - precompressedBlockWriter = new QBuffer; - precompressedBlockWriter->open(QIODevice::ReadWrite); + int write_count = blockWriter.writeRawData(compressedBlock.data(), block_size); + if (write_count != block_size) + qFatal("Didn't write enough data"); + } + else + qFatal("serialized empty compressed block (?)"); + + precompressedBlockWriter.close(); + precompressedBlockWriter.open(QIODevice::WriteOnly); } } + + if (written != initial) + qFatal("didn't write enough bytes"); + + bool condition = basis->isWritable(); + if (!condition) + qWarning("Returning -1 from write"); + return basis->isWritable() ? written : -1; } diff --git a/openbr/core/qtutils.h b/openbr/core/qtutils.h index 257ccd8..fc93059 100644 --- a/openbr/core/qtutils.h +++ b/openbr/core/qtutils.h @@ -122,7 +122,7 @@ namespace QtUtils // write to a QByteArray, when max block sized is reached, compress and write // it to basis - QBuffer * precompressedBlockWriter; + QBuffer precompressedBlockWriter; QDataStream blockWriter; qint64 writeData(const char *data, qint64 remaining); }; diff --git a/openbr/janus b/openbr/janus index e3aa915..cd22014 160000 --- a/openbr/janus +++ b/openbr/janus @@ -1 +1 @@ -Subproject commit e3aa915a5cfc3ee0332f566ae4f13370007964a1 +Subproject commit cd22014f9bd446f26c501e06b1170441bf872408 diff --git a/openbr/openbr.cpp b/openbr/openbr.cpp index 3162371..fd4814c 100644 --- a/openbr/openbr.cpp +++ b/openbr/openbr.cpp @@ -59,7 +59,7 @@ void br_cat(int num_input_galleries, const char *input_galleries[], const char * void br_cluster(int num_simmats, const char *simmats[], float aggressiveness, const char *csv) { - ClusterGallery(QtUtils::toStringList(num_simmats, simmats), aggressiveness, csv); + ClusterSimmat(QtUtils::toStringList(num_simmats, simmats), aggressiveness, csv); } void br_combine_masks(int num_input_masks, const char *input_masks[], const char *output_mask, const char *method) diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index ac99081..5b22901 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -754,6 +754,12 @@ public: Q_PROPERTY(int crossValidate READ get_crossValidate WRITE set_crossValidate RESET reset_crossValidate) BR_PROPERTY(int, crossValidate, 0) + /*! + * \brief List of paths sub-models will be searched for on + */ + Q_PROPERTY(QList modelSearch READ get_modelSearch WRITE set_modelSearch RESET reset_modelSearch) + BR_PROPERTY(QList, modelSearch, QList() ) + QHash abbreviations; /*!< \brief Used by br::Transform::make() to expand abbreviated algorithms into their complete definitions. */ QTime startTime; /*!< \brief Used to estimate timeRemaining(). */ diff --git a/openbr/plugins/classification/adaboost.cpp b/openbr/plugins/classification/adaboost.cpp new file mode 100644 index 0000000..04e094f --- /dev/null +++ b/openbr/plugins/classification/adaboost.cpp @@ -0,0 +1,134 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Wraps OpenCV's Ada Boost framework + * \author Scott Klum \cite sklum + * \brief http://docs.opencv.org/modules/ml/doc/boosting.html + */ +class AdaBoostTransform : public Transform +{ + Q_OBJECT + Q_ENUMS(Type) + Q_ENUMS(SplitCriteria) + + Q_PROPERTY(Type type READ get_type WRITE set_type RESET reset_type STORED false) + Q_PROPERTY(SplitCriteria splitCriteria READ get_splitCriteria WRITE set_splitCriteria RESET reset_splitCriteria STORED false) + Q_PROPERTY(int weakCount READ get_weakCount WRITE set_weakCount RESET reset_weakCount STORED false) + Q_PROPERTY(float trimRate READ get_trimRate WRITE set_trimRate RESET reset_trimRate STORED false) + Q_PROPERTY(int folds READ get_folds WRITE set_folds RESET reset_folds STORED false) + Q_PROPERTY(int maxDepth READ get_maxDepth WRITE set_maxDepth RESET reset_maxDepth STORED false) + Q_PROPERTY(bool returnConfidence READ get_returnConfidence WRITE set_returnConfidence RESET reset_returnConfidence STORED false) + Q_PROPERTY(bool overwriteMat READ get_overwriteMat WRITE set_overwriteMat RESET reset_overwriteMat STORED false) + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) + +public: + enum Type { Discrete = CvBoost::DISCRETE, + Real = CvBoost::REAL, + Logit = CvBoost::LOGIT, + Gentle = CvBoost::GENTLE}; + + enum SplitCriteria { Default = CvBoost::DEFAULT, + Gini = CvBoost::GINI, + Misclass = CvBoost::MISCLASS, + Sqerr = CvBoost::SQERR}; + +private: + BR_PROPERTY(Type, type, Real) + BR_PROPERTY(SplitCriteria, splitCriteria, Default) + BR_PROPERTY(int, weakCount, 100) + BR_PROPERTY(float, trimRate, .95) + BR_PROPERTY(int, folds, 0) + BR_PROPERTY(int, maxDepth, 1) + BR_PROPERTY(bool, returnConfidence, true) + BR_PROPERTY(bool, overwriteMat, true) + BR_PROPERTY(QString, inputVariable, "Label") + BR_PROPERTY(QString, outputVariable, "") + + CvBoost boost; + + void train(const TemplateList &data) + { + Mat samples = OpenCVUtils::toMat(data.data()); + Mat labels = OpenCVUtils::toMat(File::get(data, inputVariable)); + + Mat types = Mat(samples.cols + 1, 1, CV_8U); + types.setTo(Scalar(CV_VAR_NUMERICAL)); + types.at(samples.cols, 0) = CV_VAR_CATEGORICAL; + + CvBoostParams params; + params.boost_type = type; + params.split_criteria = splitCriteria; + params.weak_count = weakCount; + params.weight_trim_rate = trimRate; + params.cv_folds = folds; + params.max_depth = maxDepth; + + boost.train( samples, CV_ROW_SAMPLE, labels, Mat(), Mat(), types, Mat(), + params); + } + + void project(const Template &src, Template &dst) const + { + dst = src; + float response; + if (returnConfidence) { + response = boost.predict(src.m().reshape(1,1),Mat(),Range::all(),false,true)/weakCount; + } else { + response = boost.predict(src.m().reshape(1,1)); + } + + if (overwriteMat) { + dst.m() = Mat(1, 1, CV_32F); + dst.m().at(0, 0) = response; + } else { + dst.file.set(outputVariable, response); + } + } + + void load(QDataStream &stream) + { + OpenCVUtils::loadModel(boost,stream); + } + + void store(QDataStream &stream) const + { + OpenCVUtils::storeModel(boost,stream); + } + + + void init() + { + if (outputVariable.isEmpty()) + outputVariable = inputVariable; + } +}; + +BR_REGISTER(Transform, AdaBoostTransform) + +} // namespace br + +#include "classification/adaboost.moc" diff --git a/openbr/plugins/dlib.cpp b/openbr/plugins/classification/dlib.cpp index 6517840..9448e90 100644 --- a/openbr/plugins/dlib.cpp +++ b/openbr/plugins/classification/dlib.cpp @@ -1,4 +1,4 @@ -#include "openbr_internal.h" +#include "openbr/plugins/openbr_internal.h" #include "openbr/core/qtutils.h" #include "openbr/core/eigenutils.h" diff --git a/openbr/plugins/ebif.cpp b/openbr/plugins/classification/ebif.cpp index 718a3b9..19d79ba 100644 --- a/openbr/plugins/ebif.cpp +++ b/openbr/plugins/classification/ebif.cpp @@ -1,8 +1,24 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include -#include "openbr_internal.h" -#include "openbr/core/common.h" -#include "openbr/core/opencvutils.h" +#include +#include +#include using namespace cv; @@ -128,4 +144,4 @@ BR_REGISTER(Transform, EBIFTransform) } // namespace br -#include "ebif.moc" +#include "classification/ebif.moc" diff --git a/openbr/plugins/tree.cpp b/openbr/plugins/classification/forest.cpp index 8ecc39f..b717533 100644 --- a/openbr/plugins/tree.cpp +++ b/openbr/plugins/classification/forest.cpp @@ -1,7 +1,22 @@ -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include -using namespace std; using namespace cv; namespace br @@ -250,113 +265,6 @@ class ForestInductionTransform : public ForestTransform BR_REGISTER(Transform, ForestInductionTransform) -/*! - * \ingroup transforms - * \brief Wraps OpenCV's Ada Boost framework - * \author Scott Klum \cite sklum - * \brief http://docs.opencv.org/modules/ml/doc/boosting.html - */ -class AdaBoostTransform : public Transform -{ - Q_OBJECT - Q_ENUMS(Type) - Q_ENUMS(SplitCriteria) - - Q_PROPERTY(Type type READ get_type WRITE set_type RESET reset_type STORED false) - Q_PROPERTY(SplitCriteria splitCriteria READ get_splitCriteria WRITE set_splitCriteria RESET reset_splitCriteria STORED false) - Q_PROPERTY(int weakCount READ get_weakCount WRITE set_weakCount RESET reset_weakCount STORED false) - Q_PROPERTY(float trimRate READ get_trimRate WRITE set_trimRate RESET reset_trimRate STORED false) - Q_PROPERTY(int folds READ get_folds WRITE set_folds RESET reset_folds STORED false) - Q_PROPERTY(int maxDepth READ get_maxDepth WRITE set_maxDepth RESET reset_maxDepth STORED false) - Q_PROPERTY(bool returnConfidence READ get_returnConfidence WRITE set_returnConfidence RESET reset_returnConfidence STORED false) - Q_PROPERTY(bool overwriteMat READ get_overwriteMat WRITE set_overwriteMat RESET reset_overwriteMat STORED false) - Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) - Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) - -public: - enum Type { Discrete = CvBoost::DISCRETE, - Real = CvBoost::REAL, - Logit = CvBoost::LOGIT, - Gentle = CvBoost::GENTLE}; - - enum SplitCriteria { Default = CvBoost::DEFAULT, - Gini = CvBoost::GINI, - Misclass = CvBoost::MISCLASS, - Sqerr = CvBoost::SQERR}; - -private: - BR_PROPERTY(Type, type, Real) - BR_PROPERTY(SplitCriteria, splitCriteria, Default) - BR_PROPERTY(int, weakCount, 100) - BR_PROPERTY(float, trimRate, .95) - BR_PROPERTY(int, folds, 0) - BR_PROPERTY(int, maxDepth, 1) - BR_PROPERTY(bool, returnConfidence, true) - BR_PROPERTY(bool, overwriteMat, true) - BR_PROPERTY(QString, inputVariable, "Label") - BR_PROPERTY(QString, outputVariable, "") - - CvBoost boost; - - void train(const TemplateList &data) - { - Mat samples = OpenCVUtils::toMat(data.data()); - Mat labels = OpenCVUtils::toMat(File::get(data, inputVariable)); - - Mat types = Mat(samples.cols + 1, 1, CV_8U); - types.setTo(Scalar(CV_VAR_NUMERICAL)); - types.at(samples.cols, 0) = CV_VAR_CATEGORICAL; - - CvBoostParams params; - params.boost_type = type; - params.split_criteria = splitCriteria; - params.weak_count = weakCount; - params.weight_trim_rate = trimRate; - params.cv_folds = folds; - params.max_depth = maxDepth; - - boost.train( samples, CV_ROW_SAMPLE, labels, Mat(), Mat(), types, Mat(), - params); - } - - void project(const Template &src, Template &dst) const - { - dst = src; - float response; - if (returnConfidence) { - response = boost.predict(src.m().reshape(1,1),Mat(),Range::all(),false,true)/weakCount; - } else { - response = boost.predict(src.m().reshape(1,1)); - } - - if (overwriteMat) { - dst.m() = Mat(1, 1, CV_32F); - dst.m().at(0, 0) = response; - } else { - dst.file.set(outputVariable, response); - } - } - - void load(QDataStream &stream) - { - OpenCVUtils::loadModel(boost,stream); - } - - void store(QDataStream &stream) const - { - OpenCVUtils::storeModel(boost,stream); - } - - - void init() - { - if (outputVariable.isEmpty()) - outputVariable = inputVariable; - } -}; - -BR_REGISTER(Transform, AdaBoostTransform) - } // namespace br -#include "tree.moc" +#include "classification/forest.moc" diff --git a/openbr/plugins/ipc2013.cpp b/openbr/plugins/classification/ipc2013.cpp index 4f5ce59..d16958c 100644 --- a/openbr/plugins/ipc2013.cpp +++ b/openbr/plugins/classification/ipc2013.cpp @@ -1,9 +1,10 @@ -#include "openbr_internal.h" #include #include #include #include +#include + using namespace br; static PXCSession *pxcSession = NULL; diff --git a/openbr/plugins/eigen3.cpp b/openbr/plugins/classification/lda.cpp index dfe4ba8..bc90925 100644 --- a/openbr/plugins/eigen3.cpp +++ b/openbr/plugins/classification/lda.cpp @@ -15,12 +15,14 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include +#include +#include -#include "openbr_internal.h" +#include -#include "openbr/core/common.h" -#include "openbr/core/eigenutils.h" -#include "openbr/core/opencvutils.h" +#include +#include +#include namespace br { @@ -652,46 +654,609 @@ class SparseLDATransform : public Transform BR_REGISTER(Transform, SparseLDATransform) +using namespace Eigen; +using namespace cv; + /*! - * \ingroup distances - * \brief L1 distance computed using eigen. + * \ingroup transforms + * \brief Projects input into a within-class minimizing subspace. + * + * Like LDA but without the explicit between-class consideration. + * + * \par Compression + * Projection matricies can become quite large, resulting in proportionally large model files. + * WCDA automatically alleviates this issue with lossy compression of the projection matrix. + * Each element is stored as an 8-bit integer instead of a 32-bit float, resulting in a 75% reduction in the size of the projection matrix. + * A non-linear (sqrt) scaling is used because element values are distributed around 0, in affect allowing for higher precision storage of the more frequently occuring values. + * * \author Josh Klontz \cite jklontz */ -class L1Distance : public UntrainableDistance +class WCDATransform : public Transform { Q_OBJECT + Q_PROPERTY(float keep READ get_keep WRITE set_keep RESET reset_keep STORED false) + BR_PROPERTY(float, keep, 0.98) + + VectorXf mean; + MatrixXf projection; + + typedef Matrix CompressedMatrix; + CompressedMatrix compressed; + float a, b; - float compare(const cv::Mat &a, const cv::Mat &b) const + static CompressedMatrix compress(MatrixXf src, float &a, float &b) { - const int size = a.rows * a.cols; - Eigen::Map aMap((float*)a.data, size); - Eigen::Map bMap((float*)b.data, size); - return (aMap-bMap).cwiseAbs().sum(); + for (int i=0; i= 0) ? 1 : -1); + } + b = src.minCoeff(); + a = (src.maxCoeff() - b) / 255; + return ((src.array() - b) / a).cast(); } + + static MatrixXf decompress(const CompressedMatrix &src, float a, float b) + { + MatrixXf dst = src.cast().array() * a + b; + for (int i=0; i= 0) ? 1 : -1); + } + return dst; + } + + void train(const TemplateList &_templates) + { + // Relabel ensures that class values range from 0 to numClasses-1. + const TemplateList templates = TemplateList::relabel(_templates, "Label", false); + const int instances = templates.size(); + const int dimsIn = templates.first().m().rows * templates.first().m().cols; + + // Map data into Eigen + MatrixXf data(dimsIn, instances); + for (int i=0; i(templates[i].m().ptr(), dimsIn); + + // Perform PCA dimensionality reduction + VectorXf pcaEvals; + MatrixXf pcaEvecs; + trainCore(data, keep, mean, pcaEvals, pcaEvecs); + data = pcaEvecs.transpose() * (data.colwise() - mean); + + // Get ground truth + const QList classes = File::get(templates, "Label"); + const QMap classCounts = templates.countValues("Label"); + const int numClasses = classCounts.size(); + + // Compute and remove the class means + MatrixXf classMeans = MatrixXf::Zero(data.rows(), numClasses); + for (int i=0; i instances); + + // Compute and remove mean + mean = data.rowwise().sum() / instances; + data.colwise() -= mean; + + // Calculate and normalize covariance matrix + MatrixXf cov; + if (dominantEigenEstimation) cov = data.transpose() * data; + else cov = data * data.transpose(); + cov /= (instances-1); + + // Compute eigendecomposition, returning eigenvectors/eigenvalues in increasing order by eigenvalue. + SelfAdjointEigenSolver eSolver(cov); + VectorXf allEVals = eSolver.eigenvalues(); + MatrixXf allEVecs = eSolver.eigenvectors(); + if (dominantEigenEstimation) + allEVecs = data * allEVecs; + + if (keep < 1) { + // Keep eigenvectors that retain a certain energy percentage. + const float desiredEnergy = keep * allEVals.sum(); + float currentEnergy = 0; + int i = 0; + while ((currentEnergy < desiredEnergy) && (i < allEVals.rows())) { + currentEnergy += allEVals(allEVals.rows()-(i+1)); + i++; + } + keep = i; + } else { + if (keep > allEVals.rows()) { + qWarning("Insufficient samples, needed at least %d but only got %d.", (int)keep, (int)allEVals.rows()); + keep = allEVals.rows(); + } + } + + // Keep highest energy vectors + eVals = VectorXf((int)keep); + eVecs = MatrixXf(allEVecs.rows(), (int)keep); + for (int i=0; i inMap(src.m().ptr(), src.m().rows * src.m().cols); + Map outMap(dst.m().ptr(), projection.rows()); + outMap = projection * (inMap - mean); + } + + void store(QDataStream &stream) const + { + stream << mean << compressed << a << b; + } + + void load(QDataStream &stream) + { + stream >> mean >> compressed >> a >> b; + projection = decompress(compressed, a, b); + } +}; + +BR_REGISTER(Transform, WCDATransform) + +// For use in BBLDAAlignmentTransform +// A single decision boundary with gaussian distributed responses +struct DecisionBoundary +{ + VectorXf function; + float positiveMean, negativeMean, positiveSD, negativeSD; }; -BR_REGISTER(Distance, L1Distance) +QDataStream &operator<<(QDataStream &stream, const DecisionBoundary &db) +{ + return stream << db.function << db.positiveMean << db.negativeMean << db.positiveSD << db.negativeSD; +} + +QDataStream &operator>>(QDataStream &stream, DecisionBoundary &db) +{ + return stream >> db.function >> db.positiveMean >> db.negativeMean >> db.positiveSD >> db.negativeSD; +} /*! - * \ingroup distances - * \brief L2 distance computed using eigen. - * \author Josh Klontz \cite jklontz + * \ingroup transforms + * \brief Boosted Binary LDA classifier for local alignment. */ -class L2Distance : public UntrainableDistance +class BBLDAAlignmentTransform : public MetaTransform { Q_OBJECT + Q_PROPERTY(QList anchors READ get_anchors WRITE set_anchors RESET reset_anchors STORED false) // A list of two indicies to provide initial rotation and scaling of training data + Q_PROPERTY(QString detectionKey READ get_detectionKey WRITE set_detectionKey RESET reset_detectionKey STORED false) // Metadata key containing the detection bounding box + Q_PROPERTY(int iad READ get_iad WRITE set_iad RESET reset_iad STORED false) // Inter-anchor distance in pixels + Q_PROPERTY(float radius READ get_radius WRITE set_radius RESET reset_radius STORED false) // Kernel size as a fraction of IAD + Q_PROPERTY(int resolution READ get_resolution WRITE set_resolution RESET reset_resolution STORED false) // Project resolution for sampling rotation and scale + BR_PROPERTY(QList, anchors, QList()) + BR_PROPERTY(QString, detectionKey, "FrontalFace") + BR_PROPERTY(int, iad, 16) + BR_PROPERTY(float, radius, 2) + BR_PROPERTY(int, resolution, 20) + + typedef Matrix MatrixX8U; + + struct RegistrationMatrix + { + Mat src; + Point2f center; + double angle, scale; + + RegistrationMatrix(const Mat &src = Mat(), const Point2f ¢er = Point2f(), double angle = 0, double scale = 1) + : src(src) + , center(center) + , angle(angle) + , scale(scale) + {} + + Mat calculateRotationMatrix2D(int size) const + { + Mat rotationMatrix2D = getRotationMatrix2D(center, angle, scale); + rotationMatrix2D.at(0, 2) -= (center.x - size / 2.0); // Adjust the center to the middle of the dst image + rotationMatrix2D.at(1, 2) -= (center.y - size / 2.0); + return rotationMatrix2D; + } + + Point2f invert(double x, double y, int size) const + { + Mat m; + invertAffineTransform(calculateRotationMatrix2D(size), m); + + Mat src(1, 1, CV_64FC2); + src.ptr()[0] = y; // According to the docs these should be specified in the opposite order ... + src.ptr()[1] = x; // ... however running it seems to suggest this is the correct way + + Mat dst; + transform(src, dst, m); + return Point2f(dst.ptr()[0], dst.ptr()[1]); + } + + MatrixX8U warp(int size) const + { + const Mat rotationMatrix2D = calculateRotationMatrix2D(size); + Mat dst; + warpAffine(src, dst, rotationMatrix2D, Size(size, size), INTER_LINEAR, BORDER_REFLECT); + return Map(dst.ptr(), size, size); + } + }; + + float rotationMax, scaleMin, scaleMax, xTranslationMax, yTranslationMin, yTranslationMax; + QList decisionBoundaries; + + static void show(const char *name, const MatrixX8U &data) + { + const Mat mat(data.rows(), data.cols(), CV_8UC1, (void*) data.data()); + imshow(name, mat); + waitKey(-1); + } + + static void show(const char *name, MatrixXf data) + { + data.array() -= data.minCoeff(); + data.array() *= (255 / data.maxCoeff()); + show(name, MatrixX8U(data.cast())); + } + + static void show(const char *name, VectorXf data) + { + MatrixXf resized = data; + const int size = int(sqrtf(float(data.rows()))); + resized.resize(size, size); + show(name, resized); + } + + static MatrixXf getSample(const MatrixXf ®istered, int j, int k, int kernelSize) + { + MatrixXf sample(registered.block(j, k, kernelSize, kernelSize)); + const float norm = sample.norm(); + if (norm > 0) + sample /= norm; + return sample; + } + + static MatrixXf getSample(const QList ®istered, int i, int j, int k, int kernelSize) + { + return getSample(registered[i], j, k, kernelSize); + } + + static float IADError(const MatrixXf &responses, const MatrixXf &mask) + { + const int responseSize = sqrtf(responses.cols()); + int numImages = 0; + float totalError = 0; + for (int i=0; i::max(); + int maxX = -1; + int maxY = -1; + for (int j=0; j 0) { + const float response = responses(i, j*responseSize+k); + if (response > maxResponse) { + maxResponse = response; + maxY = j; + maxX = k; + } + } + totalError += sqrtf(powf(maxX - responseSize/2, 2) + powf(maxY - responseSize/2, 2)) / responseSize; + numImages++; + } + return totalError / numImages; + } + + static bool isPositive(int j, int k, int responseSize) + { + return (abs(j - responseSize/2) <= 0) && (abs(k - responseSize/2) <= 0); + } - float compare(const cv::Mat &a, const cv::Mat &b) const + static MatrixXf /* output responses */ computeBoundaryRecursive(const QList ®istered, int kernelSize, const MatrixXf &prior /* input responses */, QList &boundaries) + { + const int numImages = registered.size(); + const int responseSize = registered.first().cols() - kernelSize + 1; + int positiveSamples = 0; + int negativeSamples = 0; + + // Compute weights, a weight of zero operates as a mask + MatrixXf weights(numImages, responseSize*responseSize); + float totalPositiveWeight = 0, totalNegativeWeight = 0; + for (int i=0; i 0) { + if (positive) { totalPositiveWeight += weight; positiveSamples++; } + else { totalNegativeWeight += weight; negativeSamples++; } + } + } + + // Normalize weights to preserve class sample ratio + const float positiveWeightAdjustment = positiveSamples / totalPositiveWeight; + const float negativeWeightAdjustment = negativeSamples / totalNegativeWeight; + for (int i=0; i 0) { + if (isPositive(j, k, responseSize)) positiveMean += sample * weight; + else negativeMean += sample * weight; + } + } + positiveMean /= positiveSamples; + negativeMean /= negativeSamples; + + // Compute weighted scatter matrix and decision boundary + MatrixXf scatter = MatrixXf::Zero(kernelSize*kernelSize, kernelSize*kernelSize); + for (int i=0; i 0) { + const MatrixXf ¢eredSampleMatrix = getSample(registered, i, j, k, kernelSize) - (isPositive(j, k, responseSize) ? positiveMean : negativeMean); + const Map centeredSample(centeredSampleMatrix.data(), kernelSize*kernelSize); + scatter.noalias() += centeredSample * centeredSample.transpose() * weights(i, j*responseSize+k); + } + } + scatter /= (positiveSamples + negativeSamples); // normalize for numerical stability + DecisionBoundary db; + db.function = scatter.inverse() * VectorXf(positiveMean - negativeMean); // fisher linear discriminant + + // Compute response values to decision boundary + MatrixXf posterior(numImages, responseSize*responseSize); + for (int i=0; i 0) { + const MatrixXf sample(getSample(registered, i, j, k, kernelSize)); + posterior(i, j*responseSize + k) = db.function.transpose() * Map(sample.data(), kernelSize*kernelSize); + } + + // Compute class response means + db.positiveMean = 0; + db.negativeMean = 0; + for (int i=0; i 0) { + const float response = posterior(i, j*responseSize + k); + if (isPositive(j, k, responseSize)) db.positiveMean += response; + else db.negativeMean += response; + } + db.positiveMean /= positiveSamples; + db.negativeMean /= negativeSamples; + + // Compute class response standard deviations + db.positiveSD = 0; + db.negativeSD = 0; + for (int i=0; i 0) { + const float response = posterior(i, j*responseSize + k); + if (isPositive(j, k, responseSize)) db.positiveSD += powf(response-db.positiveMean, 2.f); + else db.negativeSD += powf(response-db.negativeMean, 2.f); + } + db.positiveSD = sqrtf(db.positiveSD / positiveSamples); + db.negativeSD = sqrtf(db.negativeSD / negativeSamples); + + // Normalize responses and propogating prior probabilities + for (int i=0; i 0) { + const float positiveDensity = 1 / (sqrtf(2 * CV_PI) * db.positiveSD) * expf(-0.5 * powf((response - db.positiveMean) / db.positiveSD, 2.f)); + const float negativeDensity = 1 / (sqrtf(2 * CV_PI) * db.negativeSD) * expf(-0.5 * powf((response - db.negativeMean) / db.negativeSD, 2.f)); + response = prior(i, j*responseSize+k) * positiveDensity / (positiveDensity + negativeDensity); + } else { + response = 0; + } + } + const float previousMeanError = IADError(prior, weights); + const float meanError = IADError(posterior, weights); + qDebug() << "Samples positive & negative:" << positiveSamples << negativeSamples; + qDebug() << "Positive mean & stddev:" << db.positiveMean << db.positiveSD; + qDebug() << "Negative mean & stddev:" << db.negativeMean << db.negativeSD; + qDebug() << "Mean error before & after:" << previousMeanError << meanError; + + if (meanError < previousMeanError) { + boundaries.append(db); + return computeBoundaryRecursive(registered, kernelSize, posterior, boundaries); + } else { + return prior; + } + } + + void train(const TemplateList &data) + { + QList registrationMatricies; + { + if (Globals->verbose) + qDebug("Preprocessing training data..."); + + rotationMax = -std::numeric_limits::max(); + scaleMin = std::numeric_limits::max(); + scaleMax = -std::numeric_limits::max(); + xTranslationMax = -std::numeric_limits::max(); + yTranslationMin = std::numeric_limits::max(); + yTranslationMax = -std::numeric_limits::max(); + + foreach (const Template &t, data) { + const QRectF detection = t.file.get(detectionKey); + const QList points = t.file.points(); + const float length = sqrt(pow(points[anchors[1]].x() - points[anchors[0]].x(), 2) + + pow(points[anchors[1]].y() - points[anchors[0]].y(), 2)); + const float rotation = atan2(points[anchors[1]].y() - points[anchors[0]].y(), + points[anchors[1]].x() - points[anchors[0]].x()) * 180 / CV_PI; + const float scale = length / detection.width(); + const float xCenter = (points[anchors[0]].x() + points[anchors[1]].x()) / 2; + const float yCenter = (points[anchors[0]].y() + points[anchors[1]].y()) / 2; + const float xTranslation = (xCenter - detection.x() - detection.width() /2) / detection.width(); + const float yTranslation = (yCenter - detection.y() - detection.height()/2) / detection.width(); + + if (detection.contains(xCenter, yCenter) /* Sometimes the face detector gets the wrong face */) { + rotationMax = max(rotationMax, fabsf(rotation)); + scaleMin = min(scaleMin, scale); + scaleMax = max(scaleMax, scale); + xTranslationMax = max(xTranslationMax, fabsf(xTranslation)); + yTranslationMin = min(yTranslationMin, yTranslation); + yTranslationMax = max(yTranslationMax, yTranslation); + } + + registrationMatricies.append(RegistrationMatrix(t, Point2f(xCenter, yCenter), rotation, iad / length)); + } + } + + if (Globals->verbose) + qDebug("Learning affine model ..."); + + // Construct the registered training data for the landmark + const int regionSize = 2 * iad * radius; // Train on a search size that is twice to the kernel size + QList registered; + foreach (const RegistrationMatrix ®istrationMatrix, registrationMatricies) + registered.append(registrationMatrix.warp(regionSize).cast()); + + // Boosted LDA + const int numImages = registered.size(); + const int kernelSize = iad * radius; + const int responseSize = regionSize - kernelSize + 1; + const MatrixXf prior = MatrixXf::Ones(numImages, responseSize*responseSize); + const MatrixXf responses = computeBoundaryRecursive(registered, kernelSize, prior, decisionBoundaries); + +// for (int i=0; i::max(); +// int maxX = -1; +// int maxY = -1; +// for (int j=0; j maxResponse) { +// maxResponse = response; +// maxY = j; +// maxX = k; +// } +// } + +// MatrixXf draw = registered[i]; +// const int r = 3; +// maxX += kernelSize / 2; +// maxY += kernelSize / 2; +// for (int i=-r; i<=r; i++) +// draw(regionSize/2 + i, regionSize/2) *= 2; +// for (int i=-r; i<=r; i++) +// draw(regionSize/2, regionSize/2 + i) *= 2; +// for (int i=-r; i<=r; i++) +// draw(maxX + i, maxY) /= 2; +// for (int i=-r; i<=r; i++) +// draw(maxX, maxY + i) /= 2; +// show("draw", draw); +// show("responses", VectorXf(responses.row(i))); +// } + + if (Globals->verbose) + qDebug("Learned %d function(s) with error %g", decisionBoundaries.size(), IADError(responses, prior)); + } + + void project(const Template &src, Template &dst) const + { + dst = src; + const QRectF detection = src.file.get(detectionKey); + RegistrationMatrix registrationMatrix(src, OpenCVUtils::toPoint(detection.center())); + + const int regionSize = iad * (1 + radius); + const int kernelSize = iad * radius; + const int responseSize = regionSize - kernelSize + 1; + const float rotationStep = (2 * rotationMax) / (resolution - 1); + const float scaleStep = (scaleMax - scaleMin) / (resolution - 1); + + float bestResponse = -std::numeric_limits::max(), bestRotation = 0, bestScale = 0; + int bestX = 0, bestY =0; + + for (float rotation = -rotationMax; rotation <= rotationMax; rotation += rotationStep) { + registrationMatrix.angle = rotation; + for (float scale = scaleMin; scale <= scaleMax; scale += scaleStep) { + registrationMatrix.scale = iad / (scale * detection.width()); + const MatrixXf registered = registrationMatrix.warp(regionSize).cast(); + + for (int j=0; j(sample.data(), kernelSize*kernelSize); + const float positiveDensity = 1 / (sqrtf(2 * CV_PI) * db.positiveSD) * expf(-0.5 * powf((response - db.positiveMean) / db.positiveSD, 2.f)); + const float negativeDensity = 1 / (sqrtf(2 * CV_PI) * db.negativeSD) * expf(-0.5 * powf((response - db.negativeMean) / db.negativeSD, 2.f)); + posterior *= positiveDensity / (positiveDensity + negativeDensity); + } + if (posterior > bestResponse) { + bestResponse = posterior; + bestX = k + kernelSize/2; + bestY = j + kernelSize/2; + bestRotation = rotation; + bestScale = scale; + } + } + } + } + } + + void store(QDataStream &stream) const + { + stream << rotationMax << scaleMin << scaleMax << xTranslationMax << yTranslationMin << yTranslationMax << decisionBoundaries; + } + + void load(QDataStream &stream) { - const int size = a.rows * a.cols; - Eigen::Map aMap((float*)a.data, size); - Eigen::Map bMap((float*)b.data, size); - return (aMap-bMap).squaredNorm(); + stream >> rotationMax >> scaleMin >> scaleMax >> xTranslationMax >> yTranslationMin >> yTranslationMax >> decisionBoundaries; } }; -BR_REGISTER(Distance, L2Distance) +BR_REGISTER(Transform, BBLDAAlignmentTransform) } // namespace br -#include "eigen3.moc" +#include "classification/lda.moc" diff --git a/openbr/plugins/liblinear.cpp b/openbr/plugins/classification/liblinear.cpp index 1b9649e..31b2137 100644 --- a/openbr/plugins/liblinear.cpp +++ b/openbr/plugins/classification/liblinear.cpp @@ -2,8 +2,8 @@ #include #include -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" +#include +#include #include diff --git a/openbr/plugins/nn.cpp b/openbr/plugins/classification/mlp.cpp index 83fd93f..4295004 100644 --- a/openbr/plugins/nn.cpp +++ b/openbr/plugins/classification/mlp.cpp @@ -1,51 +1,29 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include -#include "openbr_internal.h" -#include "openbr/core/qtutils.h" -#include "openbr/core/opencvutils.h" -#include "openbr/core/eigenutils.h" -#include -#include +#include +#include -using namespace std; using namespace cv; namespace br { -static void storeMLP(const CvANN_MLP &mlp, QDataStream &stream) -{ - // Create local file - QTemporaryFile tempFile; - tempFile.open(); - tempFile.close(); - - // Save MLP to local file - mlp.save(qPrintable(tempFile.fileName())); - - // Copy local file contents to stream - tempFile.open(); - QByteArray data = tempFile.readAll(); - tempFile.close(); - stream << data; -} - -static void loadMLP(CvANN_MLP &mlp, QDataStream &stream) -{ - // Copy local file contents from stream - QByteArray data; - stream >> data; - - // Create local file - QTemporaryFile tempFile(QDir::tempPath()+"/MLP"); - tempFile.open(); - tempFile.write(data); - tempFile.close(); - - // Load MLP from local file - mlp.load(qPrintable(tempFile.fileName())); -} - /*! * \ingroup transforms * \brief Wraps OpenCV's multi-layer perceptron framework @@ -124,12 +102,12 @@ private: void load(QDataStream &stream) { - loadMLP(mlp,stream); + OpenCVUtils::loadModel(mlp, stream); } void store(QDataStream &stream) const { - storeMLP(mlp,stream); + OpenCVUtils::storeModel(mlp, stream); } }; @@ -137,4 +115,4 @@ BR_REGISTER(Transform, MLPTransform) } // namespace br -#include "nn.moc" +#include "classification/mlp.moc" diff --git a/openbr/plugins/nt4.cpp b/openbr/plugins/classification/nt4.cpp index a3ded95..218ce0a 100644 --- a/openbr/plugins/nt4.cpp +++ b/openbr/plugins/classification/nt4.cpp @@ -20,8 +20,8 @@ #include #include -#include "core/resource.h" -#include "core/opencvutils.h" +#include +#include using namespace cv; using namespace br; @@ -457,4 +457,4 @@ class NT4Compare : public Distance BR_REGISTER(Distance, NT4Compare) -#include "nt4.moc" +#include "classification/nt4.moc" diff --git a/openbr/plugins/pp4.cpp b/openbr/plugins/classification/pp4.cpp index 69bac4b..6276d08 100644 --- a/openbr/plugins/pp4.cpp +++ b/openbr/plugins/classification/pp4.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "openbr_internal.h" +#include "openbr/plugins/openbr_internal.h" #include "openbr/core/resource.h" #define TRY(CC) \ diff --git a/openbr/plugins/pp5.cpp b/openbr/plugins/classification/pp5.cpp index 6c795a5..0305626 100644 --- a/openbr/plugins/pp5.cpp +++ b/openbr/plugins/classification/pp5.cpp @@ -11,7 +11,7 @@ #include #include #include -#include "openbr_internal.h" +#include "openbr/plugins/openbr_internal.h" #include "openbr/core/resource.h" #define TRY(CC) \ @@ -593,4 +593,4 @@ class PP5GalleryTransform: public UntrainableMetaTransform BR_REGISTER(Transform, PP5GalleryTransform) -#include "plugins/pp5.moc" +#include "classification/pp5.moc" diff --git a/openbr/plugins/svm.cpp b/openbr/plugins/classification/svm.cpp index 8acd3a7..e6f42d1 100644 --- a/openbr/plugins/svm.cpp +++ b/openbr/plugins/classification/svm.cpp @@ -18,8 +18,8 @@ #include #include -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" +#include +#include using namespace cv; @@ -261,4 +261,4 @@ BR_REGISTER(Distance, SVMDistance) } // namespace br -#include "svm.moc" +#include "classification/svm.moc" diff --git a/openbr/plugins/classification/turk.cpp b/openbr/plugins/classification/turk.cpp new file mode 100644 index 0000000..79594b6 --- /dev/null +++ b/openbr/plugins/classification/turk.cpp @@ -0,0 +1,72 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Convenience class for training turk attribute regressors + * \author Josh Klontz \cite jklontz + */ +class TurkClassifierTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(QString key READ get_key WRITE set_key RESET reset_key STORED false) + Q_PROPERTY(QStringList values READ get_values WRITE set_values RESET reset_values STORED false) + Q_PROPERTY(bool isMeta READ get_isMeta WRITE set_isMeta RESET reset_isMeta STORED false) + BR_PROPERTY(QString, key, QString()) + BR_PROPERTY(QStringList, values, QStringList()) + BR_PROPERTY(bool, isMeta, false) + + Transform *child; + + void init() + { + QStringList classifiers; + foreach (const QString &value, values) + classifiers.append(QString("(SVM(RBF,EPS_SVR,returnDFVal=true,inputVariable=%1,outputVariable=predicted_%1)%2)").arg(key + "_" + value, isMeta ? QString("+Average+SaveMat(predicted_%1)").arg(value) : QString())); + child = Transform::make(classifiers.join("/") + (classifiers.size() > 1 ? "+Cat" : "")); + } + + void train(const QList &data) + { + child->train(data); + } + + void project(const Template &src, Template &dst) const + { + child->project(src, dst); + } + + void store(QDataStream &stream) const + { + child->store(stream); + } + + void load(QDataStream &stream) + { + child->load(stream); + } +}; + +BR_REGISTER(Transform, TurkClassifierTransform) + +} // namespace br + +#include "classification/turk.moc" diff --git a/openbr/plugins/cluster/collectnn.cpp b/openbr/plugins/cluster/collectnn.cpp new file mode 100644 index 0000000..a077a0a --- /dev/null +++ b/openbr/plugins/cluster/collectnn.cpp @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Collect nearest neighbors and append them to metadata. + * \author Charles Otto \cite caotto + */ +class CollectNNTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + Q_PROPERTY(int keep READ get_keep WRITE set_keep RESET reset_keep STORED false) + BR_PROPERTY(int, keep, 20) + + void project(const Template &src, Template &dst) const + { + dst.file = src.file; + dst.clear(); + dst.m() = cv::Mat(); + Neighbors neighbors; + for (int i=0; i < src.m().cols;i++) { + // skip self compares + if (i == src.file.get("FrameNumber")) + continue; + neighbors.append(Neighbor(i, src.m().at(0,i))); + } + int actuallyKeep = std::min(keep, neighbors.size()); + std::partial_sort(neighbors.begin(), neighbors.begin()+actuallyKeep, neighbors.end(), compareNeighbors); + + Neighbors selected = neighbors.mid(0, actuallyKeep); + dst.file.set("neighbors", QVariant::fromValue(selected)); + } +}; + +BR_REGISTER(Transform, CollectNNTransform) + +} // namespace br + +#include "cluster/collectnn.moc" diff --git a/openbr/plugins/cluster/kmeans.cpp b/openbr/plugins/cluster/kmeans.cpp new file mode 100644 index 0000000..cea7381 --- /dev/null +++ b/openbr/plugins/cluster/kmeans.cpp @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Wraps OpenCV kmeans and flann. + * \author Josh Klontz \cite jklontz + */ +class KMeansTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(int kTrain READ get_kTrain WRITE set_kTrain RESET reset_kTrain STORED false) + Q_PROPERTY(int kSearch READ get_kSearch WRITE set_kSearch RESET reset_kSearch STORED false) + BR_PROPERTY(int, kTrain, 256) + BR_PROPERTY(int, kSearch, 1) + + Mat centers; + mutable QScopedPointer index; + mutable QMutex mutex; + + void reindex() + { + index.reset(new flann::Index(centers, flann::LinearIndexParams())); + } + + void train(const TemplateList &data) + { + Mat bestLabels; + const double compactness = kmeans(OpenCVUtils::toMatByRow(data.data()), kTrain, bestLabels, TermCriteria(TermCriteria::MAX_ITER, 10, 0), 3, KMEANS_PP_CENTERS, centers); + qDebug("KMeans compactness = %f", compactness); + reindex(); + } + + void project(const Template &src, Template &dst) const + { + QMutexLocker locker(&mutex); + Mat dists, indicies; + index->knnSearch(src, indicies, dists, kSearch); + dst = indicies.reshape(1, 1); + } + + void load(QDataStream &stream) + { + stream >> centers; + reindex(); + } + + void store(QDataStream &stream) const + { + stream << centers; + } +}; + +BR_REGISTER(Transform, KMeansTransform) + +} // namespace br + +#include "cluster/kmeans.moc" diff --git a/openbr/plugins/cluster.cpp b/openbr/plugins/cluster/knn.cpp index ab95f27..8284c78 100644 --- a/openbr/plugins/cluster.cpp +++ b/openbr/plugins/cluster/knn.cpp @@ -14,12 +14,8 @@ * limitations under the License. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include - -#include "openbr_internal.h" -#include "openbr/core/common.h" -#include "openbr/core/opencvutils.h" -#include +#include +#include using namespace cv; @@ -28,58 +24,6 @@ namespace br /*! * \ingroup transforms - * \brief Wraps OpenCV kmeans and flann. - * \author Josh Klontz \cite jklontz - */ -class KMeansTransform : public Transform -{ - Q_OBJECT - Q_PROPERTY(int kTrain READ get_kTrain WRITE set_kTrain RESET reset_kTrain STORED false) - Q_PROPERTY(int kSearch READ get_kSearch WRITE set_kSearch RESET reset_kSearch STORED false) - BR_PROPERTY(int, kTrain, 256) - BR_PROPERTY(int, kSearch, 1) - - Mat centers; - mutable QScopedPointer index; - mutable QMutex mutex; - - void reindex() - { - index.reset(new flann::Index(centers, flann::LinearIndexParams())); - } - - void train(const TemplateList &data) - { - Mat bestLabels; - const double compactness = kmeans(OpenCVUtils::toMatByRow(data.data()), kTrain, bestLabels, TermCriteria(TermCriteria::MAX_ITER, 10, 0), 3, KMEANS_PP_CENTERS, centers); - qDebug("KMeans compactness = %f", compactness); - reindex(); - } - - void project(const Template &src, Template &dst) const - { - QMutexLocker locker(&mutex); - Mat dists, indicies; - index->knnSearch(src, indicies, dists, kSearch); - dst = indicies.reshape(1, 1); - } - - void load(QDataStream &stream) - { - stream >> centers; - reindex(); - } - - void store(QDataStream &stream) const - { - stream << centers; - } -}; - -BR_REGISTER(Transform, KMeansTransform) - -/*! - * \ingroup transforms * \brief K nearest neighbors classifier. * \author Josh Klontz \cite jklontz */ @@ -151,148 +95,6 @@ class KNNTransform : public Transform BR_REGISTER(Transform, KNNTransform) -/*! - * \ingroup transforms - * \brief Chooses k random points to be centroids. - * \author Austin Blanton \cite imaus10 - * \see KMeansTransform - */ -class RandomCentroidsTransform : public Transform -{ - Q_OBJECT - Q_PROPERTY(int kTrain READ get_kTrain WRITE set_kTrain RESET reset_kTrain STORED false) - Q_PROPERTY(int kSearch READ get_kSearch WRITE set_kSearch RESET reset_kSearch STORED false) - BR_PROPERTY(int, kTrain, 256) - BR_PROPERTY(int, kSearch, 1) - - Mat centers; - mutable QScopedPointer index; - mutable QMutex mutex; - - void reindex() - { - index.reset(new flann::Index(centers, flann::LinearIndexParams())); - } - - void train(const TemplateList &data) - { - Mat flat = OpenCVUtils::toMatByRow(data.data()); - QList sample = Common::RandSample(kTrain, flat.rows, 0, true); - foreach (const int &idx, sample) - centers.push_back(flat.row(idx)); - reindex(); - } - - void project(const Template &src, Template &dst) const - { - QMutexLocker locker(&mutex); - Mat dists, indicies; - index->knnSearch(src, indicies, dists, kSearch); - dst = indicies.reshape(1, 1); - } - - void load(QDataStream &stream) - { - stream >> centers; - reindex(); - } - - void store(QDataStream &stream) const - { - stream << centers; - } -}; - -BR_REGISTER(Transform, RandomCentroidsTransform) - -class RegInitializer : public Initializer -{ - Q_OBJECT - - void initialize() const - { - qRegisterMetaType(); - } -}; -BR_REGISTER(Initializer, RegInitializer) - -class CollectNNTransform : public UntrainableMetaTransform -{ - Q_OBJECT - - Q_PROPERTY(int keep READ get_keep WRITE set_keep RESET reset_keep STORED false) - BR_PROPERTY(int, keep, 20) - - void project(const Template &src, Template &dst) const - { - dst.file = src.file; - dst.clear(); - dst.m() = cv::Mat(); - Neighbors neighbors; - for (int i=0; i < src.m().cols;i++) { - // skip self compares - if (i == src.file.get("FrameNumber")) - continue; - neighbors.append(Neighbor(i, src.m().at(0,i))); - } - int actuallyKeep = std::min(keep, neighbors.size()); - std::partial_sort(neighbors.begin(), neighbors.begin()+actuallyKeep, neighbors.end(), compareNeighbors); - - Neighbors selected = neighbors.mid(0, actuallyKeep); - dst.file.set("neighbors", QVariant::fromValue(selected)); - } -}; -BR_REGISTER(Transform, CollectNNTransform) - -class LogNNTransform : public TimeVaryingTransform -{ - Q_OBJECT - - Q_PROPERTY(QString fileName READ get_fileName WRITE set_fileName RESET reset_fileName STORED false) - BR_PROPERTY(QString, fileName, "") - - std::fstream fout; - - void projectUpdate(const Template &src, Template &dst) - { - dst = src; - - if (!dst.file.contains("neighbors")) { - fout << std::endl; - return; - } - - Neighbors neighbors = dst.file.get("neighbors"); - if (neighbors.isEmpty() ) { - fout << std::endl; - return; - } - - QString aLine; - aLine.append(QString::number(neighbors[0].first)+":"+QString::number(neighbors[0].second)); - for (int i=1; i < neighbors.size();i++) - aLine.append(","+QString::number(neighbors[i].first)+":"+QString::number(neighbors[i].second)); - - fout << qPrintable(aLine) << std::endl; - } - - void init() - { - if (!fileName.isEmpty()) - fout.open(qPrintable(fileName), std::ios_base::out); - } - - void finalize(TemplateList &output) - { - (void) output; - fout.close(); - } - -public: - LogNNTransform() : TimeVaryingTransform(false, false) {} -}; -BR_REGISTER(Transform, LogNNTransform) - } // namespace br -#include "cluster.moc" +#include "cluster/knn.moc" diff --git a/openbr/plugins/cluster/lognn.cpp b/openbr/plugins/cluster/lognn.cpp new file mode 100644 index 0000000..fc99f77 --- /dev/null +++ b/openbr/plugins/cluster/lognn.cpp @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Log nearest neighbors to specified file. + * \author Charles Otto \cite caotto + */ +class LogNNTransform : public TimeVaryingTransform +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ get_fileName WRITE set_fileName RESET reset_fileName STORED false) + BR_PROPERTY(QString, fileName, "") + + std::fstream fout; + + void projectUpdate(const Template &src, Template &dst) + { + dst = src; + + if (!dst.file.contains("neighbors")) { + fout << std::endl; + return; + } + + Neighbors neighbors = dst.file.get("neighbors"); + if (neighbors.isEmpty() ) { + fout << std::endl; + return; + } + + QString aLine; + aLine.append(QString::number(neighbors[0].first)+":"+QString::number(neighbors[0].second)); + for (int i=1; i < neighbors.size();i++) + aLine.append(","+QString::number(neighbors[i].first)+":"+QString::number(neighbors[i].second)); + + fout << qPrintable(aLine) << std::endl; + } + + void init() + { + if (!fileName.isEmpty()) + fout.open(qPrintable(fileName), std::ios_base::out); + } + + void finalize(TemplateList &output) + { + (void) output; + fout.close(); + } + +public: + LogNNTransform() : TimeVaryingTransform(false, false) {} +}; + +BR_REGISTER(Transform, LogNNTransform) + +} // namespace br + +#include "cluster/lognn.moc" diff --git a/openbr/plugins/cluster/randomcentroids.cpp b/openbr/plugins/cluster/randomcentroids.cpp new file mode 100644 index 0000000..b3a1a30 --- /dev/null +++ b/openbr/plugins/cluster/randomcentroids.cpp @@ -0,0 +1,84 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Chooses k random points to be centroids. + * \author Austin Blanton \cite imaus10 + * \see KMeansTransform + */ +class RandomCentroidsTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(int kTrain READ get_kTrain WRITE set_kTrain RESET reset_kTrain STORED false) + Q_PROPERTY(int kSearch READ get_kSearch WRITE set_kSearch RESET reset_kSearch STORED false) + BR_PROPERTY(int, kTrain, 256) + BR_PROPERTY(int, kSearch, 1) + + Mat centers; + mutable QScopedPointer index; + mutable QMutex mutex; + + void reindex() + { + index.reset(new flann::Index(centers, flann::LinearIndexParams())); + } + + void train(const TemplateList &data) + { + Mat flat = OpenCVUtils::toMatByRow(data.data()); + QList sample = Common::RandSample(kTrain, flat.rows, 0, true); + foreach (const int &idx, sample) + centers.push_back(flat.row(idx)); + reindex(); + } + + void project(const Template &src, Template &dst) const + { + QMutexLocker locker(&mutex); + Mat dists, indicies; + index->knnSearch(src, indicies, dists, kSearch); + dst = indicies.reshape(1, 1); + } + + void load(QDataStream &stream) + { + stream >> centers; + reindex(); + } + + void store(QDataStream &stream) const + { + stream << centers; + } +}; + +BR_REGISTER(Transform, RandomCentroidsTransform) + +} //namespace br + +#include "cluster/randomcentroids.moc" diff --git a/openbr/plugins/dlib.cmake b/openbr/plugins/cmake/dlib.cmake index 9c594ad..06ee3c6 100644 --- a/openbr/plugins/dlib.cmake +++ b/openbr/plugins/cmake/dlib.cmake @@ -5,7 +5,7 @@ if(${BR_WITH_DLIB}) add_definitions(-DDLIB_NO_GUI_SUPPORT) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/dlib.cpp) + set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/classification/dlib.cpp) set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} ${DLib_DIR}/dlib/all/source.cpp) #install(DIRECTORY ${DLib_DIR}/models/ DESTINATION share/openbr/models/dlib) diff --git a/openbr/plugins/cmake/eigen3.cmake b/openbr/plugins/cmake/eigen3.cmake new file mode 100644 index 0000000..73646fa --- /dev/null +++ b/openbr/plugins/cmake/eigen3.cmake @@ -0,0 +1,16 @@ +set(BR_WITH_EIGEN3 ON CACHE BOOL "Build Eigen3 plugins") + +if(${BR_WITH_EIGEN3}) + find_package(Eigen3 REQUIRED) + install(FILES ${EIGEN3_LICENSE} RENAME Eigen3 DESTINATION share/openbr/licenses) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/classification/lda.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/distance/L1.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/distance/L2.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/imgproc/revertaffine.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/imgproc/integralsampler.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/imgproc/recursiveintegralsampler.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/metadata/consolidatedetections.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/metadata/delaunay.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/metadata/procrustes.cpp) +endif() diff --git a/openbr/plugins/ipc2013.cmake b/openbr/plugins/cmake/ipc2013.cmake index d764890..617ac4d 100644 --- a/openbr/plugins/ipc2013.cmake +++ b/openbr/plugins/cmake/ipc2013.cmake @@ -2,7 +2,8 @@ set(BR_WITH_IPC2013 OFF CACHE BOOL "Build with Intel Perceptual Computing SDK 20 if(${BR_WITH_IPC2013}) find_package(IPC2013 REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/ipc2013.cpp) set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${IPC2013_LIBS}) install(DIRECTORY ${IPC2013_DIR}/bin/x64/ DESTINATION bin) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/classification/ipc2013.cpp) endif() diff --git a/openbr/plugins/jni.cmake b/openbr/plugins/cmake/jni.cmake index 0fc3f77..41cc5ba 100644 --- a/openbr/plugins/jni.cmake +++ b/openbr/plugins/cmake/jni.cmake @@ -3,10 +3,10 @@ set(BR_WITH_JAVA OFF CACHE BOOL "Use Java Code") if (${BR_WITH_JAVA}) find_package(JNI REQUIRED) find_package(JAVA REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/jni.cpp) set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${JNI_LIBRARIES}) include_directories(${JAVA_INCLUDE_PATH}) include_directories(${JAVA_INCLUDE_PATH2}) - +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/core/jni.cpp) endif() diff --git a/openbr/plugins/cmake/libav.cmake b/openbr/plugins/cmake/libav.cmake new file mode 100644 index 0000000..c5cd622 --- /dev/null +++ b/openbr/plugins/cmake/libav.cmake @@ -0,0 +1,13 @@ +set(BR_WITH_LIBAV OFF CACHE BOOL "Build with LibAV") + +if(${BR_WITH_LIBAV}) + find_package(LibAV REQUIRED) + include_directories(${LIBAV_INCLUDE_DIR}) + set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${LIBAV_LIBRARIES}) + + foreach(LIBAV_LIB ${LIBAV_LIBRARIES}) + install(FILES ${LIBAV_LIB} DESTINATION lib) + endforeach() +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/gallery/keyframes.cpp) +endif() diff --git a/openbr/plugins/liblinear.cmake b/openbr/plugins/cmake/liblinear.cmake index 3845f08..431a64c 100644 --- a/openbr/plugins/liblinear.cmake +++ b/openbr/plugins/cmake/liblinear.cmake @@ -3,5 +3,6 @@ set(BR_WITH_LIBLINEAR OFF CACHE BOOL "Build with LibLinear") if(${BR_WITH_LIBLINEAR}) find_package(LibLinear REQUIRED) set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} ${LibLinear_SRC}) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/liblinear.cpp) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/classification/liblinear.cpp) endif() diff --git a/openbr/plugins/likely.cmake b/openbr/plugins/cmake/likely.cmake index 92ab602..ba894d6 100644 --- a/openbr/plugins/likely.cmake +++ b/openbr/plugins/cmake/likely.cmake @@ -2,6 +2,9 @@ set(BR_WITH_LIKELY OFF CACHE BOOL "Build with Likely") if(${BR_WITH_LIKELY}) find_package(Likely REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/likely.cpp) set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${Likely_LIBS}) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/core/likely.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/format/lmat.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/gallery/lmat.cpp) endif() diff --git a/openbr/plugins/mongoose.cmake b/openbr/plugins/cmake/mongoose.cmake index a32ea7d..729365a 100644 --- a/openbr/plugins/mongoose.cmake +++ b/openbr/plugins/cmake/mongoose.cmake @@ -1,6 +1,8 @@ set(BR_WITH_MONGOOSE OFF CACHE BOOL "Build with Mongoose") if(${BR_WITH_MONGOOSE}) find_package(Mongoose) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/mongoose.cpp ${MONGOOSE_SRC}) + set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} ${MONGOOSE_SRC}) install(FILES ${MONGOOSE_LICENSE} RENAME mongoose DESTINATION share/openbr/licenses) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/metadata/mongoose.cpp) endif() diff --git a/openbr/plugins/qtnetwork.cmake b/openbr/plugins/cmake/network.cmake index 49e02e9..bab3dd2 100644 --- a/openbr/plugins/qtnetwork.cmake +++ b/openbr/plugins/cmake/network.cmake @@ -3,6 +3,10 @@ if(${BR_WITH_QTNETWORK}) find_package(Qt5Network) find_package(HttpParser) set(QT_DEPENDENCIES ${QT_DEPENDENCIES} Network) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/qtnetwork.cpp ${HTTPPARSER_SRC}) + set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} ${HTTPPARSER_SRC}) install(FILES ${HTTPPARSER_LICENSE} RENAME http-parser DESTINATION share/openbr/licenses) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/format/url.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/format/post.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/gallery/post.cpp) endif() diff --git a/openbr/plugins/nt4.cmake b/openbr/plugins/cmake/nt4.cmake index 41fa42e..bdba899 100644 --- a/openbr/plugins/nt4.cmake +++ b/openbr/plugins/cmake/nt4.cmake @@ -2,7 +2,8 @@ set(BR_WITH_NT4 OFF CACHE BOOL "Build with Neurotec Biometric 4") if(${BR_WITH_NT4}) find_package(NT4 REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/nt4.cpp) set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${NT4_LIBS}) install(DIRECTORY ${NT4_DIR_LIB}/ DESTINATION lib) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/classification/nt4.cpp) endif() diff --git a/openbr/plugins/pp4.cmake b/openbr/plugins/cmake/pp4.cmake index fd79240..95743a7 100644 --- a/openbr/plugins/pp4.cmake +++ b/openbr/plugins/cmake/pp4.cmake @@ -2,8 +2,9 @@ set(BR_WITH_PP4 OFF CACHE BOOL "Build with PittPatt 4") if(${BR_WITH_PP4}) find_package(PP4 REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/pp4.cpp) set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${PP4_LIBS}) install(DIRECTORY ${PP4_DIR}/lib/ DESTINATION lib) install(DIRECTORY ${PP4_DIR}/models/ DESTINATION models/pp4) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/classification/pp4.cpp) endif() diff --git a/openbr/plugins/pp5.cmake b/openbr/plugins/cmake/pp5.cmake index 7fcebd9..2df45b6 100644 --- a/openbr/plugins/pp5.cmake +++ b/openbr/plugins/cmake/pp5.cmake @@ -2,7 +2,6 @@ set(BR_WITH_PP5 OFF CACHE BOOL "Build with PittPatt 5") if(${BR_WITH_PP5}) find_package(PP5 REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/pp5.cpp) set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${PP5_LIBS}) if(WIN32) @@ -12,4 +11,6 @@ if(${BR_WITH_PP5}) endif() install(DIRECTORY ${PP5_DIR}/models/ DESTINATION share/openbr/models/pp5) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/classification/pp5.cpp) endif() diff --git a/openbr/plugins/cmake/show.cmake b/openbr/plugins/cmake/show.cmake new file mode 100644 index 0000000..54e924a --- /dev/null +++ b/openbr/plugins/cmake/show.cmake @@ -0,0 +1,3 @@ +if(${BR_EMBEDDED}) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/gui/show.cpp) +endif() diff --git a/openbr/plugins/stasm4.cmake b/openbr/plugins/cmake/stasm4.cmake index 42c8de5..0ab4181 100644 --- a/openbr/plugins/stasm4.cmake +++ b/openbr/plugins/cmake/stasm4.cmake @@ -2,14 +2,9 @@ set(BR_WITH_STASM4 ON CACHE BOOL "Build with Stasm") if(${BR_WITH_STASM4}) find_package(Stasm4 REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/stasm4.cpp) - set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${Stasm4_LIBS}) - - if(WIN32) - install(DIRECTORY ${Stasm_DIR}/build/ DESTINATION bin) - else() - install(DIRECTORY ${Stasm_DIR}/build/ DESTINATION lib) - endif() - + set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} ${Stasm_SRC}) install(DIRECTORY ${Stasm_DIR}/data/ DESTINATION share/openbr/models/stasm) +else() + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/metadata/stasm4.cpp) + set(BR_EXCLUDED_PLUGINS ${BR_EXCLUDED_PLUGINS} plugins/imgproc/revertaffine.cpp) endif() diff --git a/openbr/plugins/algorithms.cpp b/openbr/plugins/core/algorithms.cpp index 88ecaea..d58ff34 100644 --- a/openbr/plugins/algorithms.cpp +++ b/openbr/plugins/core/algorithms.cpp @@ -14,7 +14,7 @@ * limitations under the License. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "openbr_internal.h" +#include namespace br { @@ -43,7 +43,7 @@ class AlgorithmsInitializer : public Initializer Globals->abbreviations.insert("OpenBR", "FaceRecognition"); Globals->abbreviations.insert("GenderEstimation", "GenderClassification"); Globals->abbreviations.insert("AgeEstimation", "AgeRegression"); - Globals->abbreviations.insert("FaceRecognition2", "{PP5Register+Affine(128,128,0.25,0.35)+Cvt(Gray)}+(Gradient+Bin(0,360,9,true))/(Blur(1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2,true)+Bin(0,10,10,true))+Merge+Integral+RecursiveIntegralSampler(4,2,8,LDA(.98)+Normalize(L1))+Cat+PCA(768)+Normalize(L1)+Quantize:UCharL1"); + Globals->abbreviations.insert("FaceRecognition2", "{PP5Register+Affine(128,128,0.25,0.35)+Cvt(Gray)}+(Gradient+HistBin(0,360,9,true))/(Blur(1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2,true)+HistBin(0,10,10,true))+Merge+Integral+RecursiveIntegralSampler(4,2,8,LDA(.98)+Normalize(L1))+Cat+PCA(768)+Normalize(L1)+Quantize:UCharL1"); Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)"); Globals->abbreviations.insert("4SF", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Cat+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)"); @@ -55,9 +55,9 @@ class AlgorithmsInitializer : public Initializer Globals->abbreviations.insert("ShowOpticalFlowMagnitude", "AggregateFrames(2)+OpticalFlow+Normalize(Range,false,0,255)+Cvt(Color)+Draw+FPSLimit(30)+Show(false)+Discard"); Globals->abbreviations.insert("ShowMotionSegmentation", "DropFrames(5)+AggregateFrames(2)+OpticalFlow+CvtUChar+WatershedSegmentation+DrawSegmentation+Draw+FPSLimit(30)+Show(false)+Discard"); - Globals->abbreviations.insert("HOGVideo", "Stream(DropFrames(5)+Cvt(Gray)+Grid(5,5)+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat)+Contract+CatRows+KMeans(500)+Hist(500)+SVM"); - Globals->abbreviations.insert("HOFVideo", "Stream(DropFrames(5)+Grid(5,5)+AggregateFrames(2)+OpticalFlow+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat)+Contract+CatRows+KMeans(500)+Hist(500)"); - Globals->abbreviations.insert("HOGHOFVideo", "Stream(DropFrames(5)+Grid(5,5)+AggregateFrames(2)+(OpticalFlow+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat+Contract)/(First+Cvt(Gray)+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat+Contract)+CatCols)+Contract+CatRows+KMeans(500)+Hist(500)+SVM"); + Globals->abbreviations.insert("HOGVideo", "Stream(DropFrames(5)+Cvt(Gray)+Grid(5,5)+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+HistBin(0,360,8)+Hist(8)+Cat)+Contract+CatRows+KMeans(500)+Hist(500)+SVM"); + Globals->abbreviations.insert("HOFVideo", "Stream(DropFrames(5)+Grid(5,5)+AggregateFrames(2)+OpticalFlow+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+HistBin(0,360,8)+Hist(8)+Cat)+Contract+CatRows+KMeans(500)+Hist(500)"); + Globals->abbreviations.insert("HOGHOFVideo", "Stream(DropFrames(5)+Grid(5,5)+AggregateFrames(2)+(OpticalFlow+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+HistBin(0,360,8)+Hist(8)+Cat+Contract)/(First+Cvt(Gray)+ROIFromPts(32,24)+Expand+Resize(32,32)+Gradient+RectRegions+HistBin(0,360,8)+Hist(8)+Cat+Contract)+CatCols)+Contract+CatRows+KMeans(500)+Hist(500)+SVM"); // Generic Image Processing Globals->abbreviations.insert("SIFT", "Open+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)"); @@ -66,7 +66,7 @@ class AlgorithmsInitializer : public Initializer Globals->abbreviations.insert("SmallSURF", "Open+LimitSize(512)+KeyPointDetector(SURF)+KeyPointDescriptor(SURF):KeyPointMatcher(BruteForce)"); Globals->abbreviations.insert("ColorHist", "Open+LimitSize(512)+Expand+EnsureChannels(3)+SplitChannels+Hist(256,0,8)+Cat+Normalize(L1):L2"); Globals->abbreviations.insert("ImageSimilarity", "Open+EnsureChannels(3)+Resize(256,256)+SplitChannels+RectRegions(64,64,64,64)+Hist(256,0,8)+Cat:NegativeLogPlusOne(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("ImageClassification", "Open+CropSquare+LimitSize(256)+Cvt(Gray)+Gradient+HistBin(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)"); // Hash @@ -89,7 +89,7 @@ class AlgorithmsInitializer : public Initializer // Transforms Globals->abbreviations.insert("FaceDetection", "Open+Cvt(Gray)+Cascade(FrontalFace)"); Globals->abbreviations.insert("DenseLBP", "(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))"); - Globals->abbreviations.insert("DenseHOG", "Gradient+RectRegions(8,8,6,6)+Bin(0,360,8)+Hist(8)"); + Globals->abbreviations.insert("DenseHOG", "Gradient+RectRegions(8,8,6,6)+HistBin(0,360,8)+Hist(8)"); Globals->abbreviations.insert("DenseSIFT", "(Grid(10,10)+SIFTDescriptor(12)+ByRow)"); Globals->abbreviations.insert("DenseSIFT2", "(Grid(5,5)+SIFTDescriptor(12)+ByRow)"); Globals->abbreviations.insert("FaceRecognitionRegistration", "ASEFEyes+Affine(88,88,0.25,0.35)"); @@ -108,4 +108,4 @@ BR_REGISTER(Initializer, AlgorithmsInitializer) } // namespace br -#include "algorithms.moc" +#include "core/algorithms.moc" diff --git a/openbr/plugins/core/align.cpp b/openbr/plugins/core/align.cpp new file mode 100644 index 0000000..6445fa6 --- /dev/null +++ b/openbr/plugins/core/align.cpp @@ -0,0 +1,613 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2015 Noblis * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include "openbr/plugins/openbr_internal.h" +#include "openbr/core/qtutils.h" +#include "openbr/core/opencvutils.h" +#include "openbr/core/eigenutils.h" +#include "openbr/core/common.h" +#include +#include +#include + +using namespace std; +using namespace cv; +using namespace Eigen; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Improved procrustes alignment of points, to include a post processing scaling of points + * to faciliate subsequent texture mapping. + * \author Brendan Klare \cite bklare + * \param width Width of output coordinate space (before padding) + * \param padding Amount of padding around the coordinate space + * \param useFirst whether or not to use the first instance as the reference object + */ +class ProcrustesAlignTransform : public Transform +{ + Q_OBJECT + + Q_PROPERTY(float width READ get_width WRITE set_width RESET reset_width STORED false) + Q_PROPERTY(float padding READ get_padding WRITE set_padding RESET reset_padding STORED false) + Q_PROPERTY(bool useFirst READ get_useFirst WRITE set_useFirst RESET reset_useFirst STORED false) + BR_PROPERTY(float, width, 80) + BR_PROPERTY(float, padding, 8) + BR_PROPERTY(bool, useFirst, false) + + + Eigen::MatrixXf referenceShape; + float minX; + float minY; + float maxX; + float maxY; + float aspectRatio; + + void init() { + aspectRatio = 0; + } + + static MatrixXf getRotation(MatrixXf ref, MatrixXf sample) { + MatrixXf R = sample.transpose() * ref; + JacobiSVD svd(R, ComputeFullU | ComputeFullV); + R = svd.matrixU() * svd.matrixV().transpose(); + return R; + } + + //Converts x y points in a single vector to two column matrix + static MatrixXf vectorToMatrix(MatrixXf vector) { + int n = vector.rows(); + MatrixXf matrix(n / 2, 2); + for (int i = 0; i < n / 2; i++) { + for (int j = 0; j < 2; j++) { + matrix(i, j) = vector(i * 2 + j); + } + } + return matrix; + } + + static MatrixXf matrixToVector(MatrixXf matrix) { + int n2 = matrix.rows(); + MatrixXf vector(n2 * 2, 1); + for (int i = 0; i < n2; i++) { + for (int j = 0; j < 2; j++) { + vector(i * 2 + j) = matrix(i, j); + } + } + return vector; + } + + void train(const TemplateList &data) + { + MatrixXf points(data[0].file.points().size() * 2, data.size()); + + // Normalize all sets of points + int skip = 0; + for (int j = 0; j < data.size(); j++) { + QList imagePoints = data[j].file.points(); + if (imagePoints.size() == 0) { + skip++; + continue; + } + + float meanX = 0, + meanY = 0; + for (int i = 0; i < imagePoints.size(); i++) { + points(i * 2, j - skip) = imagePoints[i].x(); + points(i * 2 + 1, j - skip) = imagePoints[i].y(); + + meanX += imagePoints[i].x(); + meanY += imagePoints[i].y(); + } + + meanX /= imagePoints.size(); + meanY /= imagePoints.size(); + + for (int i = 0; i < imagePoints.size(); i++) { + points(i * 2, j - skip) -= meanX; + points(i * 2 + 1, j - skip) -= meanY; + } + } + + points = MatrixXf(points.leftCols(data.size() - skip)); + + //normalize scale + for (int i = 0; i < points.cols(); i++) + points.col(i) = points.col(i) / points.col(i).norm(); + + //Normalize rotation + if (!useFirst) { + referenceShape = vectorToMatrix(points.rowwise().sum() / points.cols()); + } else { + referenceShape = vectorToMatrix(points.col(0)); + } + + for (int i = 0; i < points.cols(); i++) { + MatrixXf p = vectorToMatrix(points.col(i)); + MatrixXf R = getRotation(referenceShape, p); + points.col(i) = matrixToVector(p * R); + } + + //Choose crop boundaries and adjustments that captures most data + MatrixXf minXs(points.cols(),1); + MatrixXf minYs(points.cols(),1); + MatrixXf maxXs(points.cols(),1); + MatrixXf maxYs(points.cols(),1); + for (int j = 0; j < points.cols(); j++) { + minX = FLT_MAX, + minY = FLT_MAX, + maxX = -FLT_MAX, + maxY = -FLT_MAX; + for (int i = 0; i < points.rows(); i++) { + if (i % 2 == 0) { + if (points(i,j) > maxX) + maxX = points(i, j); + if (points(i,j) < minX) + minX = points(i, j); + } else { + if (points(i,j) > maxY) + maxY = points(i, j); + if (points(i,j) < minY) + minY = points(i, j); + } + } + + minXs(j) = minX; + maxXs(j) = maxX; + minYs(j) = minY; + maxYs(j) = maxY; + } + + minX = minXs.mean() - 0 * EigenUtils::stddev(minXs); + minY = minYs.mean() - 0 * EigenUtils::stddev(minYs); + maxX = maxXs.mean() + 0 * EigenUtils::stddev(maxXs); + maxY = maxYs.mean() + 0 * EigenUtils::stddev(maxYs); + aspectRatio = (maxX - minX) / (maxY - minY); + } + + void project(const Template &src, Template &dst) const + { + QList imagePoints = src.file.points(); + if (imagePoints.size() == 0) { + dst.file.fte = true; + qDebug() << "No points for file " << src.file.name; + return; + } + + MatrixXf p(imagePoints.size() * 2, 1); + for (int i = 0; i < imagePoints.size(); i++) { + p(i * 2) = imagePoints[i].x(); + p(i * 2 + 1) = imagePoints[i].y(); + } + p = vectorToMatrix(p); + + //Normalize translation + p.col(0) = p.col(0) - MatrixXf::Ones(p.rows(),1) * (p.col(0).sum() / p.rows()); + p.col(1) = p.col(1) - MatrixXf::Ones(p.rows(),1) * (p.col(1).sum() / p.rows()); + + //Normalize scale + p /= matrixToVector(p).norm(); + + //Normalize rotation + MatrixXf R = getRotation(referenceShape, p); + p = p * R; + + //Translate and scale into output space and store in output list + QList procrustesPoints; + for (int i = 0; i < p.rows(); i++) + procrustesPoints.append( QPointF( + (p(i, 0) - minX) / (maxX - minX) * (width - 1) + padding, + (p(i, 1) - minY) / (maxY - minY) * (qRound( width / aspectRatio) - 1) + padding)); + + dst = src; + dst.file.setList("ProcrustesPoints", procrustesPoints); + dst.file.set("ProcrustesBound", QRectF(0, 0, width + 2 * padding, (qRound(width / aspectRatio) + 2 * padding))); + dst.file.set("ProcrustesPadding", padding); + } + + void store(QDataStream &stream) const + { + stream << referenceShape; + stream << minX; + stream << minY; + stream << maxX; + stream << maxY; + stream << aspectRatio; + } + + void load(QDataStream &stream) + { + stream >> referenceShape; + stream >> minX; + stream >> minY; + stream >> maxX; + stream >> maxY; + stream >> aspectRatio; + } +}; +BR_REGISTER(Transform, ProcrustesAlignTransform) + +/*! + * \ingroup transforms + * \brief Maps texture from one set of points to another. Assumes that points are rigidly transformed + * \author Brendan Klare \cite bklare + * \author Scott Klum \cite sklum + */ +class TextureMapTransform : public UntrainableTransform +{ + Q_OBJECT + +public: + static QRectF getBounds(const QList &points, int dstPadding) + { + float srcMinX = FLT_MAX; + float srcMinY = FLT_MAX; + float srcMaxX = -FLT_MAX; + float srcMaxY = -FLT_MAX; + foreach (const QPointF &point, points) { + if (point.x() < srcMinX) srcMinX = point.x(); + if (point.y() < srcMinY) srcMinY = point.y(); + if (point.x() > srcMaxX) srcMaxX = point.x(); + if (point.y() > srcMaxY) srcMaxY = point.y(); + } + + const float padding = (srcMaxX - srcMinX) / 80 * dstPadding; + return QRectF(qRound(srcMinX - padding), qRound(srcMinY - padding), qRound(srcMaxX - srcMinX + 2 * padding), qRound(srcMaxY - srcMinY + 2 * padding)); + } + + static int getVertexIndex(const QPointF &trianglePts, const QList &pts) + { + for (int i = 0; i < pts.size(); i++) + // Check points using single precision accuracy to avoid potential rounding error + if ((float(trianglePts.x()) == float(pts[i].x())) && (float(trianglePts.y()) == float(pts[i].y()))) + return i; + qFatal("Couldn't identify index of requested point!"); + return -1; + } + + static QList addBounds(QList points, const QRectF &bound) + { + points.append(bound.topLeft()); + points.append(QPointF(bound.right() - 1, bound.top())); + points.append(QPointF(bound.left(), bound.bottom() - 1)); + points.append(QPointF(bound.right() - 1, bound.bottom() - 1)); + return points; + } + + static QList removeBounds(const QList &points) + { + return points.mid(0, points.size() - 4); + } + + //Expand out bounds placed at end of point list by addBounds + static QList expandBounds(QList points, int pad) + { + const int n = points.size(); + points[n-4] = QPointF(points[n-4].x() - pad, points[n-4].y() - pad); + points[n-3] = QPointF(points[n-3].x() + pad, points[n-3].y() - pad); + points[n-2] = QPointF(points[n-2].x() - pad, points[n-2].y() + pad); + points[n-1] = QPointF(points[n-1].x() + pad, points[n-1].y() + pad); + return points; + } + + //Contract in bounds placed at end of point list by addBounds + static QList contractBounds(QList points, int pad) + { + const int n = points.size(); + points[n-4] = QPointF(points[n-4].x() + pad, points[n-4].y() + pad); + points[n-3] = QPointF(points[n-3].x() - pad, points[n-3].y() + pad); + points[n-2] = QPointF(points[n-2].x() + pad, points[n-2].y() - pad); + points[n-1] = QPointF(points[n-1].x() - pad, points[n-1].y() - pad); + return points; + } + + static QList > getTriangulation(const QList &points, const QRectF &bound) + { + Subdiv2D subdiv(OpenCVUtils::toRect(bound)); + foreach (const QPointF &point, points) { + if (!bound.contains(point)) + return QList >(); + subdiv.insert(OpenCVUtils::toPoint(point)); + } + + + vector triangleList; + subdiv.getTriangleList(triangleList); + + QList > triangleIndices; + foreach (const Vec6f &triangle, triangleList) { + bool valid = true; + const QPointF vertices[3] = { QPointF(triangle[0], triangle[1]), + QPointF(triangle[2], triangle[3]), + QPointF(triangle[4], triangle[5]) }; + for (int j = 0; j < 3; j++) + if (vertices[j].x() > bound.right() || vertices[j].y() > bound.bottom() || vertices[j].x() < bound.left() || vertices[j].y() < bound.top()) { + valid = false; + break; + } + + if (valid) { + QList tri; + for (int j = 0; j < 3; j++) + tri.append(getVertexIndex(vertices[j], points)); + triangleIndices.append(tri); + } + } + + return triangleIndices; + } + +private: + void project(const Template &src, Template &dst) const + { + QList dstPoints = dst.file.getList("ProcrustesPoints"); + QList srcPoints = dst.file.points(); + if (dstPoints.empty() || srcPoints.empty()) { + dst = src; + if (Globals->verbose) { + qWarning("Delauney triangulation failed because points or rects are empty."); + dst.file.fte = true; + } + return; + } + + QRectF dstBound = dst.file.get("ProcrustesBound"); + dstPoints = addBounds(dstPoints, dstBound); + + /*Add a wider bound for triangulation to prevent border triangles from being missing*/ + QRectF srcBound = getBounds(srcPoints, dst.file.get("ProcrustesPadding") + 20); + srcPoints = addBounds(srcPoints, srcBound); + QList > triIndices = getTriangulation(srcPoints, srcBound); + + /*Remove wider bound for texture mapping*/ + srcPoints = removeBounds(srcPoints); + srcBound = getBounds(srcPoints, dst.file.get("ProcrustesPadding")); + srcPoints = addBounds(srcPoints, srcBound); + + int dstWidth = dstBound.width() + dstBound.x(); + int dstHeight = dstBound.height() + dstBound.y(); + dst.m() = Mat::zeros(dstHeight, dstWidth, src.m().type()); + for (int i = 0; i < triIndices.size(); i++) { + Point2f srcPoint1[3]; + Point2f dstPoint1[3]; + for (int j = 0; j < 3; j++) { + srcPoint1[j] = OpenCVUtils::toPoint(srcPoints[triIndices[i][j]]); + dstPoint1[j] = OpenCVUtils::toPoint(dstPoints[triIndices[i][j]]); + } + + Mat buffer(dstHeight, dstWidth, src.m().type()); + warpAffine(src.m(), buffer, getAffineTransform(srcPoint1, dstPoint1), Size(dstBound.width(), dstBound.height())); + + Mat mask = Mat::zeros(dstHeight, dstWidth, CV_8UC1); + Point maskPoints[1][3]; + maskPoints[0][0] = dstPoint1[0]; + maskPoints[0][1] = dstPoint1[1]; + maskPoints[0][2] = dstPoint1[2]; + const Point* ppt = { maskPoints[0] }; + fillConvexPoly(mask, ppt, 3, Scalar(255, 255, 255), 8); + + for (int i = 0; i < dstHeight; i++) { + for (int j = 0; j < dstWidth; j++) { + if (mask.at(i,j) == 255) { + if (dst.m().type() == CV_32FC3 || dst.m().type() == CV_8UC3) + dst.m().at(i,j) = buffer.at(i,j); + else if (dst.m().type() == CV_32F) + dst.m().at(i,j) = buffer.at(i,j); + else if (dst.m().type() == CV_8U) + dst.m().at(i,j) = buffer.at(i,j); + else + qFatal("Unsupported pixel format."); + } + } + } + + } + + dst.file = src.file; + dst.file.clearPoints(); + dst.file.clearRects(); + dst.file.remove("ProcrustesPoints"); + dst.file.remove("ProcrustesPadding"); + dst.file.remove("ProcrustesBounds"); + } +}; + +BR_REGISTER(Transform, TextureMapTransform) + +// SynthesizePointsTransform helper class +struct TriangleIndicies +{ + int indicies[3]; + + TriangleIndicies() + { + indicies[0] = 0; + indicies[1] = 0; + indicies[2] = 0; + } + + TriangleIndicies(QList indexList) + { + assert(indexList.size() == 3); + qSort(indexList); + indicies[0] = indexList[0]; + indicies[1] = indexList[1]; + indicies[2] = indexList[2]; + } +}; + +inline bool operator==(const TriangleIndicies &a, const TriangleIndicies &b) +{ + return (a.indicies[0] == b.indicies[0]) && (a.indicies[1] == b.indicies[1]) && (a.indicies[2] == b.indicies[2]); +} + +inline uint qHash(const TriangleIndicies &key) +{ + return ::qHash(key.indicies[0]) ^ ::qHash(key.indicies[1]) ^ ::qHash(key.indicies[2]); +} + +QDataStream &operator<<(QDataStream &stream, const TriangleIndicies &ti) +{ + return stream << ti.indicies[0] << ti.indicies[1] << ti.indicies[2]; +} + +QDataStream &operator>>(QDataStream &stream, TriangleIndicies &ti) +{ + return stream >> ti.indicies[0] >> ti.indicies[1] >> ti.indicies[2]; +} + +/*! + * \ingroup transforms + * \brief Synthesize additional points via triangulation. + * \author Josh Klontz \cite jklontz + */ + class SynthesizePointsTransform : public MetadataTransform + { + Q_OBJECT + Q_PROPERTY(float minRelativeDistance READ get_minRelativeDistance WRITE set_minRelativeDistance RESET reset_minRelativeDistance STORED false) + BR_PROPERTY(float, minRelativeDistance, 0) // [0, 1] range controlling whether or not to nearby synthetic points. + // 0 = keep all points, 1 = keep only the most distance point. + + QList triangles; + + void train(const TemplateList &data) + { + // Because not all triangulations are the same, we have to decide on a canonical set of triangles at training time. + QHash counts; + foreach (const Template &datum, data) { + + const QList points = datum.file.points(); + if (points.size() == 0) + continue; + const QList< QList > triangulation = TextureMapTransform::getTriangulation(points, TextureMapTransform::getBounds(points, 10)); + if (triangulation.empty()) + continue; + + foreach (const QList &indicies, triangulation) + counts[TriangleIndicies(indicies)]++; + } + + triangles.clear(); + QHash::const_iterator i = counts.constBegin(); + while (i != counts.constEnd()) { + if (3 * i.value() > data.size()) + triangles.append(i.key()); // Keep triangles that occur in at least a third of the training instances + ++i; + } + + if (minRelativeDistance > 0) { // Discard relatively small triangles + QVector averageMinDistances(triangles.size()); + foreach (const Template &datum, data) { + File dst; + projectMetadata(datum.file, dst); + const QList points = dst.points(); + + QVector minDistances(triangles.size()); + for (int i=0; i::max(); + for (int j=0; j=0; i--) + if (averageMinDistances[i] / maxAverageMinDistance < minRelativeDistance) + triangles.removeAt(i); + } + + if (Globals->verbose) + qDebug() << "Kept" << triangles.size() << "of" << counts.size() << "triangles."; + } + + void projectMetadata(const File &src, File &dst) const + { + QList points = src.points(); + if (points.size() == 0) { + dst.fte = true; + return; + } + + foreach (const TriangleIndicies &triangle, triangles) { + const QPointF &p0 = points[triangle.indicies[0]]; + const QPointF &p1 = points[triangle.indicies[1]]; + const QPointF &p2 = points[triangle.indicies[2]]; + points.append((p0 + p1 + p2) / 3 /* append the centroid of the triangle */); + } + dst.setPoints(points); + } + + void store(QDataStream &stream) const + { + stream << triangles; + } + + void load(QDataStream &stream) + { + stream >> triangles; + } + }; + BR_REGISTER(Transform, SynthesizePointsTransform) + +/*! + * \ingroup initializers + * \brief Initialize Procrustes croppings + * \author Brendan Klare \cite bklare + */ +class ProcrustesInitializer : public Initializer +{ + Q_OBJECT + + void initialize() const + { + Globals->abbreviations.insert("ProcrustesStasmFace","SelectPoints([17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76])+ProcrustesAlign(padding=6,width=120)+TextureMap+Resize(96,96)"); + Globals->abbreviations.insert("ProcrustesStasmEyes","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47])+ProcrustesAlign(padding=8)+TextureMap+Resize(24,48)"); + Globals->abbreviations.insert("ProcrustesStasmPeriocular","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=10)+TextureMap+Resize(36,48)"); + Globals->abbreviations.insert("ProcrustesStasmBrow","SelectPoints([16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=8)+TextureMap+Resize(24,48)"); + Globals->abbreviations.insert("ProcrustesStasmNose","SelectPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58])+ProcrustesAlign(padding=12)+TextureMap+Resize(36,48)"); + Globals->abbreviations.insert("ProcrustesStasmMouth","SelectPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76])+ProcrustesAlign(padding=10)+TextureMap+Resize(36,48)"); + Globals->abbreviations.insert("ProcrustesStasmJaw", "SelectPoints([2,3,4,5,6,7,8,9,10])+ProcrustesAlign(padding=8)+TextureMap+Resize(36,48)"); + + Globals->abbreviations.insert("ProcrustesEyes","SelectPoints([19,20,21,22,23,24,25,26,27,28,29,30])+ProcrustesAlign(padding=8)+TextureMap+Resize(24,48)"); + Globals->abbreviations.insert("ProcrustesNose","SelectPoints([12,13,14,15,16,17,18])+ProcrustesAlign(padding=30)+TextureMap+Resize(36,48)"); + Globals->abbreviations.insert("ProcrustesMouth","SelectPoints([31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50])+ProcrustesAlign(padding=6)+TextureMap+Resize(36,48)"); + Globals->abbreviations.insert("ProcrustesBrow","SelectPoints([0,1,2,3,4,5,6,7,8,9])+ProcrustesAlign(padding=6)+TextureMap+Resize(24,48)"); + Globals->abbreviations.insert("ProcrustesFace","ProcrustesAlign(padding=6,width=120)+TextureMap+Resize(96,96)"); + + Globals->abbreviations.insert("ProcrustesLargeStasmFace","ProcrustesAlign(padding=18)+TextureMap+Resize(480,480)"); + Globals->abbreviations.insert("ProcrustesLargeStasmEyes","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47])+ProcrustesAlign(padding=8)+TextureMap+Resize(240,480)"); + Globals->abbreviations.insert("ProcrustesLargeStasmPeriocular","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=10)+TextureMap+Resize(360,480)"); + Globals->abbreviations.insert("ProcrustesLargeStasmBrow","SelectPoints([16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=8)+TextureMap+Resize(240,480)"); + Globals->abbreviations.insert("ProcrustesLargeStasmNose","SelectPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58])+ProcrustesAlign(padding=12)+TextureMap+Resize(360,480)"); + Globals->abbreviations.insert("ProcrustesLargeStasmMouth","SelectPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76])+ProcrustesAlign(padding=20)+TextureMap+Resize(360,480)"); + Globals->abbreviations.insert("ProcrustesLargeStasmJaw", "SelectPoints([2,3,4,5,6,7,8,9,10])+ProcrustesAlign(padding=8)+TextureMap+Resize(360,480)"); + } +}; +BR_REGISTER(Initializer, ProcrustesInitializer) + +} // namespace br + +#include "align.moc" diff --git a/openbr/plugins/core/attributealgorithms.cpp b/openbr/plugins/core/attributealgorithms.cpp new file mode 100644 index 0000000..05e255d --- /dev/null +++ b/openbr/plugins/core/attributealgorithms.cpp @@ -0,0 +1,153 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup initializers + * \brief Initializes global abbreviations with implemented algorithms for attributes + * \author Babatunde Ogunfemi \cite baba1472 + */ +class AttributeAlgorithmsInitializer : public Initializer +{ + Q_OBJECT + + void initialize() const + { + // Constants + QString BASE="Open+PP5Register+Rename(PP5_Landmark0_Right_Eye,Affine_0)+Rename(PP5_Landmark1_Left_Eye,Affine_1)+Affine(192,240,.345,.475)+Cvt(Gray)+Stasm(false,true,[(66.24,114),(125.76,114)])"; + QString SUBSPACE ="Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,4,4)+Hist(59)+Cat+PCA(0.95)"; + + QString NOSE="RectFromStasmNoseWithBridge+ROI+Resize(36,24)+" + SUBSPACE; + QString MOUTH="RectFromStasmMouth+ROI+Resize(24,36)+" + SUBSPACE; + QString EYES="RectFromStasmEyes+ROI+Resize(24,36)+" + SUBSPACE; + QString HAIR="RectFromStasmHair+ROI+Resize(24,36)+" + SUBSPACE; + QString BROW="RectFromStasmBrow+ROI+Resize(24,36)+" + SUBSPACE; + QString JAW="RectFromStasmJaw+ROI+Resize(36,36)+" + SUBSPACE; + QString FACE = "Crop(24,30,144,190)+Resize(36,36)+" + SUBSPACE; + + // All Attributes + Globals->abbreviations.insert("AllAttributes", "AttributeBrow/AttributeMouth/AttributeEyes/AttributeFace/AttributeHair/AttributeNose/AttributeJaw"); + Globals->abbreviations.insert("AllAttributesMatching", "(AttributeBrow)/(AttributeMouth)/(AttributeEyes)/(AttributeFace)/(AttributeHair)/(AttributeNose)/(AttributeJaw):AttributeMatch"); + + //Individual Attributes + Globals->abbreviations.insert("AttributeBrow", "(" + BASE+ "+" + BROW + "+" + "TurkClassifier(eyebrowposition,[closebrows,highbrows],3)/" + "TurkClassifier(unibrow,[unibrow],3)/" + "TurkClassifier(eyebroworientation,[eyebrowsdown,eyebrowsuptodown],3)/" + "TurkClassifier(thickeyebrows,[thickeyebrows,lighteyebrows],3))"); + Globals->abbreviations.insert("AttributeMouth", "(" + BASE + "+" + MOUTH + "+" + "TurkClassifier(smiling,[smiling],3)/" + "TurkClassifier(lipthickness,[cherry,big,small],3)/" + "TurkClassifier(mouthbite,[underbite,overbite],3)/" + "TurkClassifier(mouthopen,[closed,noteeth,halfteeth,allteeth],3)/" + "TurkClassifier(mouthwidth,[small,wide],3)/" + "TurkClassifier(mustache,[nomustache,linemustache,lightmustache,normalmustache,down],3)/" + "TurkClassifier(mouthasymmetry,[asymmetrical],3))"); + Globals->abbreviations.insert("AttributeEyes", "(" + BASE + "+" + EYES + "+ " + "TurkClassifier(eyeseparation,[close,wide],3)/" + "TurkClassifier(eyeslant,[slant2,slant1,wild],3)/" + "TurkClassifier(benteyes,[bent])/" + "TurkClassifier(eyecolor,[darkeyes,lighteyes],3)/" + "TurkClassifier(baggyeyes,[baggy],3)/" + "TurkClassifier(almondeyes,[almond],3)/" + "TurkClassifier(buriedeyes,[buriedeyes],3)/" + "TurkClassifier(sleepyeyes,[sleepy],3)/" + "TurkClassifier(lineeyes,[line],3)/" + "TurkClassifier(roundeyes,[round],3)/" + "TurkClassifier(sharpeyes,[sharp],3)/" + "TurkClassifier(smalleyes,[smalleyes],3)/" + "TurkClassifier(glasses,[glasses],3)/" + "TurkClassifier(eyelashvisibility,[feweyelashes],3))"); + Globals->abbreviations.insert("AttributeFace", "(" + BASE + "+" + FACE + "+" + "TurkClassifier(gender,[male],3)/" + "TurkClassifier(faceshape,[round,triangular,rectangular],3)/" + "TurkClassifier(cheekdensity,[puffy,in,normal],3)/" + "TurkClassifier(facemarks,[scars,moles,normal],3)/" + "TurkClassifier(facelength,[long],3)/" + "TurkClassifier(nosetoeyedist,[short,long],3)/" + "TurkClassifier(nosetomouthdist,[long,small],3))"); + Globals->abbreviations.insert("AttributeHair", "(" + BASE + "+" + HAIR + "+" + "TurkClassifier(foreheadwrinkles,[wrinkled],3)/" + "TurkClassifier(foreheadsize,[smallforehead,largeforehead],3)/" + "TurkClassifier(haircolor,[darkhair,lighthair,greyhair],3)/" + "TurkClassifier(hairdensity,[thick,bald,thin,halfbald],3)/" + "TurkClassifier(widowspeak,[widowspeak],3)/" + "TurkClassifier(hairstyle,[curlyhair],3))"); + Globals->abbreviations.insert("AttributeNose", "(" + BASE + "+" + NOSE + "+" + "TurkClassifier(noseorientation,[upnose,downnose],3)/" + "TurkClassifier(nosewidth,[small,thick],3)/" + "TurkClassifier(nosesize,[smallnose,bignose],3)/" + "TurkClassifier(brokennose,[broken],3))"); + Globals->abbreviations.insert("AttributeJaw", "(" + BASE + "+" + JAW + "+" + "TurkClassifier(beard,[nobeard,bigbeard,lightbeard,goatee,linebeard,normalbeard,lincolnbeard],3)/" + "TurkClassifier(chinsize,[shortchin,longchin],3))"); + Globals->abbreviations.insert("AttributeMatch", "Fuse([" + "Turk(eyebrowposition,[closebrows,highbrows],3)," + "Turk(unibrow,[unibrow],3)," + "Turk(eyebroworientation,[eyebrowsdown,eyebrowsuptodown],3)," + "Turk(thickeyebrows,[thickeyebrows,lighteyebrows],3)," + "Turk(smiling,[smiling],3)," + "Turk(lipthickness,[cherry,big,small],3)," + "Turk(mouthbite,[underbite,overbite],3)," + "Turk(mouthopen,[closed,noteeth,halfteeth,allteeth],3)," + "Turk(mouthwidth,[small,wide],3)," + "Turk(mustache,[nomustache,linemustache,lightmustache,normalmustache,down],3)," + "Turk(mouthasymmetry,[asymmetrical],3)," + "Turk(eyeseparation,[close,wide],3)," + "Turk(eyeslant,[slant2,slant1,wild],3)," + "Turk(benteyes,[bent],3)," + "Turk(eyecolor,[darkeyes,lighteyes],3)," + "Turk(baggyeyes,[baggy],3)," + "Turk(almondeyes,[almond],3)," + "Turk(buriedeyes,[buriedeyes],3)," + "Turk(sleepyeyes,[sleepy],3)," + "Turk(lineeyes,[line],3)," + "Turk(roundeyes,[round],3)," + "Turk(sharpeyes,[sharp],3)," + "Turk(smalleyes,[smalleyes],3)," + "Turk(glasses,[glasses],3)," + "Turk(eyelashvisibility,[feweyelashes],3)," + "Turk(gender,[male],3)," + "Turk(faceshape,[round,triangular,rectangular],3)," + "Turk(cheekdensity,[puffy,in,normal],3)," + "Turk(facemarks,[scars,moles,normal],3)," + "Turk(facelength,[long],3)," + "Turk(nosetoeyedist,[short,long],3)," + "Turk(nosetomouthdist,[long,small],3)," + "Turk(foreheadwrinkles,[wrinkled],3)," + "Turk(foreheadsize,[smallforehead,largeforehead],3)," + "Turk(haircolor,[darkhair,lighthair,greyhair],3)," + "Turk(hairdensity,[thick,bald,thin,halfbald],3)," + "Turk(widowspeak,[widowspeak],3)," + "Turk(hairstyle,[curlyhair],3)," + "Turk(noseorientation,[upnose,downnose],3)," + "Turk(nosewidth,[small,thick],3)," + "Turk(nosesize,[smallnose,bignose],3)," + "Turk(brokennose,[broken],3)," + "Turk(beard,[nobeard,bigbeard,lightbeard,goatee,linebeard,normalbeard,lincolnbeard],3)," + "Turk(chinsize,[shortchin,longchin],3)])"); + } +}; + +BR_REGISTER(Initializer, AttributeAlgorithmsInitializer) + +} // namespace br + +#include "core/attributealgorithms.moc" diff --git a/openbr/plugins/core/cache.cpp b/openbr/plugins/core/cache.cpp new file mode 100644 index 0000000..ea43813 --- /dev/null +++ b/openbr/plugins/core/cache.cpp @@ -0,0 +1,95 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Caches br::Transform::project() results. + * \author Josh Klontz \cite jklontz + */ +class CacheTransform : public MetaTransform +{ + Q_OBJECT + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform) + BR_PROPERTY(br::Transform*, transform, NULL) + + static QHash cache; + static QMutex cacheLock; + +public: + ~CacheTransform() + { + if (cache.isEmpty()) return; + + // Write to cache + QFile file("Cache"); + if (!file.open(QFile::WriteOnly)) + qFatal("Unable to open %s for writing.", qPrintable(file.fileName())); + QDataStream stream(&file); + stream << cache; + file.close(); + } + +private: + void init() + { + if (!transform) return; + + trainable = transform->trainable; + if (!cache.isEmpty()) return; + + // Read from cache + QFile file("Cache"); + if (file.exists()) { + if (!file.open(QFile::ReadOnly)) + qFatal("Unable to open %s for reading.", qPrintable(file.fileName())); + QDataStream stream(&file); + stream >> cache; + file.close(); + } + } + + void train(const QList &data) + { + transform->train(data); + } + + void project(const Template &src, Template &dst) const + { + const QString &file = src.file; + if (cache.contains(file)) { + dst = cache[file]; + } else { + transform->project(src, dst); + cacheLock.lock(); + cache[file] = dst; + cacheLock.unlock(); + } + } +}; + +QHash CacheTransform::cache; +QMutex CacheTransform::cacheLock; + +BR_REGISTER(Transform, CacheTransform) + +} // namespace br + +#include "core/cache.moc" diff --git a/openbr/plugins/core/contract.cpp b/openbr/plugins/core/contract.cpp new file mode 100644 index 0000000..018a89c --- /dev/null +++ b/openbr/plugins/core/contract.cpp @@ -0,0 +1,61 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief It's like the opposite of ExpandTransform, but not really + * \author Charles Otto \cite caotto + * + * Given a set of templatelists as input, concatenate them onto a single Template + */ +class ContractTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + virtual void project(const TemplateList &src, TemplateList &dst) const + { + if (src.empty()) return; + Template out; + + foreach (const Template &t, src) { + out.merge(t); + } + out.file.clearRects(); + foreach (const Template &t, src) { + if (!t.file.rects().empty()) + out.file.appendRects(t.file.rects()); + } + dst.clear(); + dst.append(out); + } + + virtual void project(const Template &src, Template &dst) const + { + qFatal("this has gone bad"); + (void) src; (void) dst; + } +}; + +BR_REGISTER(Transform, ContractTransform) + +} // namespace br + +#include "core/contract.moc" diff --git a/openbr/plugins/validate.cpp b/openbr/plugins/core/crossvalidate.cpp index cb0e651..eb86143 100644 --- a/openbr/plugins/validate.cpp +++ b/openbr/plugins/core/crossvalidate.cpp @@ -1,8 +1,23 @@ -#include -#include -#include "openbr_internal.h" -#include "openbr/core/common.h" -#include +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include +#include namespace br { @@ -134,138 +149,6 @@ class CrossValidateTransform : public MetaTransform BR_REGISTER(Transform, CrossValidateTransform) -/*! - * \ingroup distances - * \brief Cross validate a distance metric. - * \author Josh Klontz \cite jklontz - */ -class CrossValidateDistance : public UntrainableDistance -{ - Q_OBJECT - - float compare(const Template &a, const Template &b) const - { - static const QString key("Partition"); // More efficient to preallocate this - const int partitionA = a.file.get(key, 0); - const int partitionB = b.file.get(key, 0); - return (partitionA != partitionB) ? -std::numeric_limits::max() : 0; - } -}; - -BR_REGISTER(Distance, CrossValidateDistance) - -/*! - * \ingroup distances - * \brief Checks target metadata against filters. - * \author Josh Klontz \cite jklontz - */ -class FilterDistance : public UntrainableDistance -{ - Q_OBJECT - - float compare(const Template &a, const Template &b) const - { - (void) b; // Query template isn't checked - foreach (const QString &key, Globals->filters.keys()) { - bool keep = false; - const QString metadata = a.file.get(key, ""); - if (Globals->filters[key].isEmpty()) continue; - if (metadata.isEmpty()) return -std::numeric_limits::max(); - foreach (const QString &value, Globals->filters[key]) { - if (metadata == value) { - keep = true; - break; - } - } - if (!keep) return -std::numeric_limits::max(); - } - return 0; - } -}; - -BR_REGISTER(Distance, FilterDistance) - -/*! - * \ingroup distances - * \brief Checks target metadata against query metadata. - * \author Scott Klum \cite sklum - */ -class MetadataDistance : public UntrainableDistance -{ - Q_OBJECT - - Q_PROPERTY(QStringList filters READ get_filters WRITE set_filters RESET reset_filters STORED false) - BR_PROPERTY(QStringList, filters, QStringList()) - - float compare(const Template &a, const Template &b) const - { - foreach (const QString &key, filters) { - QString aValue = a.file.get(key, QString()); - QString bValue = b.file.get(key, QString()); - - // The query value may be a range. Let's check. - if (bValue.isEmpty()) bValue = QtUtils::toString(b.file.get(key, QPointF())); - - if (aValue.isEmpty() || bValue.isEmpty()) continue; - - bool keep = false; - bool ok; - - QPointF range = QtUtils::toPoint(bValue,&ok); - - if (ok) /* Range */ { - int value = range.x(); - int upperBound = range.y(); - - while (value <= upperBound) { - if (aValue == QString::number(value)) { - keep = true; - break; - } - value++; - } - } - else if (aValue == bValue) keep = true; - - if (!keep) return -std::numeric_limits::max(); - } - return 0; - } -}; - - -BR_REGISTER(Distance, MetadataDistance) - -/*! - * \ingroup distances - * \brief Sets distance to -FLOAT_MAX if a target template has/doesn't have a key. - * \author Scott Klum \cite sklum - */ -class RejectDistance : public UntrainableDistance -{ - Q_OBJECT - - Q_PROPERTY(QStringList keys READ get_keys WRITE set_keys RESET reset_keys STORED false) - BR_PROPERTY(QStringList, keys, QStringList()) - Q_PROPERTY(bool rejectIfContains READ get_rejectIfContains WRITE set_rejectIfContains RESET reset_rejectIfContains STORED false) - BR_PROPERTY(bool, rejectIfContains, false) - - float compare(const Template &a, const Template &b) const - { - // We don't look at the query - (void) b; - - foreach (const QString &key, keys) - if ((rejectIfContains && a.file.contains(key)) || (!rejectIfContains && !a.file.contains(key))) - return -std::numeric_limits::max(); - - return 0; - } -}; - - -BR_REGISTER(Distance, RejectDistance) - } // namespace br -#include "validate.moc" +#include "core/crossvalidate.moc" diff --git a/openbr/plugins/core/discard.cpp b/openbr/plugins/core/discard.cpp new file mode 100644 index 0000000..553a5b2 --- /dev/null +++ b/openbr/plugins/core/discard.cpp @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Removes all template's matrices. + * \see IdentityTransform FirstTransform RestTransform RemoveTransform + * \author Josh Klontz \cite jklontz + */ +class DiscardTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + dst.file = src.file; + } +}; + +BR_REGISTER(Transform, DiscardTransform) + +} // namespace br + +#include "core/discard.moc" diff --git a/openbr/plugins/core/discardtemplates.cpp b/openbr/plugins/core/discardtemplates.cpp new file mode 100644 index 0000000..baf89da --- /dev/null +++ b/openbr/plugins/core/discardtemplates.cpp @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +class DiscardTemplatesTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + (void) src; (void) dst; + qFatal("Incorrect project called on DiscardTemplatesTransform"); + } + void project(const TemplateList &src, TemplateList &dst) const + { + (void) src; + dst.clear(); + } +}; + +BR_REGISTER(Transform, DiscardTemplatesTransform) + +} // namespace br + +#include "core/discardtemplates.moc" diff --git a/openbr/plugins/core/distributetemplate.cpp b/openbr/plugins/core/distributetemplate.cpp new file mode 100644 index 0000000..6a7e5cd --- /dev/null +++ b/openbr/plugins/core/distributetemplate.cpp @@ -0,0 +1,142 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +static void _projectList(const Transform *transform, const TemplateList *src, TemplateList *dst) +{ + transform->project(*src, *dst); +} + +class DistributeTemplateTransform : public MetaTransform +{ + Q_OBJECT + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform) + BR_PROPERTY(br::Transform*, transform, NULL) + +public: + + Transform *smartCopy(bool &newTransform) + { + if (!transform->timeVarying()) { + newTransform = false; + return this; + } + newTransform = true; + + DistributeTemplateTransform *output = new DistributeTemplateTransform; + bool newChild = false; + output->transform = transform->smartCopy(newChild); + if (newChild) + output->transform->setParent(output); + + return output; + } + + void train(const QList &data) + { + if (!transform->trainable) { + qWarning("Attempted to train untrainable transform, nothing will happen."); + return; + } + + QList separated; + foreach (const TemplateList &list, data) { + foreach (const Template &t, list) { + separated.append(TemplateList()); + separated.last().append(t); + } + } + + transform->train(separated); + } + + void project(const Template &src, Template &dst) const + { + TemplateList input; + input.append(src); + TemplateList output; + project(input, output); + + if (output.size() != 1) qFatal("output contains more than 1 template"); + else dst = output[0]; + } + + // For each input template, form a single element TemplateList, push all those + // lists through transform, and form dst by concatenating the results. + // Process the single elemnt templates in parallel if parallelism is enabled. + void project(const TemplateList &src, TemplateList &dst) const + { + // Pre-allocate output for each template + QList output_buffer; + output_buffer.reserve(src.size()); + + // Can't declare this local to the loop because it would go out of scope + QList input_buffer; + input_buffer.reserve(src.size()); + + QFutureSynchronizer futures; + + for (int i =0; i < src.size();i++) { + input_buffer.append(TemplateList()); + output_buffer.append(TemplateList()); + } + QList > temp; + temp.reserve(src.size()); + for (int i=0; iparallelism > 1) temp.append(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i])); + else _projectList(transform, &input_buffer[i], &output_buffer[i]); + } + // We add the futures in reverse order, since in Qt 5.1 at least the + // waiting thread will wait on them in the order added (which for uniform priority + // threads is the order of execution), and we want the waiting thread to go in the opposite order + // so that it can steal runnables and do something besides wait. + for (int i = temp.size() - 1; i >= 0; i--) { + futures.addFuture(temp[i]); + } + + futures.waitForFinished(); + + for (int i=0; iproject(src, dst); + return; + } + + void init() + { + if (!transform) + return; + + trainable = transform->trainable; + } + +}; +BR_REGISTER(Transform, DistributeTemplateTransform) + +} // namespace br + +#include "core/distributetemplate.moc" diff --git a/openbr/plugins/core/downsampletraining.cpp b/openbr/plugins/core/downsampletraining.cpp new file mode 100644 index 0000000..e6eecb2 --- /dev/null +++ b/openbr/plugins/core/downsampletraining.cpp @@ -0,0 +1,135 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +static TemplateList Downsample(const TemplateList &templates, int classes, int instances, float fraction, const QString &inputVariable, const QStringList &gallery, const QStringList &subjects) +{ + // Return early when no downsampling is required + if ((classes == std::numeric_limits::max()) && + (instances == std::numeric_limits::max()) && + (fraction >= 1) && + (gallery.isEmpty()) && + (subjects.isEmpty())) + return templates; + + const bool atLeast = instances < 0; + instances = abs(instances); + + QList allLabels = File::get(templates, inputVariable); + + QList uniqueLabels = allLabels.toSet().toList(); + qSort(uniqueLabels); + + QMap counts = templates.countValues(inputVariable, instances != std::numeric_limits::max()); + + if ((instances != std::numeric_limits::max()) && (classes != std::numeric_limits::max())) + foreach (const QString &label, counts.keys()) + if (counts[label] < instances) + counts.remove(label); + + uniqueLabels = counts.keys(); + if ((classes != std::numeric_limits::max()) && (uniqueLabels.size() < classes)) + qWarning("Downsample requested %d classes but only %d are available.", classes, uniqueLabels.size()); + + QList selectedLabels = uniqueLabels; + if (classes < uniqueLabels.size()) { + std::random_shuffle(selectedLabels.begin(), selectedLabels.end()); + selectedLabels = selectedLabels.mid(0, classes); + } + + TemplateList downsample; + for (int i=0; i indices; + for (int j=0; j("FTE", false)) && (!templates.value(j).file.get("PossibleFTE", false))) + indices.append(j); + + std::random_shuffle(indices.begin(), indices.end()); + const int max = atLeast ? indices.size() : std::min(indices.size(), instances); + for (int j=0; j=0; i--) + if (!gallery.contains(downsample[i].file.get("Gallery"))) + downsample.removeAt(i); + + if (!subjects.isEmpty()) + for (int i=downsample.size()-1; i>=0; i--) + if (subjects.contains(downsample[i].file.get(inputVariable))) + downsample.removeAt(i); + + return downsample; +} + +class DownsampleTrainingTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform STORED true) + Q_PROPERTY(int classes READ get_classes WRITE set_classes RESET reset_classes STORED false) + Q_PROPERTY(int instances READ get_instances WRITE set_instances RESET reset_instances STORED false) + Q_PROPERTY(float fraction READ get_fraction WRITE set_fraction RESET reset_fraction STORED false) + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + Q_PROPERTY(QStringList gallery READ get_gallery WRITE set_gallery RESET reset_gallery STORED false) + Q_PROPERTY(QStringList subjects READ get_subjects WRITE set_subjects RESET reset_subjects STORED false) + BR_PROPERTY(br::Transform*, transform, NULL) + BR_PROPERTY(int, classes, std::numeric_limits::max()) + BR_PROPERTY(int, instances, std::numeric_limits::max()) + BR_PROPERTY(float, fraction, 1) + BR_PROPERTY(QString, inputVariable, "Label") + BR_PROPERTY(QStringList, gallery, QStringList()) + BR_PROPERTY(QStringList, subjects, QStringList()) + + + Transform *simplify(bool &newTForm) + { + Transform *res = transform->simplify(newTForm); + return res; + } + + void project(const Template &src, Template &dst) const + { + transform->project(src,dst); + } + + + void train(const TemplateList &data) + { + if (!transform || !transform->trainable) + return; + + TemplateList downsampled = Downsample(data, classes, instances, fraction, inputVariable, gallery, subjects); + + transform->train(downsampled); + } +}; + +BR_REGISTER(Transform, DownsampleTrainingTransform) + +} // namespace br + +#include "core/downsampletraining.moc" diff --git a/openbr/plugins/core/event.cpp b/openbr/plugins/core/event.cpp new file mode 100644 index 0000000..b9f2568 --- /dev/null +++ b/openbr/plugins/core/event.cpp @@ -0,0 +1,46 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +class EventTransform : public UntrainableMetaTransform +{ + Q_OBJECT + Q_PROPERTY(QString eventName READ get_eventName WRITE set_eventName RESET reset_eventName STORED false) + BR_PROPERTY(QString, eventName, "") + + TemplateEvent event; + + void project(const Template &src, Template &dst) const + { + dst = src; + event.pulseSignal(dst); + } + + TemplateEvent *getEvent(const QString &name) + { + return name == eventName ? &event : NULL; + } +}; + +BR_REGISTER(Transform, EventTransform) + +} // namespace br + +#include "core/event.moc" diff --git a/openbr/plugins/core/expand.cpp b/openbr/plugins/core/expand.cpp new file mode 100644 index 0000000..6c19dff --- /dev/null +++ b/openbr/plugins/core/expand.cpp @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +static TemplateList Expanded(const TemplateList &templates) +{ + TemplateList expanded; + foreach (const Template &t, templates) { + const bool enrollAll = t.file.get("enrollAll"); + if (t.isEmpty()) { + if (!enrollAll) + expanded.append(t); + continue; + } + + const QList points = t.file.points(); + const QList rects = t.file.rects(); + if (points.size() % t.size() != 0) qFatal("Uneven point count."); + if (rects.size() % t.size() != 0) qFatal("Uneven rect count."); + const int pointStep = points.size() / t.size(); + const int rectStep = rects.size() / t.size(); + + for (int i=0; i + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Removes all but the first matrix from the template. + * \see IdentityTransform DiscardTransform RestTransform RemoveTransform + * \author Josh Klontz \cite jklontz + */ +class FirstTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + // AggregateFrames will leave the Template empty + // if it hasn't filled up the buffer + // so we gotta anticipate an empty Template + if (src.empty()) return; + dst.file = src.file; + dst = src.m(); + } +}; + +BR_REGISTER(Transform, FirstTransform) + +} // namespace br + +#include "core/first.moc" diff --git a/openbr/plugins/core/fork.cpp b/openbr/plugins/core/fork.cpp new file mode 100644 index 0000000..1d82503 --- /dev/null +++ b/openbr/plugins/core/fork.cpp @@ -0,0 +1,143 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +static void _train(Transform *transform, const QList *data) +{ + transform->train(*data); +} + +/*! + * \ingroup transforms + * \brief Transforms in parallel. + * \author Josh Klontz \cite jklontz + * + * The source br::Template is seperately given to each transform and the results are appended together. + * + * \see PipeTransform + */ +class ForkTransform : public CompositeTransform +{ + Q_OBJECT + + void train(const QList &data) + { + if (!trainable) return; + QFutureSynchronizer futures; + for (int i=0; iprojectUpdate(src, res); + dst.merge(res); + } catch (...) { + qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); + dst = Template(src.file); + dst.file.fte = true; + break; + } + } + } + + void projectUpdate(const TemplateList &src, TemplateList &dst) + { + dst.reserve(src.size()); + for (int i=0; iprojectUpdate(src, m); + if (m.size() != dst.size()) qFatal("TemplateList is of an unexpected size."); + for (int i=0; ifinalize(last_set); + if (last_set.empty()) + continue; + + if (output.empty()) output = last_set; + else + { + // is the number of templates received from this transform consistent with the number + // received previously? If not we can't do anything coherent here. + if (last_set.size() != output.size()) + qFatal("mismatched template list sizes in ForkTransform"); + for (int j = 0; j < output.size(); j++) { + output[j].append(last_set[j]); + } + } + } + } + +protected: + + // Apply each transform to src, concatenate the results + void _project(const Template &src, Template &dst) const + { + foreach (const Transform *f, transforms) { + try { + dst.merge((*f)(src)); + } catch (...) { + qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); + dst = Template(src.file); + dst.file.fte = true; + break; + } + } + } + + void _project(const TemplateList &src, TemplateList &dst) const + { + dst.reserve(src.size()); + for (int i=0; iproject(src, m); + if (m.size() != dst.size()) qFatal("TemplateList is of an unexpected size."); + for (int i=0; i +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Flags images that failed to enroll based on the specified transform. + * \author Josh Klontz \cite jklontz + */ +class FTETransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform) + Q_PROPERTY(float min READ get_min WRITE set_min RESET reset_min) + Q_PROPERTY(float max READ get_max WRITE set_max RESET reset_max) + BR_PROPERTY(br::Transform*, transform, NULL) + BR_PROPERTY(float, min, -std::numeric_limits::max()) + BR_PROPERTY(float, max, std::numeric_limits::max()) + + void train(const TemplateList &data) + { + transform->train(data); + + TemplateList projectedData; + transform->project(data, projectedData); + + QList vals; + foreach (const Template &t, projectedData) { + if (!t.file.contains(transform->objectName())) + qFatal("Matrix metadata missing key %s.", qPrintable(transform->objectName())); + vals.append(t.file.get(transform->objectName())); + } + float q1, q3; + Common::Median(vals, &q1, &q3); + min = q1 - 1.5 * (q3 - q1); + max = q3 + 1.5 * (q3 - q1); + } + + void project(const Template &src, Template &dst) const + { + Template projectedSrc; + transform->project(src, projectedSrc); + const float val = projectedSrc.file.get(transform->objectName()); + + dst = src; + dst.file.set(transform->objectName(), val); + dst.file.set("PossibleFTE", (val < min) || (val > max)); + } +}; + +BR_REGISTER(Transform, FTETransform) + +} // namespace br + +#include "core/fte.moc" diff --git a/openbr/plugins/core/gallerycompare.cpp b/openbr/plugins/core/gallerycompare.cpp new file mode 100644 index 0000000..d8c0f64 --- /dev/null +++ b/openbr/plugins/core/gallerycompare.cpp @@ -0,0 +1,80 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Compare each template to a fixed gallery (with name = galleryName), using the specified distance. + * dst will contain a 1 by n vector of scores. + * \author Charles Otto \cite caotto + */ +class GalleryCompareTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED true) + Q_PROPERTY(QString galleryName READ get_galleryName WRITE set_galleryName RESET reset_galleryName STORED false) + BR_PROPERTY(br::Distance*, distance, NULL) + BR_PROPERTY(QString, galleryName, "") + + TemplateList gallery; + + void project(const Template &src, Template &dst) const + { + dst = src; + if (gallery.isEmpty()) + return; + + QList line = distance->compare(gallery, src); + dst.m() = OpenCVUtils::toMat(line, 1); + } + + void init() + { + if (!galleryName.isEmpty()) + gallery = TemplateList::fromGallery(galleryName); + } + + void train(const TemplateList &data) + { + gallery = data; + } + + void store(QDataStream &stream) const + { + br::Object::store(stream); + stream << gallery; + } + + void load(QDataStream &stream) + { + br::Object::load(stream); + stream >> gallery; + } + +public: + GalleryCompareTransform() : Transform(false, true) {} +}; + +BR_REGISTER(Transform, GalleryCompareTransform) + +} // namespace br + +#include "core/gallerycompare.moc" diff --git a/openbr/plugins/core/identity.cpp b/openbr/plugins/core/identity.cpp new file mode 100644 index 0000000..18b4c93 --- /dev/null +++ b/openbr/plugins/core/identity.cpp @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief A no-op transform. + * \see DiscardTransform FirstTransform RestTransform RemoveTransform + * \author Josh Klontz \cite jklontz + */ +class IdentityTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + dst = src; + } +}; + +BR_REGISTER(Transform, IdentityTransform) + +} // namespace br + +#include "core/identity.moc" diff --git a/openbr/plugins/core/independent.cpp b/openbr/plugins/core/independent.cpp new file mode 100644 index 0000000..b0c9289 --- /dev/null +++ b/openbr/plugins/core/independent.cpp @@ -0,0 +1,229 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Clones the transform so that it can be applied independently. + * \author Josh Klontz \cite jklontz + * \em Independent transforms expect single-matrix templates. + */ +class IndependentTransform : public MetaTransform +{ + Q_OBJECT + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform STORED false) + BR_PROPERTY(br::Transform*, transform, NULL) + + QList transforms; + + QString description(bool expanded) const + { + return transform->description(expanded); + } + + // can't use general setPropertyRecursive because of transforms oddness + bool setPropertyRecursive(const QString &name, QVariant value) + { + if (br::Object::setExistingProperty(name, value)) + return true; + + if (!transform->setPropertyRecursive(name, value)) + return false; + + for (int i=0;i < transforms.size();i++) + transforms[i]->setPropertyRecursive(name, value); + + return true; + } + + Transform *simplify(bool &newTransform) + { + newTransform = false; + bool newChild = false; + Transform *temp = transform->simplify(newChild); + if (temp == transform) { + return this; + } + IndependentTransform* indep = new IndependentTransform(); + indep->transform = temp; + + IndependentTransform *test = dynamic_cast (temp); + if (test) { + // child was independent? this changes things... + indep->transform = test->transform; + for (int i=0; i < transforms.size(); i++) { + bool newThing = false; + IndependentTransform *probe = dynamic_cast (transforms[i]->simplify(newThing)); + indep->transforms.append(probe->transform); + if (newThing) + probe->setParent(indep); + } + indep->file = indep->transform->file; + indep->trainable = indep->transform->trainable; + indep->setObjectName(indep->transform->objectName()); + + return indep; + } + + if (newChild) + indep->transform->setParent(indep); + + for (int i=0; i < transforms.size();i++) { + bool subTform = false; + indep->transforms.append(transforms[i]->simplify(subTform)); + if (subTform) + indep->transforms[i]->setParent(indep); + } + + indep->file = indep->transform->file; + indep->trainable = indep->transform->trainable; + indep->setObjectName(indep->transform->objectName()); + + return indep; + } + + void init() + { + transforms.clear(); + if (transform == NULL) + return; + + transform->setParent(this); + transforms.append(transform); + file = transform->file; + trainable = transform->trainable; + setObjectName(transform->objectName()); + } + + Transform *clone() const + { + IndependentTransform *independentTransform = new IndependentTransform(); + independentTransform->transform = transform->clone(); + independentTransform->init(); + return independentTransform; + } + + bool timeVarying() const { return transform->timeVarying(); } + + static void _train(Transform *transform, const TemplateList *data) + { + transform->train(*data); + } + + void train(const TemplateList &data) + { + // Don't bother if the transform is untrainable + if (!trainable) return; + + QList templatesList; + foreach (const Template &t, data) { + if ((templatesList.size() != t.size()) && !templatesList.isEmpty()) + qWarning("Independent::train (%s) template %s of size %d differs from expected size %d.", qPrintable(objectName()), qPrintable(t.file.name), t.size(), templatesList.size()); + while (templatesList.size() < t.size()) + templatesList.append(TemplateList()); + for (int i=0; iclone()); + + QFutureSynchronizer futures; + for (int i=0; i mats; + for (int i=0; iproject(Template(src.file, src[i]), dst); + mats.append(dst); + dst.clear(); + } + dst.append(mats); + } + + void projectUpdate(const Template &src, Template &dst) + { + dst.file = src.file; + QList mats; + for (int i=0; iprojectUpdate(Template(src.file, src[i]), dst); + mats.append(dst); + dst.clear(); + } + dst.append(mats); + } + + void finalize(TemplateList &out) + { + if (transforms.empty()) + return; + + transforms[0]->finalize(out); + for (int i=1; i < transforms.size(); i++) { + TemplateList temp; + transforms[i]->finalize(temp); + + for (int j=0; j < out.size(); j++) + out[j].append(temp[j]); + } + } + + void projectUpdate(const TemplateList &src, TemplateList &dst) + { + dst.reserve(src.size()); + foreach (const Template &t, src) { + dst.append(Template()); + projectUpdate(t, dst.last()); + } + } + + void store(QDataStream &stream) const + { + const int size = transforms.size(); + stream << size; + for (int i=0; istore(stream); + } + + void load(QDataStream &stream) + { + int size; + stream >> size; + while (transforms.size() < size) + transforms.append(transform->clone()); + for (int i=0; iload(stream); + } +}; + +BR_REGISTER(Transform, IndependentTransform) + +} // namespace br + +#include "core/independent.moc" diff --git a/openbr/plugins/jni.cpp b/openbr/plugins/core/jni.cpp index 9aa6870..1b84bb9 100644 --- a/openbr/plugins/jni.cpp +++ b/openbr/plugins/core/jni.cpp @@ -1,8 +1,8 @@ //Need to include location of jvm.dll (jdk version) and its parent directory in the environment variables #include -#include "openbr_internal.h" -#include "openbr/core/resource.h" +#include +#include #include namespace br diff --git a/openbr/plugins/likely.cpp b/openbr/plugins/core/likely.cpp index f15f4b4..400b7e7 100644 --- a/openbr/plugins/likely.cpp +++ b/openbr/plugins/core/likely.cpp @@ -1,8 +1,4 @@ -#include -#include - -#include "openbr/core/opencvutils.h" -#include "openbr_internal.h" +#include namespace br { @@ -57,68 +53,6 @@ public: BR_REGISTER(Transform, LikelyTransform) -/*! - * \ingroup formats - * \brief Likely matrix format - * - * www.liblikely.org - * \author Josh Klontz \cite jklontz - */ -class lmatFormat : public Format -{ - Q_OBJECT - - Template read() const - { - const likely_const_mat m = likely_read(qPrintable(file.name), likely_file_guess); - const Template result(likelyToOpenCVMat(m)); - likely_release_mat(m); - return result; - } - - void write(const Template &t) const - { - const likely_const_mat m = likelyFromOpenCVMat(t); - likely_write(m, qPrintable(file.name)); - likely_release_mat(m); - } -}; - -BR_REGISTER(Format, lmatFormat) - -/*! - * \ingroup formats - * \brief Likely matrix format - * - * www.liblikely.org - * \author Josh Klontz \cite jklontz - */ -class lmatGallery : public Gallery -{ - Q_OBJECT - QList mats; - - ~lmatGallery() - { - const likely_const_mat m = likelyFromOpenCVMat(OpenCVUtils::toMatByRow(mats)); - likely_write(m, qPrintable(file.name)); - likely_release_mat(m); - } - - TemplateList readBlock(bool *done) - { - *done = true; - qFatal("Not supported."); - } - - void write(const Template &t) - { - mats.append(t); - } -}; - -BR_REGISTER(Gallery, lmatGallery) - } // namespace br -#include "likely.moc" +#include "core/likely.moc" diff --git a/openbr/plugins/core/loadstore.cpp b/openbr/plugins/core/loadstore.cpp new file mode 100644 index 0000000..5657aa8 --- /dev/null +++ b/openbr/plugins/core/loadstore.cpp @@ -0,0 +1,168 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Caches transform training. + * \author Josh Klontz \cite jklontz + */ +class LoadStoreTransform : public MetaTransform +{ + Q_OBJECT + Q_PROPERTY(QString transformString READ get_transformString WRITE set_transformString RESET reset_transformString STORED false) + Q_PROPERTY(QString fileName READ get_fileName WRITE set_fileName RESET reset_fileName STORED false) + BR_PROPERTY(QString, transformString, "Identity") + BR_PROPERTY(QString, fileName, QString()) + +public: + Transform *transform; + + LoadStoreTransform() : transform(NULL) {} + + QString description(bool expanded = false) const + { + if (expanded) { + QString res = transform->description(expanded); + return res; + } + return br::Object::description(expanded); + } + + Transform *simplify(bool &newTForm) + { + Transform *res = transform->simplify(newTForm); + return res; + } + + QList getChildren() const + { + QList rval; + rval.append(transform); + return rval; + } +private: + + void init() + { + if (transform != NULL) return; + if (fileName.isEmpty()) fileName = QRegExp("^[_a-zA-Z0-9]+$").exactMatch(transformString) ? transformString : QtUtils::shortTextHash(transformString); + + if (!tryLoad()) + transform = make(transformString); + else + trainable = false; + } + + bool timeVarying() const + { + return transform->timeVarying(); + } + + void train(const QList &data) + { + if (QFileInfo(getFileName()).exists()) + return; + + transform->train(data); + + qDebug("Storing %s", qPrintable(fileName)); + QtUtils::BlockCompression compressedOut; + QFile fout(fileName); + QtUtils::touchDir(fout); + compressedOut.setBasis(&fout); + + QDataStream stream(&compressedOut); + QString desc = transform->description(); + + if (!compressedOut.open(QFile::WriteOnly)) + qFatal("Failed to open %s for writing.", qPrintable(file)); + + stream << desc; + transform->store(stream); + compressedOut.close(); + } + + void project(const Template &src, Template &dst) const + { + transform->project(src, dst); + } + + void project(const TemplateList &src, TemplateList &dst) const + { + transform->project(src, dst); + } + + void projectUpdate(const Template &src, Template &dst) + { + transform->projectUpdate(src, dst); + } + + void projectUpdate(const TemplateList &src, TemplateList &dst) + { + transform->projectUpdate(src, dst); + } + + void finalize(TemplateList &output) + { + transform->finalize(output); + } + + QString getFileName() const + { + if (QFileInfo(fileName).exists()) return fileName; + + foreach(const QString &path, Globals->modelSearch) { + const QString file = path + "/" + fileName; + if (QFileInfo(file).exists()) + return file; + } + return QString(); + } + + bool tryLoad() + { + const QString file = getFileName(); + if (file.isEmpty()) return false; + + qDebug("Loading %s", qPrintable(file)); + QFile fin(file); + QtUtils::BlockCompression reader(&fin); + if (!reader.open(QIODevice::ReadOnly)) { + if (QFileInfo(file).exists()) qFatal("Unable to open %s for reading. Check file permissions.", qPrintable(file)); + else qFatal("Unable to open %s for reading. File does not exist.", qPrintable(file)); + } + + QDataStream stream(&reader); + stream >> transformString; + + transform = Transform::make(transformString); + transform->load(stream); + + return true; + } +}; + +BR_REGISTER(Transform, LoadStoreTransform) + +} // namespace br + +#include "core/loadstore.moc" diff --git a/openbr/plugins/core/pipe.cpp b/openbr/plugins/core/pipe.cpp new file mode 100644 index 0000000..bebe445 --- /dev/null +++ b/openbr/plugins/core/pipe.cpp @@ -0,0 +1,221 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +/*! + * \ingroup Transforms + * \brief Transforms in series. + * \author Josh Klontz \cite jklontz + * + * The source br::Template is given to the first transform and the resulting br::Template is passed to the next transform, etc. + * + * \see ExpandTransform + * \see ForkTransform + */ +class PipeTransform : public CompositeTransform +{ + Q_OBJECT + + void _projectPartial(TemplateList *srcdst, int startIndex, int stopIndex) + { + TemplateList ftes; + for (int i=startIndex; iproject(*srcdst, res); + + splitFTEs(res, ftes); + *srcdst = res; + } + } + + void train(const QList &data) + { + if (!trainable) return; + + QList dataLines(data); + + int i = 0; + while (i < transforms.size()) { + // Conditional statement covers likely case that first transform is untrainable + if (transforms[i]->trainable) { + qDebug() << "Training " << transforms[i]->description() << "\n..."; + transforms[i]->train(dataLines); + } + + // if the transform is time varying, we can't project it in parallel + if (transforms[i]->timeVarying()) { + qDebug() << "Projecting " << transforms[i]->description() << "\n..."; + for (int j=0; j < dataLines.size();j++) { + TemplateList junk; + splitFTEs(dataLines[j], junk); + + transforms[i]->projectUpdate(dataLines[j], dataLines[j]); + } + + // advance i since we already projected for this stage. + i++; + + // the next stage might be trainable, so continue to evaluate it. + continue; + } + + // We project through any subsequent untrainable transforms at once + // as a memory optimization in case any of these intermediate + // transforms allocate a lot of memory (like OpenTransform) + // then we don't want all the training templates to be processed + // by that transform at once if we can avoid it. + int nextTrainableTransform = i+1; + while ((nextTrainableTransform < transforms.size()) && + !transforms[nextTrainableTransform]->trainable && + !transforms[nextTrainableTransform]->timeVarying()) + nextTrainableTransform++; + + // No more trainable transforms? Don't need any more projects then + if (nextTrainableTransform == transforms.size()) + break; + + fprintf(stderr, "Projecting %s", qPrintable(transforms[i]->description())); + for (int j=i+1; j < nextTrainableTransform; j++) + fprintf(stderr,"+%s", qPrintable(transforms[j]->description())); + fprintf(stderr, "\n...\n"); + fflush(stderr); + + QFutureSynchronizer futures; + for (int j=0; j < dataLines.size(); j++) + futures.addFuture(QtConcurrent::run(this, &PipeTransform::_projectPartial, &dataLines[j], i, nextTrainableTransform)); + futures.waitForFinished(); + + i = nextTrainableTransform; + } + } + + void projectUpdate(const Template &src, Template &dst) + { + dst = src; + foreach (Transform *f, transforms) { + try { + f->projectUpdate(dst); + if (dst.file.fte) + break; + } catch (...) { + qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); + dst = Template(src.file); + dst.file.fte = true; + break; + } + } + } + + // For time varying transforms, parallel execution over individual templates + // won't work. + void projectUpdate(const TemplateList &src, TemplateList &dst) + { + TemplateList ftes; + dst = src; + foreach (Transform *f, transforms) { + TemplateList res; + f->projectUpdate(dst, res); + splitFTEs(res, ftes); + dst = res; + } + dst.append(ftes); + } + + virtual void finalize(TemplateList &output) + { + output.clear(); + // For each transform, + for (int i = 0; i < transforms.size(); i++) + { + + // Collect any final templates + TemplateList last_set; + transforms[i]->finalize(last_set); + if (last_set.empty()) + continue; + // Push any templates received through the remaining transforms in the sequence + for (int j = (i+1); j < transforms.size();j++) + { + transforms[j]->projectUpdate(last_set); + } + // append the result to the output set + output.append(last_set); + } + } + + void init() + { + QList flattened; + for (int i=0;i < transforms.size(); i++) + { + PipeTransform *probe = dynamic_cast (transforms[i]); + if (!probe) { + flattened.append(transforms[i]); + continue; + } + for (int j=0; j < probe->transforms.size(); j++) + flattened.append(probe->transforms[j]); + } + transforms = flattened; + + CompositeTransform::init(); + } + +protected: + // Template list project -- process templates in parallel through Transform::project + // or if parallelism is disabled, handle them sequentially + void _project(const TemplateList &src, TemplateList &dst) const + { + TemplateList ftes; + dst = src; + foreach (const Transform *f, transforms) { + TemplateList res; + f->project(dst, res); + splitFTEs(res, ftes); + dst = res; + } + dst.append(ftes); + } + + // Single template const project, pass the template through each sub-transform, one after the other + virtual void _project(const Template &src, Template &dst) const + { + dst = src; + foreach (const Transform *f, transforms) { + try { + dst >> *f; + if (dst.file.fte) + break; + } catch (...) { + qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); + dst = Template(src.file); + dst.file.fte = true; + } + } + } +}; + +BR_REGISTER(Transform, PipeTransform) + +} // namespace br + +#include "core/pipe.moc" diff --git a/openbr/plugins/process.cpp b/openbr/plugins/core/processwrapper.cpp index ba3559b..5594fc8 100644 --- a/openbr/plugins/process.cpp +++ b/openbr/plugins/core/processwrapper.cpp @@ -1,4 +1,18 @@ - +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include @@ -9,8 +23,8 @@ #include #include -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" +#include +#include using namespace cv; @@ -658,4 +672,4 @@ BR_REGISTER(Transform, ProcessWrapperTransform) } -#include "process.moc" +#include "core/processwrapper.moc" diff --git a/openbr/plugins/core/progresscounter.cpp b/openbr/plugins/core/progresscounter.cpp new file mode 100644 index 0000000..e27fbfd --- /dev/null +++ b/openbr/plugins/core/progresscounter.cpp @@ -0,0 +1,97 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include +#include + +namespace br +{ + +class ProgressCounterTransform : public TimeVaryingTransform +{ + Q_OBJECT + + Q_PROPERTY(qint64 totalProgress READ get_totalProgress WRITE set_totalProgress RESET reset_totalProgress STORED false) + BR_PROPERTY(qint64, totalProgress, 1) + + void projectUpdate(const TemplateList &src, TemplateList &dst) + { + dst = src; + + qint64 elapsed = timer.elapsed(); + int last_frame = -2; + if (!dst.empty()) { + for (int i=0;i < dst.size();i++) { + int frame = dst[i].file.get("FrameNumber", -1); + if (frame == last_frame && frame != -1) + continue; + + // Use 1 as the starting index for progress output + Globals->currentProgress = dst[i].file.get("progress",0)+1; + dst[i].file.remove("progress"); + last_frame = frame; + + Globals->currentStep++; + } + } + + // updated every second + if (elapsed > 1000) { + Globals->printStatus(); + timer.start(); + } + + return; + } + + void train(const TemplateList& data) + { + (void) data; + } + + void finalize(TemplateList &data) + { + (void) data; + float p = br_progress(); + qDebug("\r%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), Globals->currentStep); + timer.start(); + Globals->startTime.start(); + Globals->currentStep = 0; + Globals->currentProgress = 0; + Globals->totalSteps = totalProgress; + } + + void init() + { + timer.start(); + Globals->startTime.start(); + Globals->currentProgress = 0; + Globals->currentStep = 0; + Globals->totalSteps = totalProgress; + } + +public: + ProgressCounterTransform() : TimeVaryingTransform(false,false) {} + QElapsedTimer timer; +}; + +BR_REGISTER(Transform, ProgressCounterTransform) + +} // namespace br + +#include "core/progresscounter.moc" diff --git a/openbr/plugins/core/propagate.cpp b/openbr/plugins/core/propagate.cpp new file mode 100644 index 0000000..9146ad0 --- /dev/null +++ b/openbr/plugins/core/propagate.cpp @@ -0,0 +1,30 @@ +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Ensures that a template will be propogated. + * \author Scott Klum \cite sklum + */ +class PropagateTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED true) + BR_PROPERTY(br::Transform *, transform, NULL) + + void project(const Template &src, Template &dst) const + { + transform->project(src,dst); + if (dst.isEmpty()) + dst = src; + } +}; + +BR_REGISTER(Transform, PropagateTransform) + +} // namespace br + +#include "core/propagate.moc" diff --git a/openbr/plugins/core/registrar.cpp b/openbr/plugins/core/registrar.cpp new file mode 100644 index 0000000..aaaf573 --- /dev/null +++ b/openbr/plugins/core/registrar.cpp @@ -0,0 +1,41 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup initializers + * \brief Register custom objects with Qt meta object system. + * \author Charles Otto \cite caotto + */ +class Registrar : public Initializer +{ + Q_OBJECT + + void initialize() const + { + qRegisterMetaType(); + } +}; + +BR_REGISTER(Initializer, Registrar) + +} // namespace br + +#include "core/registrar.moc" diff --git a/openbr/plugins/core/remove.cpp b/openbr/plugins/core/remove.cpp new file mode 100644 index 0000000..72a0fed --- /dev/null +++ b/openbr/plugins/core/remove.cpp @@ -0,0 +1,47 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Removes the matrix from the template at the specified index. + * \author Josh Klontz \cite jklontz + * \see IdentityTransform DiscardTransform FirstTransform RestTransform + */ +//! [example_transform] +class RemoveTransform : public UntrainableMetaTransform +{ + Q_OBJECT + Q_PROPERTY(int index READ get_index WRITE set_index RESET reset_index STORED false) + BR_PROPERTY(int, index, 0) + + void project(const Template &src, Template &dst) const + { + dst = src; + dst.removeAt(index); + } +}; + +BR_REGISTER(Transform, RemoveTransform) +//! [example_transform] + +} // namespace br + +#include "core/remove.moc" diff --git a/openbr/plugins/core/rest.cpp b/openbr/plugins/core/rest.cpp new file mode 100644 index 0000000..0cbcedf --- /dev/null +++ b/openbr/plugins/core/rest.cpp @@ -0,0 +1,43 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Removes the first matrix from the template. + * \see IdentityTransform DiscardTransform FirstTransform RemoveTransform + * \author Josh Klontz \cite jklontz + */ +class RestTransform : public UntrainableMetaTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + dst = src; + dst.removeFirst(); + } +}; + +BR_REGISTER(Transform, RestTransform) + +} // namespace br + +#include "core/rest.moc" diff --git a/openbr/plugins/core/schrodinger.cpp b/openbr/plugins/core/schrodinger.cpp new file mode 100644 index 0000000..3bb6ad6 --- /dev/null +++ b/openbr/plugins/core/schrodinger.cpp @@ -0,0 +1,61 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Generates two templates, one of which is passed through a transform and the other + * is not. No cats were harmed in the making of this transform. + * \author Scott Klum \cite sklum + */ +class SchrodingerTransform : public MetaTransform +{ + Q_OBJECT + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform) + BR_PROPERTY(br::Transform*, transform, NULL) + +public: + void train(const TemplateList &data) + { + transform->train(data); + } + + void project(const TemplateList &src, TemplateList &dst) const + { + foreach(const Template &t, src) { + dst.append(t); + Template u; + transform->project(t,u); + dst.append(u); + } + } + + void project(const Template &src, Template &dst) const { + TemplateList temp; + project(TemplateList() << src, temp); + if (!temp.isEmpty()) dst = temp.first(); + } + +}; +BR_REGISTER(Transform, SchrodingerTransform) + +} // namespace br + +#include "core/schrodinger.moc" diff --git a/openbr/plugins/core/singleton.cpp b/openbr/plugins/core/singleton.cpp new file mode 100644 index 0000000..965750c --- /dev/null +++ b/openbr/plugins/core/singleton.cpp @@ -0,0 +1,89 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \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 "core/singleton.moc" diff --git a/openbr/plugins/stream.cpp b/openbr/plugins/core/stream.cpp index c2e5019..8920d6e 100644 --- a/openbr/plugins/stream.cpp +++ b/openbr/plugins/core/stream.cpp @@ -1,3 +1,19 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include #include #include @@ -8,10 +24,11 @@ #include #include #include -#include "openbr_internal.h" -#include "openbr/core/common.h" -#include "openbr/core/opencvutils.h" -#include "openbr/core/qtutils.h" + +#include +#include +#include +#include using namespace cv; using namespace std; @@ -1360,5 +1377,5 @@ BR_REGISTER(Transform, StreamTransform) } // namespace br -#include "stream.moc" +#include "core/stream.moc" diff --git a/openbr/plugins/crop.cpp b/openbr/plugins/crop.cpp deleted file mode 100644 index 01b5167..0000000 --- a/openbr/plugins/crop.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include -#include "openbr_internal.h" - -#include "openbr/core/opencvutils.h" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup transforms - * \brief Crops about the specified region of interest. - * \author Josh Klontz \cite jklontz - */ -class CropTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(int x READ get_x WRITE set_x RESET reset_x STORED false) - Q_PROPERTY(int y READ get_y WRITE set_y RESET reset_y STORED false) - Q_PROPERTY(int width READ get_width WRITE set_width RESET reset_width STORED false) - Q_PROPERTY(int height READ get_height WRITE set_height RESET reset_height STORED false) - BR_PROPERTY(int, x, 0) - BR_PROPERTY(int, y, 0) - BR_PROPERTY(int, width, -1) - BR_PROPERTY(int, height, -1) - - void project(const Template &src, Template &dst) const - { - dst = Mat(src, Rect(x, y, width < 1 ? src.m().cols-x-abs(width) : width, height < 1 ? src.m().rows-y-abs(height) : height)); - } -}; - -BR_REGISTER(Transform, CropTransform) - -/*! - * \ingroup transforms - * \brief Crops the rectangular regions of interest. - * \author Josh Klontz \cite jklontz - */ -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 (!propName.isEmpty()) { - QRectF rect = src.file.get(propName); - dst += src.m()(OpenCVUtils::toRect(rect)); - } else if (!src.file.rects().empty()) { - foreach (const QRectF &rect, src.file.rects()) - dst += src.m()(OpenCVUtils::toRect(rect)); - } else if (src.file.contains(QStringList() << "X" << "Y" << "Width" << "Height")) { - dst += src.m()(Rect(src.file.get("X"), - src.file.get("Y"), - src.file.get("Width"), - src.file.get("Height"))); - } else { - dst = src; - if (Globals->verbose) - qWarning("No rects present in file."); - } - } -}; - -BR_REGISTER(Transform, ROITransform) - -/*! - * \ingroup transforms - * \brief Crops the rectangular regions of interest from given points and sizes. - * \author Austin Blanton \cite imaus10 - */ -class ROIFromPtsTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(int width READ get_width WRITE set_width RESET reset_width STORED false) - Q_PROPERTY(int height READ get_height WRITE set_height RESET reset_height STORED false) - BR_PROPERTY(int, width, 1) - BR_PROPERTY(int, height, 1) - - void project(const Template &src, Template &dst) const - { - foreach (const QPointF &pt, src.file.points()) { - int x = pt.x() - (width/2); - int y = pt.y() - (height/2); - dst += src.m()(Rect(x, y, width, height)); - } - } -}; - -BR_REGISTER(Transform, ROIFromPtsTransform) - -/*! - * \ingroup transforms - * \brief Resize the template - * \author Josh Klontz \cite jklontz - * \note Method: Area should be used for shrinking an image, Cubic for slow but accurate enlargment, Bilin for fast enlargement. - * \param preserveAspect If true, the image will be sized per specification, but - * a border will be applied to preserve aspect ratio. - */ -class ResizeTransform : public UntrainableTransform -{ - Q_OBJECT - Q_ENUMS(Method) - -public: - /*!< */ - enum Method { Near = INTER_NEAREST, - Area = INTER_AREA, - Bilin = INTER_LINEAR, - Cubic = INTER_CUBIC, - Lanczo = INTER_LANCZOS4}; - -private: - Q_PROPERTY(int rows READ get_rows WRITE set_rows RESET reset_rows STORED false) - Q_PROPERTY(int columns READ get_columns WRITE set_columns RESET reset_columns STORED false) - Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false) - Q_PROPERTY(bool preserveAspect READ get_preserveAspect WRITE set_preserveAspect RESET reset_preserveAspect STORED false) - BR_PROPERTY(int, rows, -1) - BR_PROPERTY(int, columns, -1) - BR_PROPERTY(Method, method, Bilin) - BR_PROPERTY(bool, preserveAspect, false) - - void project(const Template &src, Template &dst) const - { - if (!preserveAspect) - resize(src, dst, Size((columns == -1) ? src.m().cols*rows/src.m().rows : columns, rows), 0, 0, method); - else { - float inRatio = (float) src.m().rows / src.m().cols; - float outRatio = (float) rows / columns; - dst = Mat::zeros(rows, columns, src.m().type()); - if (outRatio > inRatio) { - float heightAR = src.m().rows * inRatio / outRatio; - Mat buffer; - resize(src, buffer, Size(columns, heightAR), 0, 0, method); - buffer.copyTo(dst.m()(Rect(0, (rows - heightAR) / 2, columns, heightAR))); - } else { - float widthAR = src.m().cols / inRatio * outRatio; - Mat buffer; - resize(src, buffer, Size(widthAR, rows), 0, 0, method); - buffer.copyTo(dst.m()(Rect((columns - widthAR) / 2, 0, widthAR, rows))); - } - } - } -}; - -BR_REGISTER(Transform, ResizeTransform) - -/*! - * \ingroup transforms - * \brief Limit the size of the template - * \author Josh Klontz \cite jklontz - */ -class LimitSizeTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(int max READ get_max WRITE set_max RESET reset_max STORED false) - BR_PROPERTY(int, max, -1) - - void project(const Template &src, Template &dst) const - { - const Mat &m = src; - if (m.rows > m.cols) - if (m.rows > max) resize(m, dst, Size(std::max(1, m.cols * max / m.rows), max)); - else dst = m; - else - if (m.cols > max) resize(m, dst, Size(max, std::max(1, m.rows * max / m.cols))); - else dst = m; - } -}; - -BR_REGISTER(Transform, LimitSizeTransform) - -/*! - * \ingroup transforms - * \brief Enforce a multiple of \em n columns. - * \author Josh Klontz \cite jklontz - */ -class DivTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false) - BR_PROPERTY(int, n, 1) - - void project(const Template &src, Template &dst) const - { - dst = Mat(src, Rect(0,0,n*(src.m().cols/n),src.m().rows)); - } -}; - -BR_REGISTER(Transform, DivTransform) - -/*! - * \ingroup transforms - * \brief Crop out black borders - * \author Josh Klontz \cite jklontz - */ -class CropBlackTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - Mat gray; - OpenCVUtils::cvtGray(src, gray); - - int xStart = 0; - while (xStart < gray.cols) { - if (mean(gray.col(xStart))[0] >= 1) break; - xStart++; - } - - int xEnd = gray.cols - 1; - while (xEnd >= 0) { - if (mean(gray.col(xEnd))[0] >= 1) break; - xEnd--; - } - - int yStart = 0; - while (yStart < gray.rows) { - if (mean(gray.col(yStart))[0] >= 1) break; - yStart++; - } - - int yEnd = gray.rows - 1; - while (yEnd >= 0) { - if (mean(gray.col(yEnd))[0] >= 1) break; - yEnd--; - } - - dst = src.m()(Rect(xStart, yStart, xEnd-xStart, yEnd-yStart)); - } -}; - -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/cvt.cpp b/openbr/plugins/cvt.cpp deleted file mode 100644 index e247ab7..0000000 --- a/openbr/plugins/cvt.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include -#include -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup transforms - * \brief Colorspace conversion. - * \author Josh Klontz \cite jklontz - */ -class CvtTransform : public UntrainableTransform -{ - Q_OBJECT - Q_ENUMS(ColorSpace) - Q_PROPERTY(ColorSpace colorSpace READ get_colorSpace WRITE set_colorSpace RESET reset_colorSpace STORED false) - Q_PROPERTY(int channel READ get_channel WRITE set_channel RESET reset_channel STORED false) - -public: - enum ColorSpace { Gray = CV_BGR2GRAY, - RGBGray = CV_RGB2GRAY, - HLS = CV_BGR2HLS, - HSV = CV_BGR2HSV, - Lab = CV_BGR2Lab, - Luv = CV_BGR2Luv, - RGB = CV_BGR2RGB, - XYZ = CV_BGR2XYZ, - YCrCb = CV_BGR2YCrCb, - Color = CV_GRAY2BGR }; - -private: - BR_PROPERTY(ColorSpace, colorSpace, Gray) - BR_PROPERTY(int, channel, -1) - - void project(const Template &src, Template &dst) const - { - if (src.m().channels() > 1 || colorSpace == CV_GRAY2BGR) cvtColor(src, dst, colorSpace); - else dst = src; - - if (channel != -1) { - std::vector mv; - split(dst, mv); - dst = mv[channel % (int)mv.size()]; - } - } -}; - -BR_REGISTER(Transform, CvtTransform) - -/*! - * \ingroup transforms - * \brief Convert to floating point format. - * \author Josh Klontz \cite jklontz - */ -class CvtFloatTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - src.m().convertTo(dst, CV_32F); - } -}; - -BR_REGISTER(Transform, CvtFloatTransform) - -/*! - * \ingroup transforms - * \brief Convert to uchar format - * \author Josh Klontz \cite jklontz - */ -class CvtUCharTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - OpenCVUtils::cvtUChar(src, dst); - } -}; - -BR_REGISTER(Transform, CvtUCharTransform) - -/*! - * \ingroup transforms - * \brief Scales using the given factor - * \author Scott Klum \cite sklum - */ -class ScaleTransform : public UntrainableTransform -{ - Q_OBJECT - - Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) - BR_PROPERTY(float, scaleFactor, 1.) - - void project(const Template &src, Template &dst) const - { - resize(src, dst, Size(src.m().cols*scaleFactor,src.m().rows*scaleFactor)); - - QList rects = src.file.rects(); - for (int i=0; i points = src.file.points(); - for (int i=0; i mv; - split(src, mv); - foreach (const Mat &m, mv) - dst += m; - } -}; - -BR_REGISTER(Transform, SplitChannelsTransform) - -/*! - * \ingroup transforms - * \brief Enforce the matrix has a certain number of channels by adding or removing channels. - * \author Josh Klontz \cite jklontz - */ -class EnsureChannelsTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false) - BR_PROPERTY(int, n, 1) - - void project(const Template &src, Template &dst) const - { - if (src.m().channels() == n) { - dst = src; - } else { - std::vector mv; - split(src, mv); - - // Add extra channels - while ((int)mv.size() < n) { - for (int i=0; i n) - mv.pop_back(); - - merge(mv, dst); - } - } -}; - -BR_REGISTER(Transform, EnsureChannelsTransform) - -/*! - * \ingroup transforms - * \brief Drop the alpha channel (if exists). - * \author Austin Blanton \cite imaus10 - */ -class DiscardAlphaTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - if (src.m().channels() > 4 || src.m().channels() == 2) { - dst.file.fte = true; - return; - } - - dst = src; - if (src.m().channels() == 4) { - std::vector mv; - split(src, mv); - mv.pop_back(); - merge(mv, dst); - } - } -}; - -BR_REGISTER(Transform, DiscardAlphaTransform) - -/*! - * \ingroup transforms - * \brief Normalized RG color space. - * \author Josh Klontz \cite jklontz - */ -class RGTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - if (src.m().type() != CV_8UC3) - qFatal("Expected CV_8UC3 images."); - - const Mat &m = src.m(); - Mat R(m.size(), CV_8UC1); // R / (R+G+B) - Mat G(m.size(), CV_8UC1); // G / (R+G+B) - - for (int i=0; i(i,j); - const int b = v[0]; - const int g = v[1]; - const int r = v[2]; - const int sum = b + g + r; - if (sum > 0) { - R.at(i, j) = saturate_cast(255.0*r/(r+g+b)); - G.at(i, j) = saturate_cast(255.0*g/(r+g+b)); - } else { - R.at(i, j) = 0; - G.at(i, j) = 0; - } - } - - dst.append(R); - dst.append(G); - } -}; - -BR_REGISTER(Transform, RGTransform) - -/*! - * \ingroup transforms - * \brief dst = a*src+b - * \author Josh Klontz \cite jklontz - */ -class MAddTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(double a READ get_a WRITE set_a RESET reset_a STORED false) - Q_PROPERTY(double b READ get_b WRITE set_b RESET reset_b STORED false) - BR_PROPERTY(double, a, 1) - BR_PROPERTY(double, b, 0) - - void project(const Template &src, Template &dst) const - { - src.m().convertTo(dst.m(), src.m().depth(), a, b); - } -}; - -BR_REGISTER(Transform, MAddTransform) - -/*! - * \ingroup transforms - * \brief Computes the absolute value of each element. - * \author Josh Klontz \cite jklontz - */ -class AbsTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - dst = abs(src); - } -}; - -BR_REGISTER(Transform, AbsTransform) - -} // namespace br - -#include "cvt.moc" diff --git a/openbr/plugins/distance.cpp b/openbr/plugins/distance.cpp deleted file mode 100644 index 4fd7014..0000000 --- a/openbr/plugins/distance.cpp +++ /dev/null @@ -1,512 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include -#include -#include -#include -#include -#include "openbr_internal.h" - -#include "openbr/core/distance_sse.h" -#include "openbr/core/qtutils.h" -#include "openbr/core/opencvutils.h" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup distances - * \brief Standard distance metrics - * \author Josh Klontz \cite jklontz - */ -class DistDistance : public UntrainableDistance -{ - Q_OBJECT - Q_ENUMS(Metric) - Q_PROPERTY(Metric metric READ get_metric WRITE set_metric RESET reset_metric STORED false) - Q_PROPERTY(bool negLogPlusOne READ get_negLogPlusOne WRITE set_negLogPlusOne RESET reset_negLogPlusOne STORED false) - -public: - /*!< */ - enum Metric { Correlation, - ChiSquared, - Intersection, - Bhattacharyya, - INF, - L1, - L2, - Cosine, - Dot}; - -private: - BR_PROPERTY(Metric, metric, L2) - BR_PROPERTY(bool, negLogPlusOne, true) - - float compare(const Mat &a, const Mat &b) const - { - if ((a.size != b.size) || - (a.type() != b.type())) - return -std::numeric_limits::max(); - -// TODO: this max value is never returned based on the switch / default - float result = std::numeric_limits::max(); - switch (metric) { - case Correlation: - return compareHist(a, b, CV_COMP_CORREL); - case ChiSquared: - result = compareHist(a, b, CV_COMP_CHISQR); - break; - case Intersection: - result = compareHist(a, b, CV_COMP_INTERSECT); - break; - case Bhattacharyya: - result = compareHist(a, b, CV_COMP_BHATTACHARYYA); - break; - case INF: - result = norm(a, b, NORM_INF); - break; - case L1: - result = norm(a, b, NORM_L1); - break; - case L2: - result = norm(a, b, NORM_L2); - break; - case Cosine: - return cosine(a, b); - case Dot: - return a.dot(b); - default: - qFatal("Invalid metric"); - } - - if (result != result) - qFatal("NaN result."); - - return negLogPlusOne ? -log(result+1) : result; - } - - static float cosine(const Mat &a, const Mat &b) - { - float dot = 0; - float magA = 0; - float magB = 0; - - for (int row=0; row(row,col); - const float query = b.at(row,col); - dot += target * query; - magA += target * target; - magB += query * query; - } - } - - return dot / (sqrt(magA)*sqrt(magB)); - } -}; - -BR_REGISTER(Distance, DistDistance) - -/*! - * \ingroup distances - * \brief DistDistance wrapper. - * \author Josh Klontz \cite jklontz - */ -class DefaultDistance : public UntrainableDistance -{ - Q_OBJECT - Distance *distance; - - void init() - { - distance = Distance::make("Dist("+file.suffix()+")"); - } - - float compare(const cv::Mat &a, const cv::Mat &b) const - { - return distance->compare(a, b); - } -}; - -BR_REGISTER(Distance, DefaultDistance) - -/*! - * \ingroup distances - * \brief Distances in series. - * \author Josh Klontz \cite jklontz - * - * The templates are compared using each br::Distance in order. - * If the result of the comparison with any given distance is -FLOAT_MAX then this result is returned early. - * Otherwise the returned result is the value of comparing the templates using the last br::Distance. - */ -class PipeDistance : public Distance -{ - Q_OBJECT - Q_PROPERTY(QList distances READ get_distances WRITE set_distances RESET reset_distances) - BR_PROPERTY(QList, distances, QList()) - - void train(const TemplateList &data) - { - QFutureSynchronizer futures; - foreach (br::Distance *distance, distances) - futures.addFuture(QtConcurrent::run(distance, &Distance::train, data)); - futures.waitForFinished(); - } - - float compare(const Template &a, const Template &b) const - { - float result = -std::numeric_limits::max(); - foreach (br::Distance *distance, distances) { - result = distance->compare(a, b); - if (result == -std::numeric_limits::max()) - return result; - } - return result; - } -}; - -BR_REGISTER(Distance, PipeDistance) - -/*! - * \ingroup distances - * \brief Fuses similarity scores across multiple matrices of compared templates - * \author Scott Klum \cite sklum - * \note Operation: Mean, sum, min, max are supported. - */ -class FuseDistance : public Distance -{ - Q_OBJECT - Q_ENUMS(Operation) - Q_PROPERTY(QString description READ get_description WRITE set_description RESET reset_description STORED false) - Q_PROPERTY(Operation operation READ get_operation WRITE set_operation RESET reset_operation STORED false) - Q_PROPERTY(QList weights READ get_weights WRITE set_weights RESET reset_weights STORED false) - - QList distances; - -public: - /*!< */ - enum Operation {Mean, Sum, Max, Min}; - -private: - BR_PROPERTY(QString, description, "L2") - BR_PROPERTY(Operation, operation, Mean) - BR_PROPERTY(QList, weights, QList()) - - void train(const TemplateList &src) - { - // Partition the templates by matrix - QList split; - for (int i=0; i partitionedSrc = src.partition(split); - - while (distances.size() < partitionedSrc.size()) - distances.append(make(description)); - - // Train on each of the partitions - for (int i=0; itrain(partitionedSrc[i]); - } - - float compare(const Template &a, const Template &b) const - { - if (a.size() != b.size()) qFatal("Comparison size mismatch"); - - QList scores; - for (int i=0; icompare(Template(a.file, a[i]),Template(b.file, b[i]))); - } - - switch (operation) { - case Mean: - return std::accumulate(scores.begin(),scores.end(),0.0)/(float)scores.size(); - break; - case Sum: - return std::accumulate(scores.begin(),scores.end(),0.0); - break; - case Min: - return *std::min_element(scores.begin(),scores.end()); - break; - case Max: - return *std::max_element(scores.begin(),scores.end()); - break; - default: - qFatal("Invalid operation."); - } - return 0; - } - - void store(QDataStream &stream) const - { - stream << distances.size(); - foreach (Distance *distance, distances) - distance->store(stream); - } - - void load(QDataStream &stream) - { - int numDistances; - stream >> numDistances; - while (distances.size() < numDistances) - distances.append(make(description)); - foreach (Distance *distance, distances) - distance->load(stream); - } -}; - -BR_REGISTER(Distance, FuseDistance) - -/*! - * \ingroup distances - * \brief Fast 8-bit L1 distance - * \author Josh Klontz \cite jklontz - */ -class ByteL1Distance : public UntrainableDistance -{ - Q_OBJECT - - float compare(const unsigned char *a, const unsigned char *b, size_t size) const - { - return l1(a, b, size); - } -}; - -BR_REGISTER(Distance, ByteL1Distance) - -/*! - * \ingroup distances - * \brief Fast 4-bit L1 distance - * \author Josh Klontz \cite jklontz - */ -class HalfByteL1Distance : public UntrainableDistance -{ - Q_OBJECT - - float compare(const Mat &a, const Mat &b) const - { - return packed_l1(a.data, b.data, a.total()); - } -}; - -BR_REGISTER(Distance, HalfByteL1Distance) - -/*! - * \ingroup distances - * \brief Returns -log(distance(a,b)+1) - * \author Josh Klontz \cite jklontz - */ -class NegativeLogPlusOneDistance : public UntrainableDistance -{ - Q_OBJECT - Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) - BR_PROPERTY(br::Distance*, distance, NULL) - - void train(const TemplateList &src) - { - distance->train(src); - } - - float compare(const Template &a, const Template &b) const - { - return -log(distance->compare(a,b)+1); - } - - void store(QDataStream &stream) const - { - distance->store(stream); - } - - void load(QDataStream &stream) - { - distance->load(stream); - } -}; - -BR_REGISTER(Distance, NegativeLogPlusOneDistance) - -/*! - * \ingroup distances - * \brief Returns \c true if the templates are identical, \c false otherwise. - * \author Josh Klontz \cite jklontz - */ -class IdenticalDistance : public UntrainableDistance -{ - Q_OBJECT - - float compare(const Mat &a, const Mat &b) const - { - const size_t size = a.total() * a.elemSize(); - if (size != b.total() * b.elemSize()) return 0; - for (size_t i=0; i scoreHash; - mutable QMutex mutex; - - float compare(const Template &target, const Template &query) const - { - float currentScore = distance->compare(target, query); - - QMutexLocker mutexLocker(&mutex); - return scoreHash[target.file.name] = (1.0- alpha) * scoreHash[target.file.name] + alpha * currentScore; - } -}; - -BR_REGISTER(Distance, OnlineDistance) - -/*! - * \ingroup distances - * \brief Attenuation function based distance from attributes - * \author Scott Klum \cite sklum - */ -class AttributeDistance : public UntrainableDistance -{ - Q_OBJECT - Q_PROPERTY(QString attribute READ get_attribute WRITE set_attribute RESET reset_attribute STORED false) - BR_PROPERTY(QString, attribute, QString()) - - float compare(const Template &target, const Template &query) const - { - float queryValue = query.file.get(attribute); - float targetValue = target.file.get(attribute); - - // TODO: Set this magic number to something meaningful - float stddev = 1; - - if (queryValue == targetValue) return 1; - else return 1/(stddev*sqrt(2*CV_PI))*exp(-0.5*pow((targetValue-queryValue)/stddev, 2)); - } -}; - -BR_REGISTER(Distance, AttributeDistance) - -/*! - * \ingroup distances - * \brief Sum match scores across multiple distances - * \author Scott Klum \cite sklum - */ -class SumDistance : public UntrainableDistance -{ - Q_OBJECT - Q_PROPERTY(QList distances READ get_distances WRITE set_distances RESET reset_distances) - BR_PROPERTY(QList, distances, QList()) - - void train(const TemplateList &data) - { - QFutureSynchronizer futures; - foreach (br::Distance *distance, distances) - futures.addFuture(QtConcurrent::run(distance, &Distance::train, data)); - futures.waitForFinished(); - } - - float compare(const Template &target, const Template &query) const - { - float result = 0; - - foreach (br::Distance *distance, distances) { - result += distance->compare(target, query); - - if (result == -std::numeric_limits::max()) - return result; - } - - return result; - } -}; - -BR_REGISTER(Distance, SumDistance) - -/*! - * \ingroup transforms - * \brief Compare each template to a fixed gallery (with name = galleryName), using the specified distance. - * dst will contain a 1 by n vector of scores. - * \author Charles Otto \cite caotto - */ -class GalleryCompareTransform : public Transform -{ - Q_OBJECT - Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED true) - Q_PROPERTY(QString galleryName READ get_galleryName WRITE set_galleryName RESET reset_galleryName STORED false) - BR_PROPERTY(br::Distance*, distance, NULL) - BR_PROPERTY(QString, galleryName, "") - - TemplateList gallery; - - void project(const Template &src, Template &dst) const - { - dst = src; - if (gallery.isEmpty()) - return; - - QList line = distance->compare(gallery, src); - dst.m() = OpenCVUtils::toMat(line, 1); - } - - void init() - { - if (!galleryName.isEmpty()) - gallery = TemplateList::fromGallery(galleryName); - } - - void train(const TemplateList &data) - { - gallery = data; - } - - void store(QDataStream &stream) const - { - br::Object::store(stream); - stream << gallery; - } - - void load(QDataStream &stream) - { - br::Object::load(stream); - stream >> gallery; - } - -public: - GalleryCompareTransform() : Transform(false, true) {} -}; - -BR_REGISTER(Transform, GalleryCompareTransform) - - -} // namespace br -#include "distance.moc" diff --git a/openbr/plugins/distance/L1.cpp b/openbr/plugins/distance/L1.cpp new file mode 100644 index 0000000..cd11cec --- /dev/null +++ b/openbr/plugins/distance/L1.cpp @@ -0,0 +1,46 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief L1 distance computed using eigen. + * \author Josh Klontz \cite jklontz + */ +class L1Distance : public UntrainableDistance +{ + Q_OBJECT + + float compare(const cv::Mat &a, const cv::Mat &b) const + { + const int size = a.rows * a.cols; + Eigen::Map aMap((float*)a.data, size); + Eigen::Map bMap((float*)b.data, size); + return (aMap-bMap).cwiseAbs().sum(); + } +}; + +BR_REGISTER(Distance, L1Distance) + +} // namespace br + +#include "distance/L1.moc" diff --git a/openbr/plugins/distance/L2.cpp b/openbr/plugins/distance/L2.cpp new file mode 100644 index 0000000..a966c20 --- /dev/null +++ b/openbr/plugins/distance/L2.cpp @@ -0,0 +1,46 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief L2 distance computed using eigen. + * \author Josh Klontz \cite jklontz + */ +class L2Distance : public UntrainableDistance +{ + Q_OBJECT + + float compare(const cv::Mat &a, const cv::Mat &b) const + { + const int size = a.rows * a.cols; + Eigen::Map aMap((float*)a.data, size); + Eigen::Map bMap((float*)b.data, size); + return (aMap-bMap).squaredNorm(); + } +}; + +BR_REGISTER(Distance, L2Distance) + +} // namespace br + +#include "distance/L2.moc" diff --git a/openbr/plugins/distance/attribute.cpp b/openbr/plugins/distance/attribute.cpp new file mode 100644 index 0000000..29f2610 --- /dev/null +++ b/openbr/plugins/distance/attribute.cpp @@ -0,0 +1,50 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Attenuation function based distance from attributes + * \author Scott Klum \cite sklum + */ +class AttributeDistance : public UntrainableDistance +{ + Q_OBJECT + Q_PROPERTY(QString attribute READ get_attribute WRITE set_attribute RESET reset_attribute STORED false) + BR_PROPERTY(QString, attribute, QString()) + + float compare(const Template &target, const Template &query) const + { + float queryValue = query.file.get(attribute); + float targetValue = target.file.get(attribute); + + // TODO: Set this magic number to something meaningful + float stddev = 1; + + if (queryValue == targetValue) return 1; + else return 1/(stddev*sqrt(2*CV_PI))*exp(-0.5*pow((targetValue-queryValue)/stddev, 2)); + } +}; + +BR_REGISTER(Distance, AttributeDistance) + +} // namespace br + +#include "distance/attribute.moc" diff --git a/openbr/plugins/distance/bayesianquantization.cpp b/openbr/plugins/distance/bayesianquantization.cpp new file mode 100644 index 0000000..ea9836c --- /dev/null +++ b/openbr/plugins/distance/bayesianquantization.cpp @@ -0,0 +1,104 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup distances + * \brief Bayesian quantization distance + * \author Josh Klontz \cite jklontz + */ +class BayesianQuantizationDistance : public Distance +{ + Q_OBJECT + + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + BR_PROPERTY(QString, inputVariable, "Label") + + QVector loglikelihoods; + + static void computeLogLikelihood(const Mat &data, const QList &labels, float *loglikelihood) + { + const QList vals = OpenCVUtils::matrixToVector(data); + if (vals.size() != labels.size()) + qFatal("Logic error."); + + QVector genuines(256, 0), impostors(256,0); + for (int i=0; i 1) || (src.first().m().type() != CV_8UC1)) + qFatal("Expected sigle matrix templates of type CV_8UC1!"); + + const Mat data = OpenCVUtils::toMat(src.data()); + const QList templateLabels = src.indexProperty(inputVariable); + loglikelihoods = QVector(data.cols*256, 0); + + QFutureSynchronizer futures; + for (int i=0; i> loglikelihoods; + } +}; + +BR_REGISTER(Distance, BayesianQuantizationDistance) + +} // namespace br + +#include "distance/bayesianquantization.moc" diff --git a/openbr/plugins/distance/byteL1.cpp b/openbr/plugins/distance/byteL1.cpp new file mode 100644 index 0000000..36a5970 --- /dev/null +++ b/openbr/plugins/distance/byteL1.cpp @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Fast 8-bit L1 distance + * \author Josh Klontz \cite jklontz + */ +class ByteL1Distance : public UntrainableDistance +{ + Q_OBJECT + + float compare(const unsigned char *a, const unsigned char *b, size_t size) const + { + return l1(a, b, size); + } +}; + +BR_REGISTER(Distance, ByteL1Distance) + +} // namespace br + +#include "distance/byteL1.moc" diff --git a/openbr/plugins/distance/crossvalidate.cpp b/openbr/plugins/distance/crossvalidate.cpp new file mode 100644 index 0000000..ef69a88 --- /dev/null +++ b/openbr/plugins/distance/crossvalidate.cpp @@ -0,0 +1,44 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Cross validate a distance metric. + * \author Josh Klontz \cite jklontz + */ +class CrossValidateDistance : public UntrainableDistance +{ + Q_OBJECT + + float compare(const Template &a, const Template &b) const + { + static const QString key("Partition"); // More efficient to preallocate this + const int partitionA = a.file.get(key, 0); + const int partitionB = b.file.get(key, 0); + return (partitionA != partitionB) ? -std::numeric_limits::max() : 0; + } +}; + +BR_REGISTER(Distance, CrossValidateDistance) + +} // namespace br + +#include "distance/crossvalidate.moc" diff --git a/openbr/plugins/distance/default.cpp b/openbr/plugins/distance/default.cpp new file mode 100644 index 0000000..e4bf121 --- /dev/null +++ b/openbr/plugins/distance/default.cpp @@ -0,0 +1,47 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief DistDistance wrapper. + * \author Josh Klontz \cite jklontz + */ +class DefaultDistance : public UntrainableDistance +{ + Q_OBJECT + Distance *distance; + + void init() + { + distance = Distance::make("Dist("+file.suffix()+")"); + } + + float compare(const cv::Mat &a, const cv::Mat &b) const + { + return distance->compare(a, b); + } +}; + +BR_REGISTER(Distance, DefaultDistance) + +} // namespace br + +#include "distance/default.moc" diff --git a/openbr/plugins/distance/dist.cpp b/openbr/plugins/distance/dist.cpp new file mode 100644 index 0000000..f98196e --- /dev/null +++ b/openbr/plugins/distance/dist.cpp @@ -0,0 +1,120 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup distances + * \brief Standard distance metrics + * \author Josh Klontz \cite jklontz + */ +class DistDistance : public UntrainableDistance +{ + Q_OBJECT + Q_ENUMS(Metric) + Q_PROPERTY(Metric metric READ get_metric WRITE set_metric RESET reset_metric STORED false) + Q_PROPERTY(bool negLogPlusOne READ get_negLogPlusOne WRITE set_negLogPlusOne RESET reset_negLogPlusOne STORED false) + +public: + /*!< */ + enum Metric { Correlation, + ChiSquared, + Intersection, + Bhattacharyya, + INF, + L1, + L2, + Cosine, + Dot}; + +private: + BR_PROPERTY(Metric, metric, L2) + BR_PROPERTY(bool, negLogPlusOne, true) + + float compare(const Mat &a, const Mat &b) const + { + if ((a.size != b.size) || + (a.type() != b.type())) + return -std::numeric_limits::max(); + +// TODO: this max value is never returned based on the switch / default + float result = std::numeric_limits::max(); + switch (metric) { + case Correlation: + return compareHist(a, b, CV_COMP_CORREL); + case ChiSquared: + result = compareHist(a, b, CV_COMP_CHISQR); + break; + case Intersection: + result = compareHist(a, b, CV_COMP_INTERSECT); + break; + case Bhattacharyya: + result = compareHist(a, b, CV_COMP_BHATTACHARYYA); + break; + case INF: + result = norm(a, b, NORM_INF); + break; + case L1: + result = norm(a, b, NORM_L1); + break; + case L2: + result = norm(a, b, NORM_L2); + break; + case Cosine: + return cosine(a, b); + case Dot: + return a.dot(b); + default: + qFatal("Invalid metric"); + } + + if (result != result) + qFatal("NaN result."); + + return negLogPlusOne ? -log(result+1) : result; + } + + static float cosine(const Mat &a, const Mat &b) + { + float dot = 0; + float magA = 0; + float magB = 0; + + for (int row=0; row(row,col); + const float query = b.at(row,col); + dot += target * query; + magA += target * target; + magB += query * query; + } + } + + return dot / (sqrt(magA)*sqrt(magB)); + } +}; + +BR_REGISTER(Distance, DistDistance) + +} // namespace br + +#include "distance/dist.moc" diff --git a/openbr/plugins/distance/filter.cpp b/openbr/plugins/distance/filter.cpp new file mode 100644 index 0000000..3570289 --- /dev/null +++ b/openbr/plugins/distance/filter.cpp @@ -0,0 +1,55 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Checks target metadata against filters. + * \author Josh Klontz \cite jklontz + */ +class FilterDistance : public UntrainableDistance +{ + Q_OBJECT + + float compare(const Template &a, const Template &b) const + { + (void) b; // Query template isn't checked + foreach (const QString &key, Globals->filters.keys()) { + bool keep = false; + const QString metadata = a.file.get(key, ""); + if (Globals->filters[key].isEmpty()) continue; + if (metadata.isEmpty()) return -std::numeric_limits::max(); + foreach (const QString &value, Globals->filters[key]) { + if (metadata == value) { + keep = true; + break; + } + } + if (!keep) return -std::numeric_limits::max(); + } + return 0; + } +}; + +BR_REGISTER(Distance, FilterDistance) + +} // namespace br + +#include "distance/filter.moc" diff --git a/openbr/plugins/distance/fuse.cpp b/openbr/plugins/distance/fuse.cpp new file mode 100644 index 0000000..c02b411 --- /dev/null +++ b/openbr/plugins/distance/fuse.cpp @@ -0,0 +1,117 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Fuses similarity scores across multiple matrices of compared templates + * \author Scott Klum \cite sklum + * \note Operation: Mean, sum, min, max are supported. + */ +class FuseDistance : public Distance +{ + Q_OBJECT + Q_ENUMS(Operation) + Q_PROPERTY(QString description READ get_description WRITE set_description RESET reset_description STORED false) + Q_PROPERTY(Operation operation READ get_operation WRITE set_operation RESET reset_operation STORED false) + Q_PROPERTY(QList weights READ get_weights WRITE set_weights RESET reset_weights STORED false) + + QList distances; + +public: + /*!< */ + enum Operation {Mean, Sum, Max, Min}; + +private: + BR_PROPERTY(QString, description, "L2") + BR_PROPERTY(Operation, operation, Mean) + BR_PROPERTY(QList, weights, QList()) + + void train(const TemplateList &src) + { + // Partition the templates by matrix + QList split; + for (int i=0; i partitionedSrc = src.partition(split); + + while (distances.size() < partitionedSrc.size()) + distances.append(make(description)); + + // Train on each of the partitions + for (int i=0; itrain(partitionedSrc[i]); + } + + float compare(const Template &a, const Template &b) const + { + if (a.size() != b.size()) qFatal("Comparison size mismatch"); + + QList scores; + for (int i=0; icompare(Template(a.file, a[i]),Template(b.file, b[i]))); + } + + switch (operation) { + case Mean: + return std::accumulate(scores.begin(),scores.end(),0.0)/(float)scores.size(); + break; + case Sum: + return std::accumulate(scores.begin(),scores.end(),0.0); + break; + case Min: + return *std::min_element(scores.begin(),scores.end()); + break; + case Max: + return *std::max_element(scores.begin(),scores.end()); + break; + default: + qFatal("Invalid operation."); + } + return 0; + } + + void store(QDataStream &stream) const + { + stream << distances.size(); + foreach (Distance *distance, distances) + distance->store(stream); + } + + void load(QDataStream &stream) + { + int numDistances; + stream >> numDistances; + while (distances.size() < numDistances) + distances.append(make(description)); + foreach (Distance *distance, distances) + distance->load(stream); + } +}; + +BR_REGISTER(Distance, FuseDistance) + +} // namespace br + +#include "distance/fuse.moc" diff --git a/openbr/plugins/distance/halfbyteL1.cpp b/openbr/plugins/distance/halfbyteL1.cpp new file mode 100644 index 0000000..d70c0cf --- /dev/null +++ b/openbr/plugins/distance/halfbyteL1.cpp @@ -0,0 +1,45 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup distances + * \brief Fast 4-bit L1 distance + * \author Josh Klontz \cite jklontz + */ +class HalfByteL1Distance : public UntrainableDistance +{ + Q_OBJECT + + float compare(const Mat &a, const Mat &b) const + { + return packed_l1(a.data, b.data, a.total()); + } +}; + +BR_REGISTER(Distance, HalfByteL1Distance) + + +} // namespace br + +#include "distance/halfbyteL1.moc" diff --git a/openbr/plugins/distance/heatmap.cpp b/openbr/plugins/distance/heatmap.cpp new file mode 100644 index 0000000..d25cf4b --- /dev/null +++ b/openbr/plugins/distance/heatmap.cpp @@ -0,0 +1,100 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief 1v1 heat map comparison + * \author Scott Klum \cite sklum + */ +class HeatMapDistance : public Distance +{ + Q_OBJECT + Q_PROPERTY(QString description READ get_description WRITE set_description RESET reset_description STORED false) + Q_PROPERTY(int step READ get_step WRITE set_step RESET reset_step STORED false) + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + BR_PROPERTY(QString, description, "IdenticalDistance") + BR_PROPERTY(int, step, 1) + BR_PROPERTY(QString, inputVariable, "Label") + + QList distances; + + void train(const TemplateList &src) + { + QList patches; + + // Split src into list of TemplateLists of corresponding patches across all Templates + for (int i=0; itrain(patches[i]); + } + + float compare(const cv::Mat &target, const cv::Mat &query) const + { + (void) target; + (void) query; + qFatal("Heatmap Distance not compatible with Template to Template comparison."); + + return 0; + } + + void compare(const TemplateList &target, const TemplateList &query, Output *output) const + { + for (int i=0; isetRelative(distances[j]->compare(target[i][j],query[i][j]), j, 0); + } + } + + void store(QDataStream &stream) const + { + stream << distances.size(); + foreach (Distance *distance, distances) + distance->store(stream); + } + + void load(QDataStream &stream) + { + int numDistances; + stream >> numDistances; + while (distances.size() < numDistances) + distances.append(make(description)); + foreach (Distance *distance, distances) + distance->load(stream); + } +}; + +BR_REGISTER(Distance, HeatMapDistance) + +} // namespace br + +#include "distance/heatmap.moc" diff --git a/openbr/plugins/distance/identical.cpp b/openbr/plugins/distance/identical.cpp new file mode 100644 index 0000000..49cb36e --- /dev/null +++ b/openbr/plugins/distance/identical.cpp @@ -0,0 +1,47 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup distances + * \brief Returns \c true if the templates are identical, \c false otherwise. + * \author Josh Klontz \cite jklontz + */ +class IdenticalDistance : public UntrainableDistance +{ + Q_OBJECT + + float compare(const Mat &a, const Mat &b) const + { + const size_t size = a.total() * a.elemSize(); + if (size != b.total() * b.elemSize()) return 0; + for (size_t i=0; i + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Wraps OpenCV Key Point Matcher + * \author Josh Klontz \cite jklontz + */ +class KeyPointMatcherDistance : public UntrainableDistance +{ + Q_OBJECT + Q_PROPERTY(QString matcher READ get_matcher WRITE set_matcher RESET reset_matcher STORED false) + Q_PROPERTY(float maxRatio READ get_maxRatio WRITE set_maxRatio RESET reset_maxRatio STORED false) + BR_PROPERTY(QString, matcher, "BruteForce") + BR_PROPERTY(float, maxRatio, 0.8) + + Ptr descriptorMatcher; + + void init() + { + descriptorMatcher = DescriptorMatcher::create(matcher.toStdString()); + if (descriptorMatcher.empty()) + qFatal("Failed to create DescriptorMatcher: %s", qPrintable(matcher)); + } + + float compare(const Mat &a, const Mat &b) const + { + if ((a.rows < 2) || (b.rows < 2)) return 0; + + std::vector< std::vector > matches; + if (a.rows < b.rows) descriptorMatcher->knnMatch(a, b, matches, 2); + else descriptorMatcher->knnMatch(b, a, matches, 2); + + QList distances; + foreach (const std::vector &match, matches) { + if (match[0].distance / match[1].distance > maxRatio) continue; + distances.append(match[0].distance); + } + qSort(distances); + + float similarity = 0; + for (int i=0; i + +#include +#include + +namespace br +{ + +float KDEPointer(const QList *scores, double x, double h) +{ + return Common::KernelDensityEstimation(*scores, x, h); +} + +/* Kernel Density Estimator */ +struct KDE +{ + float min, max; + double mean, stddev; + QList bins; + + KDE() : min(0), max(1), mean(0), stddev(1) {} + + KDE(const QList &scores, bool trainKDE) + { + Common::MinMax(scores, &min, &max); + Common::MeanStdDev(scores, &mean, &stddev); + + if (!trainKDE) + return; + + double h = Common::KernelDensityBandwidth(scores); + const int size = 255; + bins.reserve(size); + + QFutureSynchronizer futures; + + for (int i=0; i < size; i++) + futures.addFuture(QtConcurrent::run(KDEPointer, &scores, min + (max-min)*i/(size-1), h)); + futures.waitForFinished(); + + foreach(const QFuture & future, futures.futures()) + bins.append(future.result()); + } + + float operator()(float score, bool gaussian = true) const + { + if (gaussian) return 1/(stddev*sqrt(2*CV_PI))*exp(-0.5*pow((score-mean)/stddev, 2)); + if (bins.empty()) + return -std::numeric_limits::max(); + + if (score <= min) return bins.first(); + if (score >= max) return bins.last(); + const float x = (score-min)/(max-min)*bins.size(); + const float y1 = bins[floor(x)]; + const float y2 = bins[ceil(x)]; + return y1 + (y2-y1)*(x-floor(x)); + } +}; + +QDataStream &operator<<(QDataStream &stream, const KDE &kde) +{ + return stream << kde.min << kde.max << kde.mean << kde.stddev << kde.bins; +} + +QDataStream &operator>>(QDataStream &stream, KDE &kde) +{ + return stream >> kde.min >> kde.max >> kde.mean >> kde.stddev >> kde.bins; +} + +/* Match Probability */ +struct MP +{ + KDE genuine, impostor; + MP() {} + MP(const QList &genuineScores, const QList &impostorScores, bool trainKDE) + : genuine(genuineScores, trainKDE), impostor(impostorScores, trainKDE) {} + float operator()(float score, bool gaussian = true) const + { + const float g = genuine(score, gaussian); + const float s = g / (impostor(score, gaussian) + g); + return s; + } +}; + +QDataStream &operator<<(QDataStream &stream, const MP &nmp) +{ + return stream << nmp.genuine << nmp.impostor; +} + +QDataStream &operator>>(QDataStream &stream, MP &nmp) +{ + return stream >> nmp.genuine >> nmp.impostor; +} + +/*! + * \ingroup distances + * \brief Match Probability \cite klare12 + * \author Josh Klontz \cite jklontz + */ +class MatchProbabilityDistance : public Distance +{ + Q_OBJECT + Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) + Q_PROPERTY(bool gaussian READ get_gaussian WRITE set_gaussian RESET reset_gaussian STORED false) + Q_PROPERTY(bool crossModality READ get_crossModality WRITE set_crossModality RESET reset_crossModality STORED false) + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + + MP mp; + + void train(const TemplateList &src) + { + distance->train(src); + + const QList labels = src.indexProperty(inputVariable); + QScopedPointer matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size()))); + distance->compare(src, src, matrixOutput.data()); + + QList genuineScores, impostorScores; + genuineScores.reserve(labels.size()); + impostorScores.reserve(labels.size()*labels.size()); + for (int i=0; idata.at(i, j); + if (score == -std::numeric_limits::max()) continue; + if (crossModality && src[i].file.get("MODALITY") == src[j].file.get("MODALITY")) continue; + if (labels[i] == labels[j]) genuineScores.append(score); + else impostorScores.append(score); + } + } + + mp = MP(genuineScores, impostorScores, !gaussian); + } + + float compare(const Template &target, const Template &query) const + { + return normalize(distance->compare(target, query)); + } + + float compare(const cv::Mat &target, const cv::Mat &query) const + { + return normalize(distance->compare(target, query)); + } + + float compare(const uchar *a, const uchar *b, size_t size) const + { + return normalize(distance->compare(a, b, size)); + } + + float normalize(float score) const + { + if (score == -std::numeric_limits::max()) return score; + if (!Globals->scoreNormalization) return -log(score+1); + return mp(score, gaussian); + } + + void store(QDataStream &stream) const + { + distance->store(stream); + stream << mp; + } + + void load(QDataStream &stream) + { + distance->load(stream); + stream >> mp; + } + +protected: + BR_PROPERTY(br::Distance*, distance, make("Dist(L2)")) + BR_PROPERTY(bool, gaussian, true) + BR_PROPERTY(bool, crossModality, false) + BR_PROPERTY(QString, inputVariable, "Label") +}; + +BR_REGISTER(Distance, MatchProbabilityDistance) + +} // namespace br + +#include "distance/matchprobability.moc" diff --git a/openbr/plugins/distance/metadata.cpp b/openbr/plugins/distance/metadata.cpp new file mode 100644 index 0000000..11660f7 --- /dev/null +++ b/openbr/plugins/distance/metadata.cpp @@ -0,0 +1,76 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Checks target metadata against query metadata. + * \author Scott Klum \cite sklum + */ +class MetadataDistance : public UntrainableDistance +{ + Q_OBJECT + + Q_PROPERTY(QStringList filters READ get_filters WRITE set_filters RESET reset_filters STORED false) + BR_PROPERTY(QStringList, filters, QStringList()) + + float compare(const Template &a, const Template &b) const + { + foreach (const QString &key, filters) { + QString aValue = a.file.get(key, QString()); + QString bValue = b.file.get(key, QString()); + + // The query value may be a range. Let's check. + if (bValue.isEmpty()) bValue = QtUtils::toString(b.file.get(key, QPointF())); + + if (aValue.isEmpty() || bValue.isEmpty()) continue; + + bool keep = false; + bool ok; + + QPointF range = QtUtils::toPoint(bValue,&ok); + + if (ok) /* Range */ { + int value = range.x(); + int upperBound = range.y(); + + while (value <= upperBound) { + if (aValue == QString::number(value)) { + keep = true; + break; + } + value++; + } + } + else if (aValue == bValue) keep = true; + + if (!keep) return -std::numeric_limits::max(); + } + return 0; + } +}; + + +BR_REGISTER(Distance, MetadataDistance) + +} // namespace br + +#include "distance/metadata.moc" diff --git a/openbr/plugins/distance/neglogplusone.cpp b/openbr/plugins/distance/neglogplusone.cpp new file mode 100644 index 0000000..ae92db2 --- /dev/null +++ b/openbr/plugins/distance/neglogplusone.cpp @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Returns -log(distance(a,b)+1) + * \author Josh Klontz \cite jklontz + */ +class NegativeLogPlusOneDistance : public UntrainableDistance +{ + Q_OBJECT + Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) + BR_PROPERTY(br::Distance*, distance, NULL) + + void train(const TemplateList &src) + { + distance->train(src); + } + + float compare(const Template &a, const Template &b) const + { + return -log(distance->compare(a,b)+1); + } + + void store(QDataStream &stream) const + { + distance->store(stream); + } + + void load(QDataStream &stream) + { + distance->load(stream); + } +}; + +BR_REGISTER(Distance, NegativeLogPlusOneDistance) + +} // namespace br + +#include "distance/neglogplusone.moc" diff --git a/openbr/plugins/distance/online.cpp b/openbr/plugins/distance/online.cpp new file mode 100644 index 0000000..e683c0a --- /dev/null +++ b/openbr/plugins/distance/online.cpp @@ -0,0 +1,51 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Online distance metric to attenuate match scores across multiple frames + * \author Brendan klare \cite bklare + */ +class OnlineDistance : public UntrainableDistance +{ + Q_OBJECT + Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) + Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false) + BR_PROPERTY(br::Distance*, distance, NULL) + BR_PROPERTY(float, alpha, 0.1f) + + mutable QHash scoreHash; + mutable QMutex mutex; + + float compare(const Template &target, const Template &query) const + { + float currentScore = distance->compare(target, query); + + QMutexLocker mutexLocker(&mutex); + return scoreHash[target.file.name] = (1.0- alpha) * scoreHash[target.file.name] + alpha * currentScore; + } +}; + +BR_REGISTER(Distance, OnlineDistance) + +} // namespace br + +#include "distance/online.moc" diff --git a/openbr/plugins/distance/pipe.cpp b/openbr/plugins/distance/pipe.cpp new file mode 100644 index 0000000..523868f --- /dev/null +++ b/openbr/plugins/distance/pipe.cpp @@ -0,0 +1,63 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Distances in series. + * \author Josh Klontz \cite jklontz + * + * The templates are compared using each br::Distance in order. + * If the result of the comparison with any given distance is -FLOAT_MAX then this result is returned early. + * Otherwise the returned result is the value of comparing the templates using the last br::Distance. + */ +class PipeDistance : public Distance +{ + Q_OBJECT + Q_PROPERTY(QList distances READ get_distances WRITE set_distances RESET reset_distances) + BR_PROPERTY(QList, distances, QList()) + + void train(const TemplateList &data) + { + QFutureSynchronizer futures; + foreach (br::Distance *distance, distances) + futures.addFuture(QtConcurrent::run(distance, &Distance::train, data)); + futures.waitForFinished(); + } + + float compare(const Template &a, const Template &b) const + { + float result = -std::numeric_limits::max(); + foreach (br::Distance *distance, distances) { + result = distance->compare(a, b); + if (result == -std::numeric_limits::max()) + return result; + } + return result; + } +}; + +BR_REGISTER(Distance, PipeDistance) + +} // namespace br + +#include "distance/pipe.moc" diff --git a/openbr/plugins/distance/reject.cpp b/openbr/plugins/distance/reject.cpp new file mode 100644 index 0000000..c183513 --- /dev/null +++ b/openbr/plugins/distance/reject.cpp @@ -0,0 +1,54 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Sets distance to -FLOAT_MAX if a target template has/doesn't have a key. + * \author Scott Klum \cite sklum + */ +class RejectDistance : public UntrainableDistance +{ + Q_OBJECT + + Q_PROPERTY(QStringList keys READ get_keys WRITE set_keys RESET reset_keys STORED false) + BR_PROPERTY(QStringList, keys, QStringList()) + Q_PROPERTY(bool rejectIfContains READ get_rejectIfContains WRITE set_rejectIfContains RESET reset_rejectIfContains STORED false) + BR_PROPERTY(bool, rejectIfContains, false) + + float compare(const Template &a, const Template &b) const + { + // We don't look at the query + (void) b; + + foreach (const QString &key, keys) + if ((rejectIfContains && a.file.contains(key)) || (!rejectIfContains && !a.file.contains(key))) + return -std::numeric_limits::max(); + + return 0; + } +}; + + +BR_REGISTER(Distance, RejectDistance) + +} // namespace br + +#include "distance/reject.moc" diff --git a/openbr/plugins/distance/sum.cpp b/openbr/plugins/distance/sum.cpp new file mode 100644 index 0000000..07a9853 --- /dev/null +++ b/openbr/plugins/distance/sum.cpp @@ -0,0 +1,62 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Sum match scores across multiple distances + * \author Scott Klum \cite sklum + */ +class SumDistance : public UntrainableDistance +{ + Q_OBJECT + Q_PROPERTY(QList distances READ get_distances WRITE set_distances RESET reset_distances) + BR_PROPERTY(QList, distances, QList()) + + void train(const TemplateList &data) + { + QFutureSynchronizer futures; + foreach (br::Distance *distance, distances) + futures.addFuture(QtConcurrent::run(distance, &Distance::train, data)); + futures.waitForFinished(); + } + + float compare(const Template &target, const Template &query) const + { + float result = 0; + + foreach (br::Distance *distance, distances) { + result += distance->compare(target, query); + + if (result == -std::numeric_limits::max()) + return result; + } + + return result; + } +}; + +BR_REGISTER(Distance, SumDistance) + +} // namespace br + +#include "distance/sum.moc" diff --git a/openbr/plugins/distance/turk.cpp b/openbr/plugins/distance/turk.cpp new file mode 100644 index 0000000..def8326 --- /dev/null +++ b/openbr/plugins/distance/turk.cpp @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Unmaps Turk HITs to be compared against query mats + * \author Scott Klum \cite sklum + */ +class TurkDistance : public UntrainableDistance +{ + Q_OBJECT + Q_PROPERTY(QString key READ get_key WRITE set_key RESET reset_key) + Q_PROPERTY(QStringList values READ get_values WRITE set_values RESET reset_values STORED false) + BR_PROPERTY(QString, key, QString()) + BR_PROPERTY(QStringList, values, QStringList()) + + bool targetHuman; + bool queryMachine; + + void init() + { + targetHuman = Globals->property("TurkTargetHuman").toBool(); + queryMachine = Globals->property("TurkQueryMachine").toBool(); + } + + cv::Mat getValues(const Template &t) const + { + QList result; + foreach (const QString &value, values) + result.append(t.file.get(key + "_" + value)); + return OpenCVUtils::toMat(result, 1); + } + + float compare(const Template &target, const Template &query) const + { + const cv::Mat a = targetHuman ? getValues(target) : target.m(); + const cv::Mat b = queryMachine ? query.m() : getValues(query); + return -norm(a, b, cv::NORM_L1); + } +}; + +BR_REGISTER(Distance, TurkDistance) + +} // namespace br + +#include "distance/turk.moc" diff --git a/openbr/plugins/distance/unit.cpp b/openbr/plugins/distance/unit.cpp new file mode 100644 index 0000000..f8f4b54 --- /dev/null +++ b/openbr/plugins/distance/unit.cpp @@ -0,0 +1,87 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup distances + * \brief Linear normalizes of a distance so the mean impostor score is 0 and the mean genuine score is 1. + * \author Josh Klontz \cite jklontz + */ +class UnitDistance : public Distance +{ + Q_OBJECT + Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance) + Q_PROPERTY(float a READ get_a WRITE set_a RESET reset_a) + Q_PROPERTY(float b READ get_b WRITE set_b RESET reset_b) + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + BR_PROPERTY(br::Distance*, distance, make("Dist(L2)")) + BR_PROPERTY(float, a, 1) + BR_PROPERTY(float, b, 0) + BR_PROPERTY(QString, inputVariable, "Label") + + void train(const TemplateList &templates) + { + const TemplateList samples = templates.mid(0, 2000); + const QList sampleLabels = samples.indexProperty(inputVariable); + QScopedPointer matrixOutput(MatrixOutput::make(FileList(samples.size()), FileList(samples.size()))); + Distance::compare(samples, samples, matrixOutput.data()); + + double genuineAccumulator, impostorAccumulator; + int genuineCount, impostorCount; + genuineAccumulator = impostorAccumulator = genuineCount = impostorCount = 0; + + for (int i=0; idata.at(i, j); + if (sampleLabels[i] == sampleLabels[j]) { + genuineAccumulator += val; + genuineCount++; + } else { + impostorAccumulator += val; + impostorCount++; + } + } + } + + if (genuineCount == 0) { qWarning("No genuine matches."); return; } + if (impostorCount == 0) { qWarning("No impostor matches."); return; } + + double genuineMean = genuineAccumulator / genuineCount; + double impostorMean = impostorAccumulator / impostorCount; + + if (genuineMean == impostorMean) { qWarning("Genuines and impostors are indistinguishable."); return; } + + a = 1.0/(genuineMean-impostorMean); + b = impostorMean; + + qDebug("a = %f, b = %f", a, b); + } + + float compare(const Template &target, const Template &query) const + { + return a * (distance->compare(target, query) - b); + } +}; + +BR_REGISTER(Distance, UnitDistance) + +} // namespace br + +#include "distance/unit.moc" diff --git a/openbr/plugins/distance/zscore.cpp b/openbr/plugins/distance/zscore.cpp new file mode 100644 index 0000000..ddaae48 --- /dev/null +++ b/openbr/plugins/distance/zscore.cpp @@ -0,0 +1,84 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +namespace br +{ + +class ZScoreDistance : public Distance +{ + Q_OBJECT + Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) + Q_PROPERTY(bool crossModality READ get_crossModality WRITE set_crossModality RESET reset_crossModality STORED false) + BR_PROPERTY(br::Distance*, distance, make("Dist(L2)")) + BR_PROPERTY(bool, crossModality, false) + + float min, max; + double mean, stddev; + + void train(const TemplateList &src) + { + distance->train(src); + + QScopedPointer matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size()))); + distance->compare(src, src, matrixOutput.data()); + + QList scores; + scores.reserve(src.size()*src.size()); + for (int i=0; idata.at(i, j); + if (score == -std::numeric_limits::max()) continue; + if (crossModality && src[i].file.get("MODALITY") == src[j].file.get("MODALITY")) continue; + scores.append(score); + } + } + + Common::MinMax(scores, &min, &max); + Common::MeanStdDev(scores, &mean, &stddev); + + if (stddev == 0) qFatal("Stddev is 0."); + } + + float compare(const Template &target, const Template &query) const + { + float score = distance->compare(target,query); + if (score == -std::numeric_limits::max()) score = (min - mean) / stddev; + else if (score == std::numeric_limits::max()) score = (max - mean) / stddev; + else score = (score - mean) / stddev; + return score; + } + + void store(QDataStream &stream) const + { + distance->store(stream); + stream << min << max << mean << stddev; + } + + void load(QDataStream &stream) + { + distance->load(stream); + stream >> min >> max >> mean >> stddev; + } +}; + +BR_REGISTER(Distance, ZScoreDistance) + +} // namespace br + +#include "distance/zscore.moc" diff --git a/openbr/plugins/draw.cpp b/openbr/plugins/draw.cpp deleted file mode 100644 index 3dc6a9b..0000000 --- a/openbr/plugins/draw.cpp +++ /dev/null @@ -1,700 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include -#include -#include -#include -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup transforms - * \brief Renders metadata onto the image. - * - * The inPlace argument controls whether or not the image is cloned before the metadata is drawn. - * - * \author Josh Klontz \cite jklontz - */ -class DrawTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(bool verbose READ get_verbose WRITE set_verbose RESET reset_verbose STORED false) - Q_PROPERTY(bool points READ get_points WRITE set_points RESET reset_points STORED false) - Q_PROPERTY(bool rects READ get_rects WRITE set_rects RESET reset_rects STORED false) - Q_PROPERTY(bool inPlace READ get_inPlace WRITE set_inPlace RESET reset_inPlace STORED false) - Q_PROPERTY(int lineThickness READ get_lineThickness WRITE set_lineThickness RESET reset_lineThickness STORED false) - Q_PROPERTY(bool named READ get_named WRITE set_named RESET reset_named STORED false) - Q_PROPERTY(bool location READ get_location WRITE set_location RESET reset_location STORED false) - BR_PROPERTY(bool, verbose, false) - BR_PROPERTY(bool, points, true) - BR_PROPERTY(bool, rects, true) - BR_PROPERTY(bool, inPlace, false) - BR_PROPERTY(int, lineThickness, 1) - BR_PROPERTY(bool, named, true) - BR_PROPERTY(bool, location, true) - - void project(const Template &src, Template &dst) const - { - const Scalar color(0,255,0); - const Scalar verboseColor(255, 255, 0); - dst.m() = inPlace ? src.m() : src.m().clone(); - - if (points) { - const QList pointsList = (named) ? OpenCVUtils::toPoints(src.file.points()+src.file.namedPoints()) : OpenCVUtils::toPoints(src.file.points()); - for (int i=0; i opener; - void project(const Template &src, Template &dst) const - { - dst = src; - - if (imgName.isEmpty() || targetName.isEmpty() || !dst.file.contains(imgName) || !dst.file.contains(targetName)) - return; - - QVariant temp = src.file.value(imgName); - cv::Mat im; - // is this a filename? - if (temp.canConvert()) { - QString im_name = temp.toString(); - Template temp_im; - opener->project(File(im_name), temp_im); - im = temp_im.m(); - } - // a cv::Mat ? - else if (temp.canConvert()) - im = src.file.get(imgName); - else - qDebug() << "Unrecognized property type " << imgName << "for" << src.file.name; - - // Location of detected face in source image - QRectF target_location = src.file.get(targetName); - - // match width with target region - qreal target_width = target_location.width(); - qreal current_width = im.cols; - qreal current_height = im.rows; - - qreal aspect_ratio = current_height / current_width; - qreal target_height = target_width * aspect_ratio; - - cv::resize(im, im, cv::Size(target_width, target_height)); - - // ROI used to maybe crop the matched image - cv::Rect clip_roi; - clip_roi.x = 0; - clip_roi.y = 0; - clip_roi.width = im.cols; - clip_roi.height= im.rows <= dst.m().rows ? im.rows : dst.m().rows; - - int half_width = src.m().cols / 2; - int out_x = 0; - - // place in the source image we will copy the matched image to. - cv::Rect target_roi; - bool left_side = false; - int width_adjust = 0; - // Place left - if (target_location.center().rx() > half_width) { - out_x = target_location.left() - im.cols; - if (out_x < 0) { - width_adjust = abs(out_x); - out_x = 0; - } - left_side = true; - } - // place right - else { - out_x = target_location.right(); - int high = out_x + im.cols; - if (high >= src.m().cols) { - width_adjust = abs(high - src.m().cols + 1); - } - } - - cv::Mat outIm; - if (width_adjust) - { - outIm.create(dst.m().rows, dst.m().cols + width_adjust, CV_8UC3); - memset(outIm.data, 127, outIm.rows * outIm.cols * outIm.channels()); - - Rect temp; - - if (left_side) - temp = Rect(abs(width_adjust), 0, dst.m().cols, dst.m().rows); - - else - temp = Rect(0, 0, dst.m().cols, dst.m().rows); - - dst.m().copyTo(outIm(temp)); - - } - else - outIm = dst.m(); - - if (clip_roi.height + target_location.top() >= outIm.rows) - { - clip_roi.height -= abs(outIm.rows - (clip_roi.height + target_location.top() )); - } - if (clip_roi.x + clip_roi.width >= im.cols) { - clip_roi.width -= abs(im.cols - (clip_roi.x + clip_roi.width + 1)); - if (clip_roi.width < 0) - clip_roi.width = 1; - } - - if (clip_roi.y + clip_roi.height >= im.rows) { - clip_roi.height -= abs(im.rows - (clip_roi.y + clip_roi.height + 1)); - } - if (clip_roi.x < 0) - clip_roi.x = 0; - if (clip_roi.y < 0) - clip_roi.y = 0; - - if (clip_roi.height < 0) - clip_roi.height = 0; - - if (clip_roi.width < 0) - clip_roi.width = 0; - - - if (clip_roi.y + clip_roi.height >= im.rows) - { - qDebug() << "Bad clip y" << clip_roi.y + clip_roi.height << im.rows; - } - if (clip_roi.x + clip_roi.width >= im.cols) - { - qDebug() << "Bad clip x" << clip_roi.x + clip_roi.width << im.cols; - } - - if (clip_roi.y < 0 || clip_roi.height < 0) - { - qDebug() << "bad clip y, low" << clip_roi.y << clip_roi.height; - qFatal("die"); - } - if (clip_roi.x < 0 || clip_roi.width < 0) - { - qDebug() << "bad clip x, low" << clip_roi.x << clip_roi.width; - qFatal("die"); - } - - target_roi.x = out_x; - target_roi.width = clip_roi.width; - target_roi.y = target_location.top(); - target_roi.height = clip_roi.height; - - - im = im(clip_roi); - - if (target_roi.x < 0 || target_roi.x >= outIm.cols) - { - qDebug() << "Bad xdim in targetROI!" << target_roi.x << " out im x: " << outIm.cols; - qFatal("die"); - } - - if (target_roi.x + target_roi.width < 0 || (target_roi.x + target_roi.width) >= outIm.cols) - { - qDebug() << "Bad xdim in targetROI!" << target_roi.x + target_roi.width; - qFatal("die"); - } - - if (target_roi.y < 0 || target_roi.y >= outIm.rows) - { - qDebug() << "Bad ydim in targetROI!" << target_roi.y; - qFatal("die"); - } - - if ((target_roi.y + target_roi.height) < 0 || (target_roi.y + target_roi.height) > outIm.rows) - { - qDebug() << "Bad ydim in targetROI!" << target_roi.y + target_roi.height; - qDebug() << "target_roi.y: " << target_roi.y << " height: " << target_roi.height; - qFatal("die"); - } - - - std::vector channels; - cv::split(outIm, channels); - - std::vector patch_channels; - cv::split(im, patch_channels); - - for (size_t i=0; i < channels.size(); i++) - { - cv::addWeighted(channels[i](target_roi), 0, patch_channels[i % patch_channels.size()], 1, 0,channels[i](target_roi)); - } - cv::merge(channels, outIm); - dst.m() = outIm; - - } - - void init() - { - opener = QSharedPointer(br::Transform::make("Cache(Open)", NULL)); - } - -}; - -BR_REGISTER(Transform, AdjacentOverlayTransform) - -/*! - * \ingroup transforms - * \brief Draw a line representing the direction and magnitude of optical flow at the specified points. - * \author Austin Blanton \cite imaus10 - */ -class DrawOpticalFlow : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(QString original READ get_original WRITE set_original RESET reset_original STORED false) - BR_PROPERTY(QString, original, "original") - - void project(const Template &src, Template &dst) const - { - const Scalar color(0,255,0); - Mat flow = src.m(); - dst = src; - if (!dst.file.contains(original)) qFatal("The original img must be saved in the metadata with SaveMat."); - dst.m() = dst.file.get(original); - dst.file.remove(original); - foreach (const Point2f &pt, OpenCVUtils::toPoints(dst.file.points())) { - Point2f dxy = flow.at(pt.y, pt.x); - Point2f newPt(pt.x+dxy.x, pt.y+dxy.y); - line(dst, pt, newPt, color); - } - } -}; -BR_REGISTER(Transform, DrawOpticalFlow) - -/*! - * \ingroup transforms - * \brief Fill in the segmentations or draw a line between intersecting segments. - * \author Austin Blanton \cite imaus10 - */ -class DrawSegmentation : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(bool fillSegment READ get_fillSegment WRITE set_fillSegment RESET reset_fillSegment STORED false) - BR_PROPERTY(bool, fillSegment, true) - - void project(const Template &src, Template &dst) const - { - if (!src.file.contains("SegmentsMask") || !src.file.contains("NumSegments")) qFatal("Must supply a Contours object in the metadata to drawContours."); - Mat segments = src.file.get("SegmentsMask"); - int numSegments = src.file.get("NumSegments"); - - dst.file = src.file; - Mat drawn = fillSegment ? Mat(segments.size(), CV_8UC3, Scalar::all(0)) : src.m(); - - for (int i=1; i > contours; - Scalar color(0,255,0); - findContours(mask, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); - drawContours(drawn, contours, -1, color); - } - } - - dst.m() = drawn; - } -}; -BR_REGISTER(Transform, DrawSegmentation) - -/*! - * \ingroup transforms - * \brief Write all mats to disk as images. - * \author Brendan Klare \cite bklare - */ -class WriteImageTransform : public TimeVaryingTransform -{ - Q_OBJECT - Q_PROPERTY(QString outputDirectory READ get_outputDirectory WRITE set_outputDirectory RESET reset_outputDirectory STORED false) - Q_PROPERTY(QString imageName READ get_imageName WRITE set_imageName RESET reset_imageName STORED false) - Q_PROPERTY(QString imgExtension READ get_imgExtension WRITE set_imgExtension RESET reset_imgExtension STORED false) - BR_PROPERTY(QString, outputDirectory, "Temp") - BR_PROPERTY(QString, imageName, "image") - BR_PROPERTY(QString, imgExtension, "jpg") - - int cnt; - - void init() { - cnt = 0; - if (! QDir(outputDirectory).exists()) - QDir().mkdir(outputDirectory); - } - - void projectUpdate(const Template &src, Template &dst) - { - dst = src; - OpenCVUtils::saveImage(dst.m(), QString("%1/%2_%3.%4").arg(outputDirectory).arg(imageName).arg(cnt++, 5, 10, QChar('0')).arg(imgExtension)); - } - -}; -BR_REGISTER(Transform, WriteImageTransform) - - -/** - * @brief The MeanImageTransform class computes the average template/image - * and save the result as an encoded image. - */ -class MeanImageTransform : public TimeVaryingTransform -{ - Q_OBJECT - - Q_PROPERTY(QString imgname READ get_imgname WRITE set_imgname RESET reset_imgname STORED false) - Q_PROPERTY(QString ext READ get_ext WRITE set_ext RESET reset_ext STORED false) - - BR_PROPERTY(QString, imgname, "average") - BR_PROPERTY(QString, ext, "jpg") - - Mat average; - int cnt; - - void init() - { - cnt = 0; - } - - void projectUpdate(const Template &src, Template &dst) - { - dst = src; - if (cnt == 0) { - if (src.m().channels() == 1) - average = Mat::zeros(dst.m().size(),CV_64FC1); - else if (src.m().channels() == 3) - average = Mat::zeros(dst.m().size(),CV_64FC3); - else - qFatal("Unsupported number of channels"); - } - - Mat temp; - if (src.m().channels() == 1) { - src.m().convertTo(temp, CV_64FC1); - average += temp; - } else if (src.m().channels() == 3) { - src.m().convertTo(temp, CV_64FC3); - average += temp; - } else - qFatal("Unsupported number of channels"); - - cnt++; - } - - virtual void finalize(TemplateList &output) - { - average /= float(cnt); - imwrite(QString("%1.%2").arg(imgname).arg(ext).toStdString(), average); - output = TemplateList(); - } - - -public: - MeanImageTransform() : TimeVaryingTransform(false, false) {} -}; - -BR_REGISTER(Transform, MeanImageTransform) - - -// TODO: re-implement EditTransform using Qt -#if 0 -/*! - * \ingroup transforms - * \brief Remove landmarks. - * \author Josh Klontz \cite jklontz - */ -class EditTransform : public UntrainableTransform -{ - Q_OBJECT - - Transform *draw; - static Template currentTemplate; - static QMutex currentTemplateLock; - - void init() - { - draw = make("Draw"); - Globals->setProperty("parallelism", "0"); // Can only work in single threaded mode - } - - void project(const Template &src, Template &dst) const - { - dst = src; - - if (Globals->parallelism) { - qWarning("Edit::project() only works in single threaded mode."); - return; - } - - currentTemplateLock.lock(); - currentTemplate = src; - OpenCVUtils::showImage(src, "Edit", false); - setMouseCallback("Edit", mouseCallback, (void*)this); - mouseEvent(0, 0, 0, 0); - waitKey(-1); - dst = currentTemplate; - currentTemplateLock.unlock(); - } - - static void mouseCallback(int event, int x, int y, int flags, void *userdata) - { - ((const EditTransform*)userdata)->mouseEvent(event, x, y, flags); - } - - void mouseEvent(int event, int x, int y, int flags) const - { - (void) event; - if (flags) { - QList rects = currentTemplate.file.rects(); - for (int i=rects.size()-1; i>=0; i--) - if (rects[i].contains(x,y)) - rects.removeAt(i); - currentTemplate.file.setRects(rects); - } - - Template temp; - draw->project(currentTemplate, temp); - OpenCVUtils::showImage(temp, "Edit", false); - } -}; - -Template EditTransform::currentTemplate; -QMutex EditTransform::currentTemplateLock; - -BR_REGISTER(Transform, EditTransform) -#endif - -} // namespace br - -#include "draw.moc" diff --git a/openbr/plugins/eigen3.cmake b/openbr/plugins/eigen3.cmake deleted file mode 100644 index ed6a3e2..0000000 --- a/openbr/plugins/eigen3.cmake +++ /dev/null @@ -1,7 +0,0 @@ -set(BR_WITH_EIGEN3 ON CACHE BOOL "Build Eigen3 plugins") - -if(${BR_WITH_EIGEN3}) - find_package(Eigen3 REQUIRED) - set(BR_THIRDPARTY_SRC ${BR_THIRDPARTY_SRC} plugins/eigen3.cpp) - install(FILES ${EIGEN3_LICENSE} RENAME Eigen3 DESTINATION share/openbr/licenses) -endif() diff --git a/openbr/plugins/filter.cpp b/openbr/plugins/filter.cpp deleted file mode 100644 index 48fbe75..0000000 --- a/openbr/plugins/filter.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include -#include "openbr_internal.h" -#include "openbr/core/tanh_sse.h" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup transforms - * \brief Gamma correction - * \author Josh Klontz \cite jklontz - */ -class GammaTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(float gamma READ get_gamma WRITE set_gamma RESET reset_gamma STORED false) - BR_PROPERTY(float, gamma, 0.2) - - Mat lut; - - void init() - { - lut.create(256, 1, CV_32FC1); - if (gamma == 0) for (int i=0; i<256; i++) lut.at(i,0) = log((float)i); - else for (int i=0; i<256; i++) lut.at(i,0) = pow(i, gamma); - } - - void project(const Template &src, Template &dst) const - { - if (src.m().depth() == CV_8U) LUT(src, lut, dst); - else pow(src, gamma, dst); - } -}; - -BR_REGISTER(Transform, GammaTransform) - -/*! - * \ingroup transforms - * \brief Gaussian blur - * \author Josh Klontz \cite jklontz - */ -class BlurTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(float sigma READ get_sigma WRITE set_sigma RESET reset_sigma STORED false) - Q_PROPERTY(bool ROI READ get_ROI WRITE set_ROI RESET reset_ROI STORED false) - BR_PROPERTY(float, sigma, 1) - BR_PROPERTY(bool, ROI, false) - - void project(const Template &src, Template &dst) const - { - if (!ROI) GaussianBlur(src, dst, Size(0,0), sigma); - else { - dst.m() = src.m(); - foreach (const QRectF &rect, src.file.rects()) { - Rect region(rect.x(), rect.y(), rect.width(), rect.height()); - Mat input = dst.m(); - Mat output = input.clone(); - GaussianBlur(input(region), output(region), Size(0,0), sigma); - dst.m() = output; - } - } - } -}; - -BR_REGISTER(Transform, BlurTransform) - -/*! - * \ingroup transforms - * \brief Difference of gaussians - * \author Josh Klontz \cite jklontz - */ -class DoGTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(float sigma0 READ get_sigma0 WRITE set_sigma0 RESET reset_sigma0 STORED false) - Q_PROPERTY(float sigma1 READ get_sigma1 WRITE set_sigma1 RESET reset_sigma1 STORED false) - BR_PROPERTY(float, sigma0, 1) - BR_PROPERTY(float, sigma1, 2) - - Size ksize0, ksize1; - - static Size getKernelSize(double sigma) - { - // Inverts OpenCV's conversion from kernel size to sigma: - // sigma = ((ksize-1)*0.5 - 1)*0.3 + 0.8 - // See documentation for cv::getGaussianKernel() - int ksize = ((sigma - 0.8) / 0.3 + 1) * 2 + 1; - if (ksize % 2 == 0) ksize++; - return Size(ksize, ksize); - } - - void init() - { - ksize0 = getKernelSize(sigma0); - ksize1 = getKernelSize(sigma1); - } - - void project(const Template &src, Template &dst) const - { - Mat g0, g1; - GaussianBlur(src, g0, ksize0, 0); - GaussianBlur(src, g1, ksize1, 0); - subtract(g0, g1, dst); - } -}; - -BR_REGISTER(Transform, DoGTransform) - -/*! - * \ingroup transforms - * \brief Meyers, E.; Wolf, L. - * “Using biologically inspired features for face processing,” - * Int. Journal of Computer Vision, vol. 76, no. 1, pp 93–104, 2008. - * \author Scott Klum \cite sklum - */ - -class CSDNTransform : public UntrainableTransform -{ - Q_OBJECT - - Q_PROPERTY(float s READ get_s WRITE set_s RESET reset_s STORED false) - BR_PROPERTY(int, s, 16) - - void project(const Template &src, Template &dst) const - { - if (src.m().channels() != 1) qFatal("Expected single channel source matrix."); - - const int nRows = src.m().rows; - const int nCols = src.m().cols; - - Mat m; - src.m().convertTo(m, CV_32FC1); - - const int surround = s/2; - - for ( int i = 0; i < nRows; i++ ) { - for ( int j = 0; j < nCols; j++ ) { - int width = min( j+surround, nCols ) - max( 0, j-surround ); - int height = min( i+surround, nRows ) - max( 0, i-surround ); - - Rect_ ROI(max(0, j-surround), max(0, i-surround), width, height); - - Scalar_ avg = mean(m(ROI)); - - m.at(i,j) = m.at(i,j) - avg[0]; - } - } - - dst = m; - - } -}; - -BR_REGISTER(Transform, CSDNTransform) - -/*! - * \ingroup transforms - * \brief Xiaoyang Tan; Triggs, B.; - * "Enhanced Local Texture Feature Sets for Face Recognition Under Difficult Lighting Conditions," - * Image Processing, IEEE Transactions on , vol.19, no.6, pp.1635-1650, June 2010 - * \author Josh Klontz \cite jklontz - */ -class ContrastEqTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(float a READ get_a WRITE set_a RESET reset_a STORED false) - Q_PROPERTY(float t READ get_t WRITE set_t RESET reset_t STORED false) - BR_PROPERTY(float, a, 1) - BR_PROPERTY(float, t, 0.1) - - void project(const Template &src, Template &dst) const - { - if (src.m().channels() != 1) qFatal("Expected single channel source matrix."); - - // Stage 1 - Mat stage1; - { - Mat abs_dst; - absdiff(src, Scalar(0), abs_dst); - Mat pow_dst; - pow(abs_dst, a, pow_dst); - float denominator = pow((float)mean(pow_dst)[0], 1.f/a); - src.m().convertTo(stage1, CV_32F, 1/denominator); - } - - // Stage 2 - Mat stage2; - { - Mat abs_dst; - absdiff(stage1, Scalar(0), abs_dst); - Mat min_dst; - min(abs_dst, t, min_dst); - Mat pow_dst; - pow(min_dst, a, pow_dst); - float denominator = pow((float)mean(pow_dst)[0], 1.f/a); - stage1.convertTo(stage2, CV_32F, 1/denominator); - } - - // Hyperbolic tangent - const int nRows = src.m().rows; - const int nCols = src.m().cols; - const float* p = (const float*)stage2.ptr(); - Mat m(nRows, nCols, CV_32FC1); - for (int i=0; i(i, j) = fast_tanh(p[i*nCols+j]); - // TODO: m.at(i, j) = t * fast_tanh(p[i*nCols+j] / t); - - dst = m; - } -}; - -BR_REGISTER(Transform, ContrastEqTransform) - -/*! - * \ingroup transforms - * \brief Raise each element to the specified power. - * \author Josh Klontz \cite jklontz - */ -class PowTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(float power READ get_power WRITE set_power RESET reset_power STORED false) - Q_PROPERTY(bool preserveSign READ get_preserveSign WRITE set_preserveSign RESET reset_preserveSign STORED false) - BR_PROPERTY(float, power, 2) - BR_PROPERTY(bool, preserveSign, false) - - void project(const Template &src, Template &dst) const - { - pow(preserveSign ? abs(src) : src.m(), power, dst); - if (preserveSign) subtract(Scalar::all(0), dst, dst, src.m() < 0); - } -}; - -BR_REGISTER(Transform, PowTransform) - -} // namespace br - -#include "filter.moc" diff --git a/openbr/plugins/format.cpp b/openbr/plugins/format.cpp deleted file mode 100644 index 48eed7d..0000000 --- a/openbr/plugins/format.cpp +++ /dev/null @@ -1,975 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include -#include -#include -#ifndef BR_EMBEDDED -#include -#endif // BR_EMBEDDED -#include -#include - -#include -#include -#include "openbr_internal.h" - -#include "openbr/core/bee.h" -#include "openbr/core/opencvutils.h" -#include "openbr/core/qtutils.h" - -using namespace cv; - -namespace br -{ - - /*! - * \ingroup formats - * \brief Read all frames of a video using OpenCV - * \author Charles Otto \cite caotto - */ -class videoFormat : public Format -{ - Q_OBJECT - -public: - Template read() const - { - if (!file.exists() ) - return Template(); - - VideoCapture videoSource(file.name.toStdString()); - videoSource.open(file.name.toStdString() ); - - - Template frames; - if (!videoSource.isOpened()) { - qWarning("video file open failed"); - return frames; - } - - bool open = true; - while (open) { - cv::Mat frame; - open = videoSource.read(frame); - if (!open) break; - - frames.append(cv::Mat()); - frames.back() = frame.clone(); - } - - return frames; - } - - void write(const Template &t) const - { - int fourcc = OpenCVUtils::getFourcc(); - VideoWriter videoSink(file.name.toStdString(), fourcc, 30, t.begin()->size()); - - // Did we successfully open the output file? - if (!videoSink.isOpened() ) qFatal("Failed to open output file"); - - for (Template::const_iterator it = t.begin(); it!= t.end(); ++it) { - videoSink << *it; - } - } -}; - -BR_REGISTER(Format, videoFormat) - -/*! - * \ingroup formats - * \brief A simple binary matrix format. - * \author Josh Klontz \cite jklontz - * First 4 bytes indicate the number of rows. - * Second 4 bytes indicate the number of columns. - * The rest of the bytes are 32-bit floating data elements in row-major order. - */ -class binFormat : public Format -{ - Q_OBJECT - Q_PROPERTY(bool raw READ get_raw WRITE set_raw RESET reset_raw STORED false) - BR_PROPERTY(bool, raw, false) - - Template read() const - { - QByteArray data; - QtUtils::readFile(file, data); - if (raw) { - return Template(file, Mat(1, data.size(), CV_8UC1, data.data()).clone()); - } else { - return Template(file, Mat(((quint32*)data.data())[0], - ((quint32*)data.data())[1], - CV_32FC1, - data.data()+8).clone()); - } - } - - void write(const Template &t) const - { - QFile f(file); - QtUtils::touchDir(f); - if (!f.open(QFile::WriteOnly)) - qFatal("Failed to open %s for writing.", qPrintable(file)); - - Mat m; - if (!raw) { - if (t.m().type() != CV_32FC1) - t.m().convertTo(m, CV_32F); - else m = t.m(); - - if (m.channels() != 1) qFatal("Only supports single channel matrices."); - - f.write((const char *) &m.rows, 4); - f.write((const char *) &m.cols, 4); - } - else m = t.m(); - - qint64 rowSize = m.cols * sizeof(float); - for (int i=0; i < m.rows; i++) - { - f.write((const char *) m.row(i).data, rowSize); - } - f.close(); - } -}; - -BR_REGISTER(Format, binFormat) - -/*! - * \ingroup formats - * \brief Reads a comma separated value file. - * \author Josh Klontz \cite jklontz - */ -class csvFormat : public Format -{ - Q_OBJECT - - Template read() const - { - QFile f(file.name); - f.open(QFile::ReadOnly); - QStringList lines(QString(f.readAll()).split(QRegularExpression("[\n|\r\n|\r]"), QString::SkipEmptyParts)); - f.close(); - - bool isUChar = true; - QList< QList > valsList; - foreach (const QString &line, lines) { - QList vals; - foreach (const QString &word, line.split(QRegExp(" *, *"), QString::SkipEmptyParts)) { - bool ok; - const float val = word.toFloat(&ok); - vals.append(val); - isUChar = isUChar && (val == float(uchar(val))); - } - if (!vals.isEmpty()) - valsList.append(vals); - } - - Mat m(valsList.size(), valsList[0].size(), CV_32FC1); - for (int i=0; i(i,j) = valsList[i][j]; - - if (isUChar) m.convertTo(m, CV_8U); - return Template(m); - } - - void write(const Template &t) const - { - const Mat &m = t.m(); - if (t.size() != 1) qFatal("Only supports single matrix templates."); - if (m.channels() != 1) qFatal("Only supports single channel matrices."); - - QStringList lines; lines.reserve(m.rows); - for (int r=0; r::names().contains("url")) { - File urlFile = file; - urlFile.name.append(".url"); - QScopedPointer url(Factory::make(urlFile)); - t = url->read(); - } - } else { - Mat m = imread(file.resolved().toStdString()); - if (m.data) { - t.append(m); - } else { - videoFormat videoReader; - videoReader.file = file; - t = videoReader.read(); - } - } - - return t; - } - - void write(const Template &t) const - { - if (t.size() > 1) { - videoFormat videoWriter; - videoWriter.file = file; - videoWriter.write(t); - } else if (t.size() == 1) { - QtUtils::touchDir(QDir(file.path())); - imwrite(file.name.toStdString(), t); - } - } -}; - -BR_REGISTER(Format, DefaultFormat) - -/*! - * \ingroup formats - * \brief Reads a NIST LFFS file. - * \author Josh Klontz \cite jklontz - */ -class lffsFormat : public Format -{ - Q_OBJECT - - Template read() const - { - QByteArray byteArray; - QtUtils::readFile(file.name, byteArray); - return Mat(1, byteArray.size(), CV_8UC1, byteArray.data()).clone(); - } - - void write(const Template &t) const - { - QByteArray byteArray((const char*)t.m().data, t.m().total()*t.m().elemSize()); - QtUtils::writeFile(file.name, byteArray); - } -}; - -BR_REGISTER(Format, lffsFormat) - -/*! - * \ingroup formats - * \brief Reads a NIST BEE similarity matrix. - * \author Josh Klontz \cite jklontz - */ -class mtxFormat : public Format -{ - Q_OBJECT - - Template read() const - { - QString target, query; - Template result = BEE::readMatrix(file, &target, &query); - result.file.set("Target", target); - result.file.set("Query", query); - return result; - } - - void write(const Template &t) const - { - BEE::writeMatrix(t, file); - } -}; - -BR_REGISTER(Format, mtxFormat) - -/*! - * \ingroup formats - * \brief Reads a NIST BEE mask matrix. - * \author Josh Klontz \cite jklontz - */ -class maskFormat : public mtxFormat -{ - Q_OBJECT -}; - -BR_REGISTER(Format, maskFormat) - -/*! - * \ingroup formats - * \brief MATLAB .mat format. - * \author Josh Klontz \cite jklontz - * http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf - * \note matFormat is known not to work with compressed matrices - */ -class matFormat : public Format -{ - Q_OBJECT - - struct Element - { - // It is always best to cast integers to a Qt integer type, such as qint16 or quint32, when reading and writing. - // This ensures that you always know exactly what size integers you are reading and writing, no matter what the - // underlying platform and architecture the application happens to be running on. - // http://qt-project.org/doc/qt-4.8/datastreamformat.html - quint32 type, bytes; - QByteArray data; - Element() : type(0), bytes(0) {} - Element(QDataStream &stream) - : type(0), bytes(0) - { - // Read first 4 bytes into type (32 bit integer), - // specifying the type of data used - if (stream.readRawData((char*)&type, 4) != 4) - qFatal("Unexpected end of file."); - - if (type >= 1 << 16) { - // Small data format - bytes = type; - type = type & 0x0000FFFF; - bytes = bytes >> 16; - } else { - // Regular format - // Read 4 bytes into bytes (32 bit integer), - // specifying the size of the element - if (stream.readRawData((char*)&bytes, 4) != 4) - qFatal("Unexpected end of file."); - } - - // Set the size of data to bytes - data.resize(bytes); - - // Read bytes amount of data from the file into data - if (int(bytes) != stream.readRawData(data.data(), bytes)) - qFatal("Unexpected end of file."); - - // Alignment - int skipBytes = (bytes < 4) ? (4 - bytes) : (8 - bytes%8)%8; - if (skipBytes != 0) stream.skipRawData(skipBytes); - } - }; - - Template read() const - { - QByteArray byteArray; - QtUtils::readFile(file, byteArray); - QDataStream f(byteArray); - - { // Check header - QByteArray header(128, 0); - f.readRawData(header.data(), 128); - if (!header.startsWith("MATLAB 5.0 MAT-file")) - qFatal("Invalid MAT header."); - } - - Template t(file); - - while (!f.atEnd()) { - Element element(f); - - // miCOMPRESSED - if (element.type == 15) { - // Prepend the number of bytes to element.data - element.data.prepend((char*)&element.bytes, 4); // Qt zlib wrapper requires this to preallocate the buffer - QDataStream uncompressed(qUncompress(element.data)); - element = Element(uncompressed); - } - - // miMATRIX - if (element.type == 14) { - QDataStream matrix(element.data); - qint32 rows = 0, columns = 0; - int matrixType = 0; - QByteArray matrixData; - while (!matrix.atEnd()) { - Element subelement(matrix); - if (subelement.type == 5) { // Dimensions array - if (subelement.bytes == 8) { - rows = ((qint32*)subelement.data.data())[0]; - columns = ((qint32*)subelement.data.data())[1]; - } else { - qWarning("matFormat::read can only handle 2D arrays."); - } - } else if (subelement.type == 7) { //miSINGLE - matrixType = CV_32FC1; - matrixData = subelement.data; - } else if (subelement.type == 9) { //miDOUBLE - matrixType = CV_64FC1; - matrixData = subelement.data; - } - } - - if ((rows > 0) && (columns > 0) && (matrixType != 0) && !matrixData.isEmpty()) { - Mat transposed; - transpose(Mat(columns, rows, matrixType, matrixData.data()), transposed); - t.append(transposed); - } - } - } - - return t; - } - - void write(const Template &t) const - { - QByteArray data; - QDataStream stream(&data, QFile::WriteOnly); - - { // Header - QByteArray header = "MATLAB 5.0 MAT-file; Made with OpenBR | www.openbiometrics.org\n"; - QByteArray buffer(116-header.size(), 0); - stream.writeRawData(header.data(), header.size()); - stream.writeRawData(buffer.data(), buffer.size()); - quint64 subsystem = 0; - quint16 version = 0x0100; - const char *endianness = "IM"; - stream.writeRawData((const char*)&subsystem, 8); - stream.writeRawData((const char*)&version, 2); - stream.writeRawData(endianness, 2); - } - - for (int i=0; i > imageSizes; // QHash > - - Template read() const - { - QString path = file.path(); - if (!imageSizes.contains(path)) { - static QMutex mutex; - QMutexLocker locker(&mutex); - - if (!imageSizes.contains(path)) { - const QString imageSize = path+"/ImageSize.txt"; - QStringList lines; - if (QFileInfo(imageSize).exists()) { - lines = QtUtils::readLines(imageSize); - lines.removeFirst(); // Remove header - } - - QHash sizes; - QRegExp whiteSpace("\\s+"); - foreach (const QString &line, lines) { - QStringList words = line.split(whiteSpace); - if (words.size() != 3) continue; - sizes.insert(words[0], QSize(words[2].toInt(), words[1].toInt())); - } - - imageSizes.insert(path, sizes); - } - } - - QByteArray data; - QtUtils::readFile(file, data); - - QSize size = imageSizes[path][file.baseName()]; - if (!size.isValid()) size = QSize(800,768); - if (data.size() != size.width() * size.height()) - qFatal("Expected %d*%d bytes, got %d.", size.height(), size.width(), data.size()); - return Template(file, Mat(size.height(), size.width(), CV_8UC1, data.data()).clone()); - } - - void write(const Template &t) const - { - QtUtils::writeFile(file, QByteArray().setRawData((const char*)t.m().data, t.m().total() * t.m().elemSize())); - } -}; - -QHash > rawFormat::imageSizes; - -BR_REGISTER(Format, rawFormat) - -/*! - * \ingroup formats - * \brief Retrieves an image from a webcam. - * \author Josh Klontz \cite jklontz - */ -class webcamFormat : public Format -{ - Q_OBJECT - - Template read() const - { - static QScopedPointer videoCapture; - - if (videoCapture.isNull()) - videoCapture.reset(new VideoCapture(0)); - - Mat m; - videoCapture->read(m); - return Template(m); - } - - void write(const Template &t) const - { - (void) t; - qFatal("Not supported."); - } -}; - -BR_REGISTER(Format, webcamFormat) - -/*! - * \ingroup formats - * \brief Decodes images from Base64 xml - * \author Scott Klum \cite sklum - * \author Josh Klontz \cite jklontz - */ -class xmlFormat : public Format -{ - Q_OBJECT - - Template read() const - { - Template t; - -#ifndef BR_EMBEDDED - QString fileName = file.get("path") + file.name; - - QDomDocument doc(fileName); - QFile f(fileName); - - if (!f.open(QIODevice::ReadOnly)) qFatal("Unable to open %s for reading.", qPrintable(file.flat())); - if (!doc.setContent(&f)) qWarning("Unable to parse %s.", qPrintable(file.flat())); - f.close(); - - QDomElement docElem = doc.documentElement(); - QDomNode subject = docElem.firstChild(); - while (!subject.isNull()) { - QDomNode fileNode = subject.firstChild(); - - while (!fileNode.isNull()) { - QDomElement e = fileNode.toElement(); - - if (e.tagName() == "FORMAL_IMG") { - QByteArray byteArray = QByteArray::fromBase64(qPrintable(e.text())); - Mat m = imdecode(Mat(3, byteArray.size(), CV_8UC3, byteArray.data()), CV_LOAD_IMAGE_COLOR); - if (!m.data) qWarning("xmlFormat::read failed to decode image data."); - t.append(m); - } else if ((e.tagName() == "RELEASE_IMG") || - (e.tagName() == "PREBOOK_IMG") || - (e.tagName() == "LPROFILE") || - (e.tagName() == "RPROFILE")) { - // Ignore these other image fields for now - } else { - t.file.set(e.tagName(), e.text()); - } - - fileNode = fileNode.nextSibling(); - } - subject = subject.nextSibling(); - } - - // Calculate age - if (t.file.contains("DOB")) { - const QDate dob = QDate::fromString(t.file.get("DOB").left(10), "yyyy-MM-dd"); - const QDate current = QDate::currentDate(); - int age = current.year() - dob.year(); - if (current.month() < dob.month()) age--; - t.file.set("Age", age); - } -#endif // BR_EMBEDDED - - return t; - } - - void write(const Template &t) const - { - QStringList lines; - lines.append(""); - lines.append(""); - lines.append("\t"); - foreach (const QString &key, t.file.localKeys()) { - if ((key == "Index") || (key == "Label")) continue; - lines.append("\t\t<"+key+">"+QtUtils::toString(t.file.value(key))+""); - } - std::vector data; - imencode(".jpg",t.m(),data); - QByteArray byteArray = QByteArray::fromRawData((const char*)data.data(), data.size()); - lines.append("\t\t"+byteArray.toBase64()+""); - lines.append("\t"); - lines.append(""); - QtUtils::writeFile(file, lines); - } -}; - -BR_REGISTER(Format, xmlFormat) - -/*! - * \ingroup formats - * \brief Reads in scores or ground truth from a text table. - * \author Josh Klontz \cite jklontz - * - * Example of the format: - * \code - * 2.2531514 FALSE 99990377 99990164 - * 2.2549822 TRUE 99990101 99990101 - * \endcode - */ -class scoresFormat : public Format -{ - Q_OBJECT - Q_PROPERTY(int column READ get_column WRITE set_column RESET reset_column STORED false) - Q_PROPERTY(bool groundTruth READ get_groundTruth WRITE set_groundTruth RESET reset_groundTruth STORED false) - Q_PROPERTY(QString delimiter READ get_delimiter WRITE set_delimiter RESET reset_delimiter STORED false) - BR_PROPERTY(int, column, 0) - BR_PROPERTY(bool, groundTruth, false) - BR_PROPERTY(QString, delimiter, "\t") - - Template read() const - { - QFile f(file.name); - if (!f.open(QFile::ReadOnly | QFile::Text)) - qFatal("Failed to open %s for reading.", qPrintable(f.fileName())); - QList values; - while (!f.atEnd()) { - const QStringList words = QString(f.readLine()).split(delimiter); - if (words.size() <= column) qFatal("Expected file to have at least %d columns.", column+1); - const QString &word = words[column]; - bool ok; - float value = word.toFloat(&ok); - if (!ok) value = (QtUtils::toBool(word) ? BEE::Match : BEE::NonMatch); - values.append(value); - } - if (values.size() == 1) - qWarning("Only one value read, double check file line endings."); - Mat result = OpenCVUtils::toMat(values); - if (groundTruth) result.convertTo(result, CV_8U); - return result; - } - - void write(const Template &t) const - { - (void) t; - qFatal("Not implemented."); - } -}; - -BR_REGISTER(Format, scoresFormat) - -/*! - * \ingroup formats - * \brief Reads FBI EBTS transactions. - * \author Scott Klum \cite sklum - * https://www.fbibiospecs.org/ebts.html - */ -class ebtsFormat : public Format -{ - Q_OBJECT - - struct Field { - int type; - QList data; - }; - - struct Record { - int type; - quint32 bytes; - int position; // Starting position of record - - QHash > fields; - }; - - quint32 recordBytes(const QByteArray &byteArray, const float recordType, int from) const - { - bool ok; - quint32 size; - - if (recordType == 4 || recordType == 7) { - // read first four bytes - ok = true; - size = qFromBigEndian((const uchar*)byteArray.mid(from,4).constData()); - } else { - int index = byteArray.indexOf(QChar(0x1D), from); - size = byteArray.mid(from, index-from).split(':').last().toInt(&ok); - } - - return ok ? size : -1; - } - - void parseRecord(const QByteArray &byteArray, Record &record) const - { - if (record.type == 4 || record.type == 7) { - // Just a binary blob - // Read everything after the first four bytes - // Not current supported - } else { - // Continue reading fields until we get all the data - unsigned int position = record.position; - while (position < record.position + record.bytes) { - int index = byteArray.indexOf(QChar(0x1D), position); - Field field = parseField(byteArray.mid(position, index-position),QChar(0x1F)); - if (field.type == 999 ) { - // Data begin after the field identifier and the colon - int dataBegin = byteArray.indexOf(':', position)+1; - field.data.clear(); - field.data.append(byteArray.mid(dataBegin, record.bytes-(dataBegin-record.position))); - - // Data fields are always last in the record - record.fields.insert(field.type,field.data); - break; - } - // Advance the position accounting for the separator - position += index-position+1; - record.fields.insert(field.type,field.data); - } - } - } - - Field parseField(const QByteArray &byteArray, const QChar &sep) const - { - bool ok; - Field f; - - QList data = byteArray.split(':'); - - f.type = data.first().split('.').last().toInt(&ok); - f.data = data.last().split(sep.toLatin1()); - - return f; - } - - Template read() const - { - QByteArray byteArray; - QtUtils::readFile(file, byteArray); - - Template t; - - Mat m; - - QList records; - - // Read the type one record (every EBTS file will have one of these) - Record r1; - r1.type = 1; - r1.position = 0; - r1.bytes = recordBytes(byteArray,r1.type,r1.position); - - // The fields in a type 1 record are strictly defined - QList data = byteArray.mid(r1.position,r1.bytes).split(QChar(0x1D).toLatin1()); - foreach (const QByteArray &datum, data) { - Field f = parseField(datum,QChar(0x1F)); - r1.fields.insert(f.type,f.data); - } - - records.append(r1); - - // Read the type two record (every EBTS file will have one of these) - Record r2; - r2.type = 2; - r2.position = r1.bytes; - r2.bytes = recordBytes(byteArray,r2.type,r2.position); - - // The fields in a type 2 record are strictly defined - data = byteArray.mid(r2.position,r2.bytes).split(QChar(0x1D).toLatin1()); - foreach (const QByteArray &datum, data) { - Field f = parseField(datum,QChar(0x1F)); - r2.fields.insert(f.type,f.data); - } - - // Demographics - if (r2.fields.contains(18)) { - QString name = r2.fields.value(18).first(); - QStringList names = name.split(','); - t.file.set("FIRSTNAME", names.at(1)); - t.file.set("LASTNAME", names.at(0)); - } - - if (r2.fields.contains(22)) t.file.set("DOB", r2.fields.value(22).first().toInt()); - if (r2.fields.contains(24)) t.file.set("GENDER", QString(r2.fields.value(24).first())); - if (r2.fields.contains(25)) t.file.set("RACE", QString(r2.fields.value(25).first())); - - if (t.file.contains("DOB")) { - const QDate dob = QDate::fromString(t.file.get("DOB"), "yyyyMMdd"); - const QDate current = QDate::currentDate(); - int age = current.year() - dob.year(); - if (current.month() < dob.month()) age--; - t.file.set("Age", age); - } - - records.append(r2); - - // The third field of the first record contains informations about all the remaining records in the transaction - // We don't care about the first two and the final items - QList recordTypes = r1.fields.value(3); - for (int i=2; i frontalIdxs; - int position = r1.bytes + r2.bytes; - for (int i=2; i +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief A simple binary matrix format. + * \author Josh Klontz \cite jklontz + * First 4 bytes indicate the number of rows. + * Second 4 bytes indicate the number of columns. + * The rest of the bytes are 32-bit floating data elements in row-major order. + */ +class binaryFormat : public Format +{ + Q_OBJECT + Q_PROPERTY(bool raw READ get_raw WRITE set_raw RESET reset_raw STORED false) + BR_PROPERTY(bool, raw, false) + + Template read() const + { + QByteArray data; + QtUtils::readFile(file, data); + if (raw) { + return Template(file, Mat(1, data.size(), CV_8UC1, data.data()).clone()); + } else { + return Template(file, Mat(((quint32*)data.data())[0], + ((quint32*)data.data())[1], + CV_32FC1, + data.data()+8).clone()); + } + } + + void write(const Template &t) const + { + QFile f(file); + QtUtils::touchDir(f); + if (!f.open(QFile::WriteOnly)) + qFatal("Failed to open %s for writing.", qPrintable(file)); + + Mat m; + if (!raw) { + if (t.m().type() != CV_32FC1) + t.m().convertTo(m, CV_32F); + else m = t.m(); + + if (m.channels() != 1) qFatal("Only supports single channel matrices."); + + f.write((const char *) &m.rows, 4); + f.write((const char *) &m.cols, 4); + } + else m = t.m(); + + qint64 rowSize = m.cols * sizeof(float); + for (int i=0; i < m.rows; i++) + { + f.write((const char *) m.row(i).data, rowSize); + } + f.close(); + } +}; + +BR_REGISTER(Format, binaryFormat) + +} // namespace br + +#include "format/binary.moc" diff --git a/openbr/plugins/format/csv.cpp b/openbr/plugins/format/csv.cpp new file mode 100644 index 0000000..6bcad6e --- /dev/null +++ b/openbr/plugins/format/csv.cpp @@ -0,0 +1,89 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Reads a comma separated value file. + * \author Josh Klontz \cite jklontz + */ +class csvFormat : public Format +{ + Q_OBJECT + + Template read() const + { + QFile f(file.name); + f.open(QFile::ReadOnly); + QStringList lines(QString(f.readAll()).split(QRegularExpression("[\n|\r\n|\r]"), QString::SkipEmptyParts)); + f.close(); + + bool isUChar = true; + QList< QList > valsList; + foreach (const QString &line, lines) { + QList vals; + foreach (const QString &word, line.split(QRegExp(" *, *"), QString::SkipEmptyParts)) { + bool ok; + const float val = word.toFloat(&ok); + vals.append(val); + isUChar = isUChar && (val == float(uchar(val))); + } + if (!vals.isEmpty()) + valsList.append(vals); + } + + Mat m(valsList.size(), valsList[0].size(), CV_32FC1); + for (int i=0; i(i,j) = valsList[i][j]; + + if (isUChar) m.convertTo(m, CV_8U); + return Template(m); + } + + void write(const Template &t) const + { + const Mat &m = t.m(); + if (t.size() != 1) qFatal("Only supports single matrix templates."); + if (m.channels() != 1) qFatal("Only supports single channel matrices."); + + QStringList lines; lines.reserve(m.rows); + for (int r=0; r +#include + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Reads FBI EBTS transactions. + * \author Scott Klum \cite sklum + * https://www.fbibiospecs.org/ebts.html + */ +class ebtsFormat : public Format +{ + Q_OBJECT + + struct Field { + int type; + QList data; + }; + + struct Record { + int type; + quint32 bytes; + int position; // Starting position of record + + QHash > fields; + }; + + quint32 recordBytes(const QByteArray &byteArray, const float recordType, int from) const + { + bool ok; + quint32 size; + + if (recordType == 4 || recordType == 7) { + // read first four bytes + ok = true; + size = qFromBigEndian((const uchar*)byteArray.mid(from,4).constData()); + } else { + int index = byteArray.indexOf(QChar(0x1D), from); + size = byteArray.mid(from, index-from).split(':').last().toInt(&ok); + } + + return ok ? size : -1; + } + + void parseRecord(const QByteArray &byteArray, Record &record) const + { + if (record.type == 4 || record.type == 7) { + // Just a binary blob + // Read everything after the first four bytes + // Not current supported + } else { + // Continue reading fields until we get all the data + unsigned int position = record.position; + while (position < record.position + record.bytes) { + int index = byteArray.indexOf(QChar(0x1D), position); + Field field = parseField(byteArray.mid(position, index-position),QChar(0x1F)); + if (field.type == 999 ) { + // Data begin after the field identifier and the colon + int dataBegin = byteArray.indexOf(':', position)+1; + field.data.clear(); + field.data.append(byteArray.mid(dataBegin, record.bytes-(dataBegin-record.position))); + + // Data fields are always last in the record + record.fields.insert(field.type,field.data); + break; + } + // Advance the position accounting for the separator + position += index-position+1; + record.fields.insert(field.type,field.data); + } + } + } + + Field parseField(const QByteArray &byteArray, const QChar &sep) const + { + bool ok; + Field f; + + QList data = byteArray.split(':'); + + f.type = data.first().split('.').last().toInt(&ok); + f.data = data.last().split(sep.toLatin1()); + + return f; + } + + Template read() const + { + QByteArray byteArray; + QtUtils::readFile(file, byteArray); + + Template t; + + Mat m; + + QList records; + + // Read the type one record (every EBTS file will have one of these) + Record r1; + r1.type = 1; + r1.position = 0; + r1.bytes = recordBytes(byteArray,r1.type,r1.position); + + // The fields in a type 1 record are strictly defined + QList data = byteArray.mid(r1.position,r1.bytes).split(QChar(0x1D).toLatin1()); + foreach (const QByteArray &datum, data) { + Field f = parseField(datum,QChar(0x1F)); + r1.fields.insert(f.type,f.data); + } + + records.append(r1); + + // Read the type two record (every EBTS file will have one of these) + Record r2; + r2.type = 2; + r2.position = r1.bytes; + r2.bytes = recordBytes(byteArray,r2.type,r2.position); + + // The fields in a type 2 record are strictly defined + data = byteArray.mid(r2.position,r2.bytes).split(QChar(0x1D).toLatin1()); + foreach (const QByteArray &datum, data) { + Field f = parseField(datum,QChar(0x1F)); + r2.fields.insert(f.type,f.data); + } + + // Demographics + if (r2.fields.contains(18)) { + QString name = r2.fields.value(18).first(); + QStringList names = name.split(','); + t.file.set("FIRSTNAME", names.at(1)); + t.file.set("LASTNAME", names.at(0)); + } + + if (r2.fields.contains(22)) t.file.set("DOB", r2.fields.value(22).first().toInt()); + if (r2.fields.contains(24)) t.file.set("GENDER", QString(r2.fields.value(24).first())); + if (r2.fields.contains(25)) t.file.set("RACE", QString(r2.fields.value(25).first())); + + if (t.file.contains("DOB")) { + const QDate dob = QDate::fromString(t.file.get("DOB"), "yyyyMMdd"); + const QDate current = QDate::currentDate(); + int age = current.year() - dob.year(); + if (current.month() < dob.month()) age--; + t.file.set("Age", age); + } + + records.append(r2); + + // The third field of the first record contains informations about all the remaining records in the transaction + // We don't care about the first two and the final items + QList recordTypes = r1.fields.value(3); + for (int i=2; i frontalIdxs; + int position = r1.bytes + r2.bytes; + for (int i=2; i +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Reads a NIST LFFS file. + * \author Josh Klontz \cite jklontz + */ +class lffsFormat : public Format +{ + Q_OBJECT + + Template read() const + { + QByteArray byteArray; + QtUtils::readFile(file.name, byteArray); + return Mat(1, byteArray.size(), CV_8UC1, byteArray.data()).clone(); + } + + void write(const Template &t) const + { + QByteArray byteArray((const char*)t.m().data, t.m().total()*t.m().elemSize()); + QtUtils::writeFile(file.name, byteArray); + } +}; + +BR_REGISTER(Format, lffsFormat) + +} // namespace br + +#include "format/lffs.moc" diff --git a/openbr/plugins/format/lmat.cpp b/openbr/plugins/format/lmat.cpp new file mode 100644 index 0000000..7bb4e62 --- /dev/null +++ b/openbr/plugins/format/lmat.cpp @@ -0,0 +1,37 @@ +#include + +namespace br +{ + +/*! + * \ingroup formats + * \brief Likely matrix format + * + * www.liblikely.org + * \author Josh Klontz \cite jklontz + */ +class lmatFormat : public Format +{ + Q_OBJECT + + Template read() const + { + const likely_const_mat m = likely_read(qPrintable(file.name), likely_file_guess); + const Template result(likelyToOpenCVMat(m)); + likely_release_mat(m); + return result; + } + + void write(const Template &t) const + { + const likely_const_mat m = likelyFromOpenCVMat(t); + likely_write(m, qPrintable(file.name)); + likely_release_mat(m); + } +}; + +BR_REGISTER(Format, lmatFormat) + +} // namespace br + +#include "format/lmat.moc" diff --git a/openbr/plugins/format/mat.cpp b/openbr/plugins/format/mat.cpp new file mode 100644 index 0000000..f118daf --- /dev/null +++ b/openbr/plugins/format/mat.cpp @@ -0,0 +1,243 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief MATLAB .mat format. + * \author Josh Klontz \cite jklontz + * http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf + * \note matFormat is known not to work with compressed matrices + */ +class matFormat : public Format +{ + Q_OBJECT + + struct Element + { + // It is always best to cast integers to a Qt integer type, such as qint16 or quint32, when reading and writing. + // This ensures that you always know exactly what size integers you are reading and writing, no matter what the + // underlying platform and architecture the application happens to be running on. + // http://qt-project.org/doc/qt-4.8/datastreamformat.html + quint32 type, bytes; + QByteArray data; + Element() : type(0), bytes(0) {} + Element(QDataStream &stream) + : type(0), bytes(0) + { + // Read first 4 bytes into type (32 bit integer), + // specifying the type of data used + if (stream.readRawData((char*)&type, 4) != 4) + qFatal("Unexpected end of file."); + + if (type >= 1 << 16) { + // Small data format + bytes = type; + type = type & 0x0000FFFF; + bytes = bytes >> 16; + } else { + // Regular format + // Read 4 bytes into bytes (32 bit integer), + // specifying the size of the element + if (stream.readRawData((char*)&bytes, 4) != 4) + qFatal("Unexpected end of file."); + } + + // Set the size of data to bytes + data.resize(bytes); + + // Read bytes amount of data from the file into data + if (int(bytes) != stream.readRawData(data.data(), bytes)) + qFatal("Unexpected end of file."); + + // Alignment + int skipBytes = (bytes < 4) ? (4 - bytes) : (8 - bytes%8)%8; + if (skipBytes != 0) stream.skipRawData(skipBytes); + } + }; + + Template read() const + { + QByteArray byteArray; + QtUtils::readFile(file, byteArray); + QDataStream f(byteArray); + + { // Check header + QByteArray header(128, 0); + f.readRawData(header.data(), 128); + if (!header.startsWith("MATLAB 5.0 MAT-file")) + qFatal("Invalid MAT header."); + } + + Template t(file); + + while (!f.atEnd()) { + Element element(f); + + // miCOMPRESSED + if (element.type == 15) { + // Prepend the number of bytes to element.data + element.data.prepend((char*)&element.bytes, 4); // Qt zlib wrapper requires this to preallocate the buffer + QDataStream uncompressed(qUncompress(element.data)); + element = Element(uncompressed); + } + + // miMATRIX + if (element.type == 14) { + QDataStream matrix(element.data); + qint32 rows = 0, columns = 0; + int matrixType = 0; + QByteArray matrixData; + while (!matrix.atEnd()) { + Element subelement(matrix); + if (subelement.type == 5) { // Dimensions array + if (subelement.bytes == 8) { + rows = ((qint32*)subelement.data.data())[0]; + columns = ((qint32*)subelement.data.data())[1]; + } else { + qWarning("matFormat::read can only handle 2D arrays."); + } + } else if (subelement.type == 7) { //miSINGLE + matrixType = CV_32FC1; + matrixData = subelement.data; + } else if (subelement.type == 9) { //miDOUBLE + matrixType = CV_64FC1; + matrixData = subelement.data; + } + } + + if ((rows > 0) && (columns > 0) && (matrixType != 0) && !matrixData.isEmpty()) { + Mat transposed; + transpose(Mat(columns, rows, matrixType, matrixData.data()), transposed); + t.append(transposed); + } + } + } + + return t; + } + + void write(const Template &t) const + { + QByteArray data; + QDataStream stream(&data, QFile::WriteOnly); + + { // Header + QByteArray header = "MATLAB 5.0 MAT-file; Made with OpenBR | www.openbiometrics.org\n"; + QByteArray buffer(116-header.size(), 0); + stream.writeRawData(header.data(), header.size()); + stream.writeRawData(buffer.data(), buffer.size()); + quint64 subsystem = 0; + quint16 version = 0x0100; + const char *endianness = "IM"; + stream.writeRawData((const char*)&subsystem, 8); + stream.writeRawData((const char*)&version, 2); + stream.writeRawData(endianness, 2); + } + + for (int i=0; i +#include + +namespace br +{ + +/*! + * \ingroup formats + * \brief Reads a NIST BEE similarity matrix. + * \author Josh Klontz \cite jklontz + */ +class mtxFormat : public Format +{ + Q_OBJECT + + Template read() const + { + QString target, query; + Template result = BEE::readMatrix(file, &target, &query); + result.file.set("Target", target); + result.file.set("Query", query); + return result; + } + + void write(const Template &t) const + { + BEE::writeMatrix(t, file); + } +}; + +BR_REGISTER(Format, mtxFormat) + +/*! + * \ingroup formats + * \brief Reads a NIST BEE mask matrix. + * \author Josh Klontz \cite jklontz + */ +class maskFormat : public mtxFormat +{ + Q_OBJECT +}; + +BR_REGISTER(Format, maskFormat) + +} // namespace br + +#include "format/mtx.moc" diff --git a/openbr/plugins/format/null.cpp b/openbr/plugins/format/null.cpp new file mode 100644 index 0000000..97caa99 --- /dev/null +++ b/openbr/plugins/format/null.cpp @@ -0,0 +1,46 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup formats + * \brief Returns an empty matrix. + * \author Josh Klontz \cite jklontz + */ +class nullFormat : public Format +{ + Q_OBJECT + + Template read() const + { + return Template(file, cv::Mat()); + } + + void write(const Template &t) const + { + (void)t; + } +}; + +BR_REGISTER(Format, nullFormat) + +} // namespace br + +#include "format/null.moc" diff --git a/openbr/plugins/format/post.cpp b/openbr/plugins/format/post.cpp new file mode 100644 index 0000000..31ee504 --- /dev/null +++ b/openbr/plugins/format/post.cpp @@ -0,0 +1,106 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Handle POST requests + * \author Josh Klontz \cite jklontz + */ +class postFormat : public Format +{ + Q_OBJECT + + Template read() const + { + Template t(file); + + // Read from socket + QTcpSocket *socket = new QTcpSocket(); + socket->setSocketDescriptor(file.get("socketDescriptor")); + socket->write("HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + "Hello World!\r\n"); + socket->waitForBytesWritten(); + socket->waitForReadyRead(); + QByteArray data = socket->readAll(); + socket->close(); + delete socket; + + qDebug() << data; + + // Parse data + http_parser_settings settings; + settings.on_body = bodyCallback; + settings.on_headers_complete = NULL; + settings.on_header_field = NULL; + settings.on_header_value = NULL; + settings.on_message_begin = NULL; + settings.on_message_complete = NULL; + settings.on_status_complete = NULL; + settings.on_url = NULL; + + { + QByteArray body; + http_parser parser; + http_parser_init(&parser, HTTP_REQUEST); + parser.data = &body; + http_parser_execute(&parser, &settings, data.data(), data.size()); + data = body; + } + + data.prepend("HTTP/1.1 200 OK"); + QByteArray body; + { // Image data is two layers deep + http_parser parser; + http_parser_init(&parser, HTTP_BOTH); + parser.data = &body; + http_parser_execute(&parser, &settings, data.data(), data.size()); + } + + t.append(imdecode(Mat(1, body.size(), CV_8UC1, body.data()), 1)); + return t; + } + + void write(const Template &t) const + { + (void) t; + qFatal("Not supported!"); + } + + static int bodyCallback(http_parser *parser, const char *at, size_t length) + { + QByteArray *byteArray = (QByteArray*)parser->data; + *byteArray = QByteArray(at, length); + return 0; + } +}; + +BR_REGISTER(Format, postFormat) + +} // namespace br + +#include "format/post.moc" diff --git a/openbr/plugins/format/raw.cpp b/openbr/plugins/format/raw.cpp new file mode 100644 index 0000000..14ee263 --- /dev/null +++ b/openbr/plugins/format/raw.cpp @@ -0,0 +1,86 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief RAW format + * + * http://www.nist.gov/srd/nistsd27.cfm + * \author Josh Klontz \cite jklontz + */ +class rawFormat : public Format +{ + Q_OBJECT + static QHash > imageSizes; // QHash > + + Template read() const + { + QString path = file.path(); + if (!imageSizes.contains(path)) { + static QMutex mutex; + QMutexLocker locker(&mutex); + + if (!imageSizes.contains(path)) { + const QString imageSize = path+"/ImageSize.txt"; + QStringList lines; + if (QFileInfo(imageSize).exists()) { + lines = QtUtils::readLines(imageSize); + lines.removeFirst(); // Remove header + } + + QHash sizes; + QRegExp whiteSpace("\\s+"); + foreach (const QString &line, lines) { + QStringList words = line.split(whiteSpace); + if (words.size() != 3) continue; + sizes.insert(words[0], QSize(words[2].toInt(), words[1].toInt())); + } + + imageSizes.insert(path, sizes); + } + } + + QByteArray data; + QtUtils::readFile(file, data); + + QSize size = imageSizes[path][file.baseName()]; + if (!size.isValid()) size = QSize(800,768); + if (data.size() != size.width() * size.height()) + qFatal("Expected %d*%d bytes, got %d.", size.height(), size.width(), data.size()); + return Template(file, Mat(size.height(), size.width(), CV_8UC1, data.data()).clone()); + } + + void write(const Template &t) const + { + QtUtils::writeFile(file, QByteArray().setRawData((const char*)t.m().data, t.m().total() * t.m().elemSize())); + } +}; + +QHash > rawFormat::imageSizes; + +BR_REGISTER(Format, rawFormat) + +} // namespace br + +#include "format/raw.moc" diff --git a/openbr/plugins/format/scores.cpp b/openbr/plugins/format/scores.cpp new file mode 100644 index 0000000..c674991 --- /dev/null +++ b/openbr/plugins/format/scores.cpp @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Reads in scores or ground truth from a text table. + * \author Josh Klontz \cite jklontz + * + * Example of the format: + * \code + * 2.2531514 FALSE 99990377 99990164 + * 2.2549822 TRUE 99990101 99990101 + * \endcode + */ +class scoresFormat : public Format +{ + Q_OBJECT + Q_PROPERTY(int column READ get_column WRITE set_column RESET reset_column STORED false) + Q_PROPERTY(bool groundTruth READ get_groundTruth WRITE set_groundTruth RESET reset_groundTruth STORED false) + Q_PROPERTY(QString delimiter READ get_delimiter WRITE set_delimiter RESET reset_delimiter STORED false) + BR_PROPERTY(int, column, 0) + BR_PROPERTY(bool, groundTruth, false) + BR_PROPERTY(QString, delimiter, "\t") + + Template read() const + { + QFile f(file.name); + if (!f.open(QFile::ReadOnly | QFile::Text)) + qFatal("Failed to open %s for reading.", qPrintable(f.fileName())); + QList values; + while (!f.atEnd()) { + const QStringList words = QString(f.readLine()).split(delimiter); + if (words.size() <= column) qFatal("Expected file to have at least %d columns.", column+1); + const QString &word = words[column]; + bool ok; + float value = word.toFloat(&ok); + if (!ok) value = (QtUtils::toBool(word) ? BEE::Match : BEE::NonMatch); + values.append(value); + } + if (values.size() == 1) + qWarning("Only one value read, double check file line endings."); + Mat result = OpenCVUtils::toMat(values); + if (groundTruth) result.convertTo(result, CV_8U); + return result; + } + + void write(const Template &t) const + { + (void) t; + qFatal("Not implemented."); + } +}; + +BR_REGISTER(Format, scoresFormat) + +} // namespace br + +#include "format/scores.moc" diff --git a/openbr/plugins/format/url.cpp b/openbr/plugins/format/url.cpp new file mode 100644 index 0000000..24b1dbd --- /dev/null +++ b/openbr/plugins/format/url.cpp @@ -0,0 +1,68 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Reads image files from the web. + * \author Josh Klontz \cite jklontz + */ +class urlFormat : public Format +{ + Q_OBJECT + + Template read() const + { + Template t; + + QNetworkAccessManager networkAccessManager; + QNetworkRequest request(QString(file.name).remove(".url")); + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + QNetworkReply *reply = networkAccessManager.get(request); + + while (!reply->isFinished()) QCoreApplication::processEvents(); + if (reply->error()) qWarning("%s (%s)", qPrintable(reply->errorString()), qPrintable(QString::number(reply->error()))); + + QByteArray data = reply->readAll(); + delete reply; + + Mat m = imdecode(Mat(1, data.size(), CV_8UC1, data.data()), 1); + if (m.data) t.append(m); + + return t; + } + + void write(const Template &t) const + { + (void) t; + qFatal("Not supported."); + } +}; + +BR_REGISTER(Format, urlFormat) + +} // namespace br + +#include "format/url.moc" diff --git a/openbr/plugins/format/video.cpp b/openbr/plugins/format/video.cpp new file mode 100644 index 0000000..fdf659d --- /dev/null +++ b/openbr/plugins/format/video.cpp @@ -0,0 +1,163 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Read all frames of a video using OpenCV + * \author Charles Otto \cite caotto + */ +class videoFormat : public Format +{ + Q_OBJECT + +public: + Template read() const + { + if (!file.exists() ) + return Template(); + + VideoCapture videoSource(file.name.toStdString()); + videoSource.open(file.name.toStdString() ); + + + Template frames; + if (!videoSource.isOpened()) { + qWarning("video file open failed"); + return frames; + } + + bool open = true; + while (open) { + cv::Mat frame; + open = videoSource.read(frame); + if (!open) break; + + frames.append(cv::Mat()); + frames.back() = frame.clone(); + } + + return frames; + } + + void write(const Template &t) const + { + int fourcc = OpenCVUtils::getFourcc(); + VideoWriter videoSink(file.name.toStdString(), fourcc, 30, t.begin()->size()); + + // Did we successfully open the output file? + if (!videoSink.isOpened() ) qFatal("Failed to open output file"); + + for (Template::const_iterator it = t.begin(); it!= t.end(); ++it) { + videoSink << *it; + } + } +}; + +BR_REGISTER(Format, videoFormat) + +/*! + * \ingroup formats + * \brief Retrieves an image from a webcam. + * \author Josh Klontz \cite jklontz + */ +class webcamFormat : public Format +{ + Q_OBJECT + + Template read() const + { + static QScopedPointer videoCapture; + + if (videoCapture.isNull()) + videoCapture.reset(new VideoCapture(0)); + + Mat m; + videoCapture->read(m); + return Template(m); + } + + void write(const Template &t) const + { + (void) t; + qFatal("Not supported."); + } +}; + +BR_REGISTER(Format, webcamFormat) + +/*! + * \ingroup formats + * \brief Reads image files. + * \author Josh Klontz \cite jklontz + */ +class DefaultFormat : public Format +{ + Q_OBJECT + + Template read() const + { + Template t; + + if (file.name.startsWith("http://") || file.name.startsWith("https://") || file.name.startsWith("www.")) { + if (Factory::names().contains("url")) { + File urlFile = file; + urlFile.name.append(".url"); + QScopedPointer url(Factory::make(urlFile)); + t = url->read(); + } + } else { + Mat m = imread(file.resolved().toStdString()); + if (m.data) { + t.append(m); + } else { + videoFormat videoReader; + videoReader.file = file; + t = videoReader.read(); + } + } + + return t; + } + + void write(const Template &t) const + { + if (t.size() > 1) { + videoFormat videoWriter; + videoWriter.file = file; + videoWriter.write(t); + } else if (t.size() == 1) { + QtUtils::touchDir(QDir(file.path())); + imwrite(file.name.toStdString(), t); + } + } +}; + +BR_REGISTER(Format, DefaultFormat) + +} // namespace br + +#include "format/video.moc" diff --git a/openbr/plugins/format/xml.cpp b/openbr/plugins/format/xml.cpp new file mode 100644 index 0000000..9757619 --- /dev/null +++ b/openbr/plugins/format/xml.cpp @@ -0,0 +1,118 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef BR_EMBEDDED +#include +#endif // BR_EMBEDDED +#include + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup formats + * \brief Decodes images from Base64 xml + * \author Scott Klum \cite sklum + * \author Josh Klontz \cite jklontz + */ +class xmlFormat : public Format +{ + Q_OBJECT + + Template read() const + { + Template t; + +#ifndef BR_EMBEDDED + QString fileName = file.get("path") + file.name; + + QDomDocument doc(fileName); + QFile f(fileName); + + if (!f.open(QIODevice::ReadOnly)) qFatal("Unable to open %s for reading.", qPrintable(file.flat())); + if (!doc.setContent(&f)) qWarning("Unable to parse %s.", qPrintable(file.flat())); + f.close(); + + QDomElement docElem = doc.documentElement(); + QDomNode subject = docElem.firstChild(); + while (!subject.isNull()) { + QDomNode fileNode = subject.firstChild(); + + while (!fileNode.isNull()) { + QDomElement e = fileNode.toElement(); + + if (e.tagName() == "FORMAL_IMG") { + QByteArray byteArray = QByteArray::fromBase64(qPrintable(e.text())); + Mat m = imdecode(Mat(3, byteArray.size(), CV_8UC3, byteArray.data()), CV_LOAD_IMAGE_COLOR); + if (!m.data) qWarning("xmlFormat::read failed to decode image data."); + t.append(m); + } else if ((e.tagName() == "RELEASE_IMG") || + (e.tagName() == "PREBOOK_IMG") || + (e.tagName() == "LPROFILE") || + (e.tagName() == "RPROFILE")) { + // Ignore these other image fields for now + } else { + t.file.set(e.tagName(), e.text()); + } + + fileNode = fileNode.nextSibling(); + } + subject = subject.nextSibling(); + } + + // Calculate age + if (t.file.contains("DOB")) { + const QDate dob = QDate::fromString(t.file.get("DOB").left(10), "yyyy-MM-dd"); + const QDate current = QDate::currentDate(); + int age = current.year() - dob.year(); + if (current.month() < dob.month()) age--; + t.file.set("Age", age); + } +#endif // BR_EMBEDDED + + return t; + } + + void write(const Template &t) const + { + QStringList lines; + lines.append(""); + lines.append(""); + lines.append("\t"); + foreach (const QString &key, t.file.localKeys()) { + if ((key == "Index") || (key == "Label")) continue; + lines.append("\t\t<"+key+">"+QtUtils::toString(t.file.value(key))+""); + } + std::vector data; + imencode(".jpg",t.m(),data); + QByteArray byteArray = QByteArray::fromRawData((const char*)data.data(), data.size()); + lines.append("\t\t"+byteArray.toBase64()+""); + lines.append("\t"); + lines.append(""); + QtUtils::writeFile(file, lines); + } +}; + +BR_REGISTER(Format, xmlFormat) + +} // namespace br + +#include "format/xml.moc" diff --git a/openbr/plugins/gallery.cpp b/openbr/plugins/gallery.cpp deleted file mode 100644 index 5036aec..0000000 --- a/openbr/plugins/gallery.cpp +++ /dev/null @@ -1,2075 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include -#include -#ifndef BR_EMBEDDED -#include -#include -#include -#include -#include -#include -#include -#endif // BR_EMBEDDED -#include -#include "openbr_internal.h" - -#include "openbr/universal_template.h" -#include "openbr/core/bee.h" -#include "openbr/core/common.h" -#include "openbr/core/opencvutils.h" -#include "openbr/core/qtutils.h" - -#include - -#ifdef CVMATIO -#include "MatlabIO.hpp" -#include "MatlabIOContainer.hpp" -#endif - -#ifdef _WIN32 -#include -#include -#endif // _WIN32 - -namespace br -{ - -/*! - * \ingroup galleries - * \brief Weka ARFF file format. - * \author Josh Klontz \cite jklontz - * http://weka.wikispaces.com/ARFF+%28stable+version%29 - */ -class arffGallery : public Gallery -{ - Q_OBJECT - QFile arffFile; - - TemplateList readBlock(bool *done) - { - (void) done; - qFatal("Not implemented."); - return TemplateList(); - } - - void write(const Template &t) - { - if (!arffFile.isOpen()) { - arffFile.setFileName(file.name); - arffFile.open(QFile::WriteOnly); - arffFile.write("% OpenBR templates\n" - "@RELATION OpenBR\n" - "\n"); - - const int dimensions = t.m().rows * t.m().cols; - for (int i=0; i("Label") + "'\n")); - } - - void init() - { - // - } -}; - -BR_REGISTER(Gallery, arffGallery) - -class BinaryGallery : public Gallery -{ - Q_OBJECT - - void init() - { - const QString baseName = file.baseName(); - - if (baseName == "stdin") { -#ifdef _WIN32 - if(_setmode(_fileno(stdin), _O_BINARY) == -1) - qFatal("Failed to set stdin to binary mode!"); -#endif // _WIN32 - - gallery.open(stdin, QFile::ReadOnly); - } else if (baseName == "stdout") { -#ifdef _WIN32 - if(_setmode(_fileno(stdout), _O_BINARY) == -1) - qFatal("Failed to set stdout to binary mode!"); -#endif // _WIN32 - - gallery.open(stdout, QFile::WriteOnly); - } else if (baseName == "stderr") { -#ifdef _WIN32 - if(_setmode(_fileno(stderr), _O_BINARY) == -1) - qFatal("Failed to set stderr to binary mode!"); -#endif // _WIN32 - - gallery.open(stderr, QFile::WriteOnly); - } else { - // Defer opening the file, in the general case we don't know if we - // need read or write mode yet - return; - } - stream.setDevice(&gallery); - } - - void readOpen() - { - if (!gallery.isOpen()) { - gallery.setFileName(file); - if (!gallery.exists()) - qFatal("File %s does not exist", qPrintable(gallery.fileName())); - - QFile::OpenMode mode = QFile::ReadOnly; - if (!gallery.open(mode)) - qFatal("Can't open gallery: %s for reading", qPrintable(gallery.fileName())); - stream.setDevice(&gallery); - } - } - - void writeOpen() - { - if (!gallery.isOpen()) { - gallery.setFileName(file); - - // Do we remove the pre-existing gallery? - if (file.get("remove")) - gallery.remove(); - QtUtils::touchDir(gallery); - QFile::OpenMode mode = QFile::WriteOnly; - - // Do we append? - if (file.get("append")) - mode |= QFile::Append; - - if (!gallery.open(mode)) - qFatal("Can't open gallery: %s for writing", qPrintable(gallery.fileName())); - stream.setDevice(&gallery); - } - } - - TemplateList readBlock(bool *done) - { - readOpen(); - if (gallery.atEnd()) - gallery.seek(0); - - TemplateList templates; - while ((templates.size() < readBlockSize) && !gallery.atEnd()) { - const Template t = readTemplate(); - if (!t.isEmpty() || !t.file.isNull()) { - templates.append(t); - templates.last().file.set("progress", position()); - } - - // Special case for pipes where we want to process data as soon as it is available - if (gallery.isSequential()) - break; - } - - *done = gallery.atEnd(); - return templates; - } - - void write(const Template &t) - { - writeOpen(); - writeTemplate(t); - if (gallery.isSequential()) - gallery.flush(); - } - -protected: - QFile gallery; - QDataStream stream; - - qint64 totalSize() - { - readOpen(); - return gallery.size(); - } - - qint64 position() - { - return gallery.pos(); - } - - virtual Template readTemplate() = 0; - virtual void writeTemplate(const Template &t) = 0; -}; - -/*! - * \ingroup galleries - * \brief A binary gallery. - * - * Designed to be a literal translation of templates to disk. - * Compatible with TemplateList::fromBuffer. - * \author Josh Klontz \cite jklontz - */ -class galGallery : public BinaryGallery -{ - Q_OBJECT - - Template readTemplate() - { - Template t; - stream >> t; - return t; - } - - void writeTemplate(const Template &t) - { - if (t.isEmpty() && t.file.isNull()) - return; - else if (t.file.fte) - stream << Template(t.file); // only write metadata for failure to enroll - else - stream << t; - } -}; - -BR_REGISTER(Gallery, galGallery) - -/*! - * \ingroup galleries - * \brief A contiguous array of br_universal_template. - * \author Josh Klontz \cite jklontz - */ -class utGallery : public BinaryGallery -{ - Q_OBJECT - - Template readTemplate() - { - Template t; - br_universal_template ut; - if (gallery.read((char*)&ut, sizeof(br_universal_template)) == sizeof(br_universal_template)) { - QByteArray data(ut.urlSize + ut.fvSize, Qt::Uninitialized); - char *dst = data.data(); - qint64 bytesNeeded = ut.urlSize + ut.fvSize; - while (bytesNeeded > 0) { - qint64 bytesRead = gallery.read(dst, bytesNeeded); - if (bytesRead <= 0) { - qDebug() << gallery.errorString(); - qFatal("Unexepected EOF while reading universal template data, needed: %d more of: %d bytes.", int(bytesNeeded), int(ut.urlSize + ut.fvSize)); - } - bytesNeeded -= bytesRead; - dst += bytesRead; - } - - t.file.set("ImageID", QVariant(QByteArray((const char*)ut.imageID, 16).toHex())); - t.file.set("AlgorithmID", ut.algorithmID); - t.file.set("URL", QString(data.data())); - char *dataStart = data.data() + ut.urlSize; - uint32_t dataSize = ut.fvSize; - if ((ut.algorithmID <= -1) && (ut.algorithmID >= -3)) { - t.file.set("FrontalFace", QRectF(ut.x, ut.y, ut.width, ut.height)); - uint32_t *rightEyeX = reinterpret_cast(dataStart); - dataStart += sizeof(uint32_t); - uint32_t *rightEyeY = reinterpret_cast(dataStart); - dataStart += sizeof(uint32_t); - uint32_t *leftEyeX = reinterpret_cast(dataStart); - dataStart += sizeof(uint32_t); - uint32_t *leftEyeY = reinterpret_cast(dataStart); - dataStart += sizeof(uint32_t); - dataSize -= sizeof(uint32_t)*4; - t.file.set("First_Eye", QPointF(*rightEyeX, *rightEyeY)); - t.file.set("Second_Eye", QPointF(*leftEyeX, *leftEyeY)); - } else { - t.file.set("X", ut.x); - t.file.set("Y", ut.y); - t.file.set("Width", ut.width); - t.file.set("Height", ut.height); - } - t.file.set("Label", ut.label); - t.append(cv::Mat(1, dataSize, CV_8UC1, dataStart).clone() /* We don't want a shallow copy! */); - } else { - if (!gallery.atEnd()) - qFatal("Failed to read universal template header!"); - } - return t; - } - - void writeTemplate(const Template &t) - { - const QByteArray imageID = QByteArray::fromHex(t.file.get("ImageID", QByteArray(32, '0'))); - if (imageID.size() != 16) - qFatal("Expected 16-byte ImageID, got: %d bytes.", imageID.size()); - - const int32_t algorithmID = (t.isEmpty() || t.file.fte) ? 0 : t.file.get("AlgorithmID"); - const QByteArray url = t.file.get("URL", t.file.name).toLatin1(); - - uint32_t x = 0, y = 0, width = 0, height = 0; - QByteArray header; - if ((algorithmID <= -1) && (algorithmID >= -3)) { - const QRectF frontalFace = t.file.get("FrontalFace"); - x = frontalFace.x(); - y = frontalFace.y(); - width = frontalFace.width(); - height = frontalFace.height(); - - const QPointF firstEye = t.file.get("First_Eye"); - const QPointF secondEye = t.file.get("Second_Eye"); - const uint32_t rightEyeX = firstEye.x(); - const uint32_t rightEyeY = firstEye.y(); - const uint32_t leftEyeX = secondEye.x(); - const uint32_t leftEyeY = secondEye.y(); - - header.append((const char*)&rightEyeX, sizeof(uint32_t)); - header.append((const char*)&rightEyeY, sizeof(uint32_t)); - header.append((const char*)&leftEyeX , sizeof(uint32_t)); - header.append((const char*)&leftEyeY , sizeof(uint32_t)); - } else { - x = t.file.get("X", 0); - y = t.file.get("Y", 0); - width = t.file.get("Width", 0); - height = t.file.get("Height", 0); - } - const uint32_t label = t.file.get("Label", 0); - - gallery.write(imageID); - gallery.write((const char*) &algorithmID, sizeof(int32_t)); - gallery.write((const char*) &x , sizeof(uint32_t)); - gallery.write((const char*) &y , sizeof(uint32_t)); - gallery.write((const char*) &width , sizeof(uint32_t)); - gallery.write((const char*) &height , sizeof(uint32_t)); - gallery.write((const char*) &label , sizeof(uint32_t)); - - const uint32_t urlSize = url.size() + 1; - gallery.write((const char*) &urlSize, sizeof(uint32_t)); - - const uint32_t signatureSize = (algorithmID == 0) ? 0 : t.m().rows * t.m().cols * t.m().elemSize(); - const uint32_t fvSize = header.size() + signatureSize; - gallery.write((const char*) &fvSize, sizeof(uint32_t)); - - gallery.write((const char*) url.data(), urlSize); - if (algorithmID != 0) { - gallery.write(header); - gallery.write((const char*) t.m().data, signatureSize); - } - } -}; - -BR_REGISTER(Gallery, utGallery) - -/*! - * \ingroup galleries - * \brief Newline-separated URLs. - * \author Josh Klontz \cite jklontz - */ -class urlGallery : public BinaryGallery -{ - Q_OBJECT - - Template readTemplate() - { - Template t; - const QString url = QString::fromLocal8Bit(gallery.readLine()).simplified(); - if (!url.isEmpty()) - t.file.set("URL", url); - return t; - } - - void writeTemplate(const Template &t) - { - const QString url = t.file.get("URL", t.file.name); - if (!url.isEmpty()) { - gallery.write(qPrintable(url)); - gallery.write("\n"); - } - } -}; - -BR_REGISTER(Gallery, urlGallery) - -/*! - * \ingroup galleries - * \brief Newline-separated JSON objects. - * \author Josh Klontz \cite jklontz - */ -class jsonGallery : public BinaryGallery -{ - Q_OBJECT - - Template readTemplate() - { - QJsonParseError error; - const QByteArray line = gallery.readLine().simplified(); - if (line.isEmpty()) - return Template(); - File file = QJsonDocument::fromJson(line, &error).object().toVariantMap(); - if (error.error != QJsonParseError::NoError) { - qWarning("Couldn't parse: %s\n", line.data()); - qFatal("%s\n", qPrintable(error.errorString())); - } - return file; - } - - void writeTemplate(const Template &t) - { - const QByteArray json = QJsonDocument(QJsonObject::fromVariantMap(t.file.localMetadata())).toJson().replace('\n', ""); - if (!json.isEmpty()) { - gallery.write(json); - gallery.write("\n"); - } - } -}; - -BR_REGISTER(Gallery, jsonGallery) - -/*! - * \ingroup galleries - * \brief Reads/writes templates to/from folders. - * \author Josh Klontz \cite jklontz - * \param regexp An optional regular expression to match against the files extension. - */ -class EmptyGallery : public Gallery -{ - Q_OBJECT - Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) - BR_PROPERTY(QString, regexp, QString()) - - qint64 gallerySize; - - void init() - { - QDir dir(file.name); - QtUtils::touchDir(dir); - gallerySize = dir.count(); - } - - TemplateList readBlock(bool *done) - { - TemplateList templates; - *done = true; - - // Enrolling a null file is used as an idiom to initialize an algorithm - if (file.isNull()) return templates; - - // Add immediate subfolders - QDir dir(file); - QList< QFuture > futures; - foreach (const QString &folder, QtUtils::naturalSort(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))) { - const QDir subdir = dir.absoluteFilePath(folder); - futures.append(QtConcurrent::run(&EmptyGallery::getTemplates, subdir)); - } - foreach (const QFuture &future, futures) - templates.append(future.result()); - - // Add root folder - foreach (const QString &fileName, QtUtils::getFiles(file.name, false)) - templates.append(File(fileName, dir.dirName())); - - if (!regexp.isEmpty()) { - QRegExp re(regexp); - re.setPatternSyntax(QRegExp::Wildcard); - for (int i=templates.size()-1; i>=0; i--) { - if (!re.exactMatch(templates[i].file.fileName())) { - templates.removeAt(i); - } - } - } - - for (int i = 0; i < templates.size(); i++) templates[i].file.set("progress", i); - - return templates; - } - - void write(const Template &t) - { - static QMutex diskLock; - - // Enrolling a null file is used as an idiom to initialize an algorithm - if (file.name.isEmpty()) return; - - const QString newFormat = file.get("newFormat",QString()); - QString destination = file.name + "/" + (file.getBool("preservePath") ? t.file.path()+"/" : QString()); - destination += (newFormat.isEmpty() ? t.file.fileName() : t.file.baseName()+newFormat); - - QMutexLocker diskLocker(&diskLock); // Windows prefers to crash when writing to disk in parallel - if (t.isNull()) { - QtUtils::copyFile(t.file.resolved(), destination); - } else { - QScopedPointer format(Factory::make(destination)); - format->write(t); - } - } - - qint64 totalSize() - { - return gallerySize; - } - - static TemplateList getTemplates(const QDir &dir) - { - const QStringList files = QtUtils::getFiles(dir, true); - TemplateList templates; templates.reserve(files.size()); - foreach (const QString &file, files) - templates.append(File(file, dir.dirName())); - return templates; - } -}; - -BR_REGISTER(Gallery, EmptyGallery) - -/*! - * \ingroup galleries - * \brief Crawl a root location for image files. - * \author Josh Klontz \cite jklontz - */ -class crawlGallery : public Gallery -{ - Q_OBJECT - Q_PROPERTY(bool autoRoot READ get_autoRoot WRITE set_autoRoot RESET reset_autoRoot STORED false) - Q_PROPERTY(int depth READ get_depth WRITE set_depth RESET reset_depth STORED false) - Q_PROPERTY(bool depthFirst READ get_depthFirst WRITE set_depthFirst RESET reset_depthFirst STORED false) - Q_PROPERTY(int images READ get_images WRITE set_images RESET reset_images STORED false) - Q_PROPERTY(bool json READ get_json WRITE set_json RESET reset_json STORED false) - Q_PROPERTY(int timeLimit READ get_timeLimit WRITE set_timeLimit RESET reset_timeLimit STORED false) - BR_PROPERTY(bool, autoRoot, false) - BR_PROPERTY(int, depth, INT_MAX) - BR_PROPERTY(bool, depthFirst, false) - BR_PROPERTY(int, images, INT_MAX) - BR_PROPERTY(bool, json, false) - BR_PROPERTY(int, timeLimit, INT_MAX) - - QTime elapsed; - TemplateList templates; - - void crawl(QFileInfo url, int currentDepth = 0) - { - if ((templates.size() >= images) || (currentDepth >= depth) || (elapsed.elapsed()/1000 >= timeLimit)) - return; - - if (url.filePath().startsWith("file://")) - url = QFileInfo(url.filePath().mid(7)); - - if (url.isDir()) { - const QDir dir(url.absoluteFilePath()); - const QFileInfoList files = dir.entryInfoList(QDir::Files); - const QFileInfoList subdirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QFileInfo &first, depthFirst ? subdirs : files) - crawl(first, currentDepth + 1); - foreach (const QFileInfo &second, depthFirst ? files : subdirs) - crawl(second, currentDepth + 1); - } else if (url.isFile()) { - const QString suffix = url.suffix(); - if ((suffix == "bmp") || (suffix == "jpg") || (suffix == "jpeg") || (suffix == "png") || (suffix == "tiff")) { - File f; - if (json) f.set("URL", "file://"+url.canonicalFilePath()); - else f.name = "file://"+url.canonicalFilePath(); - templates.append(f); - } - } - } - - void init() - { - elapsed.start(); - const QString root = file.name.mid(0, file.name.size()-6); // Remove .crawl suffix"; - if (!root.isEmpty()) { - crawl(root); - } else { - if (autoRoot) { - foreach (const QString &path, QStandardPaths::standardLocations(QStandardPaths::HomeLocation)) - crawl(path); - } else { - QFile file; - file.open(stdin, QFile::ReadOnly); - while (!file.atEnd()) { - const QString url = QString::fromLocal8Bit(file.readLine()).simplified(); - if (!url.isEmpty()) - crawl(url); - } - } - } - } - - TemplateList readBlock(bool *done) - { - *done = true; - return templates; - } - - void write(const Template &) - { - qFatal("Not supported"); - } -}; - -BR_REGISTER(Gallery, crawlGallery) - -/*! - * \ingroup galleries - * \brief Treats the gallery as a br::Format. - * \author Josh Klontz \cite jklontz - */ -class DefaultGallery : public Gallery -{ - Q_OBJECT - - TemplateList readBlock(bool *done) - { - *done = true; - return TemplateList() << file; - } - - void write(const Template &t) - { - QScopedPointer format(Factory::make(file)); - format->write(t); - } - - qint64 totalSize() - { - return 1; - } -}; - -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 initializers - * \brief Initialization support for memGallery. - * \author Josh Klontz \cite jklontz - */ -class MemoryGalleries : public Initializer -{ - Q_OBJECT - - void initialize() const {} - - void finalize() const - { - galleries.clear(); - } - -public: - static QHash galleries; /*!< TODO */ -}; - -QHash MemoryGalleries::galleries; - -BR_REGISTER(Initializer, MemoryGalleries) - -/*! - * \ingroup galleries - * \brief A gallery held in memory. - * \author Josh Klontz \cite jklontz - */ -class memGallery : public Gallery -{ - Q_OBJECT - int block; - qint64 gallerySize; - - void init() - { - block = 0; - File galleryFile = file.name.mid(0, file.name.size()-4); - if ((galleryFile.suffix() == "gal") && galleryFile.exists() && !MemoryGalleries::galleries.contains(file)) { - QSharedPointer gallery(Factory::make(galleryFile)); - MemoryGalleries::galleries[file] = gallery->read(); - gallerySize = MemoryGalleries::galleries[file].size(); - } - } - - TemplateList readBlock(bool *done) - { - TemplateList templates = MemoryGalleries::galleries[file].mid(block*readBlockSize, readBlockSize); - for (qint64 i = 0; i < templates.size();i++) { - templates[i].file.set("progress", i + block * readBlockSize); - } - - *done = (templates.size() < readBlockSize); - block = *done ? 0 : block+1; - return templates; - } - - void write(const Template &t) - { - MemoryGalleries::galleries[file].append(t); - } - - qint64 totalSize() - { - return gallerySize; - } - - qint64 position() - { - return block * readBlockSize; - } - -}; - -BR_REGISTER(Gallery, memGallery) - -FileList FileList::fromGallery(const File &rFile, bool cache) -{ - File file = rFile; - file.remove("append"); - - File targetMeta = file; - targetMeta.name = targetMeta.path() + targetMeta.baseName() + "_meta" + targetMeta.hash() + ".mem"; - - FileList fileData; - - // Did we already read the data? - if (MemoryGalleries::galleries.contains(targetMeta)) - { - return MemoryGalleries::galleries[targetMeta].files(); - } - - TemplateList templates; - // OK we read the data in some form, does the gallery type containing matrices? - if ((QStringList() << "gal" << "mem" << "template" << "ut").contains(file.suffix())) { - // Retrieve it block by block, dropping matrices from read templates. - QScopedPointer gallery(Gallery::make(file)); - gallery->set_readBlockSize(10); - bool done = false; - while (!done) - { - TemplateList tList = gallery->readBlock(&done); - for (int i=0; i < tList.size();i++) - { - tList[i].clear(); - templates.append(tList[i].file); - } - } - } - else { - // this is a gallery format that doesn't include matrices, so we can just read it - QScopedPointer gallery(Gallery::make(file)); - templates= gallery->read(); - } - - if (cache) - { - QScopedPointer memOutput(Gallery::make(targetMeta)); - memOutput->writeBlock(templates); - } - fileData = templates.files(); - return fileData; -} - -/*! - * \ingroup galleries - * \brief Treats each line as a file. - * \author Josh Klontz \cite jklontz - * - * Columns should be comma separated with first row containing headers. - * The first column in the file should be the path to the file to enroll. - * Other columns will be treated as file metadata. - * - * \see txtGallery - */ -class csvGallery : public FileGallery -{ - Q_OBJECT - Q_PROPERTY(int fileIndex READ get_fileIndex WRITE set_fileIndex RESET reset_fileIndex) - BR_PROPERTY(int, fileIndex, 0) - - FileList files; - QStringList headers; - - ~csvGallery() - { - f.close(); - - if (files.isEmpty()) return; - - QMap samples; - foreach (const File &file, files) - foreach (const QString &key, file.localKeys()) - if (!samples.contains(key)) - samples.insert(key, file.value(key)); - - // Don't create columns in the CSV for these special fields - samples.remove("Points"); - samples.remove("Rects"); - - QStringList lines; - lines.reserve(files.size()+1); - - QMap columnCounts; - - { // Make header - QStringList words; - words.append("File"); - foreach (const QString &key, samples.keys()) { - int count = 0; - words.append(getCSVElement(key, samples[key], true, count)); - columnCounts.insert(key, count); - } - lines.append(words.join(",")); - } - - // Make table - foreach (const File &file, files) { - QStringList words; - words.append(file.name); - foreach (const QString &key, samples.keys()) { - int count = columnCounts[key]; - words.append(getCSVElement(key, file.value(key), false, count)); - } - lines.append(words.join(",")); - } - - QtUtils::writeFile(file, lines); - } - - TemplateList readBlock(bool *done) - { - readOpen(); - *done = false; - TemplateList templates; - if (!file.exists()) { - *done = true; - return templates; - } - QRegExp regexp("\\s*,\\s*"); - - if (f.pos() == 0) - { - // read a line - QByteArray lineBytes = f.readLine(); - QString line = QString::fromLocal8Bit(lineBytes).trimmed(); - headers = line.split(regexp); - } - - for (qint64 i = 0; i < this->readBlockSize && !f.atEnd(); i++){ - QByteArray lineBytes = f.readLine(); - QString line = QString::fromLocal8Bit(lineBytes).trimmed(); - - QStringList words = line.split(regexp); - if (words.size() != headers.size()) continue; - File fi; - for (int j=0; j()) { - if (header) return key; - else { - if (columnCount != 1) - qFatal("Inconsistent datatype for key %s, csv file cannot be generated", qPrintable(key)); - return value.value(); - } - } else if (value.canConvert()) { - const QPointF point = value.value(); - if (header) { - columnCount = 2; - return key+"_X,"+key+"_Y"; - } - else { - if (columnCount != 2) - qFatal("Inconsistent datatype for key %s, csv file cannot be generated", qPrintable(key)); - - return QString::number(point.x())+","+QString::number(point.y()); - } - } else if (value.canConvert()) { - const QRectF rect = value.value(); - if (header) { - columnCount = 4; - return key+"_X,"+key+"_Y,"+key+"_Width,"+key+"_Height"; - } - else { - if (columnCount != 4) - qFatal("Inconsistent datatype for key %s, csv file cannot be generated", qPrintable(key)); - - return QString::number(rect.x())+","+QString::number(rect.y())+","+QString::number(rect.width())+","+QString::number(rect.height()); - } - } else { - if (header) return key; - else { - QString output = QString::number(std::numeric_limits::quiet_NaN()); - for (int i = 1; i < columnCount; i++) - output += "," + QString::number(std::numeric_limits::quiet_NaN()); - return output; - } - } - } -}; - -BR_REGISTER(Gallery, csvGallery) - -/*! - * \ingroup galleries - * \brief Treats each line as a file. - * \author Josh Klontz \cite jklontz - * - * The entire line is treated as the file path. An optional label may be specified using a space ' ' separator: - * -\verbatim - - -... - -\endverbatim - * or -\verbatim -