diff --git a/openbr/core/eval.cpp b/openbr/core/eval.cpp index 646177d..ee87e72 100644 --- a/openbr/core/eval.cpp +++ b/openbr/core/eval.cpp @@ -175,9 +175,7 @@ float Evaluate(const Mat &simmat, const Mat &mask, const QString &csv) if ((falsePositives > previousFalsePositives) && (truePositives > previousTruePositives)) { - // Restrict the extreme ends of the curve - if ((truePositives >= 10) && (falsePositives < impostorCount/2)) - operatingPoints.append(OperatingPoint(thresh, float(falsePositives)/impostorCount, float(truePositives)/genuineCount)); + operatingPoints.append(OperatingPoint(thresh, float(falsePositives)/impostorCount, float(truePositives)/genuineCount)); previousFalsePositives = falsePositives; previousTruePositives = truePositives; } diff --git a/openbr/core/plot.cpp b/openbr/core/plot.cpp index dff8782..fde8469 100644 --- a/openbr/core/plot.cpp +++ b/openbr/core/plot.cpp @@ -228,7 +228,7 @@ bool Plot(const QStringList &files, const File &destination, bool show) QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + - QString(" + scale_x_log10(labels=percent, limits=c(min(DET$X),1)) + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\")\n\n"))); + QString(" + scale_x_log10(labels=trans_format(\"log10\", math_format())) + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\")\n\n"))); 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\"") + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) + @@ -236,7 +236,7 @@ bool Plot(const QStringList &files, const File &destination, bool show) QString(", xlab=\"False Accept Rate\", ylab=\"False Reject Rate\") + geom_abline(alpha=0.5, colour=\"grey\", linetype=\"dashed\") + theme_minimal()") + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + - QString(" + scale_x_log10(labels=percent, limits=c(min(DET$X),1)) + scale_y_log10(labels=percent) + annotation_logticks()\n\n"))); + QString(" + scale_x_log10(labels=trans_format(\"log10\", math_format())) + scale_y_log10(labels=trans_format(\"log10\", math_format())) + annotation_logticks()\n\n"))); p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") + QString(", xlab=\"Score%1\"").arg((p.flip ? p.major.size : p.minor.size) > 1 ? " / " + (p.flip ? p.major.header : p.minor.header) : QString()) + diff --git a/openbr/core/qtutils.h b/openbr/core/qtutils.h index 320e2e8..9eb2c6c 100644 --- a/openbr/core/qtutils.h +++ b/openbr/core/qtutils.h @@ -74,6 +74,16 @@ namespace QtUtils /**** Variant Utilities ****/ QString toString(const QVariant &variant); + template + QVariantList toVariantList(const QList &list) + { + QVariantList variantList; + foreach (const T &item, list) + variantList << item; + + return variantList; + } + /**** Point Utilities ****/ float euclideanLength(const QPointF &point); } diff --git a/openbr/plugins/landmarks.cpp b/openbr/plugins/landmarks.cpp index a87a161..a19439a 100644 --- a/openbr/plugins/landmarks.cpp +++ b/openbr/plugins/landmarks.cpp @@ -109,20 +109,18 @@ class ProcrustesTransform : public Transform dst = src; + // Store procrustes stats in the order: + // R(0,0), R(1,0), R(1,1), R(0,1), mean_x, mean_y, norm + QList procrustesStats; + procrustesStats << R(0,0) << R(1,0) << R(1,1) << R(0,1) << mean[0] << mean[1] << norm; + dst.file.set("ProcrustesStats",QtUtils::toVariantList(procrustesStats)); + if (warp) { Eigen::MatrixXf dstMat = srcMat*R; for (int i = 0; i < dstMat.rows(); i++) { dst.file.appendPoint(QPointF(dstMat(i,0),dstMat(i,1))); } } - - dst.file.set("Procrustes_0_0", R(0,0)); - dst.file.set("Procrustes_1_0", R(1,0)); - dst.file.set("Procrustes_1_1", R(1,1)); - dst.file.set("Procrustes_0_1", R(0,1)); - dst.file.set("Procrustes_mean_0", mean[0]); - dst.file.set("Procrustes_mean_1", mean[1]); - dst.file.set("Procrustes_norm", norm); } void store(QDataStream &stream) const @@ -150,33 +148,34 @@ class DelaunayTransform : public UntrainableTransform Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) Q_PROPERTY(bool warp READ get_warp WRITE set_warp RESET reset_warp STORED false) - Q_PROPERTY(bool draw READ get_draw WRITE set_draw RESET reset_draw STORED false) BR_PROPERTY(float, scaleFactor, 1) BR_PROPERTY(bool, warp, true) - BR_PROPERTY(bool, draw, false) void project(const Template &src, Template &dst) const { - Subdiv2D subdiv(Rect(0,0,src.m().cols,src.m().rows)); - QList points = src.file.points(); QList rects = src.file.rects(); if (points.empty() || rects.empty()) { dst = src; - dst.file.clearRects(); qWarning("Delauney triangulation failed because points or rects are empty."); return; } + int cols = src.m().cols; + int rows = src.m().rows; + // Assume rect appended last was bounding box points.append(rects.last().topLeft()); points.append(rects.last().topRight()); points.append(rects.last().bottomLeft()); points.append(rects.last().bottomRight()); + Subdiv2D subdiv(Rect(0,0,cols,rows)); + // Make sure points are valid for Subdiv2D + // TODO: Modify points to make them valid for (int i = 0; i < points.size(); i++) { - if (points[i].x() < 0 || points[i].y() < 0 || points[i].y() >= src.m().rows || points[i].x() >= src.m().cols) { + if (points[i].x() < 0 || points[i].y() < 0 || points[i].y() >= rows || points[i].x() >= cols) { dst = src; qWarning("Delauney triangulation failed because points lie on boundary."); return; @@ -187,78 +186,67 @@ class DelaunayTransform : public UntrainableTransform vector triangleList; subdiv.getTriangleList(triangleList); - QList< QList > validTriangles; + QList validTriangles; - for (size_t i = 0; i < triangleList.size(); ++i) { + for (size_t i = 0; i < triangleList.size(); i++) { + // Check the triangle to make sure it's falls within the matrix + bool valid = true; - vector pt(3); - pt[0] = Point(cvRound(triangleList[i][0]), cvRound(triangleList[i][1])); - pt[1] = Point(cvRound(triangleList[i][2]), cvRound(triangleList[i][3])); - pt[2] = Point(cvRound(triangleList[i][4]), cvRound(triangleList[i][5])); + QList vertices; + vertices.append(QPointF(triangleList[i][0],triangleList[i][1])); + vertices.append(QPointF(triangleList[i][2],triangleList[i][3])); + vertices.append(QPointF(triangleList[i][4],triangleList[i][5])); + for (int j = 0; j < 3; j++) if (vertices[j].x() > cols || vertices[j].y() > rows || vertices[j].x() < 0 || vertices[j].y() < 0) valid = false; - bool inside = true; - for (int j = 0; j < 3; j++) { - if (pt[j].x > src.m().cols || pt[j].y > src.m().rows || pt[j].x < 0 || pt[j].y < 0) inside = false; - } - - if (inside) validTriangles.append(QList()<< pt[0] << pt[1] << pt[2]); + if (valid) validTriangles.append(vertices); } - dst.m() = src.m().clone(); + if (warp) { + dst.m() = Mat::zeros(rows,cols,src.m().type()); - if (draw) { - foreach(const QList& triangle, validTriangles) { - line(dst.m(), triangle[0], triangle[1], Scalar(0,0,0), 1); - line(dst.m(), triangle[1], triangle[2], Scalar(0,0,0), 1); - line(dst.m(), triangle[2], triangle[0], Scalar(0,0,0), 1); - } - } + QList procrustesStats = src.file.getList("ProcrustesStats"); - if (warp) { Eigen::MatrixXf R(2,2); - R(0,0) = src.file.get("Procrustes_0_0"); - R(1,0) = src.file.get("Procrustes_1_0"); - R(1,1) = src.file.get("Procrustes_1_1"); - R(0,1) = src.file.get("Procrustes_0_1"); + R(0,0) = procrustesStats.at(0); + R(1,0) = procrustesStats.at(1); + R(1,1) = procrustesStats.at(2); + R(0,1) = procrustesStats.at(3); cv::Scalar mean(2); - mean[0] = src.file.get("Procrustes_mean_0"); - mean[1] = src.file.get("Procrustes_mean_1"); + mean[0] = procrustesStats.at(4); + mean[1] = procrustesStats.at(5); - float norm = src.file.get("Procrustes_norm"); - - dst.m() = Mat::zeros(src.m().rows,src.m().cols,src.m().type()); + float norm = procrustesStats.at(6); QList mappedPoints; - dst.file = src.file; - - for (int i = 0; i < validTriangles.size(); i++) { - Eigen::MatrixXf srcMat(validTriangles[i].size(), 2); + for (int i = 0; i < validTriangles.size(); i+=3) { + // Matrix to store original (pre-transformed) triangle vertices + Eigen::MatrixXf srcMat(3, 2); for (int j = 0; j < 3; j++) { - srcMat(j,0) = (validTriangles[i][j].x-mean[0])/norm; - srcMat(j,1) = (validTriangles[i][j].y-mean[1])/norm; + srcMat(j,0) = (validTriangles[i+j].x()-mean[0])/norm; + srcMat(j,1) = (validTriangles[i+j].y()-mean[1])/norm; } Eigen::MatrixXf dstMat = srcMat*R; Point2f srcPoints[3]; - for (int j = 0; j < 3; j++) srcPoints[j] = validTriangles[i][j]; + for (int j = 0; j < 3; j++) srcPoints[j] = OpenCVUtils::toPoint(validTriangles[i+j]); Point2f dstPoints[3]; for (int j = 0; j < 3; j++) { // Scale and shift destination points - Point2f warpedPoint = Point2f(dstMat(j,0)*scaleFactor+src.m().cols/2,dstMat(j,1)*scaleFactor+src.m().rows/2); + Point2f warpedPoint = Point2f(dstMat(j,0)*scaleFactor+cols/2,dstMat(j,1)*scaleFactor+rows/2); dstPoints[j] = warpedPoint; mappedPoints.append(warpedPoint); } - Mat buffer(src.m().rows,src.m().cols,src.m().type()); + Mat buffer(rows,cols,src.m().type()); - warpAffine(src.m(), buffer, getAffineTransform(srcPoints, dstPoints), Size(src.m().cols,src.m().rows)); + warpAffine(src.m(), buffer, getAffineTransform(srcPoints, dstPoints), Size(cols,rows)); - Mat mask = Mat::zeros(src.m().rows, src.m().cols, CV_8UC1); + Mat mask = Mat::zeros(rows, cols, CV_8UC1); Point maskPoints[1][3]; maskPoints[0][0] = dstPoints[0]; maskPoints[0][1] = dstPoints[1]; @@ -267,7 +255,7 @@ class DelaunayTransform : public UntrainableTransform fillConvexPoly(mask, ppt, 3, Scalar(255,255,255), 8); - Mat output(src.m().rows,src.m().cols,src.m().type()); + Mat output(rows,cols,src.m().type()); if (i > 0) { Mat overlap; @@ -283,12 +271,40 @@ class DelaunayTransform : public UntrainableTransform // Overwrite any rects Rect boundingBox = boundingRect(mappedPoints.toVector().toStdVector()); dst.file.setRects(QList() << OpenCVUtils::fromRect(boundingBox)); - } + } else dst = src; + + dst.file.set("DelaunayTriangles", QtUtils::toVariantList(validTriangles)); } }; BR_REGISTER(Transform, DelaunayTransform) +/*! + * \ingroup transforms + * \brief Creates a Delaunay triangulation based on a set of points + * \author Scott Klum \cite sklum + */ +class DrawDelaunayTransform : public UntrainableTransform +{ + Q_OBJECT + + void project(const Template &src, Template &dst) const + { + QList validTriangles = OpenCVUtils::toPoints(src.file.getList("DelaunayTriangles")); + + // Clone the matrix do draw on it + dst.m() = src.m().clone(); + + for (int i = 0; i < validTriangles.size(); i+=3) { + line(dst.m(), validTriangles[i], validTriangles[i+1], Scalar(0,0,0), 1); + line(dst.m(), validTriangles[i+1], validTriangles[i+2], Scalar(0,0,0), 1); + line(dst.m(), validTriangles[i+2], validTriangles[i], Scalar(0,0,0), 1); + } + } +}; + +BR_REGISTER(Transform, DrawDelaunayTransform) + } // namespace br #include "landmarks.moc" diff --git a/share/openbr/cmake/InstallDependencies.cmake b/share/openbr/cmake/InstallDependencies.cmake index 74e05e5..599a434 100644 --- a/share/openbr/cmake/InstallDependencies.cmake +++ b/share/openbr/cmake/InstallDependencies.cmake @@ -55,12 +55,15 @@ endfunction() # Qt Plugins function(install_qt_imageformats) if(${BR_INSTALL_DEPENDENCIES}) - install(FILES ${QT_QGIF_PLUGIN_RELEASE} - ${QT_QICO_PLUGIN_RELEASE} - ${QT_QJPEG_PLUGIN_RELEASE} - ${QT_QMNG_PLUGIN_RELEASE} - ${QT_QSVG_PLUGIN_RELEASE} - ${QT_QTIFF_PLUGIN_RELEASE} + set(IMAGE_FORMATS_DIR "${_qt5Core_install_prefix}/plugins/imageformats/") + install(FILES ${IMAGE_FORMATS_DIR}/qgif.dll + ${IMAGE_FORMATS_DIR}/qico.dll + ${IMAGE_FORMATS_DIR}/qjpeg.dll + ${IMAGE_FORMATS_DIR}/qmng.dll + ${IMAGE_FORMATS_DIR}/qsvg.dll + ${IMAGE_FORMATS_DIR}/qtga.dll + ${IMAGE_FORMATS_DIR}/qtiff.dll + ${IMAGE_FORMATS_DIR}/qwbmp.dll DESTINATION bin/imageformats) endif() endfunction()