Commit 2a74106657dd866c11660648dbf0e5d8f7e30262
Merge branch 'master' of https://github.com/biometrics/openbr
Showing
12 changed files
with
327 additions
and
53 deletions
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 &targetInput, const QString &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<T> &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<T> &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 &simmat, const QString &mask, const QString &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 &simmat, const QString &mask, const QString &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 &simmat, const QString &mask, const QString &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 &simmat, const QString &mask, const QString &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 &files, const br::File &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 &files, const br::File &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 &files, const br::File &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 &files, const br::File &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
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 | ... | ... |