Commit 9e34c2c0d6a61e09b76d10ff7788f439b4d940b5
1 parent
6349b9cf
started a page describing the algorithm grammar
Showing
4 changed files
with
73 additions
and
3 deletions
openbr/core/core.cpp
| @@ -291,14 +291,18 @@ private: | @@ -291,14 +291,18 @@ private: | ||
| 291 | if (Globals->abbreviations.contains(description)) | 291 | if (Globals->abbreviations.contains(description)) |
| 292 | return init(Globals->abbreviations[description]); | 292 | return init(Globals->abbreviations[description]); |
| 293 | 293 | ||
| 294 | + //! [Parsing the algorithm description] | ||
| 294 | QStringList words = QtUtils::parse(description.flat(), ':'); | 295 | QStringList words = QtUtils::parse(description.flat(), ':'); |
| 295 | if ((words.size() < 1) || (words.size() > 2)) qFatal("Invalid algorithm format."); | 296 | if ((words.size() < 1) || (words.size() > 2)) qFatal("Invalid algorithm format."); |
| 297 | + //! [Parsing the algorithm description] | ||
| 296 | 298 | ||
| 297 | if (description.getBool("distribute", true)) | 299 | if (description.getBool("distribute", true)) |
| 298 | words[0] = "DistributeTemplate(" + words[0] + ")"; | 300 | words[0] = "DistributeTemplate(" + words[0] + ")"; |
| 299 | 301 | ||
| 302 | + //! [Creating the template generation and comparison methods] | ||
| 300 | transform = QSharedPointer<Transform>(Transform::make(words[0], NULL)); | 303 | transform = QSharedPointer<Transform>(Transform::make(words[0], NULL)); |
| 301 | if (words.size() > 1) distance = QSharedPointer<Distance>(Distance::make(words[1], NULL)); | 304 | if (words.size() > 1) distance = QSharedPointer<Distance>(Distance::make(words[1], NULL)); |
| 305 | + //! [Creating the template generation and comparison methods] | ||
| 302 | } | 306 | } |
| 303 | }; | 307 | }; |
| 304 | 308 |
openbr/openbr_export.cpp
| @@ -31,6 +31,7 @@ | @@ -31,6 +31,7 @@ | ||
| 31 | * - \ref qmake_integration - \copybrief qmake_integration | 31 | * - \ref qmake_integration - \copybrief qmake_integration |
| 32 | * | 32 | * |
| 33 | * \section learn_more Learn More | 33 | * \section learn_more Learn More |
| 34 | + * - \ref algorithm_grammar - \copybrief algorithm_grammar | ||
| 34 | * - \ref cli - \copybrief cli | 35 | * - \ref cli - \copybrief cli |
| 35 | * - \ref c_sdk - \copybrief c_sdk | 36 | * - \ref c_sdk - \copybrief c_sdk |
| 36 | * - \ref cpp_plugin_sdk - \copybrief cpp_plugin_sdk | 37 | * - \ref cpp_plugin_sdk - \copybrief cpp_plugin_sdk |
| @@ -401,6 +402,67 @@ $ br -help | @@ -401,6 +402,67 @@ $ br -help | ||
| 401 | */ | 402 | */ |
| 402 | 403 | ||
| 403 | /*! | 404 | /*! |
| 405 | + * \page algorithm_grammar Algorithm Grammar | ||
| 406 | + * \brief How algorithms are constructed from string descriptions. | ||
| 407 | + * | ||
| 408 | + * <b>So you've run <tt>scripts/helloWorld.sh</tt> and it generally makes sense, except you have no idea what <tt>'Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+CvtFloat+PCA(0.95):Dist(L2)'</tt> means or how it is executed.</b> | ||
| 409 | + * Well if this is the case, you've found the right documentation. | ||
| 410 | + * Let's get started! | ||
| 411 | + * | ||
| 412 | + * In OpenBR an <i>algorithm</i> is a technique for enrolling templates associated with a technique for comparing them. | ||
| 413 | + * Recall that our ultimate goal is to be able to say how similar two face images are (or two fingerprints, irises, etc.). | ||
| 414 | + * Instead of storing the entire raw image for comparison, it is common practice to store an optimized representation, or <i>template</i>, of the image for the task at hand. | ||
| 415 | + * The process of generating this optimized representatation is called <i>template enrollment</i> or <i>template generation</i>. | ||
| 416 | + * Given two templates, <i>template comparison</i> computes the similarity between them, where the higher values indicate more probable matches and the threshold for determining what constitutes an adaquate match is determined operationally. | ||
| 417 | + * The goal of template generation is to design templates that are small, accurate, and fast to compare. | ||
| 418 | + * Ok, you probably knew all of this already, let's move on. | ||
| 419 | + * | ||
| 420 | + * The only way of creating an algorithm in OpenBR is from a text string that describes it. | ||
| 421 | + * We call this string the <i>algorithm description</i>. | ||
| 422 | + * The algorithm description is seperated into two parts by a ':', with the left hand side indicating how to generate templates and the right hand side indicating how to compare them. | ||
| 423 | + * Some algorithms, like \ref cpp_gender_estimation and \ref cpp_age_estimation are <i>classifiers</i> that don't create templates. | ||
| 424 | + * In these cases, the colon and the template comparison technique can be omitted from the algorithm description. | ||
| 425 | + * | ||
| 426 | + * There are several motivations for mandating that algorithms are defined from these strings, here are the most important: | ||
| 427 | + * -# It ensures good software development practices by forcibly decoupling the development of each step in an algorithm, facilitating the modification of algorithms and the re-use of individual steps. | ||
| 428 | + * -# It spares the creation and maintainance of a lot of very similar header files that would otherwise be needed for each step in an algorithm, observe the abscence of headers in <tt>openbr/plugins</tt>. | ||
| 429 | + * -# It allows for algorithm parameter tuning without recompiling. | ||
| 430 | + * -# It is completely unambiguous, both the OpenBR interpreter and anyone familiar with the project can understand exactly what your algorithm does just from this description. | ||
| 431 | + * | ||
| 432 | + * Let's look at some of the important parts of the code base that make this possible! | ||
| 433 | + * | ||
| 434 | + * In <tt>AlgorithmCore::init()</tt> in <tt>openbr/core/core.cpp</tt> you can see the code for splitting the algorithm description at the colon: | ||
| 435 | + * \snippet openbr/core/core.cpp Parsing the algorithm description | ||
| 436 | + * | ||
| 437 | + * Shortly thereafter in this function we <i>make</i> the template generation and comparison methods: | ||
| 438 | + * \snippet openbr/core/core.cpp Creating the template generation and comparison methods | ||
| 439 | + * These make calls are defined in the public \ref cpp_plugin_sdk and can also be called from end user code. | ||
| 440 | + * | ||
| 441 | + * Below we discuss some of the source code for \ref br::Transform::make in <tt>openbr/openbr_plugin.cpp</tt>. | ||
| 442 | + * Note, \ref br::Distance::make is similar in spirit and will not be covered. | ||
| 443 | + * | ||
| 444 | + * One of the first steps when converting the template enrollment description into a \ref br::Transform is to replace the operators, like '+', with their full form: | ||
| 445 | + * \snippet openbr/openbr_plugin.cpp Make a pipe | ||
| 446 | + * A pipe (see \ref br::PipeTransform in <tt>openbr/plugins/meta.cpp</tt>) is the standard way of chaining together multiple steps in series to form more sophisticated algorithms. | ||
| 447 | + * PipeTransform takes a list of transforms, and <i>projects</i> templates through each transform in order. | ||
| 448 | + * | ||
| 449 | + * After operator expansion, the template enrollment description forms a tree, and the transform is constructed from this description starting recurively starting at the root of the tree: | ||
| 450 | + * \snippet openbr/openbr_plugin.cpp Construct the root transform | ||
| 451 | + * | ||
| 452 | + * At this point we reach arguably the most important code in the entire framework, the <i>object factory</i> in <tt>openbr/openbr_plugin.h</tt>. | ||
| 453 | + * The \ref br::Factory class is responsible for constructing an object from a string: | ||
| 454 | + * \snippet openbr/openbr_plugin.h Factory make | ||
| 455 | + * | ||
| 456 | + * Going back to our original example, a \ref br::PipeTransform will be created with \ref br::OpenTransform, \ref br::CvtTransform, \ref br::CascadeTransform, \ref br::ASEFEyesTransform, \ref br::AffineTransform, \ref br::CvtFloatTransform, and \ref br::PCATransform as its children. | ||
| 457 | + * If you want all the tedious details about what exactly this algoritm does, then you should read the \ref br::Transform::project function implemented by each of these plugins. | ||
| 458 | + * The brief explanation is that it <i>reads the image from disk, converts it to grayscale, runs the face detector, runs the eye detector on any detected faces, uses the eye locations to normalize the face for rotation and scale, converts to floating point format, and then embeds it in a PCA subspaced trained on face images</i>. | ||
| 459 | + * If you are familiar with face recognition, you will likely recognize this as the Eigenfaces \cite turk1991eigenfaces algorithm. | ||
| 460 | + * | ||
| 461 | + * As a final note, the Eigenfaces algorithms uses the Euclidean distance (or L2-norm) to compare templates. | ||
| 462 | + * Since OpenBR expects <i>similarity</i> values when comparing templates, and not <i>distances</i>, \ref br::DistDistance will return <i>-log(distance+1)</i> so that larger values indicate more similarity. | ||
| 463 | + */ | ||
| 464 | + | ||
| 465 | +/*! | ||
| 404 | * \page bee Biometric Evaluation Environment | 466 | * \page bee Biometric Evaluation Environment |
| 405 | * \brief The <i>Biometric Evaluation Environment</i> (BEE) is a <a href="http://www.nist.gov/index.html">NIST</a> standard for evaluating biometric algorithms. | 467 | * \brief The <i>Biometric Evaluation Environment</i> (BEE) is a <a href="http://www.nist.gov/index.html">NIST</a> standard for evaluating biometric algorithms. |
| 406 | * | 468 | * |
openbr/openbr_plugin.cpp
| @@ -1090,11 +1090,13 @@ Transform *Transform::make(QString str, QObject *parent) | @@ -1090,11 +1090,13 @@ Transform *Transform::make(QString str, QObject *parent) | ||
| 1090 | str.replace("!","+Expand+"); | 1090 | str.replace("!","+Expand+"); |
| 1091 | } | 1091 | } |
| 1092 | 1092 | ||
| 1093 | + //! [Make a pipe] | ||
| 1093 | { // Check for use of '+' as shorthand for Pipe(...) | 1094 | { // Check for use of '+' as shorthand for Pipe(...) |
| 1094 | QStringList words = parse(str, '+'); | 1095 | QStringList words = parse(str, '+'); |
| 1095 | if (words.size() > 1) | 1096 | if (words.size() > 1) |
| 1096 | return make("Pipe([" + words.join(",") + "])", parent); | 1097 | return make("Pipe([" + words.join(",") + "])", parent); |
| 1097 | } | 1098 | } |
| 1099 | + //! [Make a pipe] | ||
| 1098 | 1100 | ||
| 1099 | { // Check for use of '/' as shorthand for Fork(...) | 1101 | { // Check for use of '/' as shorthand for Fork(...) |
| 1100 | QStringList words = parse(str, '/'); | 1102 | QStringList words = parse(str, '/'); |
| @@ -1114,8 +1116,9 @@ Transform *Transform::make(QString str, QObject *parent) | @@ -1114,8 +1116,9 @@ Transform *Transform::make(QString str, QObject *parent) | ||
| 1114 | if (str.startsWith('(') && str.endsWith(')')) | 1116 | if (str.startsWith('(') && str.endsWith(')')) |
| 1115 | return make(str.mid(1, str.size()-2), parent); | 1117 | return make(str.mid(1, str.size()-2), parent); |
| 1116 | 1118 | ||
| 1117 | - File f = "." + str; | ||
| 1118 | - Transform *transform = Factory<Transform>::make(f); | 1119 | + //! [Construct the root transform] |
| 1120 | + Transform *transform = Factory<Transform>::make("." + str); | ||
| 1121 | + //! [Construct the root transform] | ||
| 1119 | 1122 | ||
| 1120 | if (transform->independent) { | 1123 | if (transform->independent) { |
| 1121 | File independent(".Independent"); | 1124 | File independent(".Independent"); |
openbr/openbr_plugin.h
| @@ -783,6 +783,7 @@ struct Factory | @@ -783,6 +783,7 @@ struct Factory | ||
| 783 | /*! | 783 | /*! |
| 784 | * \brief Constructs a plugin from a file. | 784 | * \brief Constructs a plugin from a file. |
| 785 | */ | 785 | */ |
| 786 | + //! [Factory make] | ||
| 786 | static T *make(const File &file) | 787 | static T *make(const File &file) |
| 787 | { | 788 | { |
| 788 | QString name = file.suffix(); | 789 | QString name = file.suffix(); |
| @@ -791,11 +792,11 @@ struct Factory | @@ -791,11 +792,11 @@ struct Factory | ||
| 791 | else if (names().contains("Default")) name = "Default"; | 792 | else if (names().contains("Default")) name = "Default"; |
| 792 | else qFatal("%s registry does not contain object named: %s", qPrintable(baseClassName()), qPrintable(name)); | 793 | else qFatal("%s registry does not contain object named: %s", qPrintable(baseClassName()), qPrintable(name)); |
| 793 | } | 794 | } |
| 794 | - if (registry->contains("_"+name)) name.prepend('_'); // Hook to override with "native" implementation | ||
| 795 | T *object = registry->value(name)->_make(); | 795 | T *object = registry->value(name)->_make(); |
| 796 | object->init(file); | 796 | object->init(file); |
| 797 | return object; | 797 | return object; |
| 798 | } | 798 | } |
| 799 | + //! [Factory make] | ||
| 799 | 800 | ||
| 800 | /*! | 801 | /*! |
| 801 | * \brief Constructs all the available plugins. | 802 | * \brief Constructs all the available plugins. |