Commit 05a21101618fb0090b5546fce8878e1c747b17aa

Authored by Scott Klum
2 parents e868900c 91403317

Merge branch 'master' of https://github.com/biometrics/openbr

LICENSE.txt
1 -Copyright 2012-2014 The MITRE Corporation 1 +Copyright 2012 The MITRE Corporation
2 2
3 Licensed under the Apache License, Version 2.0 (the "License"); 3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License. 4 you may not use this file except in compliance with the License.
app/br/br.cpp
@@ -138,8 +138,8 @@ public: @@ -138,8 +138,8 @@ public:
138 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalClassification'."); 138 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalClassification'.");
139 br_eval_classification(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); 139 br_eval_classification(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : "");
140 } else if (!strcmp(fun, "evalClustering")) { 140 } else if (!strcmp(fun, "evalClustering")) {
141 - check(parc == 2, "Incorrect parameter count for 'evalClustering'.");  
142 - br_eval_clustering(parv[0], parv[1]); 141 + check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalClustering'.");
  142 + br_eval_clustering(parv[0], parv[1], parc == 3 ? parv[2] : "");
143 } else if (!strcmp(fun, "evalDetection")) { 143 } else if (!strcmp(fun, "evalDetection")) {
144 check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'."); 144 check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'.");
145 br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : ""); 145 br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : "");
openbr/core/cluster.cpp
@@ -100,7 +100,9 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats) @@ -100,7 +100,9 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats)
100 int currentRows = -1; 100 int currentRows = -1;
101 int columnOffset = 0; 101 int columnOffset = 0;
102 for (int j=0; j<numGalleries; j++) { 102 for (int j=0; j<numGalleries; j++) {
103 - cv::Mat m = BEE::readMat(simmats[i*numGalleries+j]); 103 + QScopedPointer<br::Format> format(br::Factory<br::Format>::make(simmats[i*numGalleries+j]));
  104 + br::Template t = format->read();
  105 + cv::Mat m = t.m();
104 if (j==0) { 106 if (j==0) {
105 currentRows = m.rows; 107 currentRows = m.rows;
106 allNeighbors.resize(currentRows); 108 allNeighbors.resize(currentRows);
@@ -115,8 +117,9 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats) @@ -115,8 +117,9 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats)
115 float val = m.at<float>(k,l); 117 float val = m.at<float>(k,l);
116 if ((i==j) && (k==l)) continue; // Skips self-similarity scores 118 if ((i==j) && (k==l)) continue; // Skips self-similarity scores
117 119
118 - if ((val != -std::numeric_limits<float>::infinity()) &&  
119 - (val != std::numeric_limits<float>::infinity())) { 120 + if (val != -std::numeric_limits<float>::max()
  121 + && val != -std::numeric_limits<float>::infinity()
  122 + && val != std::numeric_limits<float>::infinity()) {
120 globalMax = std::max(globalMax, val); 123 globalMax = std::max(globalMax, val);
121 globalMin = std::min(globalMin, val); 124 globalMin = std::min(globalMin, val);
122 } 125 }
@@ -157,7 +160,7 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats) @@ -157,7 +160,7 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats)
157 // Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011 160 // Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011
158 br::Clusters br::ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv) 161 br::Clusters br::ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv)
159 { 162 {
160 - qDebug("Clustering %d simmat(s)", simmats.size()); 163 + qDebug("Clustering %d simmat(s), aggressiveness %f", simmats.size(), aggressiveness);
161 164
162 // Read in gallery parts, keeping top neighbors of each template 165 // Read in gallery parts, keeping top neighbors of each template
163 Neighborhood neighborhood = getNeighborhood(simmats); 166 Neighborhood neighborhood = getNeighborhood(simmats);
@@ -275,13 +278,14 @@ float jaccardIndex(const QVector&lt;int&gt; &amp;indicesA, const QVector&lt;int&gt; &amp;indicesB) @@ -275,13 +278,14 @@ float jaccardIndex(const QVector&lt;int&gt; &amp;indicesA, const QVector&lt;int&gt; &amp;indicesB)
275 278
276 // Evaluates clustering algorithms based on metrics described in 279 // Evaluates clustering algorithms based on metrics described in
277 // Santo Fortunato "Community detection in graphs", Physics Reports 486 (2010) 280 // Santo Fortunato "Community detection in graphs", Physics Reports 486 (2010)
278 -void br::EvalClustering(const QString &csv, const QString &input) 281 +void br::EvalClustering(const QString &csv, const QString &input, QString truth_property)
279 { 282 {
  283 + if (truth_property.isEmpty())
  284 + truth_property = "Label";
280 qDebug("Evaluating %s against %s", qPrintable(csv), qPrintable(input)); 285 qDebug("Evaluating %s against %s", qPrintable(csv), qPrintable(input));
281 286
282 - // We assume clustering algorithms store assigned cluster labels as integers (since the clusters are  
283 - // not named). Direct use of ClusterID is not general -cao  
284 - QList<int> labels = File::get<int>(TemplateList::fromGallery(input), "ClusterID"); 287 + TemplateList tList = TemplateList::fromGallery(input);
  288 + QList<int> labels = tList.indexProperty(truth_property);
285 289
286 QHash<int, int> labelToIndex; 290 QHash<int, int> labelToIndex;
287 int nClusters = 0; 291 int nClusters = 0;
openbr/core/cluster.h
@@ -28,7 +28,7 @@ namespace br @@ -28,7 +28,7 @@ namespace br
28 typedef QVector<Cluster> Clusters; 28 typedef QVector<Cluster> Clusters;
29 29
30 Clusters ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv); 30 Clusters ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv);
31 - void EvalClustering(const QString &csv, const QString &input); 31 + void EvalClustering(const QString &csv, const QString &input, QString truth_property);
32 32
33 Clusters ReadClusters(const QString &csv); 33 Clusters ReadClusters(const QString &csv);
34 void WriteClusters(const Clusters &clusters, const QString &csv); 34 void WriteClusters(const Clusters &clusters, const QString &csv);
openbr/core/eigenutils.cpp
@@ -67,3 +67,12 @@ void printEigen(Eigen::MatrixXf X) { @@ -67,3 +67,12 @@ void printEigen(Eigen::MatrixXf X) {
67 void printSize(Eigen::MatrixXf X) { 67 void printSize(Eigen::MatrixXf X) {
68 qDebug() << "Rows=" << X.rows() << "\tCols=" << X.cols(); 68 qDebug() << "Rows=" << X.rows() << "\tCols=" << X.cols();
69 } 69 }
  70 +
  71 +float eigMean(const Eigen::MatrixXf& x) {
  72 + return x.array().sum() / (x.rows() * x.cols());
  73 +}
  74 +
  75 +float eigStd(const Eigen::MatrixXf& x) {
  76 + float mean = eigMean(x);
  77 + return sqrt((x.array() - mean).pow(2).sum() / (x.cols() * x.rows()));
  78 +}
openbr/core/eigenutils.h
@@ -67,4 +67,55 @@ inline QDataStream &amp;operator&gt;&gt;(QDataStream &amp;stream, Eigen::Matrix&lt; _Scalar, _Row @@ -67,4 +67,55 @@ inline QDataStream &amp;operator&gt;&gt;(QDataStream &amp;stream, Eigen::Matrix&lt; _Scalar, _Row
67 return stream; 67 return stream;
68 } 68 }
69 69
  70 +/*Compute the mean of the each column (dim == 1) or row (dim == 2)
  71 + of the matrix*/
  72 +template<typename T>
  73 +Eigen::MatrixBase<T> eigMean(const Eigen::MatrixBase<T>& x,int dim)
  74 +{
  75 + if (dim == 1) {
  76 + Eigen::MatrixBase<T> y(1,x.cols());
  77 + for (int i = 0; i < x.cols(); i++)
  78 + y(i) = x.col(i).sum() / x.rows();
  79 + return y;
  80 + } else if (dim == 2) {
  81 + Eigen::MatrixBase<T> y(x.rows(),1);
  82 + for (int i = 0; i < x.rows(); i++)
  83 + y(i) = x.row(i).sum() / x.cols();
  84 + return y;
  85 + }
  86 + qFatal("A matrix can only have two dimensions");
  87 +}
  88 +
  89 +/*Compute the element-wise mean*/
  90 +float eigMean(const Eigen::MatrixXf& x);
  91 +/*Compute the element-wise mean*/
  92 +float eigStd(const Eigen::MatrixXf& x);
  93 +
  94 +/*Compute the std dev of the each column (dim == 1) or row (dim == 2)
  95 + of the matrix*/
  96 +template<typename T>
  97 +Eigen::MatrixBase<T> eigStd(const Eigen::MatrixBase<T>& x,int dim)
  98 +{
  99 + Eigen::MatrixBase<T> mean = eigMean(x, dim);
  100 + if (dim == 1) {
  101 + Eigen::MatrixBase<T> y(1,x.cols());
  102 + for (int i = 0; i < x.cols(); i++) {
  103 + T value = 0;
  104 + for (int j = 0; j < x.rows(); j++)
  105 + value += pow(y(j, i) - mean(i), 2);
  106 + y(i) = sqrt(value / (x.rows() - 1));
  107 + }
  108 + return y;
  109 + } else if (dim == 2) {
  110 + Eigen::MatrixBase<T> y(x.rows(),1);
  111 + for (int i = 0; i < x.rows(); i++) {
  112 + T value = 0;
  113 + for (int j = 0; j < x.cols(); j++)
  114 + value += pow(y(i, j) - mean(j), 2);
  115 + y(i) = sqrt(value / (x.cols() - 1));
  116 + }
  117 + return y;
  118 + }
  119 + qFatal("A matrix can only have two dimensions");
  120 +}
70 #endif // EIGENUTILS_H 121 #endif // EIGENUTILS_H
openbr/core/eval.cpp
@@ -572,6 +572,7 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle @@ -572,6 +572,7 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
572 const QStringList predictedNames = File::get<QString>(predicted, "name"); 572 const QStringList predictedNames = File::get<QString>(predicted, "name");
573 const QStringList truthNames = File::get<QString>(truth, "name"); 573 const QStringList truthNames = File::get<QString>(truth, "name");
574 574
  575 + int skipped = 0;
575 QList< QList<float> > pointErrors; 576 QList< QList<float> > pointErrors;
576 for (int i=0; i<predicted.size(); i++) { 577 for (int i=0; i<predicted.size(); i++) {
577 const QString &predictedName = predictedNames[i]; 578 const QString &predictedName = predictedNames[i];
@@ -579,7 +580,10 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle @@ -579,7 +580,10 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
579 if (truthIndex == -1) qFatal("Could not identify ground truth for file: %s", qPrintable(predictedName)); 580 if (truthIndex == -1) qFatal("Could not identify ground truth for file: %s", qPrintable(predictedName));
580 const QList<QPointF> predictedPoints = predicted[i].file.points(); 581 const QList<QPointF> predictedPoints = predicted[i].file.points();
581 const QList<QPointF> truthPoints = truth[truthIndex].file.points(); 582 const QList<QPointF> truthPoints = truth[truthIndex].file.points();
582 - if (predictedPoints.size() != truthPoints.size()) qFatal("Points size mismatch for file: %s", qPrintable(predictedName)); 583 + if (predictedPoints.size() != truthPoints.size()) {
  584 + skipped++;
  585 + continue;
  586 + }
583 while (pointErrors.size() < predictedPoints.size()) 587 while (pointErrors.size() < predictedPoints.size())
584 pointErrors.append(QList<float>()); 588 pointErrors.append(QList<float>());
585 if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range."); 589 if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range.");
@@ -588,6 +592,7 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle @@ -588,6 +592,7 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
588 for (int j=0; j<predictedPoints.size(); j++) 592 for (int j=0; j<predictedPoints.size(); j++)
589 pointErrors[j].append(QtUtils::euclideanLength(predictedPoints[j] - truthPoints[j])/normalizedLength); 593 pointErrors[j].append(QtUtils::euclideanLength(predictedPoints[j] - truthPoints[j])/normalizedLength);
590 } 594 }
  595 + qDebug() << "Skipped " << skipped << " files do to point size mismatch.";
591 596
592 QList<float> averagePointErrors; averagePointErrors.reserve(pointErrors.size()); 597 QList<float> averagePointErrors; averagePointErrors.reserve(pointErrors.size());
593 for (int i=0; i<pointErrors.size(); i++) { 598 for (int i=0; i<pointErrors.size(); i++) {
@@ -605,6 +610,8 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle @@ -605,6 +610,8 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
605 lines.append(QString("Box,%1,%2").arg(QString::number(i), QString::number(pointError[j*(pointError.size()-1)/(keep-1)]))); 610 lines.append(QString("Box,%1,%2").arg(QString::number(i), QString::number(pointError[j*(pointError.size()-1)/(keep-1)])));
606 } 611 }
607 612
  613 + lines.append(QString("AvgError,0,%1").arg(averagePointError));
  614 +
608 QtUtils::writeFile(csv, lines); 615 QtUtils::writeFile(csv, lines);
609 qDebug("Average Error: %.3f", averagePointError); 616 qDebug("Average Error: %.3f", averagePointError);
610 return averagePointError; 617 return averagePointError;
openbr/openbr.cpp
@@ -109,9 +109,9 @@ void br_eval_classification(const char *predicted_gallery, const char *truth_gal @@ -109,9 +109,9 @@ void br_eval_classification(const char *predicted_gallery, const char *truth_gal
109 EvalClassification(predicted_gallery, truth_gallery, predicted_property, truth_property); 109 EvalClassification(predicted_gallery, truth_gallery, predicted_property, truth_property);
110 } 110 }
111 111
112 -void br_eval_clustering(const char *csv, const char *gallery) 112 +void br_eval_clustering(const char *csv, const char *gallery, const char * truth_property)
113 { 113 {
114 - EvalClustering(csv, gallery); 114 + EvalClustering(csv, gallery, truth_property);
115 } 115 }
116 116
117 float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv) 117 float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv)
@@ -435,7 +435,14 @@ br_template_list br_load_from_gallery(br_gallery gallery) @@ -435,7 +435,14 @@ br_template_list br_load_from_gallery(br_gallery gallery)
435 return (br_template_list)tl; 435 return (br_template_list)tl;
436 } 436 }
437 437
438 -void br_add_to_gallery(br_gallery gallery, br_template_list tl) 438 +void br_add_template_to_gallery(br_gallery gallery, br_template tmpl)
  439 +{
  440 + Gallery *gal = reinterpret_cast<Gallery*>(gallery);
  441 + Template *t = reinterpret_cast<Template*>(tmpl);
  442 + gal->write(*t);
  443 +}
  444 +
  445 +void br_add_template_list_to_gallery(br_gallery gallery, br_template_list tl)
439 { 446 {
440 Gallery *gal = reinterpret_cast<Gallery*>(gallery); 447 Gallery *gal = reinterpret_cast<Gallery*>(gallery);
441 TemplateList *realTL = reinterpret_cast<TemplateList*>(tl); 448 TemplateList *realTL = reinterpret_cast<TemplateList*>(tl);
openbr/openbr.h
@@ -167,9 +167,10 @@ BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char @@ -167,9 +167,10 @@ BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char
167 * \brief Evaluates and prints clustering accuracy to the terminal. 167 * \brief Evaluates and prints clustering accuracy to the terminal.
168 * \param csv The cluster results file. 168 * \param csv The cluster results file.
169 * \param gallery The br::Gallery used to generate the \ref simmat that was clustered. 169 * \param gallery The br::Gallery used to generate the \ref simmat that was clustered.
  170 + * \param truth_property (Optional) which metadata key to use from <i>gallery</i/>, defaults to Label
170 * \see br_cluster 171 * \see br_cluster
171 */ 172 */
172 -BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery); 173 +BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery, const char * truth_property);
173 174
174 /*! 175 /*!
175 * \brief Evaluates and prints detection accuracy to terminal. 176 * \brief Evaluates and prints detection accuracy to terminal.
@@ -562,9 +563,13 @@ BR_EXPORT br_gallery br_make_gallery(const char *gallery); @@ -562,9 +563,13 @@ BR_EXPORT br_gallery br_make_gallery(const char *gallery);
562 */ 563 */
563 BR_EXPORT br_template_list br_load_from_gallery(br_gallery gallery); 564 BR_EXPORT br_template_list br_load_from_gallery(br_gallery gallery);
564 /*! 565 /*!
  566 + * \brief Write a br::Template to the br::Gallery on disk.
  567 + */
  568 +BR_EXPORT void br_add_template_to_gallery(br_gallery gallery, br_template tmpl);
  569 +/*!
565 * \brief Write a br::TemplateList to the br::Gallery on disk. 570 * \brief Write a br::TemplateList to the br::Gallery on disk.
566 */ 571 */
567 -BR_EXPORT void br_add_to_gallery(br_gallery gallery, br_template_list tl); 572 +BR_EXPORT void br_add_template_list_to_gallery(br_gallery gallery, br_template_list tl);
568 /*! 573 /*!
569 * \brief Close the br::Gallery. 574 * \brief Close the br::Gallery.
570 */ 575 */
openbr/openbr_plugin.cpp
@@ -137,6 +137,8 @@ QVariant File::parse(const QString &amp;value) @@ -137,6 +137,8 @@ QVariant File::parse(const QString &amp;value)
137 if (ok) return point; 137 if (ok) return point;
138 const QRectF rect = QtUtils::toRect(value, &ok); 138 const QRectF rect = QtUtils::toRect(value, &ok);
139 if (ok) return rect; 139 if (ok) return rect;
  140 + const int i = value.toInt(&ok);
  141 + if (ok) return i;
140 const float f = value.toFloat(&ok); 142 const float f = value.toFloat(&ok);
141 if (ok) return f; 143 if (ok) return f;
142 return value; 144 return value;
openbr/plugins/algorithms.cpp
@@ -44,7 +44,7 @@ class AlgorithmsInitializer : public Initializer @@ -44,7 +44,7 @@ class AlgorithmsInitializer : public Initializer
44 Globals->abbreviations.insert("AgeEstimation", "AgeRegression"); 44 Globals->abbreviations.insert("AgeEstimation", "AgeRegression");
45 Globals->abbreviations.insert("FaceRecognition2", "{PP5Register+Affine(128,128,0.25,0.35)+Cvt(Gray)}+(Gradient+Bin(0,360,9,true))/(Blur(1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2,true)+Bin(0,10,10,true))+Merge+Integral+RecursiveIntegralSampler(4,2,8,LDA(.98)+Normalize(L1))+Cat+PCA(768)+Normalize(L1)+Quantize:UCharL1"); 45 Globals->abbreviations.insert("FaceRecognition2", "{PP5Register+Affine(128,128,0.25,0.35)+Cvt(Gray)}+(Gradient+Bin(0,360,9,true))/(Blur(1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2,true)+Bin(0,10,10,true))+Merge+Integral+RecursiveIntegralSampler(4,2,8,LDA(.98)+Normalize(L1))+Cat+PCA(768)+Normalize(L1)+Quantize:UCharL1");
46 Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)"); 46 Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)");
47 - Globals->abbreviations.insert("4SF", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)"); 47 + Globals->abbreviations.insert("4SF", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Cat+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)");
48 48
49 // Video 49 // Video
50 Globals->abbreviations.insert("DisplayVideo", "Stream(FPSLimit(30)+Show(false,[FrameNumber])+Discard)"); 50 Globals->abbreviations.insert("DisplayVideo", "Stream(FPSLimit(30)+Show(false,[FrameNumber])+Discard)");
@@ -64,6 +64,7 @@ class AlgorithmsInitializer : public Initializer @@ -64,6 +64,7 @@ class AlgorithmsInitializer : public Initializer
64 Globals->abbreviations.insert("SmallSIFT", "Open+LimitSize(512)+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)"); 64 Globals->abbreviations.insert("SmallSIFT", "Open+LimitSize(512)+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)");
65 Globals->abbreviations.insert("SmallSURF", "Open+LimitSize(512)+KeyPointDetector(SURF)+KeyPointDescriptor(SURF):KeyPointMatcher(BruteForce)"); 65 Globals->abbreviations.insert("SmallSURF", "Open+LimitSize(512)+KeyPointDetector(SURF)+KeyPointDescriptor(SURF):KeyPointMatcher(BruteForce)");
66 Globals->abbreviations.insert("ColorHist", "Open+LimitSize(512)+Expand+EnsureChannels(3)+SplitChannels+Hist(256,0,8)+Cat+Normalize(L1):L2"); 66 Globals->abbreviations.insert("ColorHist", "Open+LimitSize(512)+Expand+EnsureChannels(3)+SplitChannels+Hist(256,0,8)+Cat+Normalize(L1):L2");
  67 + Globals->abbreviations.insert("ImageSimilarity", "Open+EnsureChannels(3)+Resize(256,256)+SplitChannels+RectRegions(64,64,64,64)+Hist(256,0,8)+Cat:NegativeLogPlusOne(L2)");
67 Globals->abbreviations.insert("ImageClassification", "Open+CropSquare+LimitSize(256)+Cvt(Gray)+Gradient+Bin(0,360,9,true)+Merge+Integral+RecursiveIntegralSampler(4,2,8,Singleton(KMeans(256)))+Cat+CvtFloat+Hist(256)+KNN(5,Dist(L1),false,5)+Rename(KNN,Subject)"); 68 Globals->abbreviations.insert("ImageClassification", "Open+CropSquare+LimitSize(256)+Cvt(Gray)+Gradient+Bin(0,360,9,true)+Merge+Integral+RecursiveIntegralSampler(4,2,8,Singleton(KMeans(256)))+Cat+CvtFloat+Hist(256)+KNN(5,Dist(L1),false,5)+Rename(KNN,Subject)");
68 Globals->abbreviations.insert("TanTriggs", "Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)"); 69 Globals->abbreviations.insert("TanTriggs", "Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)");
69 70
openbr/plugins/eigen3.cpp
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 20
21 #include "openbr/core/common.h" 21 #include "openbr/core/common.h"
22 #include "openbr/core/eigenutils.h" 22 #include "openbr/core/eigenutils.h"
  23 +#include "openbr/core/opencvutils.h"
23 24
24 namespace br 25 namespace br
25 { 26 {
@@ -315,6 +316,8 @@ BR_REGISTER(Transform, DFFSTransform) @@ -315,6 +316,8 @@ BR_REGISTER(Transform, DFFSTransform)
315 */ 316 */
316 class LDATransform : public Transform 317 class LDATransform : public Transform
317 { 318 {
  319 + friend class SparseLDATransform;
  320 +
318 Q_OBJECT 321 Q_OBJECT
319 Q_PROPERTY(float pcaKeep READ get_pcaKeep WRITE set_pcaKeep RESET reset_pcaKeep STORED false) 322 Q_PROPERTY(float pcaKeep READ get_pcaKeep WRITE set_pcaKeep RESET reset_pcaKeep STORED false)
320 Q_PROPERTY(bool pcaWhiten READ get_pcaWhiten WRITE set_pcaWhiten RESET reset_pcaWhiten STORED false) 323 Q_PROPERTY(bool pcaWhiten READ get_pcaWhiten WRITE set_pcaWhiten RESET reset_pcaWhiten STORED false)
@@ -518,21 +521,30 @@ class LDATransform : public Transform @@ -518,21 +521,30 @@ class LDATransform : public Transform
518 521
519 // Do projection 522 // Do projection
520 outMap = projection.transpose() * (inMap - mean); 523 outMap = projection.transpose() * (inMap - mean);
521 -  
522 if (normalize && isBinary) 524 if (normalize && isBinary)
523 dst.m().at<float>(0,0) = dst.m().at<float>(0,0) / stdDev; 525 dst.m().at<float>(0,0) = dst.m().at<float>(0,0) / stdDev;
524 } 526 }
525 527
526 void store(QDataStream &stream) const 528 void store(QDataStream &stream) const
527 { 529 {
528 - stream << pcaKeep << directLDA << directDrop << dimsOut << mean << projection; 530 + stream << pcaKeep;
  531 + stream << directLDA;
  532 + stream << directDrop;
  533 + stream << dimsOut;
  534 + stream << mean;
  535 + stream << projection;
529 if (normalize && isBinary) 536 if (normalize && isBinary)
530 stream << stdDev; 537 stream << stdDev;
531 } 538 }
532 539
533 void load(QDataStream &stream) 540 void load(QDataStream &stream)
534 { 541 {
535 - stream >> pcaKeep >> directLDA >> directDrop >> dimsOut >> mean >> projection; 542 + stream >> pcaKeep;
  543 + stream >> directLDA;
  544 + stream >> directDrop;
  545 + stream >> dimsOut;
  546 + stream >> mean;
  547 + stream >> projection;
536 if (normalize && isBinary) 548 if (normalize && isBinary)
537 stream >> stdDev; 549 stream >> stdDev;
538 } 550 }
@@ -541,6 +553,104 @@ class LDATransform : public Transform @@ -541,6 +553,104 @@ class LDATransform : public Transform
541 BR_REGISTER(Transform, LDATransform) 553 BR_REGISTER(Transform, LDATransform)
542 554
543 /*! 555 /*!
  556 + * \ingroup transforms
  557 + * \brief Projects input into learned Linear Discriminant Analysis subspace
  558 + * learned on a sparse subset of features with the highest weight
  559 + * in the original LDA algorithm.
  560 + * \author Brendan Klare \cite bklare
  561 + */
  562 +class SparseLDATransform : public Transform
  563 +{
  564 + Q_OBJECT
  565 + Q_PROPERTY(float varThreshold READ get_varThreshold WRITE set_varThreshold RESET reset_varThreshold STORED false)
  566 + Q_PROPERTY(float pcaKeep READ get_pcaKeep WRITE set_pcaKeep RESET reset_pcaKeep STORED false)
  567 + Q_PROPERTY(bool normalize READ get_normalize WRITE set_normalize RESET reset_normalize STORED false)
  568 + BR_PROPERTY(float, varThreshold, 1.5)
  569 + BR_PROPERTY(float, pcaKeep, 0.98)
  570 + BR_PROPERTY(bool, normalize, true)
  571 +
  572 + LDATransform ldaSparse;
  573 + int dimsOut;
  574 + QList<int> selections;
  575 +
  576 + Eigen::VectorXf mean;
  577 +
  578 + void init()
  579 + {
  580 + ldaSparse.init();
  581 + ldaSparse.pcaKeep = pcaKeep;
  582 + ldaSparse.inputVariable = "Label";
  583 + ldaSparse.isBinary = true;
  584 + ldaSparse.normalize = true;
  585 + }
  586 +
  587 + void train(const TemplateList &_trainingSet)
  588 + {
  589 +
  590 + LDATransform ldaOrig;
  591 + ldaOrig.init();
  592 + ldaOrig.inputVariable = "Label";
  593 + ldaOrig.pcaKeep = pcaKeep;
  594 + ldaOrig.isBinary = true;
  595 + ldaOrig.normalize = true;
  596 +
  597 + ldaOrig.train(_trainingSet);
  598 +
  599 + //Only works on binary class problems for now
  600 + assert(ldaOrig.projection.cols() == 1);
  601 + float ldaStd = eigStd(ldaOrig.projection);
  602 + for (int i = 0; i < ldaOrig.projection.rows(); i++)
  603 + if (abs(ldaOrig.projection(i)) > varThreshold * ldaStd)
  604 + selections.append(i);
  605 +
  606 + TemplateList newSet;
  607 + for (int i = 0; i < _trainingSet.size(); i++) {
  608 + cv::Mat x(_trainingSet[i]);
  609 + cv::Mat y = cv::Mat(selections.size(), 1, CV_32FC1);
  610 + int idx = 0;
  611 + int cnt = 0;
  612 + for (int j = 0; j < x.rows; j++)
  613 + for (int k = 0; k < x.cols; k++, cnt++)
  614 + if (selections.contains(cnt))
  615 + y.at<float>(idx++,0) = x.at<float>(j, k);
  616 + newSet.append(Template(_trainingSet[i].file, y));
  617 + }
  618 + ldaSparse.train(newSet);
  619 + dimsOut = ldaSparse.dimsOut;
  620 + }
  621 +
  622 + void project(const Template &src, Template &dst) const
  623 + {
  624 + Eigen::Map<Eigen::MatrixXf> inMap((float*)src.m().ptr<float>(), src.m().rows*src.m().cols, 1);
  625 + Eigen::Map<Eigen::MatrixXf> outMap(dst.m().ptr<float>(), dimsOut, 1);
  626 +
  627 + int d = selections.size();
  628 + cv::Mat inSelect(d,1,CV_32F);
  629 + for (int i = 0; i < d; i++)
  630 + inSelect.at<float>(i) = src.m().at<float>(selections[i]);
  631 + ldaSparse.project(Template(src.file, inSelect), dst);
  632 + }
  633 +
  634 + void store(QDataStream &stream) const
  635 + {
  636 + stream << pcaKeep;
  637 + stream << ldaSparse;
  638 + stream << dimsOut;
  639 + stream << selections;
  640 + }
  641 +
  642 + void load(QDataStream &stream)
  643 + {
  644 + stream >> pcaKeep;
  645 + stream >> ldaSparse;
  646 + stream >> dimsOut;
  647 + stream >> selections;
  648 + }
  649 +};
  650 +
  651 +BR_REGISTER(Transform, SparseLDATransform)
  652 +
  653 +/*!
544 * \ingroup distances 654 * \ingroup distances
545 * \brief L1 distance computed using eigen. 655 * \brief L1 distance computed using eigen.
546 * \author Josh Klontz \cite jklontz 656 * \author Josh Klontz \cite jklontz
openbr/plugins/gallery.cpp
@@ -506,38 +506,55 @@ BR_REGISTER(Gallery, csvGallery) @@ -506,38 +506,55 @@ BR_REGISTER(Gallery, csvGallery)
506 * \brief Treats each line as a file. 506 * \brief Treats each line as a file.
507 * \author Josh Klontz \cite jklontz 507 * \author Josh Klontz \cite jklontz
508 * 508 *
509 - * The entire line is treated as the file path. 509 + * The entire line is treated as the file path. An optional label may be specified using a space ' ' separator:
510 * 510 *
  511 +\verbatim
  512 +<FILE>
  513 +<FILE>
  514 +...
  515 +<FILE>
  516 +\endverbatim
  517 + * or
  518 +\verbatim
  519 +<FILE> <LABEL>
  520 +<FILE> <LABEL>
  521 +...
  522 +<FILE> <LABEL>
  523 +\endverbatim
511 * \see csvGallery 524 * \see csvGallery
512 */ 525 */
513 class txtGallery : public Gallery 526 class txtGallery : public Gallery
514 { 527 {
515 Q_OBJECT 528 Q_OBJECT
516 - Q_PROPERTY(QString metadataKey READ get_metadataKey WRITE set_metadataKey RESET reset_metadataKey STORED false)  
517 - BR_PROPERTY(QString, metadataKey, "") 529 + Q_PROPERTY(QString label READ get_label WRITE set_label RESET reset_label STORED false)
  530 + BR_PROPERTY(QString, label, "")
518 531
519 QStringList lines; 532 QStringList lines;
520 533
521 ~txtGallery() 534 ~txtGallery()
522 { 535 {
523 - if (!lines.isEmpty()) QtUtils::writeFile(file.name, lines); 536 + if (!lines.isEmpty())
  537 + QtUtils::writeFile(file.name, lines);
524 } 538 }
525 539
526 TemplateList readBlock(bool *done) 540 TemplateList readBlock(bool *done)
527 { 541 {
528 - *done = true;  
529 TemplateList templates; 542 TemplateList templates;
530 - if (!file.exists()) return templates;  
531 -  
532 - foreach (const QString &line, QtUtils::readLines(file))  
533 - templates.append(File(line)); 543 + foreach (const QString &line, QtUtils::readLines(file)) {
  544 + int splitIndex = line.lastIndexOf(' ');
  545 + if (splitIndex == -1) templates.append(File(line));
  546 + else templates.append(File(line.mid(0, splitIndex), line.mid(splitIndex+1)));
  547 + }
534 *done = true; 548 *done = true;
535 return templates; 549 return templates;
536 } 550 }
537 551
538 void write(const Template &t) 552 void write(const Template &t)
539 { 553 {
540 - lines.append(metadataKey.isEmpty() ? t.file.flat() : t.file.get<QString>(metadataKey)); 554 + QString line = t.file.name;
  555 + if (!label.isEmpty())
  556 + line += " " + t.file.get<QString>(label);
  557 + lines.append(line);
541 } 558 }
542 }; 559 };
543 560
@@ -687,7 +704,9 @@ class dbGallery : public Gallery @@ -687,7 +704,9 @@ class dbGallery : public Gallery
687 if (!subset.isEmpty()) { 704 if (!subset.isEmpty()) {
688 const QStringList &words = subset.split(":"); 705 const QStringList &words = subset.split(":");
689 QtUtils::checkArgsSize("Input", words, 2, 4); 706 QtUtils::checkArgsSize("Input", words, 2, 4);
690 - seed = QtUtils::toInt(words[0]); 707 + if (words[0] == "train") seed = 0;
  708 + else if (words[0] == "test" ) seed = 1;
  709 + else seed = QtUtils::toInt(words[0]);
691 if (words[1].startsWith('{') && words[1].endsWith('}')) { 710 if (words[1].startsWith('{') && words[1].endsWith('}')) {
692 foreach (const QString &regexp, words[1].mid(1, words[1].size()-2).split(",")) 711 foreach (const QString &regexp, words[1].mid(1, words[1].size()-2).split(","))
693 metadataFields.append(QRegExp(regexp)); 712 metadataFields.append(QRegExp(regexp));
@@ -813,16 +832,10 @@ class googleGallery : public Gallery @@ -813,16 +832,10 @@ class googleGallery : public Gallery
813 return templates; 832 return templates;
814 } 833 }
815 834
816 - void write(const Template &t) 835 + void write(const Template &)
817 { 836 {
818 - (void) t;  
819 qFatal("Not supported."); 837 qFatal("Not supported.");
820 } 838 }
821 -  
822 - void init()  
823 - {  
824 - //  
825 - }  
826 }; 839 };
827 840
828 BR_REGISTER(Gallery, googleGallery) 841 BR_REGISTER(Gallery, googleGallery)
openbr/plugins/gui.cpp
@@ -592,8 +592,10 @@ public: @@ -592,8 +592,10 @@ public:
592 template<typename WindowType> 592 template<typename WindowType>
593 void initActual() 593 void initActual()
594 { 594 {
595 - if (!Globals->useGui) 595 + if (!Globals->useGui) {
  596 + qWarning("GUI transform %s created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.", this->metaObject()->className());
596 return; 597 return;
  598 + }
597 599
598 if (displayBuffer) 600 if (displayBuffer)
599 delete displayBuffer; 601 delete displayBuffer;
openbr/plugins/stream.cpp
@@ -1285,11 +1285,18 @@ public: @@ -1285,11 +1285,18 @@ public:
1285 qWarning("Attempted to train untrainable transform, nothing will happen."); 1285 qWarning("Attempted to train untrainable transform, nothing will happen.");
1286 return; 1286 return;
1287 } 1287 }
  1288 + QList<TemplateList> separated;
  1289 + foreach (const TemplateList & list, data) {
  1290 + foreach(const Template & t, list) {
  1291 + separated.append(TemplateList());
  1292 + separated.last().append(t);
  1293 + }
  1294 + }
1288 1295
1289 for (int i=0; i < transforms.size(); i++) { 1296 for (int i=0; i < transforms.size(); i++) {
1290 // OK we have a trainable transform, we need to get input data for it. 1297 // OK we have a trainable transform, we need to get input data for it.
1291 if (transforms[i]->trainable) { 1298 if (transforms[i]->trainable) {
1292 - QList<TemplateList> copy = data; 1299 + QList<TemplateList> copy = separated;
1293 // Project from the start to the trainable stage. 1300 // Project from the start to the trainable stage.
1294 subProject(copy,i); 1301 subProject(copy,i);
1295 1302
scripts/brpy/__init__.py
@@ -58,7 +58,7 @@ def init_brpy(br_loc=&#39;/usr/local/lib&#39;): @@ -58,7 +58,7 @@ def init_brpy(br_loc=&#39;/usr/local/lib&#39;):
58 br.br_eval.argtypes = _string_args(3) 58 br.br_eval.argtypes = _string_args(3)
59 br.br_eval.restype = c_float 59 br.br_eval.restype = c_float
60 br.br_eval_classification.argtypes = _string_args(4) 60 br.br_eval_classification.argtypes = _string_args(4)
61 - br.br_eval_clustering.argtypes = _string_args(2) 61 + br.br_eval_clustering.argtypes = _string_args(3)
62 br.br_eval_detection.argtypes = _string_args(3) 62 br.br_eval_detection.argtypes = _string_args(3)
63 br.br_eval_detection.restype = c_float 63 br.br_eval_detection.restype = c_float
64 br.br_eval_landmarking.argtypes = _string_args(3) + [c_int, c_int] 64 br.br_eval_landmarking.argtypes = _string_args(3) + [c_int, c_int]
@@ -143,7 +143,8 @@ def init_brpy(br_loc=&#39;/usr/local/lib&#39;): @@ -143,7 +143,8 @@ def init_brpy(br_loc=&#39;/usr/local/lib&#39;):
143 br.br_make_gallery.restype = c_void_p 143 br.br_make_gallery.restype = c_void_p
144 br.br_load_from_gallery.argtypes = [c_void_p] 144 br.br_load_from_gallery.argtypes = [c_void_p]
145 br.br_load_from_gallery.restype = c_void_p 145 br.br_load_from_gallery.restype = c_void_p
146 - br.br_add_to_gallery.argtypes = [c_void_p, c_void_p] 146 + br.br_add_template_to_gallery.argtypes = [c_void_p, c_void_p]
  147 + br.br_add_template_list_to_gallery.argtypes = [c_void_p, c_void_p]
147 br.br_close_gallery.argtypes = [c_void_p] 148 br.br_close_gallery.argtypes = [c_void_p]
148 149
149 return br 150 return br