Commit 107e229c5dd19347d4dc046ceaa2156ac9c7b2e0
Merge branch 'master' of https://github.com/biometrics/openbr
Showing
18 changed files
with
473 additions
and
38 deletions
CMakeLists.txt
| ... | ... | @@ -13,7 +13,7 @@ if(NOT DEFINED CPACK_PACKAGE_VERSION_MAJOR) |
| 13 | 13 | set(CPACK_PACKAGE_VERSION_MAJOR 1) |
| 14 | 14 | endif() |
| 15 | 15 | if(NOT DEFINED CPACK_PACKAGE_VERSION_MINOR) |
| 16 | - set(CPACK_PACKAGE_VERSION_MINOR 0) | |
| 16 | + set(CPACK_PACKAGE_VERSION_MINOR 1) | |
| 17 | 17 | endif() |
| 18 | 18 | if(NOT DEFINED CPACK_PACKAGE_VERSION_PATCH) |
| 19 | 19 | set(CPACK_PACKAGE_VERSION_PATCH 0) |
| ... | ... | @@ -158,8 +158,8 @@ endif() |
| 158 | 158 | |
| 159 | 159 | # Download the models |
| 160 | 160 | ExternalProject_Add(models |
| 161 | - URL http://github.com/biometrics/openbr/releases/download/v1.0.0/models.tar.gz | |
| 162 | - URL_MD5 0a7c79226d6629954aa32c835a1007b9 | |
| 161 | + URL http://github.com/biometrics/openbr/releases/download/v1.1.0/models.tar.gz | |
| 162 | + URL_MD5 d06927071120d5a50eee9475fe8da280 | |
| 163 | 163 | SOURCE_DIR "${PROJECT_SOURCE_DIR}/share/openbr/models" |
| 164 | 164 | CONFIGURE_COMMAND "" |
| 165 | 165 | BUILD_COMMAND "" | ... | ... |
app/br/br.cpp
| ... | ... | @@ -168,6 +168,9 @@ public: |
| 168 | 168 | } else if (!strcmp(fun, "evalRegression")) { |
| 169 | 169 | check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); |
| 170 | 170 | br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); |
| 171 | + } else if (!strcmp(fun, "evalKNN")) { | |
| 172 | + check(parc >= 2 && parc <= 3, "Incorrect parameter count for 'evalKNN'."); | |
| 173 | + br_eval_knn(parv[0], parv[1], parc > 2 ? parv[2] : ""); | |
| 171 | 174 | } else if (!strcmp(fun, "pairwiseCompare")) { |
| 172 | 175 | check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'pairwiseCompare'."); |
| 173 | 176 | br_pairwise_compare(parv[0], parv[1], parc == 3 ? parv[2] : ""); |
| ... | ... | @@ -183,6 +186,9 @@ public: |
| 183 | 186 | } else if (!strcmp(fun, "plotMetadata")) { |
| 184 | 187 | check(parc >= 2, "Incorrect parameter count for 'plotMetadata'."); |
| 185 | 188 | br_plot_metadata(parc-1, parv, parv[parc-1], true); |
| 189 | + } else if (!strcmp(fun, "plotKNN")) { | |
| 190 | + check(parc >=2, "Incorrect parameter count for 'plotKNN'."); | |
| 191 | + br_plot_knn(parc-1, parv, parv[parc-1], true); | |
| 186 | 192 | } else if (!strcmp(fun, "project")) { |
| 187 | 193 | check(parc == 2, "Insufficient parameter count for 'project'."); |
| 188 | 194 | br_project(parv[0], parv[1]); |
| ... | ... | @@ -276,12 +282,14 @@ private: |
| 276 | 282 | "-evalDetection <predicted_gallery> <truth_gallery> [{csv}] [{normalize}] [{minSize}] [{maxSize}]\n" |
| 277 | 283 | "-evalLandmarking <predicted_gallery> <truth_gallery> [{csv} [<normalization_index_a> <normalization_index_b>] [sample_index] [total_examples]]\n" |
| 278 | 284 | "-evalRegression <predicted_gallery> <truth_gallery> <predicted property name> <ground truth property name>\n" |
| 285 | + "-evalKNN <knn_graph> <knn_truth> [{csv}]\n" | |
| 279 | 286 | "-pairwiseCompare <target_gallery> <query_gallery> [{output}]\n" |
| 280 | 287 | "-inplaceEval <simmat> <target> <query> [{csv}]\n" |
| 281 | 288 | "-assertEval <simmat> <mask> <accuracy>\n" |
| 282 | 289 | "-plotDetection <file> ... <file> {destination}\n" |
| 283 | 290 | "-plotLandmarking <file> ... <file> {destination}\n" |
| 284 | 291 | "-plotMetadata <file> ... <file> <columns>\n" |
| 292 | + "-plotKNN <file> ... <file> {destination}\n" | |
| 285 | 293 | "-project <input_gallery> {output_gallery}\n" |
| 286 | 294 | "-deduplicate <input_gallery> <output_gallery> <threshold>\n" |
| 287 | 295 | "-likely <input_type> <output_type> <output_likely_source>\n" | ... | ... |
docs/docs/install.md
| ... | ... | @@ -35,7 +35,7 @@ A hacker's guide to building, editing, and running OpenBR. |
| 35 | 35 | |
| 36 | 36 | $ git clone https://github.com/biometrics/openbr.git |
| 37 | 37 | $ cd openbr |
| 38 | - $ git checkout 1.0 | |
| 38 | + $ git checkout v1.1.0 | |
| 39 | 39 | $ git submodule init |
| 40 | 40 | $ git submodule update |
| 41 | 41 | |
| ... | ... | @@ -116,7 +116,7 @@ A hacker's guide to building, editing, and running OpenBR. |
| 116 | 116 | |
| 117 | 117 | $ git clone https://github.com/biometrics/openbr.git |
| 118 | 118 | $ cd openbr |
| 119 | - $ git checkout 1.0 | |
| 119 | + $ git checkout v1.1.0 | |
| 120 | 120 | $ git submodule init |
| 121 | 121 | $ git submodule update |
| 122 | 122 | |
| ... | ... | @@ -196,7 +196,7 @@ A hacker's guide to building, editing, and running OpenBR. |
| 196 | 196 | $ cd /c |
| 197 | 197 | $ git clone https://github.com/biometrics/openbr.git |
| 198 | 198 | $ cd openbr |
| 199 | - $ git checkout 1.0 | |
| 199 | + $ git checkout v1.1.0 | |
| 200 | 200 | $ git submodule init |
| 201 | 201 | $ git submodule update |
| 202 | 202 | |
| ... | ... | @@ -277,7 +277,7 @@ A hacker's guide to building, editing, and running OpenBR. |
| 277 | 277 | |
| 278 | 278 | $ git clone https://github.com/biometrics/openbr.git |
| 279 | 279 | $ cd openbr |
| 280 | - $ git checkout 1.0 | |
| 280 | + $ git checkout v1.1.0 | |
| 281 | 281 | $ git submodule init |
| 282 | 282 | $ git submodule update |
| 283 | 283 | ... | ... |
openbr/core/eval.cpp
| ... | ... | @@ -102,12 +102,14 @@ static OperatingPoint getOperatingPointGivenTAR(const QList<OperatingPoint> &ope |
| 102 | 102 | } |
| 103 | 103 | |
| 104 | 104 | |
| 105 | -static float getCMC(const QVector<int> &firstGenuineReturns, int rank) | |
| 105 | +static float getCMC(const QVector<int> &firstGenuineReturns, int rank, size_t possibleReturns = 0) | |
| 106 | 106 | { |
| 107 | - int realizedReturns = 0, possibleReturns = 0; | |
| 107 | + bool calcPossible = possibleReturns ? false : true; | |
| 108 | + int realizedReturns = 0; | |
| 108 | 109 | foreach (int firstGenuineReturn, firstGenuineReturns) { |
| 109 | 110 | if (firstGenuineReturn > 0) { |
| 110 | - possibleReturns++; | |
| 111 | + if (calcPossible) | |
| 112 | + possibleReturns++; | |
| 111 | 113 | if (firstGenuineReturn <= rank) realizedReturns++; |
| 112 | 114 | } |
| 113 | 115 | } |
| ... | ... | @@ -1300,4 +1302,168 @@ void EvalRegression(const QString &predictedGallery, const QString &truthGallery |
| 1300 | 1302 | qDebug("MAE = %f", maeError/predicted.size()); |
| 1301 | 1303 | } |
| 1302 | 1304 | |
| 1305 | +void readKNN(size_t &probeCount, size_t &k, QVector<Candidate> &neighbors, const QString &fileName) | |
| 1306 | +{ | |
| 1307 | + QFile file(fileName); | |
| 1308 | + if (!file.open(QFile::ReadOnly)) | |
| 1309 | + qFatal("Failed to open k-NN file for reading!"); | |
| 1310 | + file.read((char*) &probeCount, sizeof(size_t)); | |
| 1311 | + file.read((char*) &k, sizeof(size_t)); | |
| 1312 | + neighbors.resize(probeCount * k); | |
| 1313 | + | |
| 1314 | + file.read((char*) neighbors.data(), probeCount * k * sizeof(Candidate)); | |
| 1315 | +} | |
| 1316 | + | |
| 1317 | +void readKNNTruth(size_t probeCount, QVector< QList<size_t> > &groundTruth, const QString &fileName) | |
| 1318 | +{ | |
| 1319 | + groundTruth.reserve(probeCount); | |
| 1320 | + QFile truthFile(fileName); | |
| 1321 | + if (!truthFile.open(QFile::ReadOnly | QFile::Text)) | |
| 1322 | + qFatal("Failed to open k-NN ground truth file for reading!"); | |
| 1323 | + size_t i=0; | |
| 1324 | + while (!truthFile.atEnd()) { | |
| 1325 | + const QString line = truthFile.readLine().trimmed(); | |
| 1326 | + if (!line.isEmpty()) | |
| 1327 | + foreach (const QString &index, line.split('\t')) { | |
| 1328 | + bool ok; | |
| 1329 | + groundTruth[i].append(index.toLong(&ok)); | |
| 1330 | + if (!ok) | |
| 1331 | + qFatal("Failed to parse long in k-NN ground truth!"); | |
| 1332 | + } | |
| 1333 | + i++; | |
| 1334 | + } | |
| 1335 | + if (i != probeCount) | |
| 1336 | + qFatal("Invalid ground truth file!"); | |
| 1337 | +} | |
| 1338 | + | |
| 1339 | +void EvalKNN(const QString &knnGraph, const QString &knnTruth, const QString &csv) | |
| 1340 | +{ | |
| 1341 | + qDebug("Evaluating k-NN of %s against %s", qPrintable(knnGraph), qPrintable(knnTruth)); | |
| 1342 | + | |
| 1343 | + size_t probeCount; | |
| 1344 | + size_t k; | |
| 1345 | + QVector<Candidate> neighbors; | |
| 1346 | + readKNN(probeCount, k, neighbors, knnGraph); | |
| 1347 | + | |
| 1348 | + /* | |
| 1349 | + * Read the ground truth from disk. | |
| 1350 | + * Line i contains the template indicies of the mates for probe i. | |
| 1351 | + * See the `gtGallery` implementation for details. | |
| 1352 | + */ | |
| 1353 | + QVector< QList<size_t> > truth(probeCount); | |
| 1354 | + readKNNTruth(probeCount, truth, knnTruth); | |
| 1355 | + | |
| 1356 | + /* | |
| 1357 | + * For each probe, record the similarity of the highest mate (if one exists) and the highest non-mate. | |
| 1358 | + */ | |
| 1359 | + QVector<int> firstGenuineReturns(probeCount, 0); | |
| 1360 | + QList<float> matedSimilarities, unmatedSimilarities; | |
| 1361 | + size_t numMatedSearches = 0, numUnmatedSearches = 0; | |
| 1362 | + for (size_t i=0; i<probeCount; i++) { | |
| 1363 | + const QList<size_t> &mates = truth[i]; | |
| 1364 | + bool recordedHighestMatedSimilarity = false; | |
| 1365 | + bool recordedHighestUnmatedSimilarity = false; | |
| 1366 | + if (!mates.empty()) { | |
| 1367 | + numMatedSearches++; | |
| 1368 | + recordedHighestUnmatedSimilarity = true; | |
| 1369 | + } else { | |
| 1370 | + numUnmatedSearches++; | |
| 1371 | + recordedHighestMatedSimilarity = true; | |
| 1372 | + } | |
| 1373 | + | |
| 1374 | + for (size_t j=0; j<k; j++) { | |
| 1375 | + const Candidate &neighbor = neighbors[i*k+j]; | |
| 1376 | + | |
| 1377 | + if (mates.contains(neighbor.index)) { | |
| 1378 | + // Found a mate | |
| 1379 | + if (!recordedHighestMatedSimilarity) { | |
| 1380 | + matedSimilarities.append(neighbor.similarity); | |
| 1381 | + recordedHighestMatedSimilarity = true; | |
| 1382 | + } | |
| 1383 | + if (firstGenuineReturns[i] < 1) firstGenuineReturns[i] = abs(firstGenuineReturns[i])+1; | |
| 1384 | + } else { | |
| 1385 | + // Found a non-mate | |
| 1386 | + if (!recordedHighestUnmatedSimilarity) { | |
| 1387 | + unmatedSimilarities.append(neighbor.similarity); | |
| 1388 | + recordedHighestUnmatedSimilarity = true; | |
| 1389 | + } | |
| 1390 | + if (firstGenuineReturns[i] < 1) firstGenuineReturns[i]--; | |
| 1391 | + } | |
| 1392 | + | |
| 1393 | + if (recordedHighestMatedSimilarity && recordedHighestUnmatedSimilarity) | |
| 1394 | + break; // we can stop scanning the candidate list for this probe | |
| 1395 | + } | |
| 1396 | + } | |
| 1397 | + | |
| 1398 | + // Sort the similarity scores lowest-to-highest | |
| 1399 | + std::sort(matedSimilarities.begin(), matedSimilarities.end()); | |
| 1400 | + std::sort(unmatedSimilarities.begin(), unmatedSimilarities.end()); | |
| 1401 | + const size_t numMatedSimilarities = matedSimilarities.size(); | |
| 1402 | + | |
| 1403 | + if (numMatedSearches == 0) | |
| 1404 | + qFatal("No mated searches!"); | |
| 1405 | + | |
| 1406 | + if (numUnmatedSearches == 0) | |
| 1407 | + qFatal("No unmated searches!"); | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + qDebug("Rank-%d Return Rate: %.3f", 1, getCMC(firstGenuineReturns, 1, numMatedSearches)); | |
| 1411 | + if (k >=5) | |
| 1412 | + qDebug("Rank-%d Return Rate: %.3f", 5, getCMC(firstGenuineReturns, 5, numMatedSearches)); | |
| 1413 | + if (k >=10) | |
| 1414 | + qDebug("Rank-%d Return Rate: %.3f", 10, getCMC(firstGenuineReturns, 10, numMatedSearches)); | |
| 1415 | + | |
| 1416 | + qDebug("Rank-%zu Return Rate: %.3f", k, double(numMatedSimilarities) / double(numMatedSearches)); | |
| 1417 | + | |
| 1418 | + /* | |
| 1419 | + * Iterate through the similarity scores highest-to-lowest, | |
| 1420 | + * for each threshold count the number mated and unmated searches, | |
| 1421 | + * record the corresponding FPIR and FNIR values for the threshold. | |
| 1422 | + */ | |
| 1423 | + QList<OperatingPoint> operatingPoints; | |
| 1424 | + size_t matedCount = 0, previousMatedCount = 0; | |
| 1425 | + size_t unmatedCount = 0, previousUnmatedCount = 0; | |
| 1426 | + while (!matedSimilarities.empty()) { | |
| 1427 | + const float threshold = matedSimilarities.back(); | |
| 1428 | + while (!matedSimilarities.empty() && (matedSimilarities.back() >= threshold)) { | |
| 1429 | + matedSimilarities.removeLast(); | |
| 1430 | + matedCount++; | |
| 1431 | + } | |
| 1432 | + while (!unmatedSimilarities.empty() && (unmatedSimilarities.back() >= threshold)) { | |
| 1433 | + unmatedSimilarities.removeLast(); | |
| 1434 | + unmatedCount++; | |
| 1435 | + } | |
| 1436 | + if ((unmatedCount > previousUnmatedCount) && (matedCount > previousMatedCount)) { | |
| 1437 | + previousMatedCount = matedCount; | |
| 1438 | + previousUnmatedCount = unmatedCount; | |
| 1439 | + operatingPoints.append(OperatingPoint(threshold, | |
| 1440 | + double(unmatedCount) / double(numUnmatedSearches), | |
| 1441 | + 1.0 - double(matedCount) / double(numMatedSearches))); | |
| 1442 | + } | |
| 1443 | + } | |
| 1444 | + | |
| 1445 | + if (!csv.isEmpty()) { | |
| 1446 | + // Open the output file | |
| 1447 | + QFile ietFile(csv); | |
| 1448 | + if (!ietFile.open(QFile::WriteOnly | QFile::Text)) | |
| 1449 | + qFatal("Failed to open IET file for writing!"); | |
| 1450 | + ietFile.write("Plot,X,Y,Z\n"); | |
| 1451 | + // Write CMC | |
| 1452 | + const int Max_Retrieval = min(200, (int)k); | |
| 1453 | + for (int i=1; i<=Max_Retrieval; i++) { | |
| 1454 | + const float retrievalRate = getCMC(firstGenuineReturns, i, numMatedSearches); | |
| 1455 | + ietFile.write(qPrintable(QString("CMC,%1,%2,0\n").arg(QString::number(i), QString::number(retrievalRate)))); | |
| 1456 | + } | |
| 1457 | + | |
| 1458 | + foreach(const OperatingPoint &operatingPoint, operatingPoints) | |
| 1459 | + ietFile.write(qPrintable("IET," + | |
| 1460 | + QString::number(operatingPoint.FAR) + "," + | |
| 1461 | + QString::number(operatingPoint.TAR) + "," + | |
| 1462 | + QString::number(operatingPoint.score) + "\n")); | |
| 1463 | + } | |
| 1464 | + | |
| 1465 | + qDebug("FNIR @ FPIR = 0.1: %.3f", 1-getOperatingPointGivenFAR(operatingPoints, 0.1).TAR); | |
| 1466 | + qDebug("FNIR @ FPIR = 0.01: %.3f", 1-getOperatingPointGivenFAR(operatingPoints, 0.01).TAR); | |
| 1467 | +} | |
| 1468 | + | |
| 1303 | 1469 | } // namespace br | ... | ... |
openbr/core/eval.h
| ... | ... | @@ -33,6 +33,17 @@ namespace br |
| 33 | 33 | float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", bool normalize = false, int minSize = 0, int maxSize = 0); // Return average overlap |
| 34 | 34 | float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1, int sampleIndex = 0, int totalExamples = 5); // Return average error |
| 35 | 35 | void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 36 | + void EvalKNN(const QString &knnGraph, const QString &knnTruth, const QString &csv = ""); | |
| 37 | + | |
| 38 | + struct Candidate | |
| 39 | + { | |
| 40 | + size_t index; | |
| 41 | + float similarity; | |
| 42 | + | |
| 43 | + Candidate() {} | |
| 44 | + Candidate(size_t index_, float similarity_) | |
| 45 | + : index(index_), similarity(similarity_) {} | |
| 46 | + }; | |
| 36 | 47 | } |
| 37 | 48 | |
| 38 | 49 | #endif // BR_EVAL_H | ... | ... |
openbr/core/plot.cpp
| ... | ... | @@ -346,4 +346,32 @@ bool PlotMetadata(const QStringList &files, const QString &columns, bool show) |
| 346 | 346 | return p.finalize(show); |
| 347 | 347 | } |
| 348 | 348 | |
| 349 | +bool PlotKNN(const QStringList &files, const File &destination, bool show) | |
| 350 | +{ | |
| 351 | + qDebug("Plotting %d k-NN file(s) to %s", files.size(), qPrintable(destination)); | |
| 352 | + RPlot p(files, destination); | |
| 353 | + p.file.write("\nformatData(type=\"knn\")\n\n"); | |
| 354 | + | |
| 355 | + QMap<QString,File> optMap; | |
| 356 | + optMap.insert("rocOptions", File(QString("[xTitle=False Positive Identification Rate (FPIR),yTitle=True Positive Identification Rate (TPIR),xLog=true,yLog=false]"))); | |
| 357 | + optMap.insert("ietOptions", File(QString("[xTitle=False Positive Identification Rate (FPIR),yTitle=False Negative Identification Rate (FNIR),xLog=true,yLog=true]"))); | |
| 358 | + optMap.insert("cmcOptions", File(QString("[xTitle=Rank,yTitle=Retrieval Rate,xLog=true,yLog=false,size=1,xLabels=(1,5,10,50,100),xBreaks=(1,5,10,50,100)]"))); | |
| 359 | + | |
| 360 | + foreach (const QString &key, optMap.keys()) { | |
| 361 | + const QStringList options = destination.get<QStringList>(key, QStringList()); | |
| 362 | + foreach (const QString &option, options) { | |
| 363 | + QStringList words = QtUtils::parse(option, '='); | |
| 364 | + QtUtils::checkArgsSize(words[0], words, 1, 2); | |
| 365 | + optMap[key].set(words[0], words[1]); | |
| 366 | + } | |
| 367 | + } | |
| 368 | + | |
| 369 | + QString plot = "plot <- plotLine(lineData=%1, options=list(%2), flipY=%3)\nplot\n"; | |
| 370 | + p.file.write(qPrintable(QString(plot).arg("IET", toRList(optMap["rocOptions"]), "TRUE"))); | |
| 371 | + p.file.write(qPrintable(QString(plot).arg("IET", toRList(optMap["ietOptions"]), "FALSE"))); | |
| 372 | + p.file.write(qPrintable(QString(plot).arg("CMC", toRList(optMap["cmcOptions"]), "FALSE"))); | |
| 373 | + | |
| 374 | + return p.finalize(show); | |
| 375 | +} | |
| 376 | + | |
| 349 | 377 | } // namespace br | ... | ... |
openbr/core/plot.h
| ... | ... | @@ -28,6 +28,7 @@ namespace br |
| 28 | 28 | bool PlotDetection(const QStringList &files, const File &destination, bool show = false); |
| 29 | 29 | bool PlotLandmarking(const QStringList &files, const File &destination, bool show = false); |
| 30 | 30 | bool PlotMetadata(const QStringList &files, const QString &destination, bool show = false); |
| 31 | + bool PlotKNN(const QStringList &files, const File &destination, bool show = false); | |
| 31 | 32 | } |
| 32 | 33 | |
| 33 | 34 | #endif // BR_PLOT_H | ... | ... |
openbr/openbr.cpp
| ... | ... | @@ -145,6 +145,11 @@ void br_eval_regression(const char *predicted_gallery, const char *truth_gallery |
| 145 | 145 | EvalRegression(predicted_gallery, truth_gallery, predicted_property, truth_property); |
| 146 | 146 | } |
| 147 | 147 | |
| 148 | +void br_eval_knn(const char *knnGraph, const char *knnTruth, const char *csv) | |
| 149 | +{ | |
| 150 | + EvalKNN(knnGraph, knnTruth, csv); | |
| 151 | +} | |
| 152 | + | |
| 148 | 153 | void br_finalize() |
| 149 | 154 | { |
| 150 | 155 | Context::finalize(); |
| ... | ... | @@ -216,6 +221,11 @@ bool br_plot_metadata(int num_files, const char *files[], const char *columns, b |
| 216 | 221 | return PlotMetadata(QtUtils::toStringList(num_files, files), columns, show); |
| 217 | 222 | } |
| 218 | 223 | |
| 224 | +bool br_plot_knn(int num_files, const char *files[], const char *destination, bool show) | |
| 225 | +{ | |
| 226 | + return PlotKNN(QtUtils::toStringList(num_files, files), destination, show); | |
| 227 | +} | |
| 228 | + | |
| 219 | 229 | float br_progress() |
| 220 | 230 | { |
| 221 | 231 | return Globals->progress(); | ... | ... |
openbr/openbr.h
| ... | ... | @@ -64,6 +64,8 @@ BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *t |
| 64 | 64 | |
| 65 | 65 | BR_EXPORT void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char *predicted_property = "", const char *truth_property = ""); |
| 66 | 66 | |
| 67 | +BR_EXPORT void br_eval_knn(const char *knnGraph, const char *knnTruth, const char *csv = ""); | |
| 68 | + | |
| 67 | 69 | BR_EXPORT void br_finalize(); |
| 68 | 70 | |
| 69 | 71 | BR_EXPORT void br_fuse(int num_input_simmats, const char *input_simmats[], |
| ... | ... | @@ -91,6 +93,8 @@ BR_EXPORT bool br_plot_landmarking(int num_files, const char *files[], const cha |
| 91 | 93 | |
| 92 | 94 | BR_EXPORT bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show = false); |
| 93 | 95 | |
| 96 | +BR_EXPORT bool br_plot_knn(int num_files, const char *files[], const char *destination, bool show = false); | |
| 97 | + | |
| 94 | 98 | BR_EXPORT float br_progress(); |
| 95 | 99 | |
| 96 | 100 | BR_EXPORT void br_read_pipe(const char *pipe, int *argc, char ***argv); | ... | ... |
openbr/plugins/classification/dlib.cpp
| 1 | +#include <opencv2/imgproc/imgproc.hpp> | |
| 1 | 2 | #include <dlib/image_processing/frontal_face_detector.h> |
| 2 | 3 | #include <dlib/opencv.h> |
| 3 | 4 | |
| ... | ... | @@ -45,14 +46,18 @@ private: |
| 45 | 46 | { |
| 46 | 47 | dst = src; |
| 47 | 48 | |
| 48 | - shape_predictor *sp = shapeResource.acquire(); | |
| 49 | + shape_predictor *const sp = shapeResource.acquire(); | |
| 49 | 50 | |
| 50 | - cv_image<unsigned char> cimg(src.m()); | |
| 51 | + cv::Mat cvImage = src.m(); | |
| 52 | + if (cvImage.channels() == 3) | |
| 53 | + cv::cvtColor(cvImage, cvImage, CV_BGR2GRAY); | |
| 54 | + | |
| 55 | + cv_image<unsigned char> cimg(cvImage); | |
| 51 | 56 | array2d<unsigned char> image; |
| 52 | 57 | assign_image(image,cimg); |
| 53 | 58 | |
| 54 | 59 | if (src.file.rects().isEmpty()) { //If the image has no rects assume the whole image is a face |
| 55 | - rectangle r(0, 0, src.m().cols, src.m().rows); | |
| 60 | + rectangle r(0, 0, cvImage.cols, cvImage.rows); | |
| 56 | 61 | full_object_detection shape = (*sp)(image, r); |
| 57 | 62 | for (size_t i = 0; i < shape.num_parts(); i++) |
| 58 | 63 | dst.file.appendPoint(QPointF(shape.part(i)(0),shape.part(i)(1))); | ... | ... |
openbr/plugins/core/algorithms.cpp
| ... | ... | @@ -31,9 +31,9 @@ class AlgorithmsInitializer : public Initializer |
| 31 | 31 | void initialize() const |
| 32 | 32 | { |
| 33 | 33 | // Face |
| 34 | - Globals->abbreviations.insert("FaceRecognition", "FaceRecognition_1_0"); | |
| 34 | + Globals->abbreviations.insert("FaceRecognition", "FaceRecognition_1_1"); | |
| 35 | 35 | |
| 36 | - Globals->abbreviations.insert("FaceRecognition_1_0", "FR_Detect+(FR_Eyes+FR_Represent)/(FR_Eyebrows+FR_Represent)/(FR_Mouth+FR_Represent)/(FR_Nose+FR_Represent)/(FR_Face+FR_Represent+ScaleMat(2.0))+Cat+LDA(768)+Normalize(L2):Dist(L2)"); | |
| 36 | + Globals->abbreviations.insert("FaceRecognition_1_1", "FR_Detect+(FR_Eyes+FR_Represent)/(FR_Eyebrows+FR_Represent)/(FR_Mouth+FR_Represent)/(FR_Nose+FR_Represent)/(FR_Face+FR_Represent+ScaleMat(2.0))+Cat+LDA(768)+Normalize(L2):Unit(Dist(L2))"); | |
| 37 | 37 | Globals->abbreviations.insert("FR_Eyes", "(CropFromLandmarks([30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],paddingVertical=.8,paddingHorizontal=.2)+Resize(24,48))"); |
| 38 | 38 | Globals->abbreviations.insert("FR_Eyebrows", "(CropFromLandmarks([16,17,18,19,20,21,22,23,24,25,26,27],paddingVertical=.8,paddingHorizontal=.2)+Resize(24,48))"); |
| 39 | 39 | Globals->abbreviations.insert("FR_Mouth", "(CropFromLandmarks([59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76])+Resize(24,48))"); | ... | ... |
openbr/plugins/gallery/gt.cpp
0 → 100644
| 1 | +#include "openbr/plugins/openbr_internal.h" | |
| 2 | +#include "openbr/core/qtutils.h" | |
| 3 | + | |
| 4 | +using namespace br; | |
| 5 | + | |
| 6 | +/*! | |
| 7 | + * \ingroup galleries | |
| 8 | + * \brief Ground truth format for evaluating kNN | |
| 9 | + * \author Josh Klontz \cite jklontz | |
| 10 | + */ | |
| 11 | +class gtGallery : public Gallery | |
| 12 | +{ | |
| 13 | + Q_OBJECT | |
| 14 | + | |
| 15 | + TemplateList templates; | |
| 16 | + | |
| 17 | + ~gtGallery() | |
| 18 | + { | |
| 19 | + const QList<int> labels = File::get<int>(TemplateList::relabel(templates, "Label", false), "Label"); | |
| 20 | + | |
| 21 | + QStringList lines; | |
| 22 | + for (int i=0; i<labels.size(); i++) { | |
| 23 | + QStringList words; | |
| 24 | + for (int j=0; j<labels.size(); j++) { | |
| 25 | + if ((labels[i] == labels[j]) && (i != j)) | |
| 26 | + words.append(QString::number(j)); | |
| 27 | + } | |
| 28 | + lines.append(words.join("\t")); | |
| 29 | + } | |
| 30 | + | |
| 31 | + QtUtils::writeFile(file.name, lines); | |
| 32 | + } | |
| 33 | + | |
| 34 | + TemplateList readBlock(bool *done) | |
| 35 | + { | |
| 36 | + *done = true; | |
| 37 | + qFatal("Not supported!"); | |
| 38 | + return TemplateList(); | |
| 39 | + } | |
| 40 | + | |
| 41 | + void write(const Template &t) | |
| 42 | + { | |
| 43 | + templates.append(t); | |
| 44 | + } | |
| 45 | +}; | |
| 46 | + | |
| 47 | +BR_REGISTER(Gallery, gtGallery) | |
| 48 | + | |
| 49 | +#include "gt.moc" | ... | ... |
openbr/plugins/gallery/keyframes.cpp
| ... | ... | @@ -59,6 +59,7 @@ public: |
| 59 | 59 | fps = 0.f; |
| 60 | 60 | time_base = 0.f; |
| 61 | 61 | idx = 0; |
| 62 | + idxOffset = -1; | |
| 62 | 63 | } |
| 63 | 64 | |
| 64 | 65 | ~keyframesGallery() |
| ... | ... | @@ -95,6 +96,9 @@ public: |
| 95 | 96 | frame = av_frame_alloc(); |
| 96 | 97 | cvt_frame = av_frame_alloc(); |
| 97 | 98 | |
| 99 | + av_init_packet(&packet); | |
| 100 | + packet.data = NULL; | |
| 101 | + packet.size = 0; | |
| 98 | 102 | // Get fps, stream time_base and allocate space for frame buffer with av_malloc. |
| 99 | 103 | fps = (float)avFormatCtx->streams[streamID]->avg_frame_rate.num / |
| 100 | 104 | (float)avFormatCtx->streams[streamID]->avg_frame_rate.den; |
| ... | ... | @@ -128,26 +132,50 @@ public: |
| 128 | 132 | Template output; |
| 129 | 133 | output.file = file; |
| 130 | 134 | |
| 131 | - AVPacket packet; | |
| 132 | - av_init_packet(&packet); | |
| 135 | + | |
| 133 | 136 | int ret = 0; |
| 134 | 137 | while (!ret) { |
| 135 | 138 | if (av_read_frame(avFormatCtx, &packet) >= 0) { |
| 136 | 139 | if (packet.stream_index == streamID) { |
| 137 | 140 | avcodec_decode_video2(avCodecCtx, frame, &ret, &packet); |
| 138 | - idx = packet.dts; // decompression timestamp | |
| 141 | + // Use presentation timestamp if available | |
| 142 | + // Otherwise decode timestamp | |
| 143 | + if (frame->pkt_pts != AV_NOPTS_VALUE) | |
| 144 | + idx = frame->pkt_pts; | |
| 145 | + else | |
| 146 | + idx = frame->pkt_dts; | |
| 147 | + | |
| 139 | 148 | av_free_packet(&packet); |
| 140 | 149 | } else { |
| 141 | 150 | av_free_packet(&packet); |
| 142 | 151 | } |
| 143 | 152 | } else { |
| 144 | - av_free_packet(&packet); | |
| 145 | - release(); | |
| 146 | - *done = true; | |
| 147 | - return TemplateList(); | |
| 153 | + AVPacket empty_packet; | |
| 154 | + av_init_packet(&empty_packet); | |
| 155 | + empty_packet.data = NULL; | |
| 156 | + empty_packet.size = 0; | |
| 157 | + | |
| 158 | + avcodec_decode_video2(avCodecCtx, frame, &ret, &empty_packet); | |
| 159 | + if (frame->pkt_pts != AV_NOPTS_VALUE) | |
| 160 | + idx = frame->pkt_pts; | |
| 161 | + else if (frame->pkt_dts != AV_NOPTS_VALUE) | |
| 162 | + idx = frame->pkt_dts; | |
| 163 | + else // invalid frame | |
| 164 | + ret = 0; | |
| 165 | + | |
| 166 | + if (!ret) { | |
| 167 | + av_free_packet(&packet); | |
| 168 | + av_free_packet(&empty_packet); | |
| 169 | + release(); | |
| 170 | + *done = true; | |
| 171 | + return TemplateList(); | |
| 172 | + } | |
| 148 | 173 | } |
| 149 | 174 | } |
| 150 | 175 | |
| 176 | + if (idxOffset < 0) { | |
| 177 | + idxOffset = idx; | |
| 178 | + } | |
| 151 | 179 | // Convert from native format |
| 152 | 180 | sws_scale(avSwsCtx, |
| 153 | 181 | frame->data, |
| ... | ... | @@ -164,9 +192,9 @@ public: |
| 164 | 192 | avcodec_flush_buffers(avCodecCtx); |
| 165 | 193 | |
| 166 | 194 | QString URL = file.get<QString>("URL", file.name); |
| 167 | - output.file.set("URL", URL + "#t=" + QString::number((int)(idx * time_base)) + "s"); | |
| 168 | - output.file.set("timestamp", QString::number((int)(idx * time_base * 1000))); | |
| 169 | - output.file.set("frame", QString::number(idx * time_base * fps)); | |
| 195 | + output.file.set("URL", URL + "#t=" + QString::number((int)((idx-idxOffset) * time_base)) + "s"); | |
| 196 | + output.file.set("timestamp", QString::number((int)((idx-idxOffset) * time_base * 1000))); | |
| 197 | + output.file.set("frame", QString::number((idx-idxOffset) * time_base * fps)); | |
| 170 | 198 | TemplateList dst; |
| 171 | 199 | dst.append(output); |
| 172 | 200 | return dst; |
| ... | ... | @@ -204,7 +232,10 @@ protected: |
| 204 | 232 | AVCodec *avCodec; |
| 205 | 233 | AVFrame *frame; |
| 206 | 234 | AVFrame *cvt_frame; |
| 235 | + AVPacket packet; | |
| 207 | 236 | uint8_t *buffer; |
| 237 | + | |
| 238 | + int64_t idxOffset; | |
| 208 | 239 | bool opened; |
| 209 | 240 | int streamID; |
| 210 | 241 | float fps; | ... | ... |
openbr/plugins/output/knn.cpp
0 → 100644
| 1 | +#include <openbr/plugins/openbr_internal.h> | |
| 2 | +#include <openbr/core/common.h> | |
| 3 | +#include <openbr/core/opencvutils.h> | |
| 4 | +#include <openbr/core/eval.h> | |
| 5 | + | |
| 6 | +namespace br | |
| 7 | +{ | |
| 8 | + | |
| 9 | +/*! | |
| 10 | + * \ingroup outputs | |
| 11 | + * \brief Outputs the k-Nearest Neighbors from the gallery for each probe. | |
| 12 | + * \author Ben Klein \cite bhklein | |
| 13 | + */ | |
| 14 | +class knnOutput : public MatrixOutput | |
| 15 | +{ | |
| 16 | + Q_OBJECT | |
| 17 | + | |
| 18 | + ~knnOutput() | |
| 19 | + { | |
| 20 | + size_t num_probes = (size_t)queryFiles.size(); | |
| 21 | + if (targetFiles.isEmpty() || queryFiles.isEmpty()) return; | |
| 22 | + size_t k = file.get<size_t>("k", 20); | |
| 23 | + | |
| 24 | + if ((size_t)targetFiles.size() < k) | |
| 25 | + qFatal("Gallery size %s is smaller than k = %s.", qPrintable(QString::number(targetFiles.size())), qPrintable(QString::number(k))); | |
| 26 | + | |
| 27 | + QFile f(file); | |
| 28 | + if (!f.open(QFile::WriteOnly)) | |
| 29 | + qFatal("Unable to open %s for writing.", qPrintable(file)); | |
| 30 | + f.write((const char*) &num_probes, sizeof(size_t)); | |
| 31 | + f.write((const char*) &k, sizeof(size_t)); | |
| 32 | + | |
| 33 | + QVector<Candidate> neighbors; neighbors.reserve(num_probes*k); | |
| 34 | + | |
| 35 | + for (size_t i=0; i<num_probes; i++) { | |
| 36 | + typedef QPair<float,int> Pair; | |
| 37 | + size_t rank = 0; | |
| 38 | + foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { | |
| 39 | + if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) { | |
| 40 | + Candidate candidate((size_t)pair.second, pair.first); | |
| 41 | + neighbors.push_back(candidate); | |
| 42 | + if (++rank >= k) break; | |
| 43 | + } | |
| 44 | + } | |
| 45 | + } | |
| 46 | + f.write((const char*) neighbors.data(), num_probes * k * sizeof(Candidate)); | |
| 47 | + f.close(); | |
| 48 | + } | |
| 49 | +}; | |
| 50 | + | |
| 51 | +BR_REGISTER(Output, knnOutput) | |
| 52 | + | |
| 53 | +} // namespace br | |
| 54 | + | |
| 55 | +#include "output/knn.moc" | ... | ... |
scripts/face_cluster_viz.py renamed to scripts/brpy/face_cluster_viz.py
| 1 | 1 | #!/usr/bin/env python |
| 2 | 2 | |
| 3 | -from PIL import Image | |
| 4 | 3 | import csv, sys, json, argparse |
| 4 | +from brpy.html_viz import crop_to_bb | |
| 5 | 5 | |
| 6 | 6 | parser = argparse.ArgumentParser(description='Visualize face cluster results in an HTML page.') |
| 7 | 7 | parser.add_argument('input_file', type=str, help='Results from clustering (in csv format)') |
| ... | ... | @@ -16,24 +16,15 @@ clustmap = dict() |
| 16 | 16 | with open(args.input_file) as f: |
| 17 | 17 | for line in csv.DictReader(f): |
| 18 | 18 | c = int(line[args.cluster_key]) |
| 19 | + if c not in clustmap: | |
| 20 | + clustmap[c] = [] | |
| 19 | 21 | x,y,width,height = [ float(line[k]) for k in ('Face_X','Face_Y','Face_Width','Face_Height') ] |
| 20 | 22 | imname = '%s/%s' % (args.img_loc, line['File']) |
| 21 | 23 | try: |
| 22 | - img = Image.open(imname) | |
| 23 | - imwidth, imheight = img.size | |
| 24 | + html = crop_to_bb(x,y,width,height,imname,maxheight=400) | |
| 24 | 25 | except IOError: |
| 25 | 26 | print('problem with %s' % imname) |
| 26 | 27 | continue |
| 27 | - ratio = maxheight / height | |
| 28 | - # note for future me: | |
| 29 | - # image is cropped with div width/height + overflow:hidden, | |
| 30 | - # resized with img height, | |
| 31 | - # and positioned with img margin | |
| 32 | - html = '<div style="overflow:hidden;display:inline-block;width:%ipx;height:%ipx;">' % (width*ratio, maxheight) | |
| 33 | - html += '<img src="%s" style="height:%ipx;margin:-%ipx 0 0 -%ipx;"/>' % (imname, imheight*ratio, y*ratio, x*ratio) | |
| 34 | - html += '</div>' | |
| 35 | - if c not in clustmap: | |
| 36 | - clustmap[c] = [] | |
| 37 | 28 | clustmap[c].append(html) |
| 38 | 29 | |
| 39 | 30 | # browsers crash for a DOM with this many img tags, | ... | ... |
scripts/brpy/html_viz.py
0 → 100644
| 1 | +''' | |
| 2 | +Some funcs to generate HTML visualizations. | |
| 3 | +Run from the folder you intend to save the HTML page so | |
| 4 | +the relative paths in the HTML file are correct and PIL can find the images on disk. | |
| 5 | +Requires local images, but should be pretty easy to set up an apache server (or whatev) | |
| 6 | +and host them as long as the relative paths remain the same on ya serva. | |
| 7 | +''' | |
| 8 | + | |
| 9 | +from PIL import Image | |
| 10 | + | |
| 11 | +def crop_to_bb(x, y, width, height, imname, maxheight=None): | |
| 12 | + ''' | |
| 13 | + Generates an HTML string that crops to a given bounding box and resizes to maxheight pixels. | |
| 14 | + A maxheight of None will keep the original size (default). | |
| 15 | + When two crops are put next to each other, they will be inline. To make each crop its own line, wrap it in a div. | |
| 16 | + ''' | |
| 17 | + img = Image.open(imname) | |
| 18 | + imwidth, imheight = img.size | |
| 19 | + if not maxheight: | |
| 20 | + maxheight = height | |
| 21 | + ratio = maxheight / height | |
| 22 | + # note for future me: | |
| 23 | + # image is cropped with div width/height + overflow:hidden, | |
| 24 | + # resized with img height, | |
| 25 | + # and positioned with img margin | |
| 26 | + html = '<div style="overflow:hidden; display:inline-block; width:%ipx; height:%ipx;">' % (width*ratio, maxheight) | |
| 27 | + html += '<img src="%s" style="height:%ipx; margin:-%ipx 0 0 -%ipx;"/>' % (imname, imheight*ratio, y*ratio, x*ratio) | |
| 28 | + html += '</div>' | |
| 29 | + return html | |
| 30 | + | |
| 31 | +def bbs_for_image(imname, bbs, maxheight=None, colors=None): | |
| 32 | + ''' | |
| 33 | + Generates an HTML string for an image with bounding boxes. | |
| 34 | + bbs: iterable of (x,y,width,height) bounding box tuples | |
| 35 | + ''' | |
| 36 | + img = Image.open(imname) | |
| 37 | + imwidth, imheight = img.size | |
| 38 | + if not maxheight: | |
| 39 | + maxheight = imheight | |
| 40 | + ratio = maxheight/imheight | |
| 41 | + html = [ | |
| 42 | + '<div style="position:relative">', | |
| 43 | + '<img src="%s" style="height:%ipx" />' % (imname, maxheight) | |
| 44 | + ] | |
| 45 | + if not colors: | |
| 46 | + colors = ['green']*len(bbs) | |
| 47 | + html.extend([ bb(*box, ratio=ratio, color=color) for color,box in zip(colors,bbs) ]) | |
| 48 | + html.append('</div>') | |
| 49 | + return '\n'.join(html) | |
| 50 | + | |
| 51 | + | |
| 52 | +def bb(x, y, width, height, ratio=1.0, color='green'): | |
| 53 | + ''' | |
| 54 | + Generates an HTML string bounding box. | |
| 55 | + ''' | |
| 56 | + html = '<div style="position:absolute; border:2px solid %s; color:%s; left:%ipx; top:%ipx; width:%ipx; height:%ipx;"></div>' | |
| 57 | + return html % (color, color, x*ratio, y*ratio, width*ratio, height*ratio) | ... | ... |
share/openbr/cmake/InstallDependencies.cmake
| ... | ... | @@ -83,6 +83,19 @@ function(install_qt_imageformats) |
| 83 | 83 | endif() |
| 84 | 84 | endfunction() |
| 85 | 85 | |
| 86 | +function(install_qt_platforms) | |
| 87 | + if(${BR_INSTALL_DEPENDENCIES}) | |
| 88 | + if(CMAKE_HOST_WIN32) | |
| 89 | + #TODO | |
| 90 | + elseif(CMAKE_HOST_APPLE) | |
| 91 | + install(FILES ${_qt5Core_install_prefix}/plugins/platforms/libqcocoa.dylib | |
| 92 | + DESTINATION bin/platforms) | |
| 93 | + else() | |
| 94 | + #TODO | |
| 95 | + endif() | |
| 96 | + endif() | |
| 97 | +endfunction() | |
| 98 | + | |
| 86 | 99 | # Qt Other |
| 87 | 100 | function(install_qt_misc) |
| 88 | 101 | if(MSVC) | ... | ... |
share/openbr/plotting/plot_utils.R
| ... | ... | @@ -217,6 +217,12 @@ formatData <- function(type="eval") { |
| 217 | 217 | NormLength <<- data[grep("NormLength",data$Plot),-c(1)] |
| 218 | 218 | sample <<- readImageData(Sample) |
| 219 | 219 | rows <<- sample[[1]]$value |
| 220 | + } else if (type == "knn") { | |
| 221 | + # Split data into individual plots | |
| 222 | + IET <<- data[grep("IET",data$Plot),-c(1)] | |
| 223 | + IET$Y <<- as.numeric(as.character(IET$Y)) | |
| 224 | + CMC <<- data[grep("CMC",data$Plot),-c(1)] | |
| 225 | + CMC$Y <<- as.numeric(as.character(CMC$Y)) | |
| 220 | 226 | } |
| 221 | 227 | } |
| 222 | 228 | ... | ... |