diff --git a/openbr/plugins/ebif.cpp b/openbr/plugins/classification/ebif.cpp index 718a3b9..ca9f184 100644 --- a/openbr/plugins/ebif.cpp +++ b/openbr/plugins/classification/ebif.cpp @@ -1,8 +1,8 @@ #include -#include "openbr_internal.h" -#include "openbr/core/common.h" -#include "openbr/core/opencvutils.h" +#include +#include +#include using namespace cv; diff --git a/openbr/plugins/cluster/collectnn.cpp b/openbr/plugins/cluster/collectnn.cpp new file mode 100644 index 0000000..74f2eff --- /dev/null +++ b/openbr/plugins/cluster/collectnn.cpp @@ -0,0 +1,42 @@ +#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 "collectnn.moc" diff --git a/openbr/plugins/cluster/kmeans.cpp b/openbr/plugins/cluster/kmeans.cpp new file mode 100644 index 0000000..4f30c31 --- /dev/null +++ b/openbr/plugins/cluster/kmeans.cpp @@ -0,0 +1,65 @@ +#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 "kmeans.moc" diff --git a/openbr/plugins/cluster.cpp b/openbr/plugins/cluster/knn.cpp index ab95f27..60fed0f 100644 --- a/openbr/plugins/cluster.cpp +++ b/openbr/plugins/cluster/knn.cpp @@ -1,25 +1,5 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * 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; @@ -28,58 +8,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 +79,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 "knn.moc" diff --git a/openbr/plugins/cluster/lognn.cpp b/openbr/plugins/cluster/lognn.cpp new file mode 100644 index 0000000..0cdab6e --- /dev/null +++ b/openbr/plugins/cluster/lognn.cpp @@ -0,0 +1,65 @@ +#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 "lognn.moc" diff --git a/openbr/plugins/cluster/randomcentroids.cpp b/openbr/plugins/cluster/randomcentroids.cpp new file mode 100644 index 0000000..d9b8c9d --- /dev/null +++ b/openbr/plugins/cluster/randomcentroids.cpp @@ -0,0 +1,68 @@ +#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 "randomcentroids.moc" diff --git a/openbr/plugins/algorithms.cpp b/openbr/plugins/core/algorithms.cpp index 88ecaea..2293e65 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 { diff --git a/openbr/plugins/independent.cpp b/openbr/plugins/core/downsample.cpp index 7ac93ce..b1cc62f 100644 --- a/openbr/plugins/independent.cpp +++ b/openbr/plugins/core/downsample.cpp @@ -1,10 +1,4 @@ -#include -#include - -#include "openbr_internal.h" -#include "openbr/core/common.h" - -using namespace cv; +#include namespace br { @@ -103,10 +97,10 @@ class DownsampleTrainingTransform : public Transform void project(const Template &src, Template &dst) const { - transform->project(src,dst); + transform->project(src,dst); } - - + + void train(const TemplateList &data) { if (!transform || !transform->trainable) @@ -117,274 +111,9 @@ class DownsampleTrainingTransform : public Transform transform->train(downsampled); } }; -BR_REGISTER(Transform, DownsampleTrainingTransform) - -/*! - * \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) - -/*! - * \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) +BR_REGISTER(Transform, DownsampleTrainingTransform) } // namespace br -#include "independent.moc" +#include "downsample.moc" diff --git a/openbr/plugins/core/independent.cpp b/openbr/plugins/core/independent.cpp new file mode 100644 index 0000000..41a0054 --- /dev/null +++ b/openbr/plugins/core/independent.cpp @@ -0,0 +1,213 @@ +#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 "independent.moc" diff --git a/openbr/plugins/core/singleton.cpp b/openbr/plugins/core/singleton.cpp new file mode 100644 index 0000000..df64659 --- /dev/null +++ b/openbr/plugins/core/singleton.cpp @@ -0,0 +1,73 @@ +#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 "singleton.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 362ca0c..0000000 --- a/openbr/plugins/cvt.cpp +++ /dev/null @@ -1,292 +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)); - } -}; - -BR_REGISTER(Transform, ScaleTransform) - -/*! - * \ingroup transforms - * \brief Split a multi-channel matrix into several single-channel matrices. - * \author Josh Klontz \cite jklontz - */ -class SplitChannelsTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - std::vector 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/cascade.cpp b/openbr/plugins/detection/cascade.cpp index a0b282a..f523cff 100644 --- a/openbr/plugins/cascade.cpp +++ b/openbr/plugins/detection/cascade.cpp @@ -15,7 +15,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include -#include "openbr_internal.h" +#include #include "openbr/core/opencvutils.h" #include "openbr/core/resource.h" #include "openbr/core/qtutils.h" diff --git a/openbr/plugins/eyes.cpp b/openbr/plugins/detection/eyes.cpp index 5d3929d..ae00219 100644 --- a/openbr/plugins/eyes.cpp +++ b/openbr/plugins/detection/eyes.cpp @@ -35,8 +35,9 @@ #include #include -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" + +#include +#include using namespace cv; diff --git a/openbr/plugins/detection/keypointdetector.cpp b/openbr/plugins/detection/keypointdetector.cpp new file mode 100644 index 0000000..0c59d9e --- /dev/null +++ b/openbr/plugins/detection/keypointdetector.cpp @@ -0,0 +1,54 @@ +#include + +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Wraps OpenCV Key Point Detector + * \author Josh Klontz \cite jklontz + */ +class KeyPointDetectorTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(QString detector READ get_detector WRITE set_detector RESET reset_detector STORED false) + BR_PROPERTY(QString, detector, "SIFT") + + Ptr featureDetector; + + void init() + { + featureDetector = FeatureDetector::create(detector.toStdString()); + if (featureDetector.empty()) + qFatal("Failed to create KeyPointDetector: %s", qPrintable(detector)); + } + + void project(const Template &src, Template &dst) const + { + dst = src; + + std::vector keyPoints; + try { + featureDetector->detect(src, keyPoints); + } catch (...) { + qWarning("Key point detection failed for file %s", qPrintable(src.file.name)); + dst.file.fte = true; + } + + QList rects; + foreach (const KeyPoint &keyPoint, keyPoints) + rects.append(Rect(keyPoint.pt.x-keyPoint.size/2, keyPoint.pt.y-keyPoint.size/2, keyPoint.size, keyPoint.size)); + dst.file.setRects(OpenCVUtils::fromRects(rects)); + } +}; + +BR_REGISTER(Transform, KeyPointDetectorTransform) + +} // namespace br + +#include "keypointdetector.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/L2.cpp b/openbr/plugins/distance/L2.cpp new file mode 100644 index 0000000..3543894 --- /dev/null +++ b/openbr/plugins/distance/L2.cpp @@ -0,0 +1,30 @@ +#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 "L2.moc" diff --git a/openbr/plugins/distance/attribute.cpp b/openbr/plugins/distance/attribute.cpp new file mode 100644 index 0000000..17446b2 --- /dev/null +++ b/openbr/plugins/distance/attribute.cpp @@ -0,0 +1,34 @@ +#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 "attribute.moc" diff --git a/openbr/plugins/distance/byteL1.cpp b/openbr/plugins/distance/byteL1.cpp new file mode 100644 index 0000000..4457999 --- /dev/null +++ b/openbr/plugins/distance/byteL1.cpp @@ -0,0 +1,26 @@ +#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 "byteL1.moc" diff --git a/openbr/plugins/distance/default.cpp b/openbr/plugins/distance/default.cpp new file mode 100644 index 0000000..1409145 --- /dev/null +++ b/openbr/plugins/distance/default.cpp @@ -0,0 +1,31 @@ +#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 "default.moc" diff --git a/openbr/plugins/distance/dist.cpp b/openbr/plugins/distance/dist.cpp new file mode 100644 index 0000000..1f36afc --- /dev/null +++ b/openbr/plugins/distance/dist.cpp @@ -0,0 +1,104 @@ +#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 "dist.moc" diff --git a/openbr/plugins/distance/fuse.cpp b/openbr/plugins/distance/fuse.cpp new file mode 100644 index 0000000..782fe58 --- /dev/null +++ b/openbr/plugins/distance/fuse.cpp @@ -0,0 +1,101 @@ +#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 "fuse.moc" diff --git a/openbr/plugins/distance/gallerycompare.cpp b/openbr/plugins/distance/gallerycompare.cpp new file mode 100644 index 0000000..d4aba79 --- /dev/null +++ b/openbr/plugins/distance/gallerycompare.cpp @@ -0,0 +1,64 @@ +#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 "gallerycompare.moc" diff --git a/openbr/plugins/distance/halfbyteL1.cpp b/openbr/plugins/distance/halfbyteL1.cpp new file mode 100644 index 0000000..6f89ebb --- /dev/null +++ b/openbr/plugins/distance/halfbyteL1.cpp @@ -0,0 +1,29 @@ +#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 "halfbyteL1.moc" diff --git a/openbr/plugins/distance/identical.cpp b/openbr/plugins/distance/identical.cpp new file mode 100644 index 0000000..ec36f24 --- /dev/null +++ b/openbr/plugins/distance/identical.cpp @@ -0,0 +1,31 @@ +#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 + +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 "L1.moc" diff --git a/openbr/plugins/distance/neglogplusone.cpp b/openbr/plugins/distance/neglogplusone.cpp new file mode 100644 index 0000000..ded2dfc --- /dev/null +++ b/openbr/plugins/distance/neglogplusone.cpp @@ -0,0 +1,42 @@ +#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 "neglogplusone.moc" diff --git a/openbr/plugins/distance/online.cpp b/openbr/plugins/distance/online.cpp new file mode 100644 index 0000000..1880e68 --- /dev/null +++ b/openbr/plugins/distance/online.cpp @@ -0,0 +1,35 @@ +#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 "online.moc" diff --git a/openbr/plugins/distance/pipe.cpp b/openbr/plugins/distance/pipe.cpp new file mode 100644 index 0000000..a6cc9c0 --- /dev/null +++ b/openbr/plugins/distance/pipe.cpp @@ -0,0 +1,47 @@ +#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 "pipe.moc" diff --git a/openbr/plugins/distance/sum.cpp b/openbr/plugins/distance/sum.cpp new file mode 100644 index 0000000..6ab6b38 --- /dev/null +++ b/openbr/plugins/distance/sum.cpp @@ -0,0 +1,46 @@ +#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 "sum.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.cpp b/openbr/plugins/eigen3.cpp index dfe4ba8..97bd90f 100644 --- a/openbr/plugins/eigen3.cpp +++ b/openbr/plugins/eigen3.cpp @@ -652,46 +652,6 @@ class SparseLDATransform : public Transform BR_REGISTER(Transform, SparseLDATransform) -/*! - * \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) - -/*! - * \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 "eigen3.moc" diff --git a/openbr/plugins/fill.cpp b/openbr/plugins/fill.cpp deleted file mode 100644 index 1614337..0000000 --- a/openbr/plugins/fill.cpp +++ /dev/null @@ -1,126 +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" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup transforms - * \brief Wraps OpenCV inpainting - * \author Josh Klontz \cite jklontz - */ -class InpaintTransform : public UntrainableTransform -{ - Q_OBJECT - Q_ENUMS(Method) - Q_PROPERTY(int radius READ get_radius WRITE set_radius RESET reset_radius STORED false) - Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false) - -public: - /*!< */ - enum Method { NavierStokes = INPAINT_NS, - Telea = INPAINT_TELEA }; - -private: - BR_PROPERTY(int, radius, 1) - BR_PROPERTY(Method, method, NavierStokes) - Transform *cvtGray; - - void init() - { - cvtGray = make("Cvt(Gray)"); - } - - void project(const Template &src, Template &dst) const - { - inpaint(src, (*cvtGray)(src)<5, dst, radius, method); - } -}; - -BR_REGISTER(Transform, InpaintTransform) - -/*! - * \ingroup transforms - * \brief Fill 0 pixels with the mean of non-0 pixels. - * \author Josh Klontz \cite jklontz - */ -class MeanFillTransform : public UntrainableTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - dst = src.m().clone(); - dst.m().setTo(mean(dst, dst.m()!=0), dst.m()==0); - } -}; - -BR_REGISTER(Transform, MeanFillTransform) - -/*! - * \ingroup transforms - * \brief Fill black pixels with the specified color. - * \author Josh Klontz \cite jklontz - */ -class FloodTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(int r READ get_r WRITE set_r RESET reset_r STORED false) - Q_PROPERTY(int g READ get_g WRITE set_g RESET reset_g STORED false) - Q_PROPERTY(int b READ get_b WRITE set_b RESET reset_b STORED false) - Q_PROPERTY(bool all READ get_all WRITE set_all RESET reset_all STORED false) - BR_PROPERTY(int, r, 0) - BR_PROPERTY(int, g, 0) - BR_PROPERTY(int, b, 0) - BR_PROPERTY(bool, all, false) - - void project(const Template &src, Template &dst) const - { - dst = src.m().clone(); - dst.m().setTo(Scalar(r, g, b), all ? Mat() : dst.m()==0); - } -}; - -BR_REGISTER(Transform, FloodTransform) - -/*! - * \ingroup transforms - * \brief Alpha-blend two matrices - * \author Josh Klontz \cite jklontz - */ -class BlendTransform : public UntrainableMetaTransform -{ - Q_OBJECT - Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false) - BR_PROPERTY(float, alpha, 0.5) - - void project(const Template &src, Template &dst) const - { - if (src.size() != 2) qFatal("Expected two source matrices."); - addWeighted(src[0], alpha, src[1], 1-alpha, 0, dst); - } -}; - -BR_REGISTER(Transform, BlendTransform) - -} // namespace br - -#include "fill.moc" 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 "binary.moc" diff --git a/openbr/plugins/format/csv.cpp b/openbr/plugins/format/csv.cpp new file mode 100644 index 0000000..ccede30 --- /dev/null +++ b/openbr/plugins/format/csv.cpp @@ -0,0 +1,73 @@ +#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 "lffs.moc" diff --git a/openbr/plugins/format/mat.cpp b/openbr/plugins/format/mat.cpp new file mode 100644 index 0000000..6e6e411 --- /dev/null +++ b/openbr/plugins/format/mat.cpp @@ -0,0 +1,227 @@ +#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 "mtx.moc" diff --git a/openbr/plugins/format/null.cpp b/openbr/plugins/format/null.cpp new file mode 100644 index 0000000..b5cbd38 --- /dev/null +++ b/openbr/plugins/format/null.cpp @@ -0,0 +1,30 @@ +#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 "null.moc" diff --git a/openbr/plugins/format/raw.cpp b/openbr/plugins/format/raw.cpp new file mode 100644 index 0000000..3cf868f --- /dev/null +++ b/openbr/plugins/format/raw.cpp @@ -0,0 +1,70 @@ +#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 "raw.moc" diff --git a/openbr/plugins/format/scores.cpp b/openbr/plugins/format/scores.cpp new file mode 100644 index 0000000..453dffa --- /dev/null +++ b/openbr/plugins/format/scores.cpp @@ -0,0 +1,65 @@ +#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 "scores.moc" diff --git a/openbr/plugins/format/video.cpp b/openbr/plugins/format/video.cpp new file mode 100644 index 0000000..0a1eaf8 --- /dev/null +++ b/openbr/plugins/format/video.cpp @@ -0,0 +1,147 @@ +#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 "video.moc" diff --git a/openbr/plugins/format/xml.cpp b/openbr/plugins/format/xml.cpp new file mode 100644 index 0000000..8cf3dc2 --- /dev/null +++ b/openbr/plugins/format/xml.cpp @@ -0,0 +1,102 @@ +#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 "xml.moc" diff --git a/openbr/plugins/gui.cpp b/openbr/plugins/gui.cpp index 3aab218..670fb09 100644 --- a/openbr/plugins/gui.cpp +++ b/openbr/plugins/gui.cpp @@ -928,6 +928,54 @@ public: BR_REGISTER(Transform, SurveyTransform) +class FilterTransform : public ShowTransform +{ + Q_OBJECT + + void projectUpdate(const TemplateList &src, TemplateList &dst) + { + if (Globals->parallelism > 1) + qFatal("FilterTransform cannot execute in parallel."); + + if (src.empty()) + return; + + foreach (const Template &t, src) { + Template u(t.file); + foreach (const cv::Mat &m, t) { + qImageBuffer = toQImage(m); + displayBuffer->convertFromImage(qImageBuffer); + + emit updateImage(displayBuffer->copy(displayBuffer->rect())); + + // Blocking wait for a key-press + if (this->waitInput) { + QString answer = p_window->waitForKeyPress(); + qDebug() << answer; + if (answer == "y") + u.append(m); + } + } + if (!u.empty()) + dst.append(u); + } + } + PromptWindow *p_window; + + + void init() + { + if (!Globals->useGui) + return; + + initActual(); + p_window = (PromptWindow *) window; + + emit changeTitle("Keep: y Discard: n"); + } +}; + +BR_REGISTER(Transform, FilterTransform) /*! * \ingroup transforms diff --git a/openbr/plugins/gui/adjacentoverlay.cpp b/openbr/plugins/gui/adjacentoverlay.cpp new file mode 100644 index 0000000..7df86fb --- /dev/null +++ b/openbr/plugins/gui/adjacentoverlay.cpp @@ -0,0 +1,217 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Load the image named in the specified property, draw it on the current matrix adjacent to the rect specified in the other property. + * \author Charles Otto \cite caotto + */ +class AdjacentOverlayTransform : public Transform +{ + Q_OBJECT + + Q_PROPERTY(QString imgName READ get_imgName WRITE set_imgName RESET reset_imgName STORED false) + Q_PROPERTY(QString targetName READ get_targetName WRITE set_targetName RESET reset_targetName STORED false) + BR_PROPERTY(QString, imgName, "") + BR_PROPERTY(QString, targetName, "") + + QSharedPointer 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) + +} // namespace br + +#include "adjacentoverlay.moc" diff --git a/openbr/plugins/gui/draw.cpp b/openbr/plugins/gui/draw.cpp new file mode 100644 index 0000000..63863b1 --- /dev/null +++ b/openbr/plugins/gui/draw.cpp @@ -0,0 +1,61 @@ +#include +#include + +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 + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Draws a grid on the image + * \author Josh Klontz \cite jklontz + */ +class DrawGridLinesTransform : public UntrainableTransform +{ + Q_OBJECT + 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(int r READ get_r WRITE set_r RESET reset_r STORED false) + Q_PROPERTY(int g READ get_g WRITE set_g RESET reset_g STORED false) + Q_PROPERTY(int b READ get_b WRITE set_b RESET reset_b STORED false) + BR_PROPERTY(int, rows, 0) + BR_PROPERTY(int, columns, 0) + BR_PROPERTY(int, r, 196) + BR_PROPERTY(int, g, 196) + BR_PROPERTY(int, b, 196) + + void project(const Template &src, Template &dst) const + { + Mat m = src.m().clone(); + float rowStep = 1.f * m.rows / (rows+1); + float columnStep = 1.f * m.cols / (columns+1); + int thickness = qMin(m.rows, m.cols) / 256; + for (float row = rowStep/2; row < m.rows; row += rowStep) + line(m, Point(0, row), Point(m.cols, row), Scalar(r, g, b), thickness, CV_AA); + for (float column = columnStep/2; column < m.cols; column += columnStep) + line(m, Point(column, 0), Point(column, m.rows), Scalar(r, g, b), thickness, CV_AA); + dst = m; + } +}; + +BR_REGISTER(Transform, DrawGridLinesTransform) + +} // namespace br + +#include "drawgridlines.moc" diff --git a/openbr/plugins/gui/drawopticalflow.cpp b/openbr/plugins/gui/drawopticalflow.cpp new file mode 100644 index 0000000..6f3d3ae --- /dev/null +++ b/openbr/plugins/gui/drawopticalflow.cpp @@ -0,0 +1,40 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "drawopticalflow.moc" diff --git a/openbr/plugins/gui/drawpropertiespoint.cpp b/openbr/plugins/gui/drawpropertiespoint.cpp new file mode 100644 index 0000000..0c0d087 --- /dev/null +++ b/openbr/plugins/gui/drawpropertiespoint.cpp @@ -0,0 +1,70 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Draw the values of a list of properties at the specified point on the image + * + * The inPlace argument controls whether or not the image is cloned before it is drawn on. + * + * \author Charles Otto \cite caotto + */ +class DrawPropertiesPointTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(QStringList propNames READ get_propNames WRITE set_propNames RESET reset_propNames STORED false) + Q_PROPERTY(QString pointName READ get_pointName WRITE set_pointName RESET reset_pointName STORED false) + Q_PROPERTY(bool inPlace READ get_inPlace WRITE set_inPlace RESET reset_inPlace STORED false) + BR_PROPERTY(QStringList, propNames, QStringList()) + BR_PROPERTY(QString, pointName, "") + BR_PROPERTY(bool, inPlace, false) + + void project(const Template &src, Template &dst) const + { + dst = src; + if (propNames.isEmpty() || pointName.isEmpty()) + return; + + dst.m() = inPlace ? src.m() : src.m().clone(); + + QVariant point = dst.file.value(pointName); + + if (!point.canConvert(QVariant::PointF)) + return; + + QPointF targetPoint = point.toPointF(); + + Point2f cvPoint = OpenCVUtils::toPoint(targetPoint); + + + const Scalar textColor(255, 255, 0); + + std::string outString = ""; + foreach (const QString &propName, propNames) + { + QVariant prop = dst.file.value(propName); + + if (!prop.canConvert(QVariant::String)) + continue; + QString propString = prop.toString(); + outString += propName.toStdString() + ": " + propString.toStdString() + " "; + + } + if (outString.empty()) + return; + + putText(dst, outString, cvPoint, FONT_HERSHEY_SIMPLEX, 0.5, textColor, 1); + } + +}; + +BR_REGISTER(Transform, DrawPropertiesPointTransform) + +} // namespace br + +#include "drawpropertiespoint.moc" diff --git a/openbr/plugins/gui/drawpropertypoint.cpp b/openbr/plugins/gui/drawpropertypoint.cpp new file mode 100644 index 0000000..98b6201 --- /dev/null +++ b/openbr/plugins/gui/drawpropertypoint.cpp @@ -0,0 +1,63 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Draw the value of the specified property at the specified point on the image + * + * The inPlace argument controls whether or not the image is cloned before it is drawn on. + * + * \author Charles Otto \cite caotto + */ +class DrawPropertyPointTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(QString propName READ get_propName WRITE set_propName RESET reset_propName STORED false) + Q_PROPERTY(QString pointName READ get_pointName WRITE set_pointName RESET reset_pointName STORED false) + Q_PROPERTY(bool inPlace READ get_inPlace WRITE set_inPlace RESET reset_inPlace STORED false) + BR_PROPERTY(QString, propName, "") + BR_PROPERTY(QString, pointName, "") + BR_PROPERTY(bool, inPlace, false) + + + void project(const Template &src, Template &dst) const + { + dst = src; + if (propName.isEmpty() || pointName.isEmpty()) + return; + + dst.m() = inPlace ? src.m() : src.m().clone(); + + const Scalar textColor(255, 255, 0); + + QVariant prop = dst.file.value(propName); + + + if (!prop.canConvert(QVariant::String)) + return; + QString propString = prop.toString(); + + QVariant point = dst.file.value(pointName); + + if (!point.canConvert(QVariant::PointF)) + return; + + QPointF targetPoint = point.toPointF(); + + Point2f cvPoint = OpenCVUtils::toPoint(targetPoint); + + std::string text = propName.toStdString() + ": " + propString.toStdString(); + putText(dst, text, cvPoint, FONT_HERSHEY_SIMPLEX, 0.5, textColor, 1); + } + +}; +BR_REGISTER(Transform, DrawPropertyPointTransform) + +} // namespace br + +#include "drawpropertypoint.moc" diff --git a/openbr/plugins/gui/drawsegmentation.cpp b/openbr/plugins/gui/drawsegmentation.cpp new file mode 100644 index 0000000..28eb406 --- /dev/null +++ b/openbr/plugins/gui/drawsegmentation.cpp @@ -0,0 +1,55 @@ +#include + +#include + +using namespace std; +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "drawsegmentation.moc" diff --git a/openbr/plugins/imgproc/abs.cpp b/openbr/plugins/imgproc/abs.cpp new file mode 100644 index 0000000..8e42db1 --- /dev/null +++ b/openbr/plugins/imgproc/abs.cpp @@ -0,0 +1,25 @@ +#include + +namespace br +{ + +/*! + * \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 = cv::abs(src); + } +}; + +BR_REGISTER(Transform, AbsTransform) + +} // namespace br + +#include "abs.moc" diff --git a/openbr/plugins/imgproc/blend.cpp b/openbr/plugins/imgproc/blend.cpp new file mode 100644 index 0000000..5c8d560 --- /dev/null +++ b/openbr/plugins/imgproc/blend.cpp @@ -0,0 +1,30 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Alpha-blend two matrices + * \author Josh Klontz \cite jklontz + */ +class BlendTransform : public UntrainableMetaTransform +{ + Q_OBJECT + Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false) + BR_PROPERTY(float, alpha, 0.5) + + void project(const Template &src, Template &dst) const + { + if (src.size() != 2) qFatal("Expected two source matrices."); + addWeighted(src[0], alpha, src[1], 1-alpha, 0, dst); + } +}; + +BR_REGISTER(Transform, BlendTransform) + +} // namespace br + +#include "blend.moc" diff --git a/openbr/plugins/imgproc/blur.cpp b/openbr/plugins/imgproc/blur.cpp new file mode 100644 index 0000000..9b6b18c --- /dev/null +++ b/openbr/plugins/imgproc/blur.cpp @@ -0,0 +1,43 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "blur.moc" diff --git a/openbr/plugins/imgproc/contrasteq.cpp b/openbr/plugins/imgproc/contrasteq.cpp new file mode 100644 index 0000000..0c1847b --- /dev/null +++ b/openbr/plugins/imgproc/contrasteq.cpp @@ -0,0 +1,70 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "contrasteq.moc" diff --git a/openbr/plugins/imgproc/crop.cpp b/openbr/plugins/imgproc/crop.cpp new file mode 100644 index 0000000..fbd1b78 --- /dev/null +++ b/openbr/plugins/imgproc/crop.cpp @@ -0,0 +1,35 @@ +#include + +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) + +} // namespace br + +#include "crop.moc" diff --git a/openbr/plugins/imgproc/cropblack.cpp b/openbr/plugins/imgproc/cropblack.cpp new file mode 100644 index 0000000..e944600 --- /dev/null +++ b/openbr/plugins/imgproc/cropblack.cpp @@ -0,0 +1,55 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "cropblack.moc" diff --git a/openbr/plugins/imgproc/cropsquare.cpp b/openbr/plugins/imgproc/cropsquare.cpp new file mode 100644 index 0000000..01fa805 --- /dev/null +++ b/openbr/plugins/imgproc/cropsquare.cpp @@ -0,0 +1,29 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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 "cropsquare.moc" diff --git a/openbr/plugins/imgproc/csdn.cpp b/openbr/plugins/imgproc/csdn.cpp new file mode 100644 index 0000000..a4fd214 --- /dev/null +++ b/openbr/plugins/imgproc/csdn.cpp @@ -0,0 +1,58 @@ +#include + +using namespace std; +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "csdn.moc" diff --git a/openbr/plugins/imgproc/cvtcolor.cpp b/openbr/plugins/imgproc/cvtcolor.cpp new file mode 100644 index 0000000..ffc10e9 --- /dev/null +++ b/openbr/plugins/imgproc/cvtcolor.cpp @@ -0,0 +1,55 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Colorspace conversion. + * \author Josh Klontz \cite jklontz + */ +class CvtColorTransform : 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, CvtColorTransform) + +} // namespace br + +#include "cvtcolor.moc" diff --git a/openbr/plugins/imgproc/cvtfloat.cpp b/openbr/plugins/imgproc/cvtfloat.cpp new file mode 100644 index 0000000..c508cba --- /dev/null +++ b/openbr/plugins/imgproc/cvtfloat.cpp @@ -0,0 +1,27 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "cvtfloat.moc" diff --git a/openbr/plugins/imgproc/cvtuchar.cpp b/openbr/plugins/imgproc/cvtuchar.cpp new file mode 100644 index 0000000..b829b67 --- /dev/null +++ b/openbr/plugins/imgproc/cvtuchar.cpp @@ -0,0 +1,26 @@ +#include +#include + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "cvtuchar.moc" diff --git a/openbr/plugins/denoising.cpp b/openbr/plugins/imgproc/denoising.cpp index 03ac646..93cb619 100644 --- a/openbr/plugins/denoising.cpp +++ b/openbr/plugins/imgproc/denoising.cpp @@ -15,7 +15,8 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include -#include "openbr_internal.h" + +#include using namespace cv; diff --git a/openbr/plugins/imgproc/discardalpha.cpp b/openbr/plugins/imgproc/discardalpha.cpp new file mode 100644 index 0000000..368faed --- /dev/null +++ b/openbr/plugins/imgproc/discardalpha.cpp @@ -0,0 +1,40 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "discardalpha.moc" diff --git a/openbr/plugins/imgproc/div.cpp b/openbr/plugins/imgproc/div.cpp new file mode 100644 index 0000000..2738d21 --- /dev/null +++ b/openbr/plugins/imgproc/div.cpp @@ -0,0 +1,29 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "div.moc" diff --git a/openbr/plugins/imgproc/dog.cpp b/openbr/plugins/imgproc/dog.cpp new file mode 100644 index 0000000..9c0044d --- /dev/null +++ b/openbr/plugins/imgproc/dog.cpp @@ -0,0 +1,54 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "dog.moc" diff --git a/openbr/plugins/imgproc/ensurechannels.cpp b/openbr/plugins/imgproc/ensurechannels.cpp new file mode 100644 index 0000000..4351738 --- /dev/null +++ b/openbr/plugins/imgproc/ensurechannels.cpp @@ -0,0 +1,51 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "ensurechannels.moc" diff --git a/openbr/plugins/imgproc/flood.cpp b/openbr/plugins/imgproc/flood.cpp new file mode 100644 index 0000000..60c9ec9 --- /dev/null +++ b/openbr/plugins/imgproc/flood.cpp @@ -0,0 +1,36 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Fill black pixels with the specified color. + * \author Josh Klontz \cite jklontz + */ +class FloodTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(int r READ get_r WRITE set_r RESET reset_r STORED false) + Q_PROPERTY(int g READ get_g WRITE set_g RESET reset_g STORED false) + Q_PROPERTY(int b READ get_b WRITE set_b RESET reset_b STORED false) + Q_PROPERTY(bool all READ get_all WRITE set_all RESET reset_all STORED false) + BR_PROPERTY(int, r, 0) + BR_PROPERTY(int, g, 0) + BR_PROPERTY(int, b, 0) + BR_PROPERTY(bool, all, false) + + void project(const Template &src, Template &dst) const + { + dst = src.m().clone(); + dst.m().setTo(Scalar(r, g, b), all ? Mat() : dst.m()==0); + } +}; + +BR_REGISTER(Transform, FloodTransform) + +} // namespace br + +#include "flood.moc" diff --git a/openbr/plugins/imgproc/gamma.cpp b/openbr/plugins/imgproc/gamma.cpp new file mode 100644 index 0000000..6f50132 --- /dev/null +++ b/openbr/plugins/imgproc/gamma.cpp @@ -0,0 +1,39 @@ +#include + +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) + +} // namespace br + +#include "gamma.moc" diff --git a/openbr/plugins/imgproc/gradient.cpp b/openbr/plugins/imgproc/gradient.cpp new file mode 100644 index 0000000..7df82e2 --- /dev/null +++ b/openbr/plugins/imgproc/gradient.cpp @@ -0,0 +1,50 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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 + { + Mat dx, dy, magnitude, angle; + Sobel(src, dx, CV_32F, 1, 0, CV_SCHARR); + Sobel(src, dy, CV_32F, 0, 1, CV_SCHARR); + cartToPolar(dx, dy, magnitude, angle, true); + std::vector mv; + if ((channel == Magnitude) || (channel == MagnitudeAndAngle)) { + const float theoreticalMaxMagnitude = sqrt(2*pow(float(2*(3+10+3)*255), 2.f)); + mv.push_back(magnitude / theoreticalMaxMagnitude); + } + if ((channel == Angle) || (channel == MagnitudeAndAngle)) + mv.push_back(angle); + Mat result; + merge(mv, result); + dst.append(result); + } +}; + +BR_REGISTER(Transform, GradientTransform) + +} // namespace br + +#include "gradient.moc" diff --git a/openbr/plugins/imgproc/hist.cpp b/openbr/plugins/imgproc/hist.cpp new file mode 100644 index 0000000..47296cd --- /dev/null +++ b/openbr/plugins/imgproc/hist.cpp @@ -0,0 +1,54 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Histograms the matrix + * \author Josh Klontz \cite jklontz + */ +class HistTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(float max READ get_max WRITE set_max RESET reset_max STORED false) + Q_PROPERTY(float min READ get_min WRITE set_min RESET reset_min STORED false) + Q_PROPERTY(int dims READ get_dims WRITE set_dims RESET reset_dims STORED false) + BR_PROPERTY(float, max, 256) + BR_PROPERTY(float, min, 0) + BR_PROPERTY(int, dims, -1) + + void project(const Template &src, Template &dst) const + { + const int dims = this->dims == -1 ? max - min : this->dims; + + std::vector mv; + split(src, mv); + Mat m(mv.size(), dims, CV_32FC1); + + for (size_t i=0; i -#include "openbr_internal.h" -#include "openbr/core/common.h" -#include "openbr/core/opencvutils.h" +#include using namespace cv; @@ -26,52 +7,10 @@ namespace br /*! * \ingroup transforms - * \brief Histograms the matrix - * \author Josh Klontz \cite jklontz - */ -class HistTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(float max READ get_max WRITE set_max RESET reset_max STORED false) - Q_PROPERTY(float min READ get_min WRITE set_min RESET reset_min STORED false) - Q_PROPERTY(int dims READ get_dims WRITE set_dims RESET reset_dims STORED false) - BR_PROPERTY(float, max, 256) - BR_PROPERTY(float, min, 0) - BR_PROPERTY(int, dims, -1) - - void project(const Template &src, Template &dst) const - { - const int dims = this->dims == -1 ? max - min : this->dims; - - std::vector mv; - split(src, mv); - Mat m(mv.size(), dims, CV_32FC1); - - for (size_t i=0; i Tuple; - QList tuples = Common::Sort(OpenCVUtils::matrixToVector(m)); - - float prevValue = 0; - int prevRank = 0; - for (int i=0; i(tuples[i].second / m.cols, tuples[i].second % m.cols) = rank; - prevValue = tuples[i].first; - prevRank = rank; - } - } -}; - -BR_REGISTER(Transform, RankTransform) - -/*! - * \ingroup transforms - * \brief An integral histogram - * \author Josh Klontz \cite jklontz - */ -class IntegralHistTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(int bins READ get_bins WRITE set_bins RESET reset_bins STORED false) - Q_PROPERTY(int radius READ get_radius WRITE set_radius RESET reset_radius STORED false) - BR_PROPERTY(int, bins, 256) - BR_PROPERTY(int, radius, 16) - - void project(const Template &src, Template &dst) const - { - const Mat &m = src.m(); - if (m.type() != CV_8UC1) qFatal("IntegralHist requires 8UC1 matrices."); - - Mat integral(m.rows/radius+1, (m.cols/radius+1)*bins, CV_32SC1); - integral.setTo(0); - for (int i=1; i(i, j+k) += integral.at(i-1, j +k); - for (int k=0; k(i, j+k) += integral.at(i , j-bins+k); - for (int k=0; k(i, j+k) -= integral.at(i-1, j-bins+k); - for (int k=0; k(i, j+m.at((i-1)*radius+k,(j/bins-1)*radius+l))++; - } - } - dst = integral; - } -}; - -BR_REGISTER(Transform, IntegralHistTransform) +BR_REGISTER(Transform, HistBinTransform) } // namespace br -#include "hist.moc" +#include "histbin.moc" diff --git a/openbr/plugins/imgproc/inpaint.cpp b/openbr/plugins/imgproc/inpaint.cpp new file mode 100644 index 0000000..05b22d9 --- /dev/null +++ b/openbr/plugins/imgproc/inpaint.cpp @@ -0,0 +1,45 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Wraps OpenCV inpainting + * \author Josh Klontz \cite jklontz + */ +class InpaintTransform : public UntrainableTransform +{ + Q_OBJECT + Q_ENUMS(Method) + Q_PROPERTY(int radius READ get_radius WRITE set_radius RESET reset_radius STORED false) + Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false) + +public: + /*!< */ + enum Method { NavierStokes = INPAINT_NS, + Telea = INPAINT_TELEA }; + +private: + BR_PROPERTY(int, radius, 1) + BR_PROPERTY(Method, method, NavierStokes) + Transform *cvtGray; + + void init() + { + cvtGray = make("Cvt(Gray)"); + } + + void project(const Template &src, Template &dst) const + { + inpaint(src, (*cvtGray)(src)<5, dst, radius, method); + } +}; + +} // namespace br + +#include "inpaint.moc" diff --git a/openbr/plugins/imgproc/integral.cpp b/openbr/plugins/imgproc/integral.cpp new file mode 100644 index 0000000..ac3e034 --- /dev/null +++ b/openbr/plugins/imgproc/integral.cpp @@ -0,0 +1,27 @@ +#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) + +} // namespace br + +#include "integral.moc" diff --git a/openbr/plugins/imgproc/integralhist.cpp b/openbr/plugins/imgproc/integralhist.cpp new file mode 100644 index 0000000..4bed769 --- /dev/null +++ b/openbr/plugins/imgproc/integralhist.cpp @@ -0,0 +1,46 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief An integral histogram + * \author Josh Klontz \cite jklontz + */ +class IntegralHistTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(int bins READ get_bins WRITE set_bins RESET reset_bins STORED false) + Q_PROPERTY(int radius READ get_radius WRITE set_radius RESET reset_radius STORED false) + BR_PROPERTY(int, bins, 256) + BR_PROPERTY(int, radius, 16) + + void project(const Template &src, Template &dst) const + { + const Mat &m = src.m(); + if (m.type() != CV_8UC1) qFatal("IntegralHist requires 8UC1 matrices."); + + Mat integral(m.rows/radius+1, (m.cols/radius+1)*bins, CV_32SC1); + integral.setTo(0); + for (int i=1; i(i, j+k) += integral.at(i-1, j +k); + for (int k=0; k(i, j+k) += integral.at(i , j-bins+k); + for (int k=0; k(i, j+k) -= integral.at(i-1, j-bins+k); + for (int k=0; k(i, j+m.at((i-1)*radius+k,(j/bins-1)*radius+l))++; + } + } + dst = integral; + } +}; + +BR_REGISTER(Transform, IntegralHistTransform) + +} // namespace br + +#include "integralhist.moc" diff --git a/openbr/plugins/integral.cpp b/openbr/plugins/imgproc/integralsampler.cpp index f6117a0..914787b 100644 --- a/openbr/plugins/integral.cpp +++ b/openbr/plugins/imgproc/integralsampler.cpp @@ -1,9 +1,6 @@ -#include -#include -#include -#include "openbr_internal.h" +#include -#include "openbr/core/opencvutils.h" +#include using namespace cv; @@ -12,23 +9,6 @@ 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 - { - integral(src, dst); - } -}; - -BR_REGISTER(Transform, IntegralTransform) - -/*! - * \ingroup transforms * \brief Sliding window feature extraction from a multi-channel integral image. * \author Josh Klontz \cite jklontz */ @@ -121,254 +101,6 @@ class IntegralSamplerTransform : public UntrainableTransform BR_REGISTER(Transform, IntegralSamplerTransform) -/*! - * \ingroup transforms - * \brief Construct template in a recursive decent manner. - * \author Josh Klontz \cite jklontz - */ -class RecursiveIntegralSamplerTransform : public Transform -{ - Q_OBJECT - Q_PROPERTY(int scales READ get_scales WRITE set_scales RESET reset_scales STORED false) - Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) - Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) - Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform) - BR_PROPERTY(int, scales, 6) - BR_PROPERTY(float, scaleFactor, 2) - BR_PROPERTY(int, minSize, 8) - BR_PROPERTY(br::Transform*, transform, NULL) - - Transform *subTransform; - - typedef Eigen::Map< const Eigen::Matrix > InputDescriptor; - typedef Eigen::Map< Eigen::Matrix > OutputDescriptor; - typedef Eigen::Map< const Eigen::Matrix > SecondOrderInputDescriptor; - - void init() - { - if (scales >= 2) { - File subFile = file; - subFile.set("scales", scales-1); - subTransform = make(subFile.flat()); - } else { - subTransform = NULL; - } - } - - static void integralHistogram(const Mat &src, const int x, const int y, const int width, const int height, Mat &dst, int index) - { - const int channels = src.channels(); - OutputDescriptor(dst.ptr(index), channels, 1) = - ( InputDescriptor(src.ptr(y+height, x+width), channels, 1) - - InputDescriptor(src.ptr(y, x+width), channels, 1) - - InputDescriptor(src.ptr(y+height, x), channels, 1) - + InputDescriptor(src.ptr(y, x), channels, 1)).cast()/(height*width); - } - - void computeDescriptor(const Mat &src, Mat &dst) const - { - const int channels = src.channels(); - const int rows = src.rows-1; // Integral images have an extra row and column - const int columns = src.cols-1; - - Mat tmp(5, channels, CV_32FC1); - integralHistogram(src, 0, 0, columns/2, rows/2, tmp, 0); - integralHistogram(src, columns/2, 0, columns/2, rows/2, tmp, 1); - integralHistogram(src, 0, rows/2, columns/2, rows/2, tmp, 2); - integralHistogram(src, columns/2, rows/2, columns/2, rows/2, tmp, 3); - integralHistogram(src, columns/4, rows/4, columns/2, rows/2, tmp, 4); - const SecondOrderInputDescriptor a(tmp.ptr(0), channels, 1); - const SecondOrderInputDescriptor b(tmp.ptr(1), channels, 1); - const SecondOrderInputDescriptor c(tmp.ptr(2), channels, 1); - const SecondOrderInputDescriptor d(tmp.ptr(3), channels, 1); - const SecondOrderInputDescriptor e(tmp.ptr(4), channels, 1); - - dst = Mat(5, channels, CV_32FC1); - OutputDescriptor(dst.ptr(0), channels, 1) = (a+b+c+d)/4.f; - OutputDescriptor(dst.ptr(1), channels, 1) = ((a+b+c+d)/4.f-e); - OutputDescriptor(dst.ptr(2), channels, 1) = ((a+b)-(c+d))/2.f; - OutputDescriptor(dst.ptr(3), channels, 1) = ((a+c)-(b+d))/2.f; - OutputDescriptor(dst.ptr(4), channels, 1) = ((a+d)-(b+c))/2.f; - dst = dst.reshape(1, 1); - } - - Template subdivide(const Template &src) const - { - // Integral images have an extra row and column - int subWidth = (src.m().cols-1) / scaleFactor + 1; - int subHeight = (src.m().rows-1) / scaleFactor + 1; - return Template(src.file, QList() << Mat(src, Rect(0, 0, subWidth, subHeight)) - << Mat(src, Rect(src.m().cols-subWidth, 0, subWidth, subHeight)) - << Mat(src, Rect(0, src.m().rows-subHeight, subWidth, subHeight)) - << Mat(src, Rect(src.m().cols-subWidth, src.m().rows-subHeight, subWidth, subHeight))); - } - - bool canSubdivide(const Template &t) const - { - // Integral images have an extra row and column - const int subWidth = (t.m().cols-1) / scaleFactor; - const int subHeight = (t.m().rows-1) / scaleFactor; - return ((subWidth >= minSize) && (subHeight >= minSize)); - } - - void train(const TemplateList &src) - { - if (src.first().m().depth() != CV_32S) - qFatal("Expected CV_32S depth!"); - - if (subTransform != NULL) { - TemplateList subSrc; subSrc.reserve(src.size()); - foreach (const Template &t, src) - if (canSubdivide(t)) - subSrc.append(subdivide(t)); - - if (subSrc.isEmpty()) { - delete subTransform; - subTransform = NULL; - } else { - subTransform->train(subSrc); - } - } - - TemplateList dst; dst.reserve(src.size()); - foreach (const Template &t, src) { - Template u(t.file); - computeDescriptor(t, u); - dst.append(u); - } - transform->train(dst); - } - - void project(const Template &src, Template &dst) const - { - computeDescriptor(src, dst); - transform->project(dst, dst); - - if ((subTransform != NULL) && canSubdivide(src)) { - Template subDst; - subTransform->project(subdivide(src), subDst); - dst.append(subDst); - } - } - - void store(QDataStream &stream) const - { - transform->store(stream); - stream << (subTransform != NULL); - if (subTransform != NULL) - subTransform->store(stream); - } - - void load(QDataStream &stream) - { - transform->load(stream); - bool hasSubTransform; - stream >> hasSubTransform; - if (hasSubTransform) subTransform->load(stream); - else { delete subTransform; subTransform = NULL; } - } -}; - -BR_REGISTER(Transform, RecursiveIntegralSamplerTransform) - -/*! - * \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 - { - Mat dx, dy, magnitude, angle; - Sobel(src, dx, CV_32F, 1, 0, CV_SCHARR); - Sobel(src, dy, CV_32F, 0, 1, CV_SCHARR); - cartToPolar(dx, dy, magnitude, angle, true); - std::vector mv; - if ((channel == Magnitude) || (channel == MagnitudeAndAngle)) { - const float theoreticalMaxMagnitude = sqrt(2*pow(float(2*(3+10+3)*255), 2.f)); - mv.push_back(magnitude / theoreticalMaxMagnitude); - } - if ((channel == Angle) || (channel == MagnitudeAndAngle)) - mv.push_back(angle); - Mat result; - merge(mv, result); - dst.append(result); - } -}; - -BR_REGISTER(Transform, GradientTransform) - -/*! - * \ingroup transforms - * \brief Projects each row based on a computed word. - * \author Josh Klontz \cite jklontz - */ -class WordWiseTransform : public Transform -{ - Q_OBJECT - Q_PROPERTY(br::Transform* getWords READ get_getWords WRITE set_getWords RESET reset_getWords) - Q_PROPERTY(br::Transform* byWord READ get_byWord WRITE set_byWord RESET reset_byWord) - Q_PROPERTY(int numWords READ get_numWords WRITE set_numWords RESET reset_numWords) - BR_PROPERTY(br::Transform*, getWords, NULL) - BR_PROPERTY(br::Transform*, byWord, NULL) - BR_PROPERTY(int, numWords, 0) - - void train(const TemplateList &data) - { - getWords->train(data); - TemplateList bins; - getWords->project(data, bins); - - numWords = 0; - foreach (const Template &t, bins) { - double minVal, maxVal; - minMaxLoc(t, &minVal, &maxVal); - numWords = max(numWords, int(maxVal)+1); - } - - TemplateList reworded; reworded.reserve(data.size()); - foreach (const Template &t, data) - reworded.append(reword(t)); - byWord->train(reworded); - } - - void project(const Template &src, Template &dst) const - { - byWord->project(reword(src), dst); - } - - Template reword(const Template &src) const - { - Template words; - getWords->project(src, words); - QVector wordCounts(numWords, 0); - for (int i=0; i(i,0)]++; - Template reworded(src.file); reworded.reserve(numWords); - for (int i=0; i indicies(numWords, 0); - for (int i=0; i(i,0); - src.m().row(i).copyTo(reworded[word].row(indicies[word]++)); - } - return reworded; - } -}; - -BR_REGISTER(Transform, WordWiseTransform) - } // namespace br -#include "integral.moc" +#include "integralsampler.moc" diff --git a/openbr/plugins/imgproc/limitsize.cpp b/openbr/plugins/imgproc/limitsize.cpp new file mode 100644 index 0000000..fcf1591 --- /dev/null +++ b/openbr/plugins/imgproc/limitsize.cpp @@ -0,0 +1,37 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "limitsize.moc" diff --git a/openbr/plugins/imgproc/madd.cpp b/openbr/plugins/imgproc/madd.cpp new file mode 100644 index 0000000..793f535 --- /dev/null +++ b/openbr/plugins/imgproc/madd.cpp @@ -0,0 +1,29 @@ +#include + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "madd.moc" diff --git a/openbr/plugins/imgproc/mean.cpp b/openbr/plugins/imgproc/mean.cpp new file mode 100644 index 0000000..ca28ce3 --- /dev/null +++ b/openbr/plugins/imgproc/mean.cpp @@ -0,0 +1,45 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Computes the mean of a set of templates. + * \note Suitable for visualization only as it sets every projected template to the mean template. + * \author Scott Klum \cite sklum + */ +class MeanTransform : public Transform +{ + Q_OBJECT + + Mat mean; + + void train(const TemplateList &data) + { + mean = Mat::zeros(data[0].m().rows,data[0].m().cols,CV_32F); + + for (int i = 0; i < data.size(); i++) { + Mat converted; + data[i].m().convertTo(converted, CV_32F); + mean += converted; + } + + mean /= data.size(); + } + + void project(const Template &src, Template &dst) const + { + dst = src; + dst.m() = mean; + } + +}; + +BR_REGISTER(Transform, MeanTransform) + +} // namespace br + +#include "mean.moc" diff --git a/openbr/plugins/imgproc/meanfill.cpp b/openbr/plugins/imgproc/meanfill.cpp new file mode 100644 index 0000000..5096c2b --- /dev/null +++ b/openbr/plugins/imgproc/meanfill.cpp @@ -0,0 +1,28 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Fill 0 pixels with the mean of non-0 pixels. + * \author Josh Klontz \cite jklontz + */ +class MeanFillTransform : public UntrainableTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + dst = src.m().clone(); + dst.m().setTo(mean(dst, dst.m()!=0), dst.m()==0); + } +}; + +BR_REGISTER(Transform, MeanFillTransform) + +} // namespace br + +#include "meanfill.moc" diff --git a/openbr/plugins/imgproc/pow.cpp b/openbr/plugins/imgproc/pow.cpp new file mode 100644 index 0000000..997f634 --- /dev/null +++ b/openbr/plugins/imgproc/pow.cpp @@ -0,0 +1,32 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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 "pow.moc" diff --git a/openbr/plugins/imgproc/rank.cpp b/openbr/plugins/imgproc/rank.cpp new file mode 100644 index 0000000..9e660e0 --- /dev/null +++ b/openbr/plugins/imgproc/rank.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Converts each element to its rank-ordered value. + * \author Josh Klontz \cite jklontz + */ +class RankTransform : public UntrainableTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + const Mat &m = src; + assert(m.channels() == 1); + dst = Mat(m.rows, m.cols, CV_32FC1); + typedef QPair Tuple; + QList tuples = Common::Sort(OpenCVUtils::matrixToVector(m)); + + float prevValue = 0; + int prevRank = 0; + for (int i=0; i(tuples[i].second / m.cols, tuples[i].second % m.cols) = rank; + prevValue = tuples[i].first; + prevRank = rank; + } + } +}; + +BR_REGISTER(Transform, RankTransform) + +} // namespace br + +#include "rank.moc" diff --git a/openbr/plugins/imgproc/recursiveintegralsampler.cpp b/openbr/plugins/imgproc/recursiveintegralsampler.cpp new file mode 100644 index 0000000..e3b7d27 --- /dev/null +++ b/openbr/plugins/imgproc/recursiveintegralsampler.cpp @@ -0,0 +1,162 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Construct template in a recursive decent manner. + * \author Josh Klontz \cite jklontz + */ +class RecursiveIntegralSamplerTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(int scales READ get_scales WRITE set_scales RESET reset_scales STORED false) + Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) + Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform) + BR_PROPERTY(int, scales, 6) + BR_PROPERTY(float, scaleFactor, 2) + BR_PROPERTY(int, minSize, 8) + BR_PROPERTY(br::Transform*, transform, NULL) + + Transform *subTransform; + + typedef Eigen::Map< const Eigen::Matrix > InputDescriptor; + typedef Eigen::Map< Eigen::Matrix > OutputDescriptor; + typedef Eigen::Map< const Eigen::Matrix > SecondOrderInputDescriptor; + + void init() + { + if (scales >= 2) { + File subFile = file; + subFile.set("scales", scales-1); + subTransform = make(subFile.flat()); + } else { + subTransform = NULL; + } + } + + static void integralHistogram(const Mat &src, const int x, const int y, const int width, const int height, Mat &dst, int index) + { + const int channels = src.channels(); + OutputDescriptor(dst.ptr(index), channels, 1) = + ( InputDescriptor(src.ptr(y+height, x+width), channels, 1) + - InputDescriptor(src.ptr(y, x+width), channels, 1) + - InputDescriptor(src.ptr(y+height, x), channels, 1) + + InputDescriptor(src.ptr(y, x), channels, 1)).cast()/(height*width); + } + + void computeDescriptor(const Mat &src, Mat &dst) const + { + const int channels = src.channels(); + const int rows = src.rows-1; // Integral images have an extra row and column + const int columns = src.cols-1; + + Mat tmp(5, channels, CV_32FC1); + integralHistogram(src, 0, 0, columns/2, rows/2, tmp, 0); + integralHistogram(src, columns/2, 0, columns/2, rows/2, tmp, 1); + integralHistogram(src, 0, rows/2, columns/2, rows/2, tmp, 2); + integralHistogram(src, columns/2, rows/2, columns/2, rows/2, tmp, 3); + integralHistogram(src, columns/4, rows/4, columns/2, rows/2, tmp, 4); + const SecondOrderInputDescriptor a(tmp.ptr(0), channels, 1); + const SecondOrderInputDescriptor b(tmp.ptr(1), channels, 1); + const SecondOrderInputDescriptor c(tmp.ptr(2), channels, 1); + const SecondOrderInputDescriptor d(tmp.ptr(3), channels, 1); + const SecondOrderInputDescriptor e(tmp.ptr(4), channels, 1); + + dst = Mat(5, channels, CV_32FC1); + OutputDescriptor(dst.ptr(0), channels, 1) = (a+b+c+d)/4.f; + OutputDescriptor(dst.ptr(1), channels, 1) = ((a+b+c+d)/4.f-e); + OutputDescriptor(dst.ptr(2), channels, 1) = ((a+b)-(c+d))/2.f; + OutputDescriptor(dst.ptr(3), channels, 1) = ((a+c)-(b+d))/2.f; + OutputDescriptor(dst.ptr(4), channels, 1) = ((a+d)-(b+c))/2.f; + dst = dst.reshape(1, 1); + } + + Template subdivide(const Template &src) const + { + // Integral images have an extra row and column + int subWidth = (src.m().cols-1) / scaleFactor + 1; + int subHeight = (src.m().rows-1) / scaleFactor + 1; + return Template(src.file, QList() << Mat(src, Rect(0, 0, subWidth, subHeight)) + << Mat(src, Rect(src.m().cols-subWidth, 0, subWidth, subHeight)) + << Mat(src, Rect(0, src.m().rows-subHeight, subWidth, subHeight)) + << Mat(src, Rect(src.m().cols-subWidth, src.m().rows-subHeight, subWidth, subHeight))); + } + + bool canSubdivide(const Template &t) const + { + // Integral images have an extra row and column + const int subWidth = (t.m().cols-1) / scaleFactor; + const int subHeight = (t.m().rows-1) / scaleFactor; + return ((subWidth >= minSize) && (subHeight >= minSize)); + } + + void train(const TemplateList &src) + { + if (src.first().m().depth() != CV_32S) + qFatal("Expected CV_32S depth!"); + + if (subTransform != NULL) { + TemplateList subSrc; subSrc.reserve(src.size()); + foreach (const Template &t, src) + if (canSubdivide(t)) + subSrc.append(subdivide(t)); + + if (subSrc.isEmpty()) { + delete subTransform; + subTransform = NULL; + } else { + subTransform->train(subSrc); + } + } + + TemplateList dst; dst.reserve(src.size()); + foreach (const Template &t, src) { + Template u(t.file); + computeDescriptor(t, u); + dst.append(u); + } + transform->train(dst); + } + + void project(const Template &src, Template &dst) const + { + computeDescriptor(src, dst); + transform->project(dst, dst); + + if ((subTransform != NULL) && canSubdivide(src)) { + Template subDst; + subTransform->project(subdivide(src), subDst); + dst.append(subDst); + } + } + + void store(QDataStream &stream) const + { + transform->store(stream); + stream << (subTransform != NULL); + if (subTransform != NULL) + subTransform->store(stream); + } + + void load(QDataStream &stream) + { + transform->load(stream); + bool hasSubTransform; + stream >> hasSubTransform; + if (hasSubTransform) subTransform->load(stream); + else { delete subTransform; subTransform = NULL; } + } +}; + +BR_REGISTER(Transform, RecursiveIntegralSamplerTransform) + +} // namespace br + +#include "recursiveintegralsampler.moc" diff --git a/openbr/plugins/imgproc/resize.cpp b/openbr/plugins/imgproc/resize.cpp new file mode 100644 index 0000000..b1cf93f --- /dev/null +++ b/openbr/plugins/imgproc/resize.cpp @@ -0,0 +1,68 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "resize.moc" diff --git a/openbr/plugins/imgproc/rg.cpp b/openbr/plugins/imgproc/rg.cpp new file mode 100644 index 0000000..aa890d4 --- /dev/null +++ b/openbr/plugins/imgproc/rg.cpp @@ -0,0 +1,51 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "rg.moc" diff --git a/openbr/plugins/imgproc/roi.cpp b/openbr/plugins/imgproc/roi.cpp new file mode 100644 index 0000000..9eba1a0 --- /dev/null +++ b/openbr/plugins/imgproc/roi.cpp @@ -0,0 +1,46 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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."); + } + dst.file.clearRects(); + } +}; + +BR_REGISTER(Transform, ROITransform) + +} // namespace br + +#include "roi.moc" diff --git a/openbr/plugins/imgproc/roifrompoints.cpp b/openbr/plugins/imgproc/roifrompoints.cpp new file mode 100644 index 0000000..1f655c7 --- /dev/null +++ b/openbr/plugins/imgproc/roifrompoints.cpp @@ -0,0 +1,35 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "roifrompoints.moc" diff --git a/openbr/plugins/imgproc/scale.cpp b/openbr/plugins/imgproc/scale.cpp new file mode 100644 index 0000000..5036220 --- /dev/null +++ b/openbr/plugins/imgproc/scale.cpp @@ -0,0 +1,32 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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)); + } +}; + +BR_REGISTER(Transform, ScaleTransform) + +} // namespace br + +#include "scale.moc" diff --git a/openbr/plugins/imgproc/splitchannels.cpp b/openbr/plugins/imgproc/splitchannels.cpp new file mode 100644 index 0000000..17f2c83 --- /dev/null +++ b/openbr/plugins/imgproc/splitchannels.cpp @@ -0,0 +1,32 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Split a multi-channel matrix into several single-channel matrices. + * \author Josh Klontz \cite jklontz + */ +class SplitChannelsTransform : public UntrainableTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + std::vector mv; + split(src, mv); + foreach (const Mat &m, mv) + dst += m; + } +}; + +BR_REGISTER(Transform, SplitChannelsTransform) + +} // namespace br + +#include "splitchannels.moc" diff --git a/openbr/plugins/imgproc/subdivide.cpp b/openbr/plugins/imgproc/subdivide.cpp new file mode 100644 index 0000000..847e859 --- /dev/null +++ b/openbr/plugins/imgproc/subdivide.cpp @@ -0,0 +1,33 @@ +#include + +using namespace cv; + +namespace br +{ + +/*! + * \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) + +} // namespace br + +#include "subdivide.moc" diff --git a/openbr/plugins/io/write.cpp b/openbr/plugins/io/write.cpp new file mode 100644 index 0000000..d94a9cc --- /dev/null +++ b/openbr/plugins/io/write.cpp @@ -0,0 +1,44 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Write all mats to disk as images. + * \author Brendan Klare \cite bklare + */ +class WriteTransform : 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, WriteTransform) + +} // namespace br + +#include "write.moc" diff --git a/openbr/plugins/keypoint.cpp b/openbr/plugins/keypoint.cpp deleted file mode 100644 index 6c3d88d..0000000 --- a/openbr/plugins/keypoint.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 -#include -#include "openbr_internal.h" -#include "openbr/core/opencvutils.h" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup transforms - * \brief Wraps OpenCV Key Point Detector - * \author Josh Klontz \cite jklontz - */ -class KeyPointDetectorTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(QString detector READ get_detector WRITE set_detector RESET reset_detector STORED false) - BR_PROPERTY(QString, detector, "SIFT") - - Ptr featureDetector; - - void init() - { - featureDetector = FeatureDetector::create(detector.toStdString()); - if (featureDetector.empty()) - qFatal("Failed to create KeyPointDetector: %s", qPrintable(detector)); - } - - void project(const Template &src, Template &dst) const - { - dst = src; - - std::vector keyPoints; - try { - featureDetector->detect(src, keyPoints); - } catch (...) { - qWarning("Key point detection failed for file %s", qPrintable(src.file.name)); - dst.file.fte = true; - } - - QList rects; - foreach (const KeyPoint &keyPoint, keyPoints) - rects.append(Rect(keyPoint.pt.x-keyPoint.size/2, keyPoint.pt.y-keyPoint.size/2, keyPoint.size, keyPoint.size)); - dst.file.setRects(OpenCVUtils::fromRects(rects)); - } -}; - -BR_REGISTER(Transform, KeyPointDetectorTransform) - -/*! - * \ingroup transforms - * \brief Wraps OpenCV Key Point Descriptor - * \author Josh Klontz \cite jklontz - */ -class KeyPointDescriptorTransform : public UntrainableTransform -{ - Q_OBJECT - Q_PROPERTY(QString descriptor READ get_descriptor WRITE set_descriptor RESET reset_descriptor STORED false) - Q_PROPERTY(int size READ get_size WRITE set_size RESET reset_size STORED false) - BR_PROPERTY(QString, descriptor, "SIFT") - BR_PROPERTY(int, size, -1) - - Ptr descriptorExtractor; - - void init() - { - descriptorExtractor = DescriptorExtractor::create(descriptor.toStdString()); - if (descriptorExtractor.empty()) - qFatal("Failed to create DescriptorExtractor: %s", qPrintable(descriptor)); - } - - void project(const Template &src, Template &dst) const - { - std::vector keyPoints; - if (size == -1) - foreach (const QRectF &ROI, src.file.rects()) - keyPoints.push_back(KeyPoint(ROI.x()+ROI.width()/2, ROI.y()+ROI.height()/2, (ROI.width() + ROI.height())/2)); - else - foreach (const QPointF &landmark, src.file.points()) - keyPoints.push_back(KeyPoint(landmark.x(), landmark.y(), size)); - if (keyPoints.empty()) return; - descriptorExtractor->compute(src, keyPoints, dst); - } -}; - -BR_REGISTER(Transform, KeyPointDescriptorTransform) - -/*! - * \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 sizes READ get_sizes WRITE set_sizes RESET reset_sizes STORED false) - Q_PROPERTY(int nFeatures READ get_nFeatures WRITE set_nFeatures RESET reset_nFeatures STORED false) - Q_PROPERTY(int nOctaveLayers READ get_nOctaveLayers WRITE set_nOctaveLayers RESET reset_nOctaveLayers STORED false) - Q_PROPERTY(double contrastThreshold READ get_contrastThreshold WRITE set_contrastThreshold RESET reset_contrastThreshold STORED false) - Q_PROPERTY(double edgeThreshold READ get_edgeThreshold WRITE set_edgeThreshold RESET reset_edgeThreshold STORED false) - Q_PROPERTY(double sigma READ get_sigma WRITE set_sigma RESET reset_sigma STORED false) - BR_PROPERTY(int, size, 1) - BR_PROPERTY(QList, sizes, QList()) - BR_PROPERTY(int, nFeatures, 0) - BR_PROPERTY(int, nOctaveLayers, 3) - BR_PROPERTY(double, contrastThreshold, 0.04) - BR_PROPERTY(double, edgeThreshold, 10) - BR_PROPERTY(double, sigma, 1.6) - - SIFT sift; - - void init() - { - if (sizes.empty()) - sizes.append(size); - sift = SIFT(nFeatures, nOctaveLayers, contrastThreshold, edgeThreshold, sigma); - } - - void project(const Template &src, Template &dst) const - { - std::vector keyPoints; - foreach (const QPointF &val, src.file.points()) - foreach (const int r, sizes) - keyPoints.push_back(KeyPoint(val.x(), val.y(), r)); - - Mat m; - sift(src, Mat(), keyPoints, m, true); - m.setTo(0, m<0); // SIFT returns large negative values when it goes off the edge of the image. - dst += m; - } -}; - -BR_REGISTER(Transform, SIFTDescriptorTransform) - -/*! - * \ingroup transforms - * \brief OpenCV HOGDescriptor wrapper - * \author Austin Blanton \cite imaus10 - */ -class HoGDescriptorTransform : public UntrainableTransform -{ - Q_OBJECT - - HOGDescriptor hog; - - void project(const Template &src, Template &dst) const - { - std::vector descriptorVals; - std::vector locations; - Size winStride = Size(0,0); - Size padding = Size(0,0); - foreach (const Mat &rect, src) { - hog.compute(rect, descriptorVals, winStride, padding, locations); - Mat HoGFeats(descriptorVals, true); - dst += HoGFeats; - } - } -}; - -BR_REGISTER(Transform, HoGDescriptorTransform) - -/*! - * \ingroup transforms - * \brief Add landmarks to the template in a grid layout - * \author Josh Klontz \cite jklontz - */ -class GridTransform : public UntrainableTransform -{ - Q_OBJECT - 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) - BR_PROPERTY(int, rows, 1) - BR_PROPERTY(int, columns, 1) - - void project(const Template &src, Template &dst) const - { - QList landmarks; - const float row_step = 1.f * src.m().rows / rows; - const float column_step = 1.f * src.m().cols / columns; - for (float y=row_step/2; y rects = src.rects(); + if (rects.size() != 1) + qFatal("Must have one and only one rect per template"); + QRectF rect = rects.first(); + + QList srcPoints = src.points(); + QList dstPoints; + foreach (const QPointF &point, srcPoints) + dstPoints.append(QPointF(point.x() - rect.x(), point.y() - rect.y())); + + dst.setPoints(dstPoints); + } +}; + +BR_REGISTER(Transform, SetPointsInRectTransform) + /*! * \ingroup transforms * \brief Normalize points to be relative to a single point diff --git a/openbr/plugins/lbp.cpp b/openbr/plugins/lbp.cpp index b295286..1073b0e 100644 --- a/openbr/plugins/lbp.cpp +++ b/openbr/plugins/lbp.cpp @@ -46,8 +46,6 @@ class LBPTransform : public UntrainableTransform uchar lut[256]; uchar null; - friend class ColoredU2Transform; - /* Returns the number of 0->1 or 1->0 transitions in i */ static int numTransitions(int i) { @@ -125,74 +123,6 @@ class LBPTransform : public UntrainableTransform BR_REGISTER(Transform, LBPTransform) -/*! - * \ingroup transforms - * \brief For visualization of LBP patterns. - * \author Josh Klontz \cite jklontz - */ -class ColoredU2Transform : public UntrainableTransform -{ - Q_OBJECT - - /* Returns the number of 1 bits in i */ - static int bitCount(int i) - { - int count = 0; - for (int j=0; j<8; j++) - count += (i>>j)%2; - return count; - } - - void project(const Template &src, Template &dst) const - { - static Mat hueLUT, saturationLUT, valueLUT; - - if (!hueLUT.data) { - const int NUM_COLORS = 10; - hueLUT.create(1, 256, CV_8UC1); - hueLUT.setTo(0); - - uchar uid = 0; - for (int i=0; i<256; i++) { - const int transitions = LBPTransform::numTransitions(i); - int u2; - if (transitions <= 2) u2 = uid++; - else u2 = 58; - - // Assign hue based on bit count - int color = bitCount(i); - if (transitions > 2) color = NUM_COLORS-1; - hueLUT.at(0, u2) = 255*color/NUM_COLORS; - } - - saturationLUT.create(1, 256, CV_8UC1); - saturationLUT.setTo(255); - - valueLUT.create(1, 256, CV_8UC1); - valueLUT.setTo(255*3/4); - } - - if (src.m().type() != CV_8UC1) - qFatal("Expected 8UC1 source type."); - - Mat hue, saturation, value; - LUT(src, hueLUT, hue); - LUT(src, saturationLUT, saturation); - LUT(src, valueLUT, value); - - std::vector mv; - mv.push_back(hue); - mv.push_back(saturation); - mv.push_back(value); - - Mat coloredU2; - merge(mv, coloredU2); - cvtColor(coloredU2, dst, CV_HSV2BGR); - } -}; - -BR_REGISTER(Transform, ColoredU2Transform) - } // namespace br #include "lbp.moc" diff --git a/openbr/plugins/metadata/grid.cpp b/openbr/plugins/metadata/grid.cpp new file mode 100644 index 0000000..27484e9 --- /dev/null +++ b/openbr/plugins/metadata/grid.cpp @@ -0,0 +1,36 @@ +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Add landmarks to the template in a grid layout + * \author Josh Klontz \cite jklontz + */ +class GridTransform : public UntrainableTransform +{ + Q_OBJECT + 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) + BR_PROPERTY(int, rows, 1) + BR_PROPERTY(int, columns, 1) + + void project(const Template &src, Template &dst) const + { + QList landmarks; + const float row_step = 1.f * src.m().rows / rows; + const float column_step = 1.f * src.m().cols / columns; + for (float y=row_step/2; y + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Convert values of key_X, key_Y, key_Width, key_Height to a rect. + * \author Jordan Cheney \cite JordanCheney + */ +class KeyToRectTransform : public UntrainableMetadataTransform +{ + Q_OBJECT + Q_PROPERTY(QString key READ get_key WRITE set_key RESET reset_key STORED false) + BR_PROPERTY(QString, key, "") + + void projectMetadata(const File &src, File &dst) const + { + dst = src; + + if (src.contains(QStringList() << key + "_X" << key + "_Y" << key + "_Width" << key + "_Height")) + dst.appendRect(QRectF(src.get(key + "_X"), + src.get(key + "_Y"), + src.get(key + "_Width"), + src.get(key + "_Height"))); + + } + +}; + +BR_REGISTER(Transform, KeyToRectTransform) + +} // namespace br + +#include "keytorect.moc" diff --git a/openbr/plugins/metadata/procrustes.cpp b/openbr/plugins/metadata/procrustes.cpp new file mode 100644 index 0000000..defa993 --- /dev/null +++ b/openbr/plugins/metadata/procrustes.cpp @@ -0,0 +1,135 @@ +#include + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Procrustes alignment of points + * \author Scott Klum \cite sklum + */ +class ProcrustesTransform : public MetadataTransform +{ + Q_OBJECT + + Q_PROPERTY(bool warp READ get_warp WRITE set_warp RESET reset_warp STORED false) + BR_PROPERTY(bool, warp, true) + + Eigen::MatrixXf meanShape; + + void train(const TemplateList &data) + { + QList< QList > normalizedPoints; + + // Normalize all sets of points + foreach (br::Template datum, data) { + QList points = datum.file.points(); + QList rects = datum.file.rects(); + + if (points.empty() || rects.empty()) continue; + + // Assume rect appended last was bounding box + points.append(rects.last().topLeft()); + points.append(rects.last().topRight()); + points.append(rects.last().bottomLeft()); + points.append(rects.last().bottomRight()); + + // Center shape at origin + Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector()); + for (int i = 0; i < points.size(); i++) points[i] -= QPointF(mean[0],mean[1]); + + // Remove scale component + float norm = cv::norm(OpenCVUtils::toPoints(points).toVector().toStdVector()); + for (int i = 0; i < points.size(); i++) points[i] /= norm; + + normalizedPoints.append(points); + } + + if (normalizedPoints.empty()) qFatal("Unable to calculate normalized points"); + + // Determine mean shape, assuming all shapes contain the same number of points + meanShape = Eigen::MatrixXf(normalizedPoints[0].size(), 2); + + for (int i = 0; i < normalizedPoints[0].size(); i++) { + double x = 0; + double y = 0; + + for (int j = 0; j < normalizedPoints.size(); j++) { + x += normalizedPoints[j][i].x(); + y += normalizedPoints[j][i].y(); + } + + x /= (double)normalizedPoints.size(); + y /= (double)normalizedPoints.size(); + + meanShape(i,0) = x; + meanShape(i,1) = y; + } + } + + void project(const Template &src, Template &dst) const + { + QList points = src.file.points(); + QList rects = src.file.rects(); + + if (points.empty() || rects.empty()) { + dst = src; + if (Globals->verbose) qWarning("Procrustes alignment failed because points or rects are empty."); + return; + } + + // Assume rect appended last was bounding box + points.append(rects.last().topLeft()); + points.append(rects.last().topRight()); + points.append(rects.last().bottomLeft()); + points.append(rects.last().bottomRight()); + + Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector()); + for (int i = 0; i < points.size(); i++) points[i] -= QPointF(mean[0],mean[1]); + + Eigen::MatrixXf srcMat(points.size(), 2); + float norm = cv::norm(OpenCVUtils::toPoints(points).toVector().toStdVector()); + for (int i = 0; i < points.size(); i++) { + points[i] /= norm; + srcMat(i,0) = points[i].x(); + srcMat(i,1) = points[i].y(); + } + + Eigen::JacobiSVD svd(srcMat.transpose()*meanShape, Eigen::ComputeThinU | Eigen::ComputeThinV); + Eigen::MatrixXf R = svd.matrixU()*svd.matrixV().transpose(); + + dst = src; + + // Store procrustes stats in the order: + // R(0,0), R(1,0), R(1,1), R(0,1), mean_x, mean_y, norm + QList procrustesStats; + procrustesStats << R(0,0) << R(1,0) << R(1,1) << R(0,1) << mean[0] << mean[1] << norm; + dst.file.setList("ProcrustesStats",procrustesStats); + + if (warp) { + Eigen::MatrixXf dstMat = srcMat*R; + for (int i = 0; i < dstMat.rows(); i++) { + dst.file.appendPoint(QPointF(dstMat(i,0),dstMat(i,1))); + } + } + } + + void store(QDataStream &stream) const + { + stream << meanShape; + } + + void load(QDataStream &stream) + { + stream >> meanShape; + } + +}; + +BR_REGISTER(Transform, ProcrustesTransform) + +} // namespace br + +#include "procrustes.moc" diff --git a/openbr/plugins/motion.cpp b/openbr/plugins/motion.cpp index 4511336..5def61d 100644 --- a/openbr/plugins/motion.cpp +++ b/openbr/plugins/motion.cpp @@ -59,48 +59,6 @@ class OpticalFlowTransform : public UntrainableMetaTransform BR_REGISTER(Transform, OpticalFlowTransform) -/*! - * \ingroup transforms - * \brief Wraps OpenCV's BackgroundSubtractorMOG2 and puts the foreground mask in the Template metadata. - * \author Austin Blanton \cite imaus10 - */ -class SubtractBackgroundTransform : public TimeVaryingTransform -{ - Q_OBJECT - - // TODO: This is broken. - // BackgroundSubtractorMOG2 mog; - -public: - SubtractBackgroundTransform() : TimeVaryingTransform(false, false) {} - -private: - void projectUpdate(const Template &src, Template &dst) - { - dst = src; - Mat mask; - // TODO: broken - // mog(src, mask); - erode(mask, mask, Mat()); - dilate(mask, mask, Mat()); - dst.file.set("Mask", QVariant::fromValue(mask)); - } - - void project(const Template &src, Template &dst) const - { - (void) src; (void) dst; qFatal("no way"); - } - - void finalize(TemplateList &output) - { - (void) output; - // TODO: Broken - // mog = BackgroundSubtractorMOG2(); - } -}; - -BR_REGISTER(Transform, SubtractBackgroundTransform) - } // namespace br #include "motion.moc" diff --git a/openbr/plugins/plugins.cmake b/openbr/plugins/plugins.cmake index 163e15c..d71aeb8 100644 --- a/openbr/plugins/plugins.cmake +++ b/openbr/plugins/plugins.cmake @@ -12,8 +12,28 @@ foreach(DIR ${BR_THIRDPARTY_PLUGINS_DIR}) set(BR_THIRDPARTY_PLUGINS ${BR_THIRDPARTY_PLUGINS} ${PLUGINS}) endforeach() +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} core) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} io) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} gui) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} imgproc) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} video) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} cluster) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} distance) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} format) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} representation) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} detection) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} classification) +set(BR_PLUGINS_DIR ${BR_PLUGINS_DIR} metadata) + + +foreach(DIR ${BR_PLUGINS_DIR}) + file(GLOB PLUGINS plugins/${DIR}/*.cpp plugins/${DIR}/*.h) + set(BR_PLUGINS ${BR_PLUGINS} ${PLUGINS}) +endforeach() + file(GLOB PLUGINS plugins/*.cpp plugins/*.h) -foreach(PLUGIN ${PLUGINS} ${BR_THIRDPARTY_PLUGINS}) +set(BR_PLUGINS ${BR_PLUGINS} ${PLUGINS}) +foreach(PLUGIN ${BR_PLUGINS} ${BR_THIRDPARTY_PLUGINS}) get_filename_component(PLUGIN_BASENAME ${PLUGIN} NAME_WE) get_filename_component(PLUGIN_PATH ${PLUGIN} PATH) set(PLUGIN_CMAKE "${PLUGIN_PATH}/${PLUGIN_BASENAME}.cmake") diff --git a/openbr/plugins/quantize.cpp b/openbr/plugins/quantize.cpp index 14ae276..3617486 100644 --- a/openbr/plugins/quantize.cpp +++ b/openbr/plugins/quantize.cpp @@ -287,56 +287,6 @@ class ProductQuantizationDistance : public UntrainableDistance BR_REGISTER(Distance, ProductQuantizationDistance) /*! - * \ingroup distances - * \brief Recurively computed distance in a product quantized space - * \author Josh Klontz \cite jklontz - */ -class RecursiveProductQuantizationDistance : public UntrainableDistance -{ - Q_OBJECT - Q_PROPERTY(float t READ get_t WRITE set_t RESET reset_t STORED false) - BR_PROPERTY(float, t, -std::numeric_limits::max()) - - float compare(const Template &a, const Template &b) const - { - return compareRecursive(a, b, 0, a.size(), 0); - } - - float compareRecursive(const QList &a, const QList &b, int i, int size, float evidence) const - { - float similarity = 0; - - const int elements = a[i].total()-sizeof(quint16); - uchar *aData = a[i].data; - uchar *bData = b[i].data; - quint16 index = *reinterpret_cast(aData); - aData += sizeof(quint16); - bData += sizeof(quint16); - - const float *lut = (const float*)ProductQuantizationLUTs[index].data; - for (int j=0; j(propName, negVal+1) == negVal) + return; RNG &rng = theRNG(); - width = rng.uniform(0.f, 1.f); - height = rng.uniform(0.f, 1.f); - x = rng.uniform(0.f, 1.f-width); - y = rng.uniform(0.f, 1.f-height); + for (int n = 0; n < numSamples; n++) { + int x = rng.uniform(0, src.m().cols - width - 1); + int y = rng.uniform(0, src.m().rows - height - 1); + dst.file.appendRect(Rect(x, y, width, height)); + } } +}; + +BR_REGISTER(Transform, RndNegSampleTransform) + +/*! + * \ingroup transforms + * \brief Selects a random region. + * \author Josh Klontz \cite jklontz + */ +class RndRegionTransform : public UntrainableTransform +{ + Q_OBJECT void project(const Template &src, Template &dst) const { + RNG &rng = theRNG(); + float size = rng.uniform(0.2f, 1.f); + float x = rng.uniform(0.f, 1.f-size); + float y = rng.uniform(0.f, 1.f-size); + dst = src.m()(Rect(src.m().cols * x, src.m().rows * y, - src.m().cols * width, - src.m().rows * height)); + src.m().cols * size, + src.m().rows * size)); } }; diff --git a/openbr/plugins/regions.cpp b/openbr/plugins/regions.cpp index dad3d91..684d7bd 100644 --- a/openbr/plugins/regions.cpp +++ b/openbr/plugins/regions.cpp @@ -374,8 +374,12 @@ class RectFromPointsTransform : public UntrainableTransform QList points; - foreach (int index, indices) { + int numPoints = (indices.empty() ? src.file.points().size() : indices.size()); + for (int idx = 0; idx < numPoints; idx++) { + int index = indices.empty() ? idx : indices[idx]; if (src.file.points().size() > index) { + if (src.file.points()[index].x() <= 0 || + src.file.points()[index].y() <= 0) continue; 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(); @@ -390,8 +394,9 @@ class RectFromPointsTransform : public UntrainableTransform width += deltaWidth; double height = maxY-minY; - double deltaHeight = width/aspectRatio - height; - height += deltaHeight; + //double deltaHeight = width/aspectRatio - height; + double deltaHeight = height*padding; + height += deltaHeight; const int x = std::max(0.0, minX - deltaWidth/2.0); const int y = std::max(0.0, minY - deltaHeight/2.0); diff --git a/openbr/plugins/representation/hogdescriptor.cpp b/openbr/plugins/representation/hogdescriptor.cpp new file mode 100644 index 0000000..b3a032a --- /dev/null +++ b/openbr/plugins/representation/hogdescriptor.cpp @@ -0,0 +1,39 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief OpenCV HOGDescriptor wrapper + * \author Austin Blanton \cite imaus10 + */ +class HoGDescriptorTransform : public UntrainableTransform +{ + Q_OBJECT + + HOGDescriptor hog; + + void project(const Template &src, Template &dst) const + { + std::vector descriptorVals; + std::vector locations; + Size winStride = Size(0,0); + Size padding = Size(0,0); + foreach (const Mat &rect, src) { + hog.compute(rect, descriptorVals, winStride, padding, locations); + Mat HoGFeats(descriptorVals, true); + dst += HoGFeats; + } + } +}; + +BR_REGISTER(Transform, HoGDescriptorTransform) + +} // namespace br + +#include "hogdescriptor.moc" diff --git a/openbr/plugins/representation/keypointdescriptor.cpp b/openbr/plugins/representation/keypointdescriptor.cpp new file mode 100644 index 0000000..33d7993 --- /dev/null +++ b/openbr/plugins/representation/keypointdescriptor.cpp @@ -0,0 +1,50 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Wraps OpenCV Key Point Descriptor + * \author Josh Klontz \cite jklontz + */ +class KeyPointDescriptorTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(QString descriptor READ get_descriptor WRITE set_descriptor RESET reset_descriptor STORED false) + Q_PROPERTY(int size READ get_size WRITE set_size RESET reset_size STORED false) + BR_PROPERTY(QString, descriptor, "SIFT") + BR_PROPERTY(int, size, -1) + + Ptr descriptorExtractor; + + void init() + { + descriptorExtractor = DescriptorExtractor::create(descriptor.toStdString()); + if (descriptorExtractor.empty()) + qFatal("Failed to create DescriptorExtractor: %s", qPrintable(descriptor)); + } + + void project(const Template &src, Template &dst) const + { + std::vector keyPoints; + if (size == -1) + foreach (const QRectF &ROI, src.file.rects()) + keyPoints.push_back(KeyPoint(ROI.x()+ROI.width()/2, ROI.y()+ROI.height()/2, (ROI.width() + ROI.height())/2)); + else + foreach (const QPointF &landmark, src.file.points()) + keyPoints.push_back(KeyPoint(landmark.x(), landmark.y(), size)); + if (keyPoints.empty()) return; + descriptorExtractor->compute(src, keyPoints, dst); + } +}; + +BR_REGISTER(Transform, KeyPointDescriptorTransform) + +} // namespace br + +#include "keypointdescriptor.moc" diff --git a/openbr/plugins/representation/siftdescriptor.cpp b/openbr/plugins/representation/siftdescriptor.cpp new file mode 100644 index 0000000..9d8918f --- /dev/null +++ b/openbr/plugins/representation/siftdescriptor.cpp @@ -0,0 +1,60 @@ +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Specialize wrapper OpenCV SIFT wrapper + * \author Josh Klontz \cite jklontz + */ +class SIFTDescriptorTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(int size READ get_size WRITE set_size RESET reset_size STORED false) + Q_PROPERTY(QList sizes READ get_sizes WRITE set_sizes RESET reset_sizes STORED false) + Q_PROPERTY(int nFeatures READ get_nFeatures WRITE set_nFeatures RESET reset_nFeatures STORED false) + Q_PROPERTY(int nOctaveLayers READ get_nOctaveLayers WRITE set_nOctaveLayers RESET reset_nOctaveLayers STORED false) + Q_PROPERTY(double contrastThreshold READ get_contrastThreshold WRITE set_contrastThreshold RESET reset_contrastThreshold STORED false) + Q_PROPERTY(double edgeThreshold READ get_edgeThreshold WRITE set_edgeThreshold RESET reset_edgeThreshold STORED false) + Q_PROPERTY(double sigma READ get_sigma WRITE set_sigma RESET reset_sigma STORED false) + BR_PROPERTY(int, size, 1) + BR_PROPERTY(QList, sizes, QList()) + BR_PROPERTY(int, nFeatures, 0) + BR_PROPERTY(int, nOctaveLayers, 3) + BR_PROPERTY(double, contrastThreshold, 0.04) + BR_PROPERTY(double, edgeThreshold, 10) + BR_PROPERTY(double, sigma, 1.6) + + SIFT sift; + + void init() + { + if (sizes.empty()) + sizes.append(size); + sift = SIFT(nFeatures, nOctaveLayers, contrastThreshold, edgeThreshold, sigma); + } + + void project(const Template &src, Template &dst) const + { + std::vector keyPoints; + foreach (const QPointF &val, src.file.points()) + foreach (const int r, sizes) + keyPoints.push_back(KeyPoint(val.x(), val.y(), r)); + + Mat m; + sift(src, Mat(), keyPoints, m, true); + m.setTo(0, m<0); // SIFT returns large negative values when it goes off the edge of the image. + dst += m; + } +}; + +BR_REGISTER(Transform, SIFTDescriptorTransform) + +} // namespace br + +#include "siftdescriptor.moc" diff --git a/openbr/plugins/sentence.cpp b/openbr/plugins/sentence.cpp deleted file mode 100644 index fca4cd6..0000000 --- a/openbr/plugins/sentence.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include "openbr_internal.h" - -using namespace cv; - -namespace br -{ - -/*! - * \ingroup transforms - * \brief Ordered words - * \author Josh Klontz \cite jklontz - */ -class SentenceTransform : public UntrainableMetaTransform -{ - Q_OBJECT - - void project(const Template &src, Template &dst) const - { - QByteArray sentence; - QDataStream stream(&sentence, QIODevice::WriteOnly); - for (int i=0; i::max() : comparisons / distance; - aWord = *reinterpret_cast(aBuffer); - aRows = *reinterpret_cast(aBuffer+4); - aColumns = *reinterpret_cast(aBuffer+8); - aData = reinterpret_cast(aBuffer+12); - aBuffer += 12 + 4*aRows*aColumns; - } else if (bWord < aWord) { - if (bBuffer == bEnd) return comparisons == 0 ? -std::numeric_limits::max() : comparisons / distance; - bWord = *reinterpret_cast(bBuffer); - bRows = *reinterpret_cast(bBuffer+4); - bColumns = *reinterpret_cast(bBuffer+8); - bData = reinterpret_cast(bBuffer+12); - bBuffer += 12 + 4*bRows*bColumns; - } else { - for (int i=0; i namespace br { @@ -56,33 +56,6 @@ private: BR_REGISTER(Transform, AggregateFrames) -/*! - * \ingroup transforms - * \brief Only use one frame every n frames. - * \author Austin Blanton \cite imaus10 - * - * For a video with m frames, DropFrames will pass on m/n frames. - */ -class DropFrames : public UntrainableMetaTransform -{ - 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 TemplateList &src, TemplateList &dst) const - { - if (src.first().file.get("FrameNumber") % n != 0) return; - dst = src; - } - - void project(const Template &src, Template &dst) const - { - (void) src; (void) dst; qFatal("shouldn't be here"); - } -}; - -BR_REGISTER(Transform, DropFrames) - } // namespace br -#include "frames.moc" +#include "aggregate.moc" diff --git a/openbr/plugins/video/drop.cpp b/openbr/plugins/video/drop.cpp new file mode 100644 index 0000000..3cd2d75 --- /dev/null +++ b/openbr/plugins/video/drop.cpp @@ -0,0 +1,35 @@ +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Only use one frame every n frames. + * \author Austin Blanton \cite imaus10 + * + * For a video with m frames, DropFrames will pass on m/n frames. + */ +class DropFrames : public UntrainableMetaTransform +{ + 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 TemplateList &src, TemplateList &dst) const + { + if (src.first().file.get("FrameNumber") % n != 0) return; + dst = src; + } + + void project(const Template &src, Template &dst) const + { + (void) src; (void) dst; qFatal("shouldn't be here"); + } +}; + +BR_REGISTER(Transform, DropFrames) + +} // namespace br + +#include "drop.moc"