Commit 2a74106657dd866c11660648dbf0e5d8f7e30262

Authored by Scott Klum
2 parents f7f0dac1 ff98581b

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

openbr/core/bee.cpp
... ... @@ -234,22 +234,26 @@ void BEE::writeMask(const Mat &m, const QString &mask, const QString &targetSigs
234 234 void BEE::makeMask(const QString &targetInput, const QString &queryInput, const QString &mask)
235 235 {
236 236 qDebug("Making mask from %s and %s to %s", qPrintable(targetInput), qPrintable(queryInput), qPrintable(mask));
  237 + FileList targes = TemplateList::fromGallery(targetInput).files();
  238 + FileList queries = (queryInput == ".") ? targes : TemplateList::fromGallery(queryInput).files();
  239 + writeMask(makeMask(targes, queries), mask, targetInput, queryInput);
  240 +}
237 241  
238   - FileList targetFiles = TemplateList::fromGallery(targetInput).files();
239   - FileList queryFiles = (queryInput == ".") ? targetFiles : TemplateList::fromGallery(queryInput).files();
240   - QList<float> targetLabels = targetFiles.labels();
241   - QList<float> queryLabels = queryFiles.labels();
242   - QList<int> targetPartitions = targetFiles.crossValidationPartitions();
243   - QList<int> queryPartitions = queryFiles.crossValidationPartitions();
244   -
245   - Mat vals(queryFiles.size(), targetFiles.size(), CV_8UC1);
246   - for (int i=0; i<queryFiles.size(); i++) {
247   - const QString &fileA = queryFiles[i];
  242 +cv::Mat BEE::makeMask(const br::FileList &targets, const br::FileList &queries)
  243 +{
  244 + QList<float> targetLabels = targets.labels();
  245 + QList<float> queryLabels = queries.labels();
  246 + QList<int> targetPartitions = targets.crossValidationPartitions();
  247 + QList<int> queryPartitions = queries.crossValidationPartitions();
  248 +
  249 + Mat mask(queries.size(), targets.size(), CV_8UC1);
  250 + for (int i=0; i<queries.size(); i++) {
  251 + const QString &fileA = queries[i];
248 252 const int labelA = queryLabels[i];
249 253 const int partitionA = queryPartitions[i];
250 254  
251   - for (int j=0; j<targetFiles.size(); j++) {
252   - const QString &fileB = targetFiles[j];
  255 + for (int j=0; j<targets.size(); j++) {
  256 + const QString &fileB = targets[j];
253 257 const int labelB = targetLabels[j];
254 258 const int partitionB = targetPartitions[j];
255 259  
... ... @@ -260,10 +264,11 @@ void BEE::makeMask(const QString &amp;targetInput, const QString &amp;queryInput, const
260 264 else if (partitionA != partitionB) val = DontCare;
261 265 else if (labelA == labelB) val = Match;
262 266 else val = NonMatch;
263   - vals.at<Mask_t>(i,j) = val;
  267 + mask.at<Mask_t>(i,j) = val;
264 268 }
265 269 }
266   - writeMask(vals, mask, targetInput, queryInput);
  270 +
  271 + return mask;
267 272 }
268 273  
269 274 void BEE::combineMasks(const QStringList &inputMasks, const QString &outputMask, const QString &method)
... ...
openbr/core/bee.h
... ... @@ -46,6 +46,7 @@ namespace BEE
46 46  
47 47 // Write BEE files
48 48 void makeMask(const QString &targetInput, const QString &queryInput, const QString &mask);
  49 + cv::Mat makeMask(const br::FileList &targets, const br::FileList &queries);
49 50 void combineMasks(const QStringList &inputMasks, const QString &outputMask, const QString &method);
50 51 }
51 52  
... ...
openbr/core/common.h
... ... @@ -119,14 +119,14 @@ T Max(const QList&lt;T&gt; &amp;vals)
119 119 * \brief Returns the mean and standard deviation of a vector of values.
120 120 */
121 121 template <template<class> class V, typename T>
122   -void Mean(const V<T> &vals, double *mean)
  122 +double Mean(const V<T> &vals)
123 123 {
124 124 const int size = vals.size();
125 125  
126 126 // Compute Mean
127 127 double sum = 0;
128 128 foreach (int val, vals) sum += val;
129   - *mean = (size == 0) ? 0 : sum / size;
  129 + return (size == 0) ? 0 : sum / size;
130 130 }
131 131  
132 132 /*!
... ... @@ -137,7 +137,7 @@ void MeanStdDev(const V&lt;T&gt; &amp;vals, double *mean, double *stddev)
137 137 {
138 138 const int size = vals.size();
139 139  
140   - Mean(vals, mean);
  140 + *mean = Mean(vals);
141 141  
142 142 // Compute Standard Deviation
143 143 double variance = 0;
... ...
openbr/core/plot.cpp
... ... @@ -113,9 +113,6 @@ float Evaluate(const QString &amp;simmat, const QString &amp;mask, const QString &amp;csv)
113 113 {
114 114 qDebug("Evaluating %s with %s", qPrintable(simmat), qPrintable(mask));
115 115  
116   - const int Max_Points = 500;
117   - float result = -1;
118   -
119 116 // Read files
120 117 const Mat scores = BEE::readSimmat(simmat);
121 118 File maskFile(mask);
... ... @@ -124,13 +121,21 @@ float Evaluate(const QString &amp;simmat, const QString &amp;mask, const QString &amp;csv)
124 121 const Mat masks = BEE::readMask(maskFile);
125 122 if (scores.size() != masks.size()) qFatal("Simmat (%i,%i) / Mask (%i,%i) size mismatch.", scores.rows, scores.cols, masks.rows, masks.cols);
126 123  
  124 + return Evaluate(scores, masks, csv);
  125 +}
  126 +
  127 +float Evaluate(const Mat &simmat, const Mat &mask, const QString &csv)
  128 +{
  129 + const int Max_Points = 500;
  130 + float result = -1;
  131 +
127 132 // Make comparisons
128   - QList<Comparison> comparisons; comparisons.reserve(scores.rows*scores.cols);
  133 + QList<Comparison> comparisons; comparisons.reserve(simmat.rows*simmat.cols);
129 134 int genuineCount = 0, impostorCount = 0, numNaNs = 0;
130   - for (int i=0; i<scores.rows; i++) {
131   - for (int j=0; j<scores.cols; j++) {
132   - const BEE::Mask_t mask_val = masks.at<BEE::Mask_t>(i,j);
133   - const BEE::Simmat_t simmat_val = scores.at<BEE::Simmat_t>(i,j);
  135 + for (int i=0; i<simmat.rows; i++) {
  136 + for (int j=0; j<simmat.cols; j++) {
  137 + const BEE::Mask_t mask_val = mask.at<BEE::Mask_t>(i,j);
  138 + const BEE::Simmat_t simmat_val = simmat.at<BEE::Simmat_t>(i,j);
134 139 if (mask_val == BEE::DontCare) continue;
135 140 if (simmat_val != simmat_val) { numNaNs++; continue; }
136 141 comparisons.append(Comparison(simmat_val, j, i, mask_val == BEE::Match));
... ... @@ -149,7 +154,7 @@ float Evaluate(const QString &amp;simmat, const QString &amp;mask, const QString &amp;csv)
149 154 double genuineSum = 0, impostorSum = 0;
150 155 QList<OperatingPoint> operatingPoints;
151 156 QList<float> genuines, impostors;
152   - QVector<int> firstGenuineReturns(scores.rows, 0);
  157 + QVector<int> firstGenuineReturns(simmat.rows, 0);
153 158  
154 159 int falsePositives = 0, previousFalsePositives = 0;
155 160 int truePositives = 0, previousTruePositives = 0;
... ... @@ -202,11 +207,11 @@ float Evaluate(const QString &amp;simmat, const QString &amp;mask, const QString &amp;csv)
202 207 // Write Metadata table
203 208 QStringList lines;
204 209 lines.append("Plot,X,Y");
205   - lines.append("Metadata,"+QString::number(scores.cols)+",Gallery");
206   - lines.append("Metadata,"+QString::number(scores.rows)+",Probe");
  210 + lines.append("Metadata,"+QString::number(simmat.cols)+",Gallery");
  211 + lines.append("Metadata,"+QString::number(simmat.rows)+",Probe");
207 212 lines.append("Metadata,"+QString::number(genuineCount)+",Genuine");
208 213 lines.append("Metadata,"+QString::number(impostorCount)+",Impostor");
209   - lines.append("Metadata,"+QString::number(scores.cols*scores.rows-(genuineCount+impostorCount))+",Ignored");
  214 + lines.append("Metadata,"+QString::number(simmat.cols*simmat.rows-(genuineCount+impostorCount))+",Ignored");
210 215  
211 216 // Write Detection Error Tradeoff (DET), PRE, REC
212 217 int points = qMin(operatingPoints.size(), Max_Points);
... ... @@ -382,7 +387,11 @@ struct RPlot
382 387 "ERR$Y <- as.numeric(as.character(ERR$Y))\n"
383 388 "SD$Y <- as.factor(unique(as.character(SD$Y)))\n"
384 389 "BC$Y <- as.numeric(as.character(BC$Y))\n"
385   - "CMC$Y <- as.numeric(as.character(CMC$Y))\n");
  390 + "CMC$Y <- as.numeric(as.character(CMC$Y))\n"
  391 + "\n"
  392 + "# Code to format FAR values\n"
  393 + "far_names <- list('0.001'=\"FAR = 0.1%\", '0.01'=\"FAR = 1%\")\n"
  394 + "far_labeller <- function(variable,value) { return(far_names[as.character(value)]) }\n");
386 395  
387 396 // Open output device
388 397 file.write(qPrintable(QString("\n"
... ... @@ -461,7 +470,7 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show)
461 470 QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") +
462 471 (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
463 472 (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
464   - QString(" + scale_x_log10() + scale_y_continuous(labels=percent)") +
  473 + QString(" + scale_x_log10(labels=percent) + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\")") +
465 474 QString("\nggsave(\"%1\")\n").arg(p.subfile("ROC"))));
466 475  
467 476 p.file.write(qPrintable(QString("qplot(X, Y, data=DET%1").arg((p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : ", geom=\"line\"") +
... ... @@ -470,7 +479,7 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show)
470 479 QString(", xlab=\"False Accept Rate\", ylab=\"False Reject Rate\") + geom_abline(alpha=0.5, colour=\"grey\", linetype=\"dashed\") + theme_minimal()") +
471 480 (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
472 481 (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
473   - QString(" + scale_x_log10() + scale_y_log10()") +
  482 + QString(" + scale_x_log10(labels=percent) + scale_y_log10(labels=percent) + annotation_logticks()") +
474 483 QString("\nggsave(\"%1\")\n").arg(p.subfile("DET"))));
475 484  
476 485 p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") +
... ... @@ -495,7 +504,7 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show)
495 504 QString(", xlab=\"%1False Accept Rate\"").arg(p.major.size > 1 ? p.major.header + " / " : QString()) +
496 505 QString(", ylab=\"True Accept Rate%1\") + theme_minimal()").arg(p.minor.size > 1 ? " / " + p.minor.header : QString()) +
497 506 (p.major.size > 1 ? getScale("fill", p.major.header, p.major.size) : QString()) +
498   - (p.minor.size > 1 ? QString(" + facet_grid(%2 ~ X)").arg(p.minor.header) : QString(" + facet_wrap(~ X)")) +
  507 + (p.minor.size > 1 ? QString(" + facet_grid(%2 ~ X)").arg(p.minor.header) : QString(" + facet_grid(. ~ X, labeller=far_labeller)")) +
499 508 QString(" + scale_y_continuous(labels=percent) + theme(legend.position=\"none\", axis.text.x=element_text(angle=-90, hjust=0))%1").arg((p.major.smooth || p.minor.smooth) ? "" : " + geom_text(data=BC, aes(label=Y, y=0.05))") +
500 509 QString("\nggsave(\"%1\")\n").arg(p.subfile("BC"))));
501 510  
... ... @@ -503,7 +512,7 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show)
503 512 ((p.flip ? p.major.size : p.minor.size) > 1 ? QString(", colour=factor(%1)").arg(p.flip ? p.major.header : p.minor.header) : QString()) +
504 513 QString(", xlab=\"Score%1\", ylab=\"Error Rate\") + theme_minimal()").arg((p.flip ? p.minor.size : p.major.size) > 1 ? " / " + (p.flip ? p.minor.header : p.major.header) : QString()) +
505 514 ((p.flip ? p.major.size : p.minor.size) > 1 ? getScale("colour", p.flip ? p.major.header : p.minor.header, p.flip ? p.major.size : p.minor.size) : QString()) +
506   - QString(" + scale_y_log10()") +
  515 + QString(" + scale_y_log10(labels=percent) + annotation_logticks(sides=\"l\")") +
507 516 ((p.flip ? p.minor.size : p.major.size) > 1 ? QString(" + facet_wrap(~ %1, scales=\"free_x\")").arg(p.flip ? p.minor.header : p.major.header) : QString()) +
508 517 QString(" + theme(aspect.ratio=1)") +
509 518 QString("\nggsave(\"%1\")\n").arg(p.subfile("ERR"))));
... ...
openbr/core/plot.h
... ... @@ -27,6 +27,7 @@ namespace br
27 27  
28 28 void Confusion(const QString &file, float score, int &true_positives, int &false_positives, int &true_negatives, int &false_negatives);
29 29 float Evaluate(const QString &simmat, const QString &mask, const QString &csv = ""); // Returns TAR @ FAR = 0.01
  30 +float Evaluate(const cv::Mat &scores, const cv::Mat &masks, const QString &csv = "");
30 31 bool Plot(const QStringList &files, const br::File &destination, bool show = false);
31 32 bool PlotMetadata(const QStringList &files, const QString &destination, bool show = false);
32 33  
... ...
openbr/plugins/crop.cpp
... ... @@ -90,6 +90,25 @@ BR_REGISTER(Transform, LimitSizeTransform)
90 90  
91 91 /*!
92 92 * \ingroup transforms
  93 + * \brief Enforce a multiple of \em n columns.
  94 + * \author Josh Klontz \cite jklontz
  95 + */
  96 +class DivTransform : public UntrainableTransform
  97 +{
  98 + Q_OBJECT
  99 + Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false)
  100 + BR_PROPERTY(int, n, 1)
  101 +
  102 + void project(const Template &src, Template &dst) const
  103 + {
  104 + dst = Mat(src, Rect(0,0,n*(src.m().cols/n),src.m().rows));
  105 + }
  106 +};
  107 +
  108 +BR_REGISTER(Transform, DivTransform)
  109 +
  110 +/*!
  111 + * \ingroup transforms
93 112 * \brief Crop out black borders
94 113 * \author Josh Klontz \cite jklontz
95 114 */
... ...
openbr/plugins/filter.cpp
... ... @@ -235,7 +235,7 @@ class PowTransform : public UntrainableTransform
235 235  
236 236 void project(const Template &src, Template &dst) const
237 237 {
238   - pow(src, power, dst);
  238 + pow(preserveSign ? abs(src) : src.m(), power, dst);
239 239 if (preserveSign) subtract(Scalar::all(0), dst, dst, src.m() < 0);
240 240 }
241 241 };
... ...
openbr/plugins/integral.cpp
... ... @@ -38,15 +38,19 @@ class IntegralSamplerTransform : public UntrainableTransform
38 38 Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false)
39 39 Q_PROPERTY(float stepFactor READ get_stepFactor WRITE set_stepFactor RESET reset_stepFactor STORED false)
40 40 Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false)
  41 + Q_PROPERTY(bool secondOrder READ get_secondOrder WRITE set_secondOrder RESET reset_secondOrder STORED false)
41 42 BR_PROPERTY(int, scales, 6)
42 43 BR_PROPERTY(float, scaleFactor, 2)
43 44 BR_PROPERTY(float, stepFactor, 0.75)
44   - BR_PROPERTY(int, minSize, 6)
  45 + BR_PROPERTY(int, minSize, 8)
  46 + BR_PROPERTY(bool, secondOrder, false)
45 47  
46 48 void project(const Template &src, Template &dst) const
47 49 {
48 50 typedef Eigen::Map< const Eigen::Matrix<qint32,Eigen::Dynamic,1> > InputDescriptor;
  51 + typedef Eigen::Map< const Eigen::Matrix<float,Eigen::Dynamic,1> > SecondOrderInputDescriptor;
49 52 typedef Eigen::Map< Eigen::Matrix<float,Eigen::Dynamic,1> > OutputDescriptor;
  53 +
50 54 const Mat &m = src.m();
51 55 if (m.depth() != CV_32S) qFatal("Expected CV_32S matrix depth.");
52 56 const int channels = m.channels();
... ... @@ -56,8 +60,10 @@ class IntegralSamplerTransform : public UntrainableTransform
56 60 float idealSize = min(m.rows, m.cols)-1;
57 61 for (int scale=0; scale<scales; scale++) {
58 62 const int currentSize(idealSize);
59   - descriptors += (1+(m.rows-currentSize-1)/int(idealSize*stepFactor)) *
60   - (1+(m.cols-currentSize-1)/int(idealSize*stepFactor));
  63 + const int numDown = 1+(m.rows-currentSize-1)/int(idealSize*stepFactor);
  64 + const int numAcross = 1+(m.cols-currentSize-1)/int(idealSize*stepFactor);
  65 + descriptors += numDown*numAcross;
  66 + if (secondOrder) descriptors += numDown*(numAcross-1) + (numDown-1)*numAcross;
61 67 idealSize /= scaleFactor;
62 68 if (idealSize < minSize) break;
63 69 }
... ... @@ -81,6 +87,26 @@ class IntegralSamplerTransform : public UntrainableTransform
81 87 index++;
82 88 }
83 89 }
  90 + if (secondOrder) {
  91 + const int numDown = 1+(m.rows-currentSize-1)/currentStep;
  92 + const int numAcross = 1+(m.cols-currentSize-1)/currentStep;
  93 + const float *dataIn = n.ptr<float>(index - numDown*numAcross);
  94 + for (int i=0; i<numDown; i++) {
  95 + for (int j=0; j<numAcross; j++) {
  96 + SecondOrderInputDescriptor a(dataIn + (i*numAcross+j)*channels, channels, 1);
  97 + if (j < numAcross-1) {
  98 + OutputDescriptor y(dataOut+(index*channels), channels, 1);
  99 + y = a - SecondOrderInputDescriptor(dataIn + (i*numAcross+j+1)*channels, channels, 1);
  100 + index++;
  101 + }
  102 + if (i < numDown-1) {
  103 + OutputDescriptor y(dataOut+(index*channels), channels, 1);
  104 + y = a - SecondOrderInputDescriptor(dataIn + ((i+1)*numAcross+j)*channels, channels, 1);
  105 + index++;
  106 + }
  107 + }
  108 + }
  109 + }
84 110 idealSize /= scaleFactor;
85 111 if (idealSize < minSize) break;
86 112 }
... ...
openbr/plugins/meta.cpp
... ... @@ -418,6 +418,8 @@ public:
418 418 private:
419 419 void init()
420 420 {
  421 + if (!transform) return;
  422 +
421 423 trainable = transform->trainable;
422 424 if (!cache.isEmpty()) return;
423 425  
... ...
openbr/plugins/output.cpp
... ... @@ -40,6 +40,7 @@
40 40 #include "openbr/core/bee.h"
41 41 #include "openbr/core/common.h"
42 42 #include "openbr/core/opencvutils.h"
  43 +#include "openbr/core/plot.h"
43 44 #include "openbr/core/qtutils.h"
44 45  
45 46 namespace br
... ... @@ -259,6 +260,23 @@ BR_REGISTER(Output, EmptyOutput)
259 260  
260 261 /*!
261 262 * \ingroup outputs
  263 + * \brief Evaluate the output matrix.
  264 + * \author Josh Klontz \cite jklontz
  265 + */
  266 +class evalOutput : public MatrixOutput
  267 +{
  268 + Q_OBJECT
  269 +
  270 + ~evalOutput()
  271 + {
  272 + Evaluate(data, BEE::makeMask(targetFiles, queryFiles), "");
  273 + }
  274 +};
  275 +
  276 +BR_REGISTER(Output, evalOutput)
  277 +
  278 +/*!
  279 + * \ingroup outputs
262 280 * \brief Outputs highest ranked matches with scores.
263 281 * \author Scott Klum \cite sklum
264 282 */
... ...
openbr/plugins/quality.cpp
  1 +#include <QFutureSynchronizer>
  2 +#include <QtConcurrent>
1 3 #include <openbr/openbr_plugin.h>
2 4  
3 5 #include "openbr/core/common.h"
  6 +#include "openbr/core/opencvutils.h"
4 7  
5 8 namespace br
6 9 {
... ... @@ -32,8 +35,7 @@ class ImpostorUniquenessMeasureTransform : public Transform
32 35 QList<float> scores = distance->compare(subset, probe);
33 36 float min, max;
34 37 Common::MinMax(scores, &min, &max);
35   - double mean;
36   - Common::Mean(scores, &mean);
  38 + double mean = Common::Mean(scores);
37 39 return (max-mean)/(max-min);
38 40 }
39 41  
... ... @@ -199,6 +201,89 @@ class MatchProbabilityDistance : public Distance
199 201 BR_REGISTER(Distance, MatchProbabilityDistance)
200 202  
201 203 /*!
  204 + * \ingroup transforms
  205 + * \brief Normalize by Bhattacharyya coefficient.
  206 + * \author Josh Klontz \cite jklontz
  207 + */
  208 +class BhattacharyyaTransform : public Transform
  209 +{
  210 + Q_OBJECT
  211 + cv::Mat scale;
  212 +
  213 + static float bhattacharyyaCoefficient(const cv::Mat &data, const QList<int> &labels)
  214 + {
  215 + const QList<float> vals = OpenCVUtils::matrixToVector<float>(data);
  216 + if (vals.size() != labels.size())
  217 + qFatal("Logic error.");
  218 +
  219 + QList<float> genuineScores; genuineScores.reserve(vals.size());
  220 + QList<float> impostorScores; impostorScores.reserve(vals.size()*vals.size()/2);
  221 + for (int i=0; i<vals.size(); i++)
  222 + for (int j=i+1; j<vals.size(); j++)
  223 + if (labels[i] == labels[j]) genuineScores.append(fabs(vals[i]-vals[j]));
  224 + else impostorScores.append(fabs(vals[i]-vals[j]));
  225 +
  226 + genuineScores = Common::Downsample(genuineScores, 256);
  227 + impostorScores = Common::Downsample(impostorScores, 256);
  228 + double hGenuine = Common::KernelDensityBandwidth(genuineScores);
  229 + double hImpostor = Common::KernelDensityBandwidth(impostorScores);
  230 +
  231 + float genuineMin, genuineMax, impostorMin, impostorMax, min, max;
  232 + Common::MinMax(genuineScores, &genuineMin, &genuineMax);
  233 + Common::MinMax(impostorScores, &impostorMin, &impostorMax);
  234 + min = std::min(genuineMin, impostorMin);
  235 + max = std::max(genuineMax, impostorMax);
  236 +
  237 + const int steps = 512;
  238 + double bc = 0;
  239 + for (int i=0; i<steps; i++) {
  240 + float score = min + i*(max-min)/(steps-1);
  241 + bc += sqrt(Common::KernelDensityEstimation(genuineScores, score, hGenuine) *
  242 + Common::KernelDensityEstimation(impostorScores, score, hImpostor))/steps;
  243 + }
  244 + if ((bc <= 0) || (bc > 1)) qFatal("Logic error.");
  245 + return -log(bc);
  246 + }
  247 +
  248 + void train(const TemplateList &src)
  249 + {
  250 + const cv::Mat data = OpenCVUtils::toMat(src.data());
  251 + const QList<int> labels = src.labels<int>();
  252 +
  253 + QFutureSynchronizer<float> futures;
  254 + QList<float> coefficients; coefficients.reserve(data.cols);
  255 + for (int i=0; i<data.cols; i++)
  256 + if (Globals->parallelism) futures.addFuture(QtConcurrent::run(&BhattacharyyaTransform::bhattacharyyaCoefficient, data.col(i), labels));
  257 + else coefficients.append( bhattacharyyaCoefficient( data.col(i), labels));
  258 + futures.waitForFinished();
  259 + foreach (const QFuture<float> &future, futures.futures())
  260 + coefficients.append(future.result());
  261 +
  262 + scale = cv::Mat(1, coefficients.size(), CV_32FC1);
  263 + for (int i=0; i<coefficients.size(); i++)
  264 + scale.at<float>(0,i) = coefficients[i];
  265 +
  266 + }
  267 +
  268 + void project(const Template &src, Template &dst) const
  269 + {
  270 + cv::multiply(src.m().reshape(1,1), scale, dst);
  271 + }
  272 +
  273 + void store(QDataStream &stream) const
  274 + {
  275 + stream << scale;
  276 + }
  277 +
  278 + void load(QDataStream &stream)
  279 + {
  280 + stream >> scale;
  281 + }
  282 +};
  283 +
  284 +BR_REGISTER(Transform, BhattacharyyaTransform)
  285 +
  286 +/*!
202 287 * \ingroup distances
203 288 * \brief Linear normalizes of a distance so the mean impostor score is 0 and the mean genuine score is 1.
204 289 * \author Josh Klontz \cite jklontz
... ...
openbr/plugins/quantize.cpp
... ... @@ -56,6 +56,72 @@ class QuantizeTransform : public Transform
56 56 BR_REGISTER(Transform, QuantizeTransform)
57 57  
58 58 /*!
  59 + * \ingroup distances
  60 + * \brief Bayesian quantization distance
  61 + * \author Josh Klontz \cite jklontz
  62 + */
  63 +class BayesianQuantizationDistance : public Distance
  64 +{
  65 + Q_OBJECT
  66 + QVector<float> loglikelihood;
  67 +
  68 + void train(const TemplateList &src)
  69 + {
  70 + if (src.first().size() > 1)
  71 + qFatal("Expected sigle matrix templates.");
  72 +
  73 + Mat data = OpenCVUtils::toMat(src.data());
  74 + QList<int> labels = src.labels<int>();
  75 +
  76 + QVector<qint64> genuines(256*256,0), impostors(256*256,0);
  77 + for (int i=0; i<labels.size(); i++) {
  78 + const uchar *a = data.ptr(i);
  79 + for (int j=0; j<labels.size(); j++) {
  80 + const uchar *b = data.ptr(j);
  81 + const bool genuine = (labels[i] == labels[j]);
  82 + for (int k=0; k<data.cols; k++)
  83 + genuine ? genuines[256*a[k]+b[k]]++ : impostors[256*a[k]+b[k]]++;
  84 + }
  85 + }
  86 +
  87 + qint64 totalGenuines(0), totalImpostors(0);
  88 + for (int i=0; i<256*256; i++) {
  89 + totalGenuines += genuines[i];
  90 + totalImpostors += impostors[i];
  91 + }
  92 +
  93 + loglikelihood = QVector<float>(256*256);
  94 + for (int i=0; i<256; i++)
  95 + for (int j=0; j<256; j++)
  96 + loglikelihood[i*256+j] = log((double(genuines[i*256+j]+genuines[j*256+i]+1)/totalGenuines)/
  97 + (double(impostors[i*256+j]+impostors[j*256+i]+1)/totalImpostors));
  98 + }
  99 +
  100 + float compare(const Template &a, const Template &b) const
  101 + {
  102 + const uchar *aData = a.m().data;
  103 + const uchar *bData = b.m().data;
  104 + const int size = a.m().rows * a.m().cols;
  105 + float likelihood = 0;
  106 + for (int i=0; i<size; i++)
  107 + likelihood += loglikelihood[256*aData[i]+bData[i]];
  108 + return likelihood;
  109 + }
  110 +
  111 + void load(QDataStream &stream)
  112 + {
  113 + stream >> loglikelihood;
  114 + }
  115 +
  116 + void store(QDataStream &stream) const
  117 + {
  118 + stream << loglikelihood;
  119 + }
  120 +};
  121 +
  122 +BR_REGISTER(Distance, BayesianQuantizationDistance)
  123 +
  124 +/*!
59 125 * \ingroup transforms
60 126 * \brief Approximate floats as signed bit.
61 127 * \author Josh Klontz \cite jklontz
... ... @@ -169,6 +235,19 @@ public:
169 235 }
170 236  
171 237 private:
  238 + static void getScores(const QList<int> &indicies, const QList<int> &labels, const Mat &lut, QVector<float> &genuineScores, QVector<float> &impostorScores)
  239 + {
  240 + genuineScores.clear(); impostorScores.clear();
  241 + genuineScores.reserve(indicies.size());
  242 + impostorScores.reserve(indicies.size()*indicies.size()/2);
  243 + for (int i=0; i<indicies.size(); i++)
  244 + for (int j=i+1; j<indicies.size(); j++) {
  245 + const float score = lut.at<float>(0, indicies[i]*256+indicies[j]);
  246 + if (labels[i] == labels[j]) genuineScores.append(score);
  247 + else impostorScores.append(score);
  248 + }
  249 + }
  250 +
172 251 void _train(const Mat &data, const QList<int> &labels, Mat *lut, Mat *center)
173 252 {
174 253 Mat clusterLabels;
... ... @@ -181,14 +260,17 @@ private:
181 260 if (!bayesian) return;
182 261  
183 262 QList<int> indicies = OpenCVUtils::matrixToVector<int>(clusterLabels);
184   - QVector<float> genuineScores; genuineScores.reserve(data.rows);
185   - QVector<float> impostorScores; impostorScores.reserve(data.rows*data.rows/2);
186   - for (int i=0; i<indicies.size(); i++)
187   - for (int j=i+1; j<indicies.size(); j++) {
188   - const float score = lut->at<float>(0, indicies[i]*256+indicies[j]);
189   - if (labels[i] == labels[j]) genuineScores.append(score);
190   - else impostorScores.append(score);
191   - }
  263 + QVector<float> genuineScores, impostorScores;
  264 +
  265 + // RBF Kernel
  266 +// getScores(indicies, labels, *lut, genuineScores, impostorScores);
  267 +// float sigma = 1.0 / Common::Mean(impostorScores);
  268 +// for (int j=0; j<256; j++)
  269 +// for (int k=0; k<256; k++)
  270 +// lut->at<float>(0,j*256+k) = exp(-lut->at<float>(0,j*256+k)/(2*pow(sigma, 2.f)));
  271 +
  272 + // Bayesian PDF
  273 + getScores(indicies, labels, *lut, genuineScores, impostorScores);
192 274 genuineScores = Common::Downsample(genuineScores, 256);
193 275 impostorScores = Common::Downsample(impostorScores, 256);
194 276  
... ... @@ -199,21 +281,45 @@ private:
199 281 for (int k=0; k<256; k++)
200 282 lut->at<float>(0,j*256+k) = log(Common::KernelDensityEstimation(genuineScores, lut->at<float>(0,j*256+k), hGenuine) /
201 283 Common::KernelDensityEstimation(impostorScores, lut->at<float>(0,j*256+k), hImpostor));
  284 +// lut->at<float>(0,j*256+k) = std::max(0.0, log(Common::KernelDensityEstimation(genuineScores, lut->at<float>(0,j*256+k), hGenuine) /
  285 +// Common::KernelDensityEstimation(impostorScores, lut->at<float>(0,j*256+k), hImpostor)));
  286 + }
  287 +
  288 + int getStep(int cols) const
  289 + {
  290 + if (n > 0) return n;
  291 + if (n == 0) return cols;
  292 + return ceil(float(cols)/abs(n));
  293 + }
  294 +
  295 + int getOffset(int cols) const
  296 + {
  297 + if (n >= 0) return 0;
  298 + const int step = getStep(cols);
  299 + return (step - cols%step) % step;
  300 + }
  301 +
  302 + int getDims(int cols) const
  303 + {
  304 + const int step = getStep(cols);
  305 + if (n >= 0) return cols/step;
  306 + return ceil(float(cols)/step);
202 307 }
203 308  
204 309 void train(const TemplateList &src)
205 310 {
206 311 Mat data = OpenCVUtils::toMat(src.data());
207   - if (data.cols % n != 0) qFatal("Expected dimensionality to be divisible by n.");
  312 + const int step = getStep(data.cols);
208 313 const QList<int> labels = src.labels<int>();
209 314  
210 315 Mat &lut = ProductQuantizationLUTs[index];
211   - lut = Mat(data.cols/n, 256*256, CV_32FC1);
  316 + lut = Mat(getDims(data.cols), 256*256, CV_32FC1);
212 317  
213 318 QList<Mat> subdata, subluts;
  319 + const int offset = getOffset(data.cols);
214 320 for (int i=0; i<lut.rows; i++) {
215 321 centers.append(Mat());
216   - subdata.append(data.colRange(i*n,(i+1)*n));
  322 + subdata.append(data.colRange(max(0, i*step-offset), (i+1)*step-offset));
217 323 subluts.append(lut.row(i));
218 324 }
219 325  
... ... @@ -242,9 +348,11 @@ private:
242 348 void project(const Template &src, Template &dst) const
243 349 {
244 350 Mat m = src.m().reshape(1, 1);
245   - dst = Mat(1, m.cols/n, CV_8UC1);
  351 + const int step = getStep(m.cols);
  352 + const int offset = getOffset(m.cols);
  353 + dst = Mat(1, getDims(m.cols), CV_8UC1);
246 354 for (int i=0; i<dst.m().cols; i++)
247   - dst.m().at<uchar>(0,i) = getIndex(m.colRange(i*n, (i+1)*n), centers[i]);
  355 + dst.m().at<uchar>(0,i) = getIndex(m.colRange(max(0, i*step-offset), (i+1)*step-offset), centers[i]);
248 356 }
249 357  
250 358 void store(QDataStream &stream) const
... ...