diff --git a/CHANGELOG.md b/CHANGELOG.md index f30a05a..0313c69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Enrolling files/folders are now sorted naturally instead of alpha numerically * YouTubeFacesDBTransform implements Dr. Wolf's experimental protocol * NEC3 refactored +* Updated transform API to add support for time-varying transforms per issue (#23) +* Refactored File class to improve point and rect storage (#22) 0.2.0 - 2/23/13 =============== diff --git a/app/br/CMakeLists.txt b/app/br/CMakeLists.txt index 7788e24..fc37e7f 100644 --- a/app/br/CMakeLists.txt +++ b/app/br/CMakeLists.txt @@ -5,3 +5,6 @@ endif() add_executable(br br.cpp ${BR_RESOURCES}) target_link_libraries(br openbr ${CMAKE_THREAD_LIBS_INIT}) install(TARGETS br RUNTIME DESTINATION bin) + +add_test(NAME br_initialize WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND br) +add_test(NAME br_objects WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND br -objects) diff --git a/app/examples/age_estimation.cpp b/app/examples/age_estimation.cpp index fa55ade..2b2b97b 100644 --- a/app/examples/age_estimation.cpp +++ b/app/examples/age_estimation.cpp @@ -31,7 +31,7 @@ static void printTemplate(const br::Template &t) { printf("%s age: %d\n", qPrintable(t.file.fileName()), - t.file.getInt("Label")); + t.file.get("Label")); } int main(int argc, char *argv[]) diff --git a/app/examples/face_recognition.cpp b/app/examples/face_recognition.cpp index 48b0c4b..d1d7cbc 100644 --- a/app/examples/face_recognition.cpp +++ b/app/examples/face_recognition.cpp @@ -30,10 +30,9 @@ static void printTemplate(const br::Template &t) { - printf("%s eyes: (%d, %d) (%d, %d)\n", - qPrintable(t.file.fileName()), - t.file.getInt("Affine_0_X"), t.file.getInt("Affine_0_Y"), - t.file.getInt("Affine_1_X"), t.file.getInt("Affine_1_Y")); + const QPoint firstEye = t.file.get("Affine_0"); + const QPoint secondEye = t.file.get("Affine_1"); + printf("%s eyes: (%d, %d) (%d, %d)\n", qPrintable(t.file.fileName()), firstEye.x(), firstEye.y(), secondEye.x(), secondEye.y()); } int main(int argc, char *argv[]) diff --git a/app/examples/face_recognition_evaluation.cpp b/app/examples/face_recognition_evaluation.cpp index 9b70abb..e861b7b 100644 --- a/app/examples/face_recognition_evaluation.cpp +++ b/app/examples/face_recognition_evaluation.cpp @@ -43,9 +43,9 @@ int main(int argc, char *argv[]) // Equivalent to 'Globals->path = "../data/MEDS/img/";' in C++ API br_set_property("path", "../data/MEDS/img/"); - // Enroll galleries - br_enroll("../data/MEDS/sigset/MEDS_frontal_target.xml", "target.gal"); - br_enroll("../data/MEDS/sigset/MEDS_frontal_query.xml", "query.gal"); + // Enroll galleries, don't re-enroll if they already exist (cache) + br_enroll("../data/MEDS/sigset/MEDS_frontal_target.xml", "target.gal[cache]"); + br_enroll("../data/MEDS/sigset/MEDS_frontal_query.xml", "query.gal[cache]"); // Compare galleries and store result in a binary similarity matrix br_compare("target.gal", "query.gal", "FaceRecognition_MEDS.mtx"); diff --git a/app/examples/face_recognition_search.cpp b/app/examples/face_recognition_search.cpp new file mode 100644 index 0000000..9d3ae36 --- /dev/null +++ b/app/examples/face_recognition_search.cpp @@ -0,0 +1,60 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * 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. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + * \ingroup cli + * \page cli_face_recognition_search Face Recognition Search + * \ref cpp_face_recognition_search "C++ Equivalent" + * \code + * $ br -algorithm FaceRecognition -enrollAll -enroll ../data/MEDS/img 'meds.gal;meds.csv[separator=;]' + * $ br -algorithm FaceRecognition -compare meds.gal ../data/MEDS/img/S001-01-t10_01.jpg match_scores.csv + * \endcode + */ + +//! [face_recognition_search] +#include + +int main(int argc, char *argv[]) +{ + br::Context::initialize(argc, argv); + + // Retrieve classes for enrolling and comparing templates using the FaceRecognition algorithm + QSharedPointer transform = br::Transform::fromAlgorithm("FaceRecognition"); + QSharedPointer distance = br::Distance::fromAlgorithm("FaceRecognition"); + + // Initialize templates + br::TemplateList target = br::TemplateList::fromGallery("../data/MEDS/img"); + br::Template query("../data/MEDS/img/S001-01-t10_01.jpg"); + + // Enroll templates + br::Globals->enrollAll = true; // Enroll 0 or more faces per image + target >> *transform; + br::Globals->enrollAll = false; // Enroll exactly one face per image + query >> *transform; + + // Compare templates + QList scores = distance->compare(target, query); + + // Print an example score + printf("Images %s and %s have a match score of %.3f\n", + qPrintable(target[3].file.name), + qPrintable(query.file.name), + scores[3]); + + br::Context::finalize(); + return 0; +} +//! [face_recognition_search] diff --git a/app/examples/gender_estimation.cpp b/app/examples/gender_estimation.cpp index fd88eea..6987707 100644 --- a/app/examples/gender_estimation.cpp +++ b/app/examples/gender_estimation.cpp @@ -31,7 +31,7 @@ static void printTemplate(const br::Template &t) { printf("%s gender: %s\n", qPrintable(t.file.fileName()), - t.file.getInt("Label") == 1 ? "Female" : "Male"); + t.file.get("Label") == 1 ? "Female" : "Male"); } int main(int argc, char *argv[]) diff --git a/app/openbr-gui/classifier.cpp b/app/openbr-gui/classifier.cpp index 481950c..8b65e52 100644 --- a/app/openbr-gui/classifier.cpp +++ b/app/openbr-gui/classifier.cpp @@ -39,19 +39,18 @@ void Classifier::_classify(File file) { QString key, value; foreach (const File &f, Enroll(file.flat(), File("[algorithm=" + algorithm + "]"))) { - qDebug() << f.flat(); if (!f.contains("Label")) continue; if (algorithm == "GenderClassification") { key = "Gender"; - value = (f.getInt("Label", 0) == 0 ? "Male" : "Female"); + value = (f.get("Label", 0) == 0 ? "Male" : "Female"); } else if (algorithm == "AgeRegression") { key = "Age"; - value = QString::number(int(f.getFloat("Label", 0)+0.5)) + " Years"; + value = QString::number(int(f.get("Label", 0)+0.5)) + " Years"; } else { key = algorithm; - value = f.getString("Label"); + value = f.get("Label"); } break; } diff --git a/app/openbr-gui/gallerytoolbar.cpp b/app/openbr-gui/gallerytoolbar.cpp index 87dd5cd..79855e0 100644 --- a/app/openbr-gui/gallerytoolbar.cpp +++ b/app/openbr-gui/gallerytoolbar.cpp @@ -117,7 +117,7 @@ void br::GalleryToolBar::checkWebcam() void br::GalleryToolBar::enrollmentFinished() { if (files.isEmpty()) { - if (input.getBool("enrollAll") && !tbWebcam.isChecked()) { + if (input.get("enrollAll", false) && !tbWebcam.isChecked()) { QMessageBox msgBox; msgBox.setText("Quality test failed."); msgBox.setInformativeText("Enroll anyway?"); @@ -127,7 +127,7 @@ void br::GalleryToolBar::enrollmentFinished() if (ret == QMessageBox::Ok) { br::File file = input; - file.setBool("enrollAll", false); + file.set("enrollAll", false); enroll(file); } } diff --git a/app/openbr-gui/templatemetadata.cpp b/app/openbr-gui/templatemetadata.cpp index ee5fb9d..1c4b8b3 100644 --- a/app/openbr-gui/templatemetadata.cpp +++ b/app/openbr-gui/templatemetadata.cpp @@ -28,7 +28,7 @@ void br::TemplateMetadata::setFile(const br::File &file) { if (file.isNull()) lFile.clear(); else lFile.setText("File: " + file.fileName()); - lQuality.setText(QString("Quality: %1").arg(file.getBool("FTE") ? "Low" : "High")); + lQuality.setText(QString("Quality: %1").arg(file.get("FTE", false) ? "Low" : "High")); foreach (const ConditionalClassifier &classifier, conditionalClassifiers) if (classifier.action->isVisible()) classifier.classifier->classify(file); } diff --git a/app/openbr-gui/templateviewer.cpp b/app/openbr-gui/templateviewer.cpp index c3b1b88..7c5b0af 100644 --- a/app/openbr-gui/templateviewer.cpp +++ b/app/openbr-gui/templateviewer.cpp @@ -39,10 +39,8 @@ void TemplateViewer::setFile(const File &file_) // Update landmarks landmarks.clear(); - if (file.contains("Affine_0_X") && file.contains("Affine_0_Y")) - landmarks.append(QPointF(file.getFloat("Affine_0_X"), file.getFloat("Affine_0_Y"))); - if (file.contains("Affine_1_X") && file.contains("Affine_1_Y")) - landmarks.append(QPointF(file.getFloat("Affine_1_X"), file.getFloat("Affine_1_Y"))); + if (file.contains("Affine_0")) landmarks.append(file.get("Affine_0")); + if (file.contains("Affine_1")) landmarks.append(file.get("Affine_1")); while (landmarks.size() < NumLandmarks) landmarks.append(QPointF()); nearestLandmark = -1; diff --git a/sdk/core/bee.cpp b/sdk/core/bee.cpp index 9016f6c..1bbca0d 100644 --- a/sdk/core/bee.cpp +++ b/sdk/core/bee.cpp @@ -71,7 +71,7 @@ FileList BEE::readSigset(const QString &sigset, bool ignoreMetadata) newFile.append(file); file = newFile; } else if (!ignoreMetadata) { - file.insert(key, value); + file.set(key, value); } } @@ -99,7 +99,7 @@ void BEE::writeSigset(const QString &sigset, const br::FileList &files, bool ign QStringList metadata; if (!ignoreMetadata) foreach (const QString &key, file.localKeys()) - metadata.append(key+"=\""+file.getString(key, "?")+"\""); + metadata.append(key+"=\""+file.get(key, "?")+"\""); lines.append("\t"); lines.append("\t\t"); lines.append("\t"); @@ -113,19 +113,19 @@ Mat readMatrix(const br::File &matrix) { // Special case matrix construction if (matrix == "Identity") { - int rows = matrix.getInt("rows", -1); - int columns = matrix.getInt("columns", -1); - const int size = matrix.getInt("size", -1); + int rows = matrix.get("rows", -1); + int columns = matrix.get("columns", -1); + const int size = matrix.get("size", -1); if (size != -1) { if (rows == -1) rows = size; if (columns == -1) columns = size; } - const int step = matrix.getInt("step", 1); + const int step = matrix.get("step", 1); if (rows % step != 0) qFatal("Step does not divide rows evenly."); if (columns % step != 0) qFatal("Step does not divide columns evenly."); if (sizeof(T) == sizeof(BEE::Mask_t)) { - const bool selfSimilar = matrix.getBool("selfSimilar"); + const bool selfSimilar = matrix.get("selfSimilar", false); Mat m(rows, columns, CV_8UC1); m.setTo(BEE::NonMatch); @@ -171,8 +171,8 @@ Mat readMatrix(const br::File &matrix) file.close(); Mat result; - if (isDistance ^ matrix.getBool("negate")) m.convertTo(result, -1, -1); - else result = m.clone(); + if (isDistance ^ matrix.get("negate", false)) m.convertTo(result, -1, -1); + else result = m.clone(); return result; } diff --git a/sdk/core/core.cpp b/sdk/core/core.cpp index d85f49d..713e2b4 100644 --- a/sdk/core/core.cpp +++ b/sdk/core/core.cpp @@ -189,12 +189,14 @@ struct AlgorithmCore enroll(file); gallery.reset(Gallery::make(getMemoryGallery(file))); galleryFiles = gallery->files(); + + qDebug() << galleryFiles; } } void compare(File targetGallery, File queryGallery, File output) { - if (output.exists() && output.getBool("cache")) return; + if (output.exists() && output.get("cache", false)) return; if (queryGallery == ".") queryGallery = targetGallery; QScopedPointer t, q; @@ -250,7 +252,7 @@ private: if (!file.isEmpty()) description = file; if (QFileInfo(description).exists()) { - if (Globals->verbose) qDebug("Loading %s", qPrintable(QFileInfo(description).fileName())); + qDebug("Loading %s", qPrintable(QFileInfo(description).fileName())); load(description); return; } @@ -259,7 +261,7 @@ private: if (Globals->abbreviations.contains(description)) return init(Globals->abbreviations[description]); - QStringList words = description.split(':'); + QStringList words = QtUtils::parse(description, ':'); if (words.size() > 2) qFatal("Invalid algorithm format."); transform = QSharedPointer(Transform::make(words[0], NULL)); @@ -316,14 +318,14 @@ void br::Train(const File &input, const File &model) { qDebug("Training on %s%s", qPrintable(input.flat()), model.isNull() ? "" : qPrintable(" to " + model.flat())); - AlgorithmManager::getAlgorithm(model.getString("algorithm"))->train(input, model); + AlgorithmManager::getAlgorithm(model.get("algorithm"))->train(input, model); } FileList br::Enroll(const File &input, const File &gallery) { qDebug("Enrolling %s%s", qPrintable(input.flat()), gallery.isNull() ? "" : qPrintable(" to " + gallery.flat())); - return AlgorithmManager::getAlgorithm(gallery.getString("algorithm"))->enroll(input, gallery); + return AlgorithmManager::getAlgorithm(gallery.get("algorithm"))->enroll(input, gallery); } void br::Compare(const File &targetGallery, const File &queryGallery, const File &output) @@ -331,7 +333,7 @@ void br::Compare(const File &targetGallery, const File &queryGallery, const File qDebug("Comparing %s and %s%s", qPrintable(targetGallery.flat()), qPrintable(queryGallery.flat()), output.isNull() ? "" : qPrintable(" to " + output.flat())); - AlgorithmManager::getAlgorithm(output.getString("algorithm"))->compare(targetGallery, queryGallery, output); + AlgorithmManager::getAlgorithm(output.get("algorithm"))->compare(targetGallery, queryGallery, output); } void br::Convert(const File &src, const File &dst) diff --git a/sdk/core/plot.cpp b/sdk/core/plot.cpp index 53982a7..9f0872a 100644 --- a/sdk/core/plot.cpp +++ b/sdk/core/plot.cpp @@ -119,8 +119,8 @@ float Evaluate(const QString &simmat, const QString &mask, const QString &csv) // Read files const Mat scores = BEE::readSimmat(simmat); File maskFile(mask); - maskFile.insert("rows", scores.rows); - maskFile.insert("columns", scores.cols); + maskFile.set("rows", scores.rows); + maskFile.set("columns", scores.cols); const Mat masks = BEE::readMask(maskFile); if (scores.size() != masks.size()) qFatal("Simmat/Mask size mismatch."); @@ -143,6 +143,7 @@ float Evaluate(const QString &simmat, const QString &mask, const QString &csv) if (genuineCount == 0) qFatal("No genuine scores!"); if (impostorCount == 0) qFatal("No impostor scores!"); + // Sort comparisons by simmat_val (score) std::sort(comparisons.begin(), comparisons.end()); double genuineSum = 0, impostorSum = 0; @@ -155,8 +156,10 @@ float Evaluate(const QString &simmat, const QString &mask, const QString &csv) int index = 0; float minGenuineScore = std::numeric_limits::max(); float minImpostorScore = std::numeric_limits::max(); + while (index < comparisons.size()) { float thresh = comparisons[index].score; + // Compute genuine and imposter statistics at a threshold while ((index < comparisons.size()) && (comparisons[index].score == thresh)) { const Comparison &comparison = comparisons[index]; @@ -205,7 +208,7 @@ float Evaluate(const QString &simmat, const QString &mask, const QString &csv) lines.append("Metadata,"+QString::number(impostorCount)+",Impostor"); lines.append("Metadata,"+QString::number(scores.cols*scores.rows-(genuineCount+impostorCount))+",Ignored"); - // Write DET, PRE, REC + // Write Detection Error Tradeoff (DET), PRE, REC int points = qMin(operatingPoints.size(), Max_Points); for (int i=0; i("smooth", ""); major.smooth = !smooth.isEmpty() && (major.header == smooth) && (major.size > 1); minor.smooth = !smooth.isEmpty() && (minor.header == smooth) && (minor.size > 1); if (major.smooth) major.size = 1; diff --git a/sdk/openbr_plugin.cpp b/sdk/openbr_plugin.cpp index 9fffe4b..d54f016 100644 --- a/sdk/openbr_plugin.cpp +++ b/sdk/openbr_plugin.cpp @@ -58,10 +58,10 @@ QString File::hash() const return QtUtils::shortTextHash(flat()); } -void File::append(const QHash &metadata) +void File::append(const QMap &metadata) { foreach (const QString &key, metadata.keys()) - insert(key, metadata[key]); + set(key, metadata[key]); } void File::append(const File &other) @@ -70,7 +70,7 @@ void File::append(const File &other) if (name.isEmpty()) { name = other.name; } else { - if (!contains("separator")) insert("separator", ";"); + if (!contains("separator")) set("separator", ";"); name += value("separator").toString() + other.name; } } @@ -110,29 +110,6 @@ QVariant File::value(const QString &key) const return m_metadata.contains(key) ? m_metadata.value(key) : Globals->property(qPrintable(key)); } -QString File::subject(int label) -{ - return Globals->classes.key(label, QString::number(label)); -} - -float File::label() const -{ - const QVariant variant = value("Label"); - if (variant.isNull()) return -1; - - if (Globals->classes.contains(variant.toString())) - return Globals->classes.value(variant.toString()); - - bool ok; - const float val = variant.toFloat(&ok); - return ok ? val : -1; -} - -void File::remove(const QString &key) -{ - m_metadata.remove(key); -} - void File::set(const QString &key, const QVariant &value) { if (key == "Label") { @@ -153,155 +130,90 @@ void File::set(const QString &key, const QVariant &value) m_metadata.insert(key, value); } -QVariant File::get(const QString &key) const -{ - if (!contains(key)) qFatal("Missing key: %s", qPrintable(key)); - return value(key); -} - -QVariant File::get(const QString &key, const QVariant &defaultValue) const -{ - if (!contains(key)) return defaultValue; - return value(key); -} - -bool File::getBool(const QString &key) const -{ - if (!contains(key)) return false; - QString v = value(key).toString(); - if (v.isEmpty() || (v == "true")) return true; - if (v == "false") return false; - return v.toInt(); -} - -void File::setBool(const QString &key, bool value) -{ - if (value) m_metadata.insert(key, QVariant()); - else m_metadata.remove(key); -} - -int File::getInt(const QString &key) const -{ - if (!contains(key)) qFatal("Missing key: %s", qPrintable(key)); - bool ok; int result = value(key).toInt(&ok); - if (!ok) qFatal("Invalid conversion from: %s", qPrintable(getString(key))); - return result; -} - -int File::getInt(const QString &key, int defaultValue) const -{ - if (!contains(key)) return defaultValue; - bool ok; int result = value(key).toInt(&ok); - if (!ok) return defaultValue; - return result; -} - -float File::getFloat(const QString &key) const -{ - if (!contains(key)) qFatal("Missing key: %s", qPrintable(key)); - bool ok; float result = value(key).toFloat(&ok); - if (!ok) qFatal("Invalid conversion from: %s", qPrintable(getString(key))); - return result; -} - -float File::getFloat(const QString &key, float defaultValue) const +QString File::subject(int label) { - if (!contains(key)) return defaultValue; - bool ok; float result = value(key).toFloat(&ok); - if (!ok) return defaultValue; - return result; + return Globals->classes.key(label, QString::number(label)); } -QString File::getString(const QString &key) const +float File::label() const { - if (!contains(key)) qFatal("Missing key: %s", qPrintable(key)); - return value(key).toString(); -} + const QVariant variant = value("Label"); + if (variant.isNull()) return -1; -QString File::getString(const QString &key, const QString &defaultValue) const -{ - if (!contains(key)) return defaultValue; - return value(key).toString(); -} + if (Globals->classes.contains(variant.toString())) + return Globals->classes.value(variant.toString()); -QList File::landmarks() const -{ - QList landmarks; - foreach (const QVariant &landmark, value("Landmarks").toList()) - landmarks.append(landmark.toPointF()); - return landmarks; + bool ok; + const float val = variant.toFloat(&ok); + return ok ? val : -1; } -QList File::namedLandmarks() const +QList File::namedPoints() const { QList landmarks; - QStringList keys = localMetadata().keys(); - foreach (const QString &key, keys) { - if (!key.endsWith("_X")) - continue; - QString keyBaseName = key.left(key.size()-2); - if (!keys.contains(keyBaseName+"_Y") || - keys.contains(keyBaseName+"_Width") || - keys.contains(keyBaseName+"_Height") || - keys.contains(keyBaseName+"_Radius")) - continue; - landmarks.append(QPointF(getFloat(keyBaseName+"_X"), getFloat(keyBaseName+"_Y"))); + foreach (const QString &key, localMetadata().keys()) { + const QVariant &variant = m_metadata[key]; + if (variant.canConvert()) + landmarks.append(variant.value()); } return landmarks; } -void File::appendLandmark(const QPointF &landmark) +QList File::points() const { - QList newLandmarks = m_metadata["Landmarks"].toList(); - newLandmarks.append(landmark); - m_metadata["Landmarks"] = newLandmarks; + QList points; + foreach (const QVariant &point, m_metadata["Points"].toList()) + points.append(point.toPointF()); + return points; } -void File::appendLandmarks(const QList &landmarks) +void File::appendPoint(const QPointF &point) { - QList newLandmarks = m_metadata["Landmarks"].toList(); - foreach (const QPointF &landmark, landmarks) - newLandmarks.append(landmark); - m_metadata["Landmarks"] = newLandmarks; + QList newPoints = m_metadata["Points"].toList(); + newPoints.append(point); + m_metadata["Points"] = newPoints; } -void File::setLandmarks(const QList &landmarks) +void File::appendPoints(const QList &points) { - QList landmarkList; landmarkList.reserve(landmarks.size()); - foreach (const QPointF &landmark, landmarks) - landmarkList.append(landmark); - m_metadata["Landmarks"] = landmarkList; + QList newPoints = m_metadata["Points"].toList(); + foreach (const QPointF &point, points) + newPoints.append(point); + m_metadata["Points"] = newPoints; } -QList File::ROIs() const +QList File::namedRects() const { - QList ROIs; - foreach (const QVariant &ROI, value("ROIs").toList()) - ROIs.append(ROI.toRect()); - return ROIs; + QList rects; + foreach (const QString &key, localMetadata().keys()) { + const QVariant &variant = m_metadata[key]; + if (variant.canConvert()) + rects.append(variant.value()); + } + return rects; } -void File::appendROI(const QRectF &ROI) +QList File::rects() const { - QList newROIs = m_metadata["ROIs"].toList(); - newROIs.append(ROI); - m_metadata["ROIs"] = newROIs; + QList rects; + foreach (const QVariant &rect, m_metadata["Rects"].toList()) + rects.append(rect.toRect()); + return rects; } -void File::appendROIs(const QList &ROIs) +void File::appendRect(const QRectF &rect) { - QList newROIs = m_metadata["ROIs"].toList(); - foreach (const QRectF &ROI, ROIs) - newROIs.append(ROI); - m_metadata["ROIs"] = newROIs; + QList newRects = m_metadata["Rects"].toList(); + newRects.append(rect); + m_metadata["Rects"] = newRects; } -void File::setROIs(const QList &ROIs) +void File::appendRects(const QList &rects) { - QList ROIList; ROIList.reserve(ROIs.size()); - foreach (const QRectF &ROI, ROIs) - ROIList.append(ROI); - m_metadata["ROIs"] = ROIList; + QList newRects = m_metadata["Rects"].toList(); + foreach (const QRectF &rect, rects) + newRects.append(rect); + m_metadata["Rects"] = newRects; } /* File - private methods */ @@ -325,10 +237,10 @@ void File::init(const QString &file) QStringList words = QtUtils::parse(parameters[i], '='); QtUtils::checkArgsSize("File", words, 1, 2); if (words.size() < 2) { - if (unnamed) insertParameter(i, words[0]); - else insert(words[0], QVariant()); + if (unnamed) setParameter(i, words[0]); + else set(words[0], QVariant()); } else { - insert(words[0], words[1]); + set(words[0], words[1]); } } name = name.left(index); @@ -391,9 +303,8 @@ void FileList::sort(const QString& key) FileList sortedList; for (int i = 0; i < size(); i++) { - if (at(i).contains(key)) { - metadata.append(at(i).get(key).toString()); - } + if (at(i).contains(key)) + metadata.append(at(i).get(key)); else sortedList.push_back(at(i)); } @@ -416,7 +327,7 @@ QList FileList::crossValidationPartitions() const { QList crossValidationPartitions; crossValidationPartitions.reserve(size()); foreach (const File &f, *this) - crossValidationPartitions.append(f.getInt("Cross_Validation_Partition", 0)); + crossValidationPartitions.append(f.get("Cross_Validation_Partition", 0)); return crossValidationPartitions; } @@ -424,7 +335,7 @@ int FileList::failures() const { int failures = 0; foreach (const File &file, *this) - if (file.getBool("FTO") || file.getBool("FTE")) + if (file.get("FTO", false) || file.get("FTE", false)) failures++; return failures; } @@ -447,9 +358,9 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) foreach (const br::File &file, gallery.split()) { QScopedPointer i(Gallery::make(file)); TemplateList newTemplates = i->read(); - newTemplates = newTemplates.mid(gallery.getInt("pos", 0), gallery.getInt("length", -1)); - if (gallery.getBool("reduce")) newTemplates = newTemplates.reduced(); - const int crossValidate = gallery.getInt("crossValidate"); + newTemplates = newTemplates.mid(gallery.get("pos", 0), gallery.get("length", -1)); + if (gallery.get("reduce", false)) newTemplates = newTemplates.reduced(); + const int crossValidate = gallery.get("crossValidate"); if (crossValidate > 0) srand(0); // If file is a Format not a Gallery @@ -460,11 +371,11 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) for (int i=0; i 0) newTemplates[i].file.insert("Cross_Validation_Partition", rand()%crossValidate); + newTemplates[i].file.set("Index", i+templates.size()); + if (crossValidate > 0) newTemplates[i].file.set("Cross_Validation_Partition", rand()%crossValidate); } - if (!templates.isEmpty() && gallery.getBool("merge")) { + if (!templates.isEmpty() && gallery.get("merge", false)) { if (newTemplates.size() != templates.size()) qFatal("Inputs must be the same size in order to merge."); for (int i=0; ipropertyOffset(); ipropertyCount(); i++) { + + for (int i = firstAvailablePropertyIdx; i < metaObject()->propertyCount();i++) { QMetaProperty property = metaObject()->property(i); if (property.isStored(this)) continue; parameters.append(QString("%1 %2 = %3").arg(property.typeName(), property.name(), property.read(this).toString())); @@ -713,18 +625,37 @@ void Object::init(const File &file_) // Set name QString name = metaObject()->className(); if (name.startsWith("br::")) name = name.right(name.size()-4); - const QMetaObject *superClass = metaObject()->superClass(); + + firstAvailablePropertyIdx = metaObject()->propertyCount(); + + const QMetaObject * baseClass = metaObject(); + const QMetaObject * superClass = metaObject()->superClass(); + while (superClass != NULL) { + const QMetaObject * nextClass = superClass->superClass(); + + // baseClass <- something <- br::Object + // baseClass is the highest class whose properties we can set via positional arguments + if (nextClass && !strcmp(nextClass->className(),"br::Object")) { + firstAvailablePropertyIdx = baseClass->propertyOffset(); + } + QString superClassName = superClass->className(); + + // strip br:: prefix from superclass name if (superClassName.startsWith("br::")) superClassName = superClassName.right(superClassName.size()-4); + + // Strip superclass name from base class name (e.g. PipeTransform -> Pipe) if (name.endsWith(superClassName)) name = name.left(name.size() - superClassName.size()); + baseClass = superClass; superClass = superClass->superClass(); + } setObjectName(name); - // Set properties + // Reset all properties for (int i=0; ipropertyCount(); i++) { QMetaProperty property = metaObject()->property(i); if (property.isResettable()) @@ -734,8 +665,17 @@ void Object::init(const File &file_) foreach (QString key, file.localKeys()) { const QString value = file.value(key).toString(); - if (key.startsWith("_Arg")) - key = metaObject()->property(metaObject()->propertyOffset()+key.mid(4).toInt()).name(); + + if (key.startsWith(("_Arg"))) { + int argument_number = key.mid(4).toInt(); + int target_idx = argument_number + firstAvailablePropertyIdx; + + if (target_idx >= metaObject()->propertyCount()) { + qWarning("too many arguments for transform, ignoring %s\n", qPrintable(value)); + continue; + } + key = metaObject()->property(target_idx).name(); + } setProperty(key, value); } @@ -1107,7 +1047,7 @@ static TemplateList Downsample(const TemplateList &templates, const Transform *t const int selectedLabel = selectedLabels[i]; QList indices; for (int j=0; j("FTE", false))) indices.append(j); std::random_shuffle(indices.begin(), indices.end()); @@ -1291,7 +1231,7 @@ static void _project(const Transform *transform, const Template *src, Template * } catch (...) { qWarning("Exception triggered when processing %s with transform %s", qPrintable(src->file.flat()), qPrintable(transform->objectName())); *dst = Template(src->file); - dst->file.setBool("FTE"); + dst->file.set("FTE", true); } } @@ -1302,7 +1242,7 @@ static void _backProject(const Transform *transform, const Template *dst, Templa } catch (...) { qWarning("Exception triggered when processing %s with transform %s", qPrintable(src->file.flat()), qPrintable(transform->objectName())); *src = Template(dst->file); - src->file.setBool("FTE"); + src->file.set("FTE", true); } } diff --git a/sdk/openbr_plugin.h b/sdk/openbr_plugin.h index 33ed8d1..89cdd24 100644 --- a/sdk/openbr_plugin.h +++ b/sdk/openbr_plugin.h @@ -61,6 +61,7 @@ * * \section examples Examples * - \ref cpp_face_recognition + * - \ref cpp_face_recognition_search * - \ref cpp_age_estimation * - \ref cpp_gender_estimation * @@ -68,6 +69,10 @@ * \ref cli_face_recognition "Command Line Interface Equivalent" * \snippet app/examples/face_recognition.cpp face_recognition * + * \subsection cpp_face_recognition_search Face Recognition Search + * \ref cli_face_recognition_search "Command Line Interface Equivalent" + * \snippet app/examples/face_recognition_search.cpp face_recognition_search + * * \subsection cpp_age_estimation Age Estimation * \ref cli_age_estimation "Command Line Interface Equivalent" * \snippet app/examples/age_estimation.cpp age_estimation @@ -123,10 +128,8 @@ void reset_##NAME() { NAME = DEFAULT; } * * Key | Value | Description * --- | ---- | ----------- - * path | QString | Resolve complete file paths from file names - * enrollAll | bool | Enroll zero or more templates per file * separator | QString | Seperate #name into multiple files - * Index | int | Index of a template in a template list + * Index | int | Index of a template in a template list * Label | float | Classification/Regression class * Confidence | float | Classification/Regression quality * FTE | bool | Failure to enroll @@ -140,8 +143,8 @@ void reset_##NAME() { NAME = DEFAULT; } * Roll | float | Pose * Pitch | float | Pose * Yaw | float | Pose - * Landmarks | QList | Landmark list - * ROIs | QList | Region Of Interest (ROI) list + * Points | QList | List of unnamed points + * Rects | QList | List of unnamed rects * Age | QString | Age used for demographic filtering * _* | * | Reserved for internal use */ @@ -151,24 +154,26 @@ struct BR_EXPORT File File() {} File(const QString &file) { init(file); } /*!< \brief Construct a file from a string. */ - File(const QString &file, const QVariant &label) { init(file); insert("Label", label); } /*!< \brief Construct a file from a string and assign a label. */ + File(const QString &file, const QVariant &label) { init(file); set("Label", label); } /*!< \brief Construct a file from a string and assign a label. */ File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */ inline operator QString() const { return name; } /*!< \brief Returns #name. */ QString flat() const; /*!< \brief A stringified version of the file with metadata. */ QString hash() const; /*!< \brief A hash of the file. */ - inline void clear() { name.clear(); m_metadata.clear(); } /*!< \brief Clears the file's name and metadata. */ inline QList localKeys() const { return m_metadata.keys(); } /*!< \brief Returns the private metadata keys. */ - inline QHash localMetadata() const { return m_metadata; } /*!< \brief Returns the private metadata. */ - inline void insert(const QString &key, const QVariant &value) { set(key, value); } /*!< \brief Equivalent to set(). */ - void append(const QHash &localMetadata); /*!< \brief Add new metadata fields. */ + inline QMap localMetadata() const { return m_metadata; } /*!< \brief Returns the private metadata. */ + + void append(const QMap &localMetadata); /*!< \brief Add new metadata fields. */ void append(const File &other); /*!< \brief Append another file using \c separator. */ + inline File &operator+=(const QMap &other) { append(other); return *this; } /*!< \brief Add new metadata fields. */ + inline File &operator+=(const File &other) { append(other); return *this; } /*!< \brief Append another file using \c separator. */ + QList split() const; /*!< \brief Split the file using \c separator. */ QList split(const QString &separator) const; /*!< \brief Split the file. */ - inline void insertParameter(int index, const QVariant &value) { insert("_Arg" + QString::number(index), value); } /*!< \brief Insert a keyless value. */ - inline bool containsParameter(int index) const { return m_metadata.contains("_Arg" + QString::number(index)); } /*!< \brief Check for the existence of a keyless value. */ - inline QVariant parameter(int index) const { return m_metadata.value("_Arg" + QString::number(index)); } /*!< \brief Retrieve a keyless value. */ + inline void setParameter(int index, const QVariant &value) { set("_Arg" + QString::number(index), value); } /*!< \brief Insert a keyless value. */ + inline bool containsParameter(int index) const { return contains("_Arg" + QString::number(index)); } /*!< \brief Check for the existence of a keyless value. */ + inline QVariant getParameter(int index) const { return get("_Arg" + QString::number(index)); } /*!< \brief Retrieve a keyless value. */ inline bool operator==(const char* other) const { return name == other; } /*!< \brief Compare name to c-style string. */ inline bool operator==(const File &other) const { return (name == other.name) && (m_metadata == other.m_metadata); } /*!< \brief Compare name and metadata for equality. */ @@ -177,8 +182,6 @@ struct BR_EXPORT File inline bool operator<=(const File &other) const { return name <= other.name; } /*!< \brief Compare name. */ inline bool operator>(const File &other) const { return name > other.name; } /*!< \brief Compare name. */ inline bool operator>=(const File &other) const { return name >= other.name; } /*!< \brief Compare name. */ - inline File &operator+=(const QHash &other) { append(other); return *this; } /*!< \brief Add new metadata fields. */ - inline File &operator+=(const File &other) { append(other); return *this; } /*!< \brief Append another file using \c separator. */ inline bool isNull() const { return name.isEmpty() && m_metadata.isEmpty(); } /*!< \brief Returns \c true if name and metadata are empty, \c false otherwise. */ inline bool isTerminal() const { return name == "terminal"; } /*!< \brief Returns \c true if #name is "terminal", \c false otherwise. */ @@ -191,42 +194,53 @@ struct BR_EXPORT File bool contains(const QString &key) const; /*!< \brief Returns \c true if the key has an associated value, \c false otherwise. */ QVariant value(const QString &key) const; /*!< \brief Returns the value for the specified key. */ + void set(const QString &key, const QVariant &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ + inline void remove(const QString &key) { m_metadata.remove(key); } /*!< \brief Remove the metadata key. */ + + /*!< \brief Returns a value for the key, throwing an error if the key does not exist. */ + template + T get(const QString &key) const + { + if (!contains(key)) qFatal("Missing key: %s", qPrintable(key)); + QVariant variant = value(key); + if (!variant.canConvert()) qFatal("Can't convert: %s", qPrintable(key)); + return variant.value(); + } + + /*!< \brief Returns a value for the key, returning \em defaultValue if the key does not exist or can't be converted. */ + template + T get(const QString &key, const T &defaultValue) const + { + if (!contains(key)) return defaultValue; + QVariant variant = value(key); + if (!variant.canConvert()) return defaultValue; + return variant.value(); + } + static QString subject(int label); /*!< \brief Looks up the subject for the provided label. */ inline QString subject() const { return subject(label()); } /*!< \brief Looks up the subject from the file's label. */ - inline bool failed() const { return getBool("FTE") || getBool("FTO"); } /*!< \brief Returns \c true if the file failed to open or enroll, \c false otherwise. */ - - void remove(const QString &key); /*!< \brief Remove the metadata key. */ - void set(const QString &key, const QVariant &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ - QVariant get(const QString &key) const; /*!< \brief Returns a QVariant for the key, throwing an error if the key does not exist. */ - QVariant get(const QString &key, const QVariant &value) const; /*!< \brief Returns a QVariant for the key, returning \em defaultValue if the key does not exist. */ float label() const; /*!< \brief Convenience function for retrieving the file's \c Label. */ - inline void setLabel(float label) { insert("Label", label); } /*!< \brief Convenience function for setting the file's \c Label. */ - bool getBool(const QString &key) const; /*!< \brief Returns a boolean value for the key. */ - void setBool(const QString &key, bool value = true); /*!< \brief Sets a boolean value for the key. */ - int getInt(const QString &key) const; /*!< \brief Returns an int value for the key, throwing an error if the key does not exist. */ - int getInt(const QString &key, int defaultValue) const; /*!< \brief Returns an int value for the key, returning \em defaultValue if the key does not exist. */ - float getFloat(const QString &key) const; /*!< \brief Returns a float value for the key, throwing an error if the key does not exist. */ - float getFloat(const QString &key, float defaultValue) const; /*!< \brief Returns a float value for the key, returning \em defaultValue if the key does not exist. */ - QString getString(const QString &key) const; /*!< \brief Returns a string value for the key, throwing an error if the key does not exist. */ - QString getString(const QString &key, const QString &defaultValue) const; /*!< \brief Returns a string value for the key, returning \em defaultValue if the key does not exist. */ - - QList landmarks() const; /*!< \brief Returns the file's landmark list. */ - QList namedLandmarks() const; /*!< \brief Returns landmarks derived from metadata keys. */ - void appendLandmark(const QPointF &landmark); /*!< \brief Adds a landmark to the file's landmark list. */ - void appendLandmarks(const QList &landmarks); /*!< \brief Adds landmarks to the file's landmark list. */ - inline void clearLandmarks() { m_metadata["Landmarks"] = QList(); } /*!< \brief Clears the file's landmark list. */ - void setLandmarks(const QList &landmarks); /*!< \brief Assigns the file's landmark list. */ - - QList ROIs() const; /*!< \brief Returns the file's ROI list. */ - void appendROI(const QRectF &ROI); /*!< \brief Adds a ROI to the file's ROI list. */ - void appendROIs(const QList &ROIs); /*!< \brief Adds ROIs to the file's ROI list. */ - inline void clearROIs() { m_metadata["ROIs"] = QList(); } /*!< \brief Clears the file's landmark list. */ - void setROIs(const QList &ROIs); /*!< \brief Assigns the file's landmark list. */ + inline void setLabel(float label) { set("Label", label); } /*!< \brief Convenience function for setting the file's \c Label. */ + inline bool failed() const { return get("FTE", false) || get("FTO", false); } /*!< \brief Returns \c true if the file failed to open or enroll, \c false otherwise. */ + + QList namedPoints() const; /*!< \brief Returns points convertible from metadata keys. */ + QList points() const; /*!< \brief Returns the file's points list. */ + void appendPoint(const QPointF &point); /*!< \brief Adds a point to the file's point list. */ + void appendPoints(const QList &points); /*!< \brief Adds landmarks to the file's landmark list. */ + inline void clearPoints() { m_metadata["Points"] = QList(); } /*!< \brief Clears the file's landmark list. */ + inline void setPoints(const QList &points) { clearPoints(); appendPoints(points); } /*!< \brief Overwrites the file's landmark list. */ + + QList namedRects() const; /*!< \brief Returns rects convertible from metadata values. */ + QList rects() const; /*!< \brief Returns the file's rects list. */ + void appendRect(const QRectF &rect); /*!< \brief Adds a rect to the file's rect list. */ + void appendRects(const QList &rects); /*!< \brief Adds rects to the file's rect list. */ + inline void clearRects() { m_metadata["Rects"] = QList(); } /*!< \brief Clears the file's rect list. */ + inline void setRects(const QList &rects) { clearRects(); appendRects(rects); } /*!< \brief Overwrites the file's rect list. */ private: - QHash m_metadata; - BR_EXPORT friend QDataStream &operator<<(QDataStream &stream, const File &file); /*!< */ - BR_EXPORT friend QDataStream &operator>>(QDataStream &stream, File &file); /*!< */ + QMap m_metadata; + BR_EXPORT friend QDataStream &operator<<(QDataStream &stream, const File &file); + BR_EXPORT friend QDataStream &operator>>(QDataStream &stream, File &file); void init(const QString &file); }; @@ -416,6 +430,9 @@ class BR_EXPORT Object : public QObject { Q_OBJECT + // Index of the first property that can be set via command line arguments + int firstAvailablePropertyIdx; + public: File file; /*!< \brief The file used to construct the plugin. */ @@ -984,6 +1001,49 @@ public: virtual void backProject(const Template &dst, Template &src) const; /*!< \brief Invert the transform. */ virtual void backProject(const TemplateList &dst, TemplateList &src) const; /*!< \brief Invert the transform. */ + /*!< \brief Apply the transform, may update the transform's internal state */ + virtual void projectUpdate(const Template &src, Template &dst) + { + project(src, dst); + } + + /*!< \brief Apply the transform, may update the transform's internal state */ + virtual void projectUpdate(const TemplateList &src, TemplateList &dst) + { + project(src,dst); + } + + /*!< \brief inplace projectUpdate. */ + void projectUpdate(Template &srcdst) + { + Template dst; + projectUpdate(srcdst, dst); + srcdst = dst; + } + + /*!< \brief inplace projectUpdate. */ + void projectUpdate(TemplateList &srcdst) + { + TemplateList dst; + projectUpdate(srcdst, dst); + srcdst = dst; + } + + /*! + * Time-varying transforms may move away from a single input->single output model, and only emit + * templates under some conditions (e.g. a tracking thing may emit a template for each detected + * unique object), in this case finalize indicates that no further calls to project will be made + * and the transform can emit a final set if templates if it wants. Time-invariant transforms + * don't have to do anything. + */ + virtual void finalize(TemplateList & output) { output = TemplateList(); } + + /*! + * \brief Does the transform require the non-const version of project? Can vary for aggregation type transforms + * (if their children are time varying, they are also time varying, otherwise probably not) + */ + virtual bool timeVarying() const { return false; } + /*! * \brief Convenience function equivalent to project(). */ @@ -1047,6 +1107,31 @@ inline QDataStream &operator>>(QDataStream &stream, Transform &f) } /*! + * \brief A br::Transform for which the results of project may change due to prior calls to project + */ +class BR_EXPORT TimeVaryingTransform : public Transform +{ + Q_OBJECT + + virtual bool timeVarying() const { return true; } + + virtual void project(const Template &src, Template &dst) const + { + qFatal("No const project defined for time-varying transform"); + (void) dst; (void) src; + } + + virtual void project(const TemplateList &src, TemplateList &dst) const + { + qFatal("No const project defined for time-varying transform"); + (void) dst; (void) src; + } + +protected: + TimeVaryingTransform(bool independent = true, bool trainable = true) : Transform(independent, trainable) {} +}; + +/*! * \brief A br::Transform expecting multiple matrices per template. */ class BR_EXPORT MetaTransform : public Transform @@ -1074,7 +1159,6 @@ private: void load(QDataStream &stream) { (void) stream; } }; - /*! * \brief A br::MetaTransform that does not require training data. */ diff --git a/sdk/plugins/cascade.cpp b/sdk/plugins/cascade.cpp index 480ab7b..2e7cdcb 100644 --- a/sdk/plugins/cascade.cpp +++ b/sdk/plugins/cascade.cpp @@ -75,15 +75,15 @@ class CascadeTransform : public UntrainableTransform { CascadeClassifier *cascade = cascadeResource.acquire(); vector rects; - cascade->detectMultiScale(src, rects, 1.2, 5, src.file.getBool("enrollAll") ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT, Size(minSize, minSize)); + cascade->detectMultiScale(src, rects, 1.2, 5, src.file.get("enrollAll", false) ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT, Size(minSize, minSize)); cascadeResource.release(cascade); - if (!src.file.getBool("enrollAll") && rects.empty()) + if (!src.file.get("enrollAll", false) && rects.empty()) rects.push_back(Rect(0, 0, src.m().cols, src.m().rows)); foreach (const Rect &rect, rects) { dst += src; - dst.file.appendROI(OpenCVUtils::fromRect(rect)); + dst.file.appendRect(OpenCVUtils::fromRect(rect)); } } }; diff --git a/sdk/plugins/crop.cpp b/sdk/plugins/crop.cpp index ca128aa..b881201 100644 --- a/sdk/plugins/crop.cpp +++ b/sdk/plugins/crop.cpp @@ -26,7 +26,7 @@ namespace br /*! * \ingroup transforms - * \brief Crops the regions of interest. + * \brief Crops the rectangular regions of interest. * \author Josh Klontz \cite jklontz */ class ROITransform : public UntrainableTransform @@ -35,8 +35,8 @@ class ROITransform : public UntrainableTransform void project(const Template &src, Template &dst) const { - foreach (const QRectF ROI, src.file.ROIs()) - dst += src.m()(OpenCVUtils::toRect(ROI)); + foreach (const QRectF &rect, src.file.rects()) + dst += src.m()(OpenCVUtils::toRect(rect)); } }; diff --git a/sdk/plugins/draw.cpp b/sdk/plugins/draw.cpp index 51e85ce..529b218 100644 --- a/sdk/plugins/draw.cpp +++ b/sdk/plugins/draw.cpp @@ -47,19 +47,19 @@ class DrawTransform : public UntrainableTransform const Scalar verboseColor(255, 255, 0); dst = src.m().clone(); - QList landmarks = OpenCVUtils::toPoints(src.file.landmarks()); + QList landmarks = OpenCVUtils::toPoints(src.file.points()); if (unnamed) { foreach (const Point2f &landmark, landmarks) circle(dst, landmark, 3, color, -1); } if (named) { - QList namedLandmarks = OpenCVUtils::toPoints(src.file.namedLandmarks()); + QList namedLandmarks = OpenCVUtils::toPoints(src.file.namedPoints()); foreach (const Point2f &landmark, namedLandmarks) circle(dst, landmark, 3, color); } if (ROI) { - QList ROIs = OpenCVUtils::toRects(src.file.ROIs()); + QList ROIs = OpenCVUtils::toRects(src.file.rects()); foreach (const Rect ROI, ROIs) rectangle(dst, ROI, color); } @@ -154,11 +154,11 @@ class EditTransform : public UntrainableTransform { (void) event; if (flags) { - QList ROIs = currentTemplate.file.ROIs(); - for (int i=ROIs.size()-1; i>=0; i--) - if (ROIs[i].contains(x,y)) - ROIs.removeAt(i); - currentTemplate.file.setROIs(ROIs); + 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; diff --git a/sdk/plugins/eigen3.cpp b/sdk/plugins/eigen3.cpp index 557af04..3026651 100644 --- a/sdk/plugins/eigen3.cpp +++ b/sdk/plugins/eigen3.cpp @@ -228,7 +228,7 @@ class DFFSTransform : public Transform void project(const Template &src, Template &dst) const { dst = src; - dst.file.insert("DFFS", sqrt(pca.residualReconstructionError((*cvtFloat)(src)))); + dst.file.set("DFFS", sqrt(pca.residualReconstructionError((*cvtFloat)(src)))); } void store(QDataStream &stream) const diff --git a/sdk/plugins/eyes.cpp b/sdk/plugins/eyes.cpp index 233ed6e..20f67ed 100644 --- a/sdk/plugins/eyes.cpp +++ b/sdk/plugins/eyes.cpp @@ -147,7 +147,7 @@ public: private: void project(const Template &src, Template &dst) const { - Rect roi = OpenCVUtils::toRect(src.file.ROIs().first()); + Rect roi = OpenCVUtils::toRect(src.file.rects().first()); Mat gray; OpenCVUtils::cvtGray(src.m()(roi), gray); @@ -183,12 +183,10 @@ private: float second_eye_y = (right_rect.y + maxLoc.y)*gray.rows/height+roi.y; dst = src; - dst.file.appendLandmark(QPointF(first_eye_x, first_eye_y)); - dst.file.appendLandmark(QPointF(second_eye_x, second_eye_y)); - dst.file.insert("ASEF_Right_Eye_X", first_eye_x); - dst.file.insert("ASEF_Right_Eye_Y", first_eye_y); - dst.file.insert("ASEF_Left_Eye_X", second_eye_x); - dst.file.insert("ASEF_Left_Eye_Y", second_eye_y); + dst.file.appendPoint(QPointF(first_eye_x, first_eye_y)); + dst.file.appendPoint(QPointF(second_eye_x, second_eye_y)); + dst.file.set("ASEF_Right_Eye", QPointF(first_eye_x, first_eye_y)); + dst.file.set("ASEF_Left_Eye", QPointF(second_eye_x, second_eye_y)); } }; diff --git a/sdk/plugins/format.cpp b/sdk/plugins/format.cpp index 47f10b8..680c452 100644 --- a/sdk/plugins/format.cpp +++ b/sdk/plugins/format.cpp @@ -217,11 +217,11 @@ class DefaultFormat : public Format } else { QString fileName = file.name; if (!QFileInfo(fileName).exists()) { - fileName = file.getString("path") + "/" + file.name; + fileName = file.get("path") + "/" + file.name; if (!QFileInfo(fileName).exists()) { fileName = file.fileName(); if (!QFileInfo(fileName).exists()) { - fileName = file.getString("path") + "/" + file.fileName(); + fileName = file.get("path") + "/" + file.fileName(); if (!QFileInfo(fileName).exists()) return t; } } @@ -604,7 +604,7 @@ class xmlFormat : public Format (e.tagName() == "RPROFILE")) { // Ignore these other image fields for now } else { - t.file.insert(e.tagName(), e.text()); + t.file.set(e.tagName(), e.text()); } fileNode = fileNode.nextSibling(); @@ -614,11 +614,11 @@ class xmlFormat : public Format // Calculate age if (t.file.contains("DOB")) { - const QDate dob = QDate::fromString(t.file.getString("DOB").left(10), "yyyy-MM-dd"); + 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.insert("Age", age); + t.file.set("Age", age); } return t; diff --git a/sdk/plugins/gallery.cpp b/sdk/plugins/gallery.cpp index 7c07e57..c5965a7 100644 --- a/sdk/plugins/gallery.cpp +++ b/sdk/plugins/gallery.cpp @@ -22,13 +22,13 @@ #include #include #endif // BR_EMBEDDED +#include #include #include "NaturalStringCompare.h" #include "core/bee.h" #include "core/opencvutils.h" #include "core/qtutils.h" -#include namespace br { @@ -47,7 +47,7 @@ class galGallery : public Gallery void init() { gallery.setFileName(file); - if (file.getBool("remove")) + if (file.get("remove", false)) gallery.remove(); QtUtils::touchDir(gallery); if (!gallery.open(QFile::ReadWrite | QFile::Append)) @@ -339,20 +339,33 @@ class csvGallery : public Gallery { if (files.isEmpty()) return; - QStringList keys; + QMap samples; foreach (const File &file, files) foreach (const QString &key, file.localKeys()) - if (!keys.contains(key)) keys += key; - qSort(keys); + if (!samples.contains(key)) + samples.insert(key, file.value(key)); + + QStringList lines; + lines.reserve(files.size()+1); + + { // Make header + QStringList words; + words.append("File"); + foreach (const QString &key, samples.keys()) + words.append(getCSVElement(key, samples[key], true)); + lines.append(words.join(",")); + } - const int rows = files.size(); - const int columns = keys.size(); - QSharedPointer output(Output::make(file, keys, files)); + // Make table + foreach (const File &file, files) { + QStringList words; + words.append(file.name); + foreach (const QString &key, samples.keys()) + words.append(getCSVElement(key, file.value(key), false)); + lines.append(words.join(",")); + } - for (int i=0; isetRelative(files[i].label(), i, j); - else output->setRelative(files[i].getFloat(keys[j], std::numeric_limits::quiet_NaN()), i, j); + QtUtils::writeFile(file, lines); } TemplateList readBlock(bool *done) @@ -377,6 +390,25 @@ class csvGallery : public Gallery { files.append(t.file); } + + static QString getCSVElement(const QString &key, const QVariant &value, bool header) + { + if (value.canConvert()) { + if (header) return key; + else return value.value(); + } else if (value.canConvert()) { + const QPointF point = value.value(); + if (header) return key+"_X,"+key+"_Y"; + else return QString::number(point.x())+","+QString::number(point.y()); + } else if (value.canConvert()) { + const QRectF rect = value.value(); + if (header) return key+"_X,"+key+"_Y,"+key+"_Width,"+key+"_Height"; + else return QString::number(rect.x())+","+QString::number(rect.y())+","+QString::number(rect.width())+","+QString::number(rect.height()); + } else { + if (header) return key; + else return QString::number(std::numeric_limits::quiet_NaN()); + } + } }; BR_REGISTER(Gallery, csvGallery) @@ -465,9 +497,9 @@ class dbGallery : public Gallery TemplateList readBlock(bool *done) { TemplateList templates; - br::File import = file.getString("import", ""); - QString query = file.getString("query"); - QString subset = file.getString("subset", ""); + br::File import = file.get("import", ""); + QString query = file.get("query"); + QString subset = file.get("subset", ""); #ifndef BR_EMBEDDED QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); diff --git a/sdk/plugins/hist.cpp b/sdk/plugins/hist.cpp index 3829d7f..0a87290 100644 --- a/sdk/plugins/hist.cpp +++ b/sdk/plugins/hist.cpp @@ -66,6 +66,38 @@ BR_REGISTER(Transform, HistTransform) /*! * \ingroup transforms + * \brief Quantizes the values into bins. + * \author Josh Klontz \cite jklontz + */ +class BinTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(float min READ get_min WRITE set_min RESET reset_min STORED false) + Q_PROPERTY(float max READ get_max WRITE set_max RESET reset_max STORED false) + Q_PROPERTY(int bins READ get_bins WRITE set_bins RESET reset_bins STORED false) + Q_PROPERTY(bool split READ get_split WRITE set_split RESET reset_split STORED false) + BR_PROPERTY(float, min, 0) + BR_PROPERTY(float, max, 255) + BR_PROPERTY(int, bins, 8) + BR_PROPERTY(bool, split, false) + + void project(const Template &src, Template &dst) const + { + src.m().convertTo(dst, bins > 256 ? CV_16U : CV_8U, bins/(max-min)); + if (!split) return; + + Mat input = dst; + QList outputs; outputs.reserve(bins); + for (int i=0; i::max(); - QRect bestROI; + QRectF bestRect; const int rows = m.rows; const int cols = m.cols/bins; @@ -202,7 +234,7 @@ class VarianceChangeDetectorTransform : public UntrainableTransform if (ratio > bestRatio) { bestRatio = ratio; - bestROI = QRect(j*radius, i*radius, scale*radius, scale*radius); + bestRect = QRect(j*radius, i*radius, scale*radius, scale*radius); } } } @@ -210,7 +242,7 @@ class VarianceChangeDetectorTransform : public UntrainableTransform } delete[] buffer; - dst.file.appendROI(bestROI); + dst.file.appendRect(bestRect); dst.file.setLabel(bestRatio); } }; diff --git a/sdk/plugins/integral.cpp b/sdk/plugins/integral.cpp new file mode 100644 index 0000000..152cb41 --- /dev/null +++ b/sdk/plugins/integral.cpp @@ -0,0 +1,59 @@ +#include +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Computes integral image. + * \author Josh Klontz \cite jklontz + */ +class IntegralTransform : public UntrainableTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + cv::integral(src, dst); + } +}; + +BR_REGISTER(Transform, IntegralTransform) + +/*! + * \ingroup transforms + * \brief Computes magnitude and/or angle of image. + * \author Josh Klontz \cite jklontz + */ +class GradientTransform : public UntrainableTransform +{ + Q_OBJECT + Q_ENUMS(Channel) + Q_PROPERTY(Channel channel READ get_channel WRITE set_channel RESET reset_channel STORED false) + +public: + enum Channel { Magnitude, Angle, MagnitudeAndAngle }; + +private: + BR_PROPERTY(Channel, channel, Angle) + + void project(const Template &src, Template &dst) const + { + if (src.m().type() != CV_8UC1) qFatal("Requires CV_8UC1 input."); + cv::Mat dx, dy, magnitude, angle; + cv::Sobel(src, dx, CV_32F, 1, 0); + cv::Sobel(src, dy, CV_32F, 0, 1); + cv::cartToPolar(dx, dy, magnitude, angle, true); + if ((channel == Magnitude) || (channel == MagnitudeAndAngle)) + dst.append(magnitude); + if ((channel == Angle) || (channel == MagnitudeAndAngle)) + dst.append(angle); + } +}; + +BR_REGISTER(Transform, GradientTransform) + +} // namespace br + +#include "integral.moc" diff --git a/sdk/plugins/keypoint.cpp b/sdk/plugins/keypoint.cpp index 318d375..4adcfcd 100644 --- a/sdk/plugins/keypoint.cpp +++ b/sdk/plugins/keypoint.cpp @@ -54,13 +54,13 @@ class KeyPointDetectorTransform : public UntrainableTransform featureDetector->detect(src, keyPoints); } catch (...) { qWarning("Key point detection failed for file %s", qPrintable(src.file.name)); - dst.file.setBool("FTE"); + dst.file.set("FTE", true); } - QList ROIs; + QList rects; foreach (const KeyPoint &keyPoint, keyPoints) - ROIs.append(Rect(keyPoint.pt.x, keyPoint.pt.y, keyPoint.size, keyPoint.size)); - dst.file.setROIs(OpenCVUtils::fromRects(ROIs)); + rects.append(Rect(keyPoint.pt.x, keyPoint.pt.y, keyPoint.size, keyPoint.size)); + dst.file.setRects(OpenCVUtils::fromRects(rects)); } }; @@ -92,10 +92,10 @@ class KeyPointDescriptorTransform : public UntrainableTransform { std::vector keyPoints; if (size == -1) { - foreach (const QRectF &ROI, src.file.ROIs()) + foreach (const QRectF &ROI, src.file.rects()) keyPoints.push_back(KeyPoint(ROI.x(), ROI.y(), (ROI.width() + ROI.height())/2)); } else { - foreach (const QPointF &landmark, src.file.landmarks()) + foreach (const QPointF &landmark, src.file.points()) keyPoints.push_back(KeyPoint(landmark.x(), landmark.y(), size)); } descriptorExtractor->compute(src, keyPoints, dst); @@ -166,7 +166,7 @@ class SIFTDescriptorTransform : public UntrainableTransform void project(const Template &src, Template &dst) const { std::vector keyPoints; - foreach (const QPointF &val, src.file.landmarks()) + foreach (const QPointF &val, src.file.points()) keyPoints.push_back(KeyPoint(val.x(), val.y(), size)); Mat m; @@ -200,7 +200,7 @@ class GridTransform : public UntrainableTransform for (float j=column_step/2; j("enrollAll", false)) expanded.append(t); continue; } - const bool fte = t.file.getBool("FTE"); - QList landmarks = t.file.landmarks(); - QList ROIs = t.file.ROIs(); - if (landmarks.size() % t.size() != 0) qFatal("Uneven landmark count."); - if (ROIs.size() % t.size() != 0) qFatal("Uneven ROI count."); - const int landmarkStep = landmarks.size() / t.size(); - const int ROIStep = ROIs.size() / t.size(); + const bool fte = t.file.get("FTE", false); + QList points = t.file.points(); + 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("enrollAll", false)) { expanded.append(Template(t.file, t[i])); - expanded.last().file.setROIs(ROIs.mid(i*ROIStep, ROIStep)); - expanded.last().file.setLandmarks(landmarks.mid(i*landmarkStep, landmarkStep)); + expanded.last().file.setRects(rects.mid(i*rectStep, rectStep)); + expanded.last().file.setPoints(points.mid(i*pointStep, pointStep)); } } } @@ -86,6 +86,111 @@ static void incrementStep() } /*! + * \brief Use Expanded after basic calls that take a template list, used to implement ExpandTransform + */ +class ExpandDecorator : public Transform +{ + Q_OBJECT + + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform) + BR_PROPERTY(br::Transform*, transform, NULL) + +public: + ExpandDecorator(Transform * input) + { + transform = input; + transform->setParent(this); + file = transform->file; + setObjectName(transform->objectName()); + } + + void train(const TemplateList &data) + { + transform->train(data); + } + + void project(const Template &src, Template &dst) const + { + transform->project(src, dst); + } + + void project(const TemplateList &src, TemplateList &dst) const + { + transform->project(src, dst); + dst = Expanded(dst); + } + + + void projectUpdate(const Template &src, Template &dst) + { + transform->projectUpdate(src, dst); + } + + void projectUpdate(const TemplateList & src, TemplateList & dst) + { + transform->projectUpdate(src, dst); + dst = Expanded(dst); + } + + bool timeVarying() const + { + return transform->timeVarying(); + } + + void finalize(TemplateList & output) + { + transform->finalize(output); + output = Expanded(output); + } + +}; + +/*! + * \brief A MetaTransform that aggregates some sub-transforms + */ +class BR_EXPORT CompositeTransform : public TimeVaryingTransform +{ + Q_OBJECT + +public: + Q_PROPERTY(QList transforms READ get_transforms WRITE set_transforms RESET reset_transforms) + BR_PROPERTY(QList, transforms, QList()) + + virtual void project(const Template &src, Template &dst) const + { + if (timeVarying()) qFatal("No const project defined for time-varying transform"); + _project(src, dst); + } + + virtual void project(const TemplateList &src, TemplateList &dst) const + { + if (timeVarying()) qFatal("No const project defined for time-varying transform"); + _project(src, dst); + } + + bool timeVarying() const { return isTimeVarying; } + + void init() + { + isTimeVarying = false; + foreach (const br::Transform *transform, transforms) { + if (transform->timeVarying()) { + isTimeVarying = true; + break; + } + } + } + +protected: + bool isTimeVarying; + + virtual void _project(const Template & src, Template & dst) const = 0; + virtual void _project(const TemplateList & src, TemplateList & dst) const = 0; + + CompositeTransform() : TimeVaryingTransform(false) {} +}; + +/*! * \ingroup Transforms * \brief Transforms in series. * \author Josh Klontz \cite jklontz @@ -95,11 +200,9 @@ static void incrementStep() * \see ExpandTransform * \see ForkTransform */ -class PipeTransform : public MetaTransform +class PipeTransform : public CompositeTransform { Q_OBJECT - Q_PROPERTY(QList transforms READ get_transforms WRITE set_transforms RESET reset_transforms) - BR_PROPERTY(QList, transforms, QList()) void train(const TemplateList &data) { @@ -115,21 +218,78 @@ class PipeTransform : public MetaTransform releaseStep(); } - void project(const Template &src, Template &dst) const + void backProject(const Template &dst, Template &src) const + { + // Backprojecting a time-varying transform is probably not going to work. + if (timeVarying()) qFatal("No backProject defined for time-varying transform"); + + src = dst; + // Reverse order in which transforms are processed + int length = transforms.length(); + for (int i=length-1; i>=0; i--) { + Transform *f = transforms.at(i); + try { + src >> *f; + } catch (...) { + qWarning("Exception triggered when processing %s with transform %s", qPrintable(dst.file.flat()), qPrintable(f->objectName())); + src = Template(src.file); + src.file.set("FTE", true); + } + } + } + + void projectUpdate(const Template &src, Template &dst) { dst = src; - foreach (const Transform *f, transforms) { + foreach (Transform *f, transforms) { try { - dst >> *f; + f->projectUpdate(dst); } catch (...) { qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); dst = Template(src.file); - dst.file.setBool("FTE"); + dst.file.set("FTE", true); } } } - void project(const TemplateList &src, TemplateList &dst) const + // For time varying transforms, parallel execution over individual templates + // won't work. + void projectUpdate(const TemplateList & src, TemplateList & dst) + { + dst = src; + foreach (Transform *f, transforms) + { + f->projectUpdate(dst); + } + } + + 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); + } + } + + +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 { if (Globals->parallelism < 0) { dst = src; @@ -140,22 +300,20 @@ class PipeTransform : public MetaTransform } } - void backProject(const Template &dst, Template &src) const - { - src = dst; - // Reverse order in which transforms are processed - int length = transforms.length(); - for (int i=length-1; i>=0; i--) { - Transform *f = transforms.at(i); - try { - src >> *f; - } catch (...) { - qWarning("Exception triggered when processing %s with transform %s", qPrintable(dst.file.flat()), qPrintable(f->objectName())); - src = Template(src.file); - src.file.setBool("FTE"); - } - } - } + // 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; + } catch (...) { + qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); + dst = Template(src.file); + dst.file.set("FTE", true); + } + } + } }; BR_REGISTER(Transform, PipeTransform) @@ -170,64 +328,31 @@ BR_REGISTER(Transform, PipeTransform) * * \see PipeTransform */ -class ExpandTransform : public MetaTransform +class ExpandTransform : public PipeTransform { Q_OBJECT - Q_PROPERTY(QList transforms READ get_transforms WRITE set_transforms RESET reset_transforms) - BR_PROPERTY(QList, transforms, QList()) - void train(const TemplateList &data) + void init() { - acquireStep(); - - TemplateList copy(data); - for (int i=0; itrain(copy); - copy >> *transforms[i]; - copy = Expanded(copy); - incrementStep(); + for (int i = 0; i < transforms.size(); i++) + { + transforms[i] = new ExpandDecorator(transforms[i]); } - - releaseStep(); + // Need to call this to set up timevariance correctly, and it won't + // be called automatically + CompositeTransform::init(); } - void project(const Template &src, Template &dst) const - { - dst = src; - foreach (const Transform *f, transforms) { - try { - dst >> *f; - } catch (...) { - qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); - dst = Template(src.file); - dst.file.setBool("FTE"); - } - } - } +protected: - void project(const TemplateList &src, TemplateList &dst) const + // Template list project -- project through transforms sequentially, + // then expand the results, can't use Transform::Project(templateList) since + // we need to expand between tranforms, so actually do need to overload this method + void _project(const TemplateList &src, TemplateList &dst) const { dst = src; for (int i=0; i> *transforms[i]; - dst = Expanded(dst); - } - } - - void backProject(const Template &dst, Template &src) const - { - src = dst; - // Reverse order in which transforms are processed - int length = transforms.length(); - for (int i=length-1; i>=0; i--) { - Transform *f = transforms.at(i); - try { - src >> *f; - } catch (...) { - qWarning("Exception triggered when processing %s with transform %s", qPrintable(dst.file.flat()), qPrintable(f->objectName())); - src = Template(src.file); - src.file.setBool("FTE"); - } } } }; @@ -243,11 +368,9 @@ BR_REGISTER(Transform, ExpandTransform) * * \see PipeTransform */ -class ForkTransform : public MetaTransform +class ForkTransform : public CompositeTransform { Q_OBJECT - Q_PROPERTY(QList transforms READ get_transforms WRITE set_transforms RESET reset_transforms) - BR_PROPERTY(QList, transforms, QList()) void train(const TemplateList &data) { @@ -260,7 +383,69 @@ class ForkTransform : public MetaTransform if (threaded) Globals->trackFutures(futures); } - void project(const Template &src, Template &dst) const + void backProject(const Template &dst, Template &src) const {Transform::backProject(dst, src);} + + // same as _project, but calls projectUpdate on sub-transforms + void projectupdate(const Template & src, Template & dst) + { + foreach (Transform *f, transforms) { + try { + Template res; + f->projectUpdate(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.set("FTE", true); + } + } + } + + void projectUpdate(const TemplateList & src, TemplateList & dst) + { + dst = src; + 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 { @@ -268,12 +453,12 @@ class ForkTransform : public MetaTransform } catch (...) { qWarning("Exception triggered when processing %s with transform %s", qPrintable(src.file.flat()), qPrintable(f->objectName())); dst = Template(src.file); - dst.file.setBool("FTE"); + dst.file.set("FTE", true); } } } - void project(const TemplateList &src, TemplateList &dst) const + void _project(const TemplateList &src, TemplateList &dst) const { if (Globals->parallelism < 0) { dst.reserve(src.size()); @@ -288,6 +473,7 @@ class ForkTransform : public MetaTransform Transform::project(src, dst); } } + }; BR_REGISTER(Transform, ForkTransform) @@ -423,7 +609,7 @@ private: const QString file = getFileName(); if (file.isEmpty()) return false; - qDebug("Loading %s", qPrintable(baseName)); + if (Globals->verbose) qDebug("Loading %s", qPrintable(baseName)); QByteArray data; QtUtils::readFile(file, data, true); QDataStream stream(&data, QFile::ReadOnly); @@ -462,7 +648,7 @@ class FTETransform : public Transform foreach (const Template &t, projectedData) { if (!t.file.contains(transform->objectName())) qFatal("Matrix metadata missing key %s.", qPrintable(transform->objectName())); - vals.append(t.file.getFloat(transform->objectName())); + vals.append(t.file.get(transform->objectName())); } float q1, q3; Common::Median(vals, &q1, &q3); @@ -474,11 +660,11 @@ class FTETransform : public Transform { Template projectedSrc; transform->project(src, projectedSrc); - const float val = projectedSrc.file.getFloat(transform->objectName()); + const float val = projectedSrc.file.get(transform->objectName()); dst = src; - dst.file.insert(transform->objectName(), val); - dst.file.insert("FTE", (val < min) || (val > max)); + dst.file.set(transform->objectName(), val); + dst.file.set("FTE", (val < min) || (val > max)); } }; diff --git a/sdk/plugins/misc.cpp b/sdk/plugins/misc.cpp index 97b30aa..90081b3 100644 --- a/sdk/plugins/misc.cpp +++ b/sdk/plugins/misc.cpp @@ -44,7 +44,7 @@ class OpenTransform : public UntrainableMetaTransform dst.append(t); dst.file.append(t.file.localMetadata()); } - dst.file.insert("FTO", dst.isEmpty()); + dst.file.set("FTO", dst.isEmpty()); } }; @@ -100,16 +100,19 @@ class PrintTransform : public UntrainableMetaTransform Q_OBJECT Q_PROPERTY(bool error READ get_error WRITE set_error RESET reset_error) Q_PROPERTY(bool data READ get_data WRITE set_data RESET reset_data) + Q_PROPERTY(bool nTemplates READ get_data WRITE set_data RESET reset_data) BR_PROPERTY(bool, error, true) BR_PROPERTY(bool, data, false) + BR_PROPERTY(bool, size, false) void project(const Template &src, Template &dst) const { dst = src; const QString nameString = src.file.flat(); const QString dataString = data ? OpenCVUtils::matrixToString(src)+"\n" : QString(); - if (error) qDebug("%s\n%s", qPrintable(nameString), qPrintable(dataString)); - else printf("%s\n%s", qPrintable(nameString), qPrintable(dataString)); + const QString nTemplates = size ? QString::number(src.size()) : QString(); + if (error) qDebug("%s\n%s\n%s", qPrintable(nameString), qPrintable(dataString), qPrintable(nTemplates)); + else printf("%s\n%s\n%s", qPrintable(nameString), qPrintable(dataString), qPrintable(nTemplates)); } }; @@ -282,7 +285,7 @@ class RenameTransform : public UntrainableMetaTransform { dst = src; if (dst.file.localKeys().contains(find)) { - dst.file.insert(replace, dst.file.get(find)); + dst.file.set(replace, dst.file.value(find)); dst.file.remove(find); } } @@ -308,7 +311,7 @@ class RenameFirstTransform : public UntrainableMetaTransform dst = src; foreach (const QString &key, find) if (dst.file.localKeys().contains(key)) { - dst.file.insert(replace, dst.file.get(key)); + dst.file.set(replace, dst.file.value(key)); dst.file.remove(key); break; } diff --git a/sdk/plugins/output.cpp b/sdk/plugins/output.cpp index 83c626a..44c2a0d 100644 --- a/sdk/plugins/output.cpp +++ b/sdk/plugins/output.cpp @@ -106,7 +106,7 @@ class meltOutput : public MatrixOutput const bool genuineOnly = file.contains("Genuine") && !file.contains("Impostor"); const bool impostorOnly = file.contains("Impostor") && !file.contains("Genuine"); - QHash args = file.localMetadata(); + QMap args = file.localMetadata(); args.remove("Genuine"); args.remove("Impostor"); @@ -165,9 +165,9 @@ class rrOutput : public MatrixOutput ~rrOutput() { if (file.isNull() || targetFiles.isEmpty() || queryFiles.isEmpty()) return; - const int limit = file.getInt("limit", 20); - const bool byLine = file.getBool("byLine"); - const float threshold = file.getFloat("threshold", -std::numeric_limits::max()); + const int limit = file.get("limit", 20); + const bool byLine = file.get("byLine", false); + const float threshold = file.get("threshold", -std::numeric_limits::max()); QStringList lines; @@ -279,7 +279,7 @@ class rankOutput : public MatrixOutput typedef QPair Pair; int rank = 1; foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector(data.row(i)), true)) { - if(targetFiles[pair.second].getString("Label") == queryFiles[i].getString("Label")) { + if(targetFiles[pair.second].get("Label") == queryFiles[i].get("Label")) { ranks.append(rank); positions.append(pair.second); scores.append(pair.first); @@ -347,10 +347,10 @@ class tailOutput : public Output void initialize(const FileList &targetFiles, const FileList &queryFiles) { Output::initialize(targetFiles, queryFiles); - threshold = file.getFloat("threshold", -std::numeric_limits::max()); - atLeast = file.getInt("atLeast", 1); - atMost = file.getInt("atMost", std::numeric_limits::max()); - args = file.getBool("args"); + threshold = file.get("threshold", -std::numeric_limits::max()); + atLeast = file.get("atLeast", 1); + atMost = file.get("atMost", std::numeric_limits::max()); + args = file.get("args", false); lastValue = -std::numeric_limits::max(); } @@ -462,9 +462,9 @@ class histOutput : public Output void initialize(const FileList &targetFiles, const FileList &queryFiles) { Output::initialize(targetFiles, queryFiles); - min = file.getFloat("min", -5); - max = file.getFloat("max", 5); - step = file.getFloat("step", 0.1); + min = file.get("min", -5); + max = file.get("max", 5); + step = file.get("step", 0.1); bins = QVector((max-min)/step, 0); } diff --git a/sdk/plugins/pixel.cpp b/sdk/plugins/pixel.cpp index 3514b53..e301442 100644 --- a/sdk/plugins/pixel.cpp +++ b/sdk/plugins/pixel.cpp @@ -120,7 +120,7 @@ class PerPixelClassifierTransform : public MetaTransform uchar *psrc = src[n].ptr(); ptemp[n] = psrc[index]; } - cv::Mat labelMat = src.file.get("labels").value(); + cv::Mat labelMat = src.file.value("labels").value(); uchar* plabel = labelMat.ptr(); temp.file.setLabel(plabel[index]); diff --git a/sdk/plugins/pp5.cpp b/sdk/plugins/pp5.cpp index abb25a6..ae54344 100644 --- a/sdk/plugins/pp5.cpp +++ b/sdk/plugins/pp5.cpp @@ -148,9 +148,9 @@ struct PP5Context return "Unknown"; } - static QHash toMetadata(const ppr_face_type &face) + static QMap toMetadata(const ppr_face_type &face) { - QHash metadata; + QMap metadata; ppr_face_attributes_type face_attributes; ppr_get_face_attributes(face, &face_attributes); @@ -250,7 +250,7 @@ class PP5Enroll : public UntrainableTransform dst.file.append(PP5Context::toMetadata(face)); dst += m; - if (!src.file.getBool("enrollAll")) break; + if (!src.file.get("enrollAll", false)) break; } ppr_free_face_list(face_list); @@ -259,7 +259,7 @@ class PP5Enroll : public UntrainableTransform contexts.release(context); - if (!src.file.getBool("enrollAll") && dst.isEmpty()) { + if (!src.file.get("enrollAll", false) && dst.isEmpty()) { if (detectOnly) dst += src; else dst += cv::Mat(); } diff --git a/sdk/plugins/quality.cpp b/sdk/plugins/quality.cpp index ce476bd..561fa48 100644 --- a/sdk/plugins/quality.cpp +++ b/sdk/plugins/quality.cpp @@ -53,8 +53,8 @@ class ImpostorUniquenessMeasureTransform : public Transform { dst = src; float ium = calculateIUM(src, impostors); - dst.file.insert("Impostor_Uniqueness_Measure", ium); - dst.file.insert("Impostor_Uniqueness_Measure_Bin", ium < mean-stddev ? 0 : (ium < mean+stddev ? 1 : 2)); + dst.file.set("Impostor_Uniqueness_Measure", ium); + dst.file.set("Impostor_Uniqueness_Measure_Bin", ium < mean-stddev ? 0 : (ium < mean+stddev ? 1 : 2)); } void store(QDataStream &stream) const diff --git a/sdk/plugins/random.cpp b/sdk/plugins/random.cpp index a16eb81..827fd77 100644 --- a/sdk/plugins/random.cpp +++ b/sdk/plugins/random.cpp @@ -27,47 +27,6 @@ namespace br /*! * \ingroup transforms - * \brief Selects a random transform. - * \author Josh Klontz \cite jklontz - */ -class RndTransformTransform : public Transform -{ - Q_OBJECT - Q_PROPERTY(QList transforms READ get_transforms WRITE set_transforms RESET reset_transforms STORED false) - BR_PROPERTY(QList, transforms, QList()) - - int selectedIndex; - Transform *selectedTransform; - - void train(const TemplateList &data) - { - selectedIndex = theRNG().uniform(0, transforms.size()); - selectedTransform = transforms[selectedIndex]->clone(); - selectedTransform->train(data); - } - - void project(const Template &src, Template &dst) const - { - selectedTransform->project(src, dst); - } - - void store(QDataStream &stream) const - { - stream << selectedIndex << *selectedTransform; - } - - void load(QDataStream &stream) - { - stream >> selectedIndex; - selectedTransform = transforms[selectedIndex]->clone(); - stream >> *selectedTransform; - } -}; - -BR_REGISTER(Transform, RndTransformTransform) - -/*! - * \ingroup transforms * \brief Generates a random subspace. * \author Josh Klontz \cite jklontz */ @@ -196,7 +155,7 @@ class RndPointTransform : public Transform void project(const Template &src, Template &dst) const { dst = src; - dst.file.appendLandmark(QPointF(src.m().cols * x, src.m().rows * y)); + dst.file.appendPoint(QPointF(src.m().cols * x, src.m().rows * y)); } }; diff --git a/sdk/plugins/regions.cpp b/sdk/plugins/regions.cpp index 3bc9425..7f13a9e 100644 --- a/sdk/plugins/regions.cpp +++ b/sdk/plugins/regions.cpp @@ -148,8 +148,8 @@ class RectFromLandmarksTransform : public UntrainableTransform void project(const Template &src, Template &dst) const { - if (src.file.landmarks().isEmpty()) { - qWarning("No landmarks for %s", qPrintable(src.file.fileName())); + if (src.file.points().isEmpty()) { + qWarning("No landmarks"); dst = src; return; } @@ -160,12 +160,12 @@ class RectFromLandmarksTransform : public UntrainableTransform maxX = maxY = -std::numeric_limits::max(); foreach(int index, indices) { - if (src.file.landmarks().size() > index+1) { - if (src.file.landmarks()[index].x() < minX) minX = src.file.landmarks()[index].x(); - if (src.file.landmarks()[index].x() > maxX) maxX = src.file.landmarks()[index].x(); - if (src.file.landmarks()[index].y() < minY) minY = src.file.landmarks()[index].y(); - if (src.file.landmarks()[index].y() > maxY) maxY = src.file.landmarks()[index].y(); - dst.file.appendLandmark(src.file.landmarks()[index]); + if (src.file.points().size() > index+1) { + if (src.file.points()[index].x() < minX) minX = src.file.points()[index].x(); + if (src.file.points()[index].x() > maxX) maxX = src.file.points()[index].x(); + if (src.file.points()[index].y() < minY) minY = src.file.points()[index].y(); + if (src.file.points()[index].y() > maxY) maxY = src.file.points()[index].y(); + dst.file.appendPoint(src.file.points()[index]); } } diff --git a/sdk/plugins/register.cpp b/sdk/plugins/register.cpp index d8c2dbc..d6e0355 100644 --- a/sdk/plugins/register.cpp +++ b/sdk/plugins/register.cpp @@ -67,17 +67,14 @@ class AffineTransform : public UntrainableTransform else dstPoints[2] = Point2f(x3*width, y3*height); Point2f srcPoints[3]; - if (src.file.contains("Affine_0_X") && - src.file.contains("Affine_0_Y") && - src.file.contains("Affine_1_X") && - src.file.contains("Affine_1_Y") && - (src.file.contains("Affine_2_X") || twoPoints) && - (src.file.contains("Affine_2_Y") || twoPoints)) { - srcPoints[0] = Point2f(src.file.getFloat("Affine_0_X"), src.file.getFloat("Affine_0_Y")); - srcPoints[1] = Point2f(src.file.getFloat("Affine_1_X"), src.file.getFloat("Affine_1_Y")); - if (!twoPoints) srcPoints[2] = Point2f(src.file.getFloat("Affine_2_X"), src.file.getFloat("Affine_2_Y")); + if (src.file.contains("Affine_0") && + src.file.contains("Affine_1") && + (src.file.contains("Affine_2") || twoPoints)) { + srcPoints[0] = OpenCVUtils::toPoint(src.file.get("Affine_0")); + srcPoints[1] = OpenCVUtils::toPoint(src.file.get("Affine_1")); + if (!twoPoints) srcPoints[2] = OpenCVUtils::toPoint(src.file.get("Affine_2")); } else { - const QList landmarks = OpenCVUtils::toPoints(src.file.landmarks()); + const QList landmarks = OpenCVUtils::toPoints(src.file.points()); if ((landmarks.size() < 2) || (!twoPoints && (landmarks.size() < 3))) { resize(src, dst, Size(width, height)); @@ -87,14 +84,9 @@ class AffineTransform : public UntrainableTransform srcPoints[1] = landmarks[1]; if (!twoPoints) srcPoints[2] = landmarks[2]; - dst.file.insert("Affine_0_X", landmarks[0].x); - dst.file.insert("Affine_0_Y", landmarks[0].y); - dst.file.insert("Affine_1_X", landmarks[1].x); - dst.file.insert("Affine_1_Y", landmarks[1].y); - if (!twoPoints) { - dst.file.insert("Affine_2_X", landmarks[2].x); - dst.file.insert("Affine_2_Y", landmarks[2].y); - } + dst.file.set("Affine_0", OpenCVUtils::fromPoint(landmarks[0])); + dst.file.set("Affine_1", OpenCVUtils::fromPoint(landmarks[1])); + if (!twoPoints) dst.file.set("Affine_2", OpenCVUtils::fromPoint(landmarks[2])); } } if (twoPoints) srcPoints[2] = getThirdAffinePoint(srcPoints[0], srcPoints[1]); diff --git a/sdk/plugins/stasm.cpp b/sdk/plugins/stasm.cpp index 385d5c2..25f6eed 100644 --- a/sdk/plugins/stasm.cpp +++ b/sdk/plugins/stasm.cpp @@ -18,7 +18,7 @@ class StasmInitializer : public Initializer void initialize() const { - Globals->abbreviations.insert("RectFromStasmEyes","RectFromLandmarks([27, 28, 29, 30, 31, 32, 33, 34, 35, 36],0.125,6.0)+Resize(44,168)"); // + Globals->abbreviations.insert("RectFromStasmEyes","RectFromLandmarks([27, 28, 29, 30, 31, 32, 33, 34, 35, 36],0.125,6.0)+Resize(44,164)"); // Globals->abbreviations.insert("RectFromStasmJaw","RectFromLandmarks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],10)"); Globals->abbreviations.insert("RectFromStasmBrow","RectFromLandmarks([15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],0.25,6.5)+Resize(44,230)"); Globals->abbreviations.insert("RectFromStasmNose","RectFromLandmarks([38, 39, 40, 41, 42, 43, 44, 67],0.1,1.5)+Resize(44,44)"); @@ -54,13 +54,13 @@ class StasmTransform : public UntrainableTransform if (nlandmarks == 0) { qWarning("Unable to detect Stasm landmarks for %s", qPrintable(src.file.fileName())); - dst.file.setBool("FTE"); + dst.file.set("FTE", true); dst.m() = src.m(); return; } for (int i = 0; i < nlandmarks; i++) - dst.file.appendLandmark(QPointF(landmarks[2 * i], landmarks[2 * i + 1])); + dst.file.appendPoint(QPointF(landmarks[2 * i], landmarks[2 * i + 1])); dst.m() = src.m(); } diff --git a/sdk/plugins/validate.cpp b/sdk/plugins/validate.cpp index db9a2a5..e372858 100644 --- a/sdk/plugins/validate.cpp +++ b/sdk/plugins/validate.cpp @@ -22,7 +22,7 @@ class CrossValidateTransform : public MetaTransform int numPartitions = 0; QList partitions; partitions.reserve(data.size()); foreach (const File &file, data.files()) { - partitions.append(file.getInt("Cross_Validation_Partition", 0)); + partitions.append(file.get("Cross_Validation_Partition", 0)); numPartitions = std::max(numPartitions, partitions.last()+1); } @@ -48,7 +48,7 @@ class CrossValidateTransform : public MetaTransform void project(const Template &src, Template &dst) const { - transforms[src.file.getInt("Cross_Validation_Partition", 0)]->project(src, dst); + transforms[src.file.get("Cross_Validation_Partition", 0)]->project(src, dst); } void store(QDataStream &stream) const @@ -82,8 +82,8 @@ class CrossValidateDistance : public Distance float compare(const Template &a, const Template &b) const { - const int partitionA = a.file.getInt("Cross_Validation_Partition", 0); - const int partitionB = b.file.getInt("Cross_Validation_Partition", 0); + const int partitionA = a.file.get("Cross_Validation_Partition", 0); + const int partitionB = b.file.get("Cross_Validation_Partition", 0); return (partitionA != partitionB) ? -std::numeric_limits::max() : 0; } }; @@ -104,7 +104,7 @@ class FilterDistance : public Distance (void) b; // Query template isn't checked foreach (const QString &key, Globals->filters.keys()) { bool keep = false; - const QString metadata = a.file.getString(key, ""); + const QString metadata = a.file.get(key, ""); if (metadata.isEmpty()) continue; foreach (const QString &value, Globals->filters[key]) { if (metadata == value) { diff --git a/sdk/plugins/wavelet.cpp b/sdk/plugins/wavelet.cpp index f1d924a..a5ac86c 100644 --- a/sdk/plugins/wavelet.cpp +++ b/sdk/plugins/wavelet.cpp @@ -189,11 +189,11 @@ class GaborJetTransform : public UntrainableTransform void project(const Template &src, Template &dst) const { - const QList landmarks = src.file.landmarks(); - dst = Mat(landmarks.size(), kReals.size(), CV_32FC1); - for (int i=0; i points = src.file.points(); + dst = Mat(points.size(), kReals.size(), CV_32FC1); + for (int i=0; i(i,j) = response(src, landmarks[i], kReals[j], kImaginaries[j], component); + dst.m().at(i,j) = response(src, points[i], kReals[j], kImaginaries[j], component); } }; diff --git a/sdk/plugins/youtube.cpp b/sdk/plugins/youtube.cpp index feec07e..1a96ddc 100644 --- a/sdk/plugins/youtube.cpp +++ b/sdk/plugins/youtube.cpp @@ -1,5 +1,7 @@ #include +#include "core/common.h" + namespace br { @@ -19,24 +21,27 @@ class YouTubeFacesDBTransform : public UntrainableMetaTransform void init() { + if (algorithm.isEmpty()) return; transform = Transform::fromAlgorithm(algorithm); distance = Distance::fromAlgorithm(algorithm); } - void project(const Template &src, Template &dst) const + void project(const TemplateList &src, TemplateList &dst) const { - dst = src; - - // First input is the header in 'splits.txt' - if (src.file.getInt("Index") == 0) return; + Transform::project(src.mid(1) /* First template is the header in 'splits.txt' */, dst); + } + void project(const Template &src, Template &dst) const + { const QStringList words = src.file.name.split(", "); dst.file.name = words[0] + "_" + words[1] + "_" + words[4] + ".mtx"; TemplateList queryTemplates = TemplateList::fromGallery(File(words[2]).resolved()); + sort(queryTemplates); queryTemplates >> *transform; TemplateList targetTemplates = TemplateList::fromGallery(File(words[3]).resolved()); + sort(targetTemplates); targetTemplates >> *transform; QScopedPointer memoryOutput(MatrixOutput::make(targetTemplates.files(), queryTemplates.files())); @@ -45,6 +50,26 @@ class YouTubeFacesDBTransform : public UntrainableMetaTransform dst.clear(); dst.m() = memoryOutput.data()->data; } + + static void sort(TemplateList &templates) + { + // The file names in the YouTube Faces Database make it very difficult + // for them to be ordered by frame number automatically, + // hence we do it manually here. + QList frames; + foreach (const Template &t, templates) { + QStringList words = t.file.name.split('.'); + frames.append(words[words.size()-2].toInt()); + } + + typedef QPair SortedFrame; // + QList sortedFrames = Common::Sort(frames); + TemplateList sortedTemplates; sortedTemplates.reserve(templates.size()); + foreach (const SortedFrame &sortedFrame, sortedFrames) + sortedTemplates.append(templates[sortedFrame.second]); + + templates = sortedTemplates; + } }; BR_REGISTER(Transform, YouTubeFacesDBTransform)