diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 7d811d0..bf1be14 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -10,4 +10,5 @@ if(NOT ${BR_EMBEDDED}) add_subdirectory(br-crawl) add_subdirectory(br-enroll) add_subdirectory(br-gui) + add_subdirectory(br-search) endif() diff --git a/app/br-download/br-download.cpp b/app/br-download/br-download.cpp index af96e2c..a15237e 100644 --- a/app/br-download/br-download.cpp +++ b/app/br-download/br-download.cpp @@ -61,7 +61,7 @@ static bool processReply(QNetworkReply* reply) return false; const QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Md5); - br_append_utemplate_contents(stdout, reinterpret_cast(hash.data()), reinterpret_cast(hash.data()), 3, data.size(), reinterpret_cast(data.data())); + br_append_utemplate_contents(stdout, reinterpret_cast(hash.data()), reinterpret_cast(hash.data()), 3, data.size(), reinterpret_cast(data.data())); return true; } diff --git a/app/br-enroll/br-enroll.cpp b/app/br-enroll/br-enroll.cpp index b10d9a7..cb7ee65 100644 --- a/app/br-enroll/br-enroll.cpp +++ b/app/br-enroll/br-enroll.cpp @@ -41,7 +41,7 @@ static void help() static QSharedPointer algorithm; -static void enroll_utemplate(br_const_utemplate utemplate) +static void enroll_utemplate(br_const_utemplate utemplate, br_callback_context) { if (utemplate->algorithmID != 3) qFatal("Expected an encoded image."); @@ -54,7 +54,7 @@ static void enroll_utemplate(br_const_utemplate utemplate) const Mat &m = t.m(); const uint32_t size = m.rows * m.cols * m.elemSize(); const QByteArray templateID = QCryptographicHash::hash(QByteArray((const char*) m.data, size), QCryptographicHash::Md5); - br_append_utemplate_contents(stdout, utemplate->imageID, (const int8_t*) templateID.data(), -1, size, (const int8_t*) m.data); + br_append_utemplate_contents(stdout, utemplate->imageID, (const int8_t*) templateID.data(), -1, size, m.data); } } @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) Context::initialize(argc, argv, "", false); Globals->quiet = true; algorithm = Transform::fromAlgorithm("FaceRecognition"); - br_iterate_utemplates_file(stdin, enroll_utemplate); + br_iterate_utemplates_file(stdin, enroll_utemplate, NULL); Context::finalize(); return EXIT_SUCCESS; } diff --git a/app/br-search/CMakeLists.txt b/app/br-search/CMakeLists.txt new file mode 100644 index 0000000..b1fd566 --- /dev/null +++ b/app/br-search/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(br-search br-search.cpp ${BR_RESOURCES}) +target_link_libraries(br-search openbr ${BR_THIRDPARTY_LIBS}) +qt5_use_modules(br-search ${QT_DEPENDENCIES}) +install(TARGETS br-search RUNTIME DESTINATION bin) diff --git a/app/br-search/br-search.cpp b/app/br-search/br-search.cpp new file mode 100644 index 0000000..39a0888 --- /dev/null +++ b/app/br-search/br-search.cpp @@ -0,0 +1,144 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2014 Noblis * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include +#include + +using namespace br; +using namespace cv; + +static void help() +{ + printf("br-search URL(s) [args]\n" + "=======================\n" + "* __stdin__ - Templates (feature vectors)\n" + "* __stdout__ - JSON\n" + "\n" + "_br-search_ does retrieval by comparing query templates to target gallery(s).\n" + "The search strategy is implementation defined.\n" + "\n" + "For every template read from _stdin_, search writes the top sorted matches as JSON objects to _stdout_ by comparing the query template against gallery URLs.\n" + "The JSON objects include `AlgorithmID`, `QueryImageID`, `QueryTemplateID`, `TargetImageID`, `TargetTemplateID`, `Score`, and any algorithm-specific metadata fields set during _enroll_. \n" + "\n" + "Optional Arguments\n" + "------------------\n" + "* -help - Print usage information.\n" + "* -limit - Maximum number of returns (20 otherwise).\n"); +} + +static int limit = 20; +static float threshold = -std::numeric_limits::max(); + +struct Result +{ + int8_t targetImageID[16], targetTemplateID[16], queryImageID[16], queryTemplateID[16]; + int32_t algorithmID; + float score; +}; + +struct TopTargets : QList< QPair > +{ + br_const_utemplate query; + + TopTargets(br_const_utemplate query) + : query(query) {} + + void tryAdd(br_const_utemplate target, float score) + { + if ((score < threshold) || ((size() == limit) && (score < last().second))) + return; + (void) target; + } + + void print() const + { + + } +}; + +struct FaceRecognitionResult : public Result +{ + int32_t x, y, width, height; + + FaceRecognitionResult() + { + algorithmID = -1; + } +}; + +struct MappedGallery +{ + QSharedPointer file; + qint64 size; + uchar *data; + + MappedGallery(QString url) + { + if (url.startsWith("file://")) + url = url.mid(7); + file.reset(new QFile(url)); + file->open(QFile::ReadOnly); + size = file->size(); + data = file->map(0, size); + if (data == NULL) + qFatal("Unable to map gallery: %s", qPrintable(url)); + } +}; + +static QSharedPointer distance; +static QList galleries; + +static void compare_utemplates(br_const_utemplate target, br_callback_context context) +{ + TopTargets *topTargets = (TopTargets*) context; + topTargets->tryAdd(target, distance->compare(target->data, topTargets->query->data, 768)); +} + +static void search_utemplate(br_const_utemplate query, br_callback_context) +{ + TopTargets *topTargets = new TopTargets(query); + foreach (const MappedGallery &gallery, galleries) + br_iterate_utemplates(reinterpret_cast(gallery.data), reinterpret_cast(gallery.data + gallery.size), compare_utemplates, topTargets); + topTargets->print(); + delete topTargets; +} + +int main(int argc, char *argv[]) +{ + QStringList urls; + for (int i=1; iquiet = true; + distance = Distance::fromAlgorithm("FaceRecognition"); + br_iterate_utemplates_file(stdin, search_utemplate, NULL); + + Context::finalize(); + return EXIT_SUCCESS; +} diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index 887f19f..eafc31e 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -1405,7 +1405,12 @@ float Distance::compare(const Template &a, const Template &b) const return similarity; } -float Distance::compare(const cv::Mat &, const cv::Mat &) const +float Distance::compare(const cv::Mat &a, const cv::Mat &b) const +{ + return compare(a.data, b.data, a.rows * a.cols * a.elemSize()); +} + +float Distance::compare(const uchar *, const uchar *, size_t) const { qFatal("Logic error: %s did not implement a comparison function or was accessed at an unsupported level of abstraction.", metaObject()->className()); return -std::numeric_limits::max(); diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 26f5852..c00924b 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -1335,6 +1335,7 @@ public: virtual QList compare(const TemplateList &targets, const Template &query) const; /*!< \brief Compute the normalized distance between a template and a template list. */ virtual float compare(const Template &a, const Template &b) const; /*!< \brief Compute the distance between two templates. */ virtual float compare(const cv::Mat &a, const cv::Mat &b) const; /*!< \brief Compute the distance between two biometric signatures. */ + virtual float compare(const uchar *a, const uchar *b, size_t size) const; /*!< \brief Compute the distance between two buffers. */ protected: inline Distance *make(const QString &description) { return make(description, this); } /*!< \brief Make a subdistance. */ diff --git a/openbr/plugins/distance.cpp b/openbr/plugins/distance.cpp index c79d161..34e91e8 100644 --- a/openbr/plugins/distance.cpp +++ b/openbr/plugins/distance.cpp @@ -282,9 +282,9 @@ class ByteL1Distance : public Distance { Q_OBJECT - float compare(const Mat &a, const Mat &b) const + float compare(const unsigned char *a, const unsigned char *b, size_t size) const { - return l1(a.data, b.data, a.total()); + return l1(a, b, size); } }; diff --git a/openbr/universal_template.cpp b/openbr/universal_template.cpp index eb47258..67fbfda 100644 --- a/openbr/universal_template.cpp +++ b/openbr/universal_template.cpp @@ -27,7 +27,7 @@ void br_append_utemplate(FILE *file, br_const_utemplate utemplate) br_append_utemplate_contents(file, utemplate->imageID, utemplate->templateID, utemplate->algorithmID, utemplate->size, utemplate->data); } -void br_append_utemplate_contents(FILE *file, const int8_t *imageID, const int8_t *templateID, int32_t algorithmID, uint32_t size, const int8_t *data) +void br_append_utemplate_contents(FILE *file, const int8_t *imageID, const int8_t *templateID, int32_t algorithmID, uint32_t size, const unsigned char *data) { static QMutex lock; QMutexLocker locker(&lock); @@ -39,15 +39,15 @@ void br_append_utemplate_contents(FILE *file, const int8_t *imageID, const int8_ fwrite(data, 1, size, file); } -void br_iterate_utemplates(br_const_utemplate begin, br_const_utemplate end, br_utemplate_callback callback) +void br_iterate_utemplates(br_const_utemplate begin, br_const_utemplate end, br_utemplate_callback callback, br_callback_context context) { while (begin != end) { - callback(begin); + callback(begin, context); begin = reinterpret_cast(reinterpret_cast(begin) + sizeof(br_const_utemplate) + begin->size); } } -void br_iterate_utemplates_file(FILE *file, br_utemplate_callback callback) +void br_iterate_utemplates_file(FILE *file, br_utemplate_callback callback, br_callback_context context) { while (!feof(file)) { br_utemplate t = (br_utemplate) malloc(sizeof(br_universal_template)); @@ -56,7 +56,7 @@ void br_iterate_utemplates_file(FILE *file, br_utemplate_callback callback) t = (br_utemplate) realloc(t, sizeof(br_universal_template) + t->size); if (fread(t+1, 1, t->size, file) != t->size) qFatal("Unexepected EOF when reading universal template data."); - callback(t); + callback(t, context); } free(t); diff --git a/openbr/universal_template.h b/openbr/universal_template.h index 9a8e3c6..a71f711 100644 --- a/openbr/universal_template.h +++ b/openbr/universal_template.h @@ -34,7 +34,7 @@ struct br_universal_template int8_t templateID[16]; /*!< MD5 hash of _data_. */ int32_t algorithmID; /*!< type of _data_. */ uint32_t size; /*!< length of _data_. */ - int8_t data[]; /*!< _size_-byte buffer. */ + unsigned char data[]; /*!< _size_-byte buffer. */ }; typedef struct br_universal_template *br_utemplate; @@ -62,25 +62,26 @@ BR_EXPORT void br_append_utemplate(FILE *file, br_const_utemplate utemplate); * \brief Serialize a br_universal_template to a file. * \see br_append_utemplate */ -BR_EXPORT void br_append_utemplate_contents(FILE *file, const int8_t *imageID, const int8_t *templateID, int32_t algorithmID, uint32_t size, const int8_t *data); +BR_EXPORT void br_append_utemplate_contents(FILE *file, const int8_t *imageID, const int8_t *templateID, int32_t algorithmID, uint32_t size, const unsigned char *data); /*! * \brief br_universal_template iterator callback. * \see br_iterate_utemplates */ -typedef void (*br_utemplate_callback)(br_const_utemplate); +typedef void *br_callback_context; +typedef void (*br_utemplate_callback)(br_const_utemplate, br_callback_context); /*! * \brief Iterate over an inplace array of br_universal_template. * \see br_iterate_utemplates_file */ -BR_EXPORT void br_iterate_utemplates(br_const_utemplate begin, br_const_utemplate end, br_utemplate_callback callback); +BR_EXPORT void br_iterate_utemplates(br_const_utemplate begin, br_const_utemplate end, br_utemplate_callback callback, br_callback_context context); /*! * \brief Iterate over br_universal_template in a file. * \see br_iterate_utemplates */ -BR_EXPORT void br_iterate_utemplates_file(FILE *file, br_utemplate_callback callback); +BR_EXPORT void br_iterate_utemplates_file(FILE *file, br_utemplate_callback callback, br_callback_context context); #ifdef __cplusplus }