diff --git a/3rdparty/stasm4.0.0/CMakeLists.txt b/3rdparty/stasm4.0.0/CMakeLists.txt index b769d47..1675828 100644 --- a/3rdparty/stasm4.0.0/CMakeLists.txt +++ b/3rdparty/stasm4.0.0/CMakeLists.txt @@ -32,8 +32,12 @@ if(MSVC) endif() add_subdirectory(${PROJECT_SOURCE_DIR}/stasm) +if(MSVC) + add_library(stasm STATIC ${SOURCE}) +else() + add_library(stasm SHARED ${SOURCE}) +endif() -add_library(stasm SHARED ${SOURCE}) qt5_use_modules(stasm ${QT_DEPENDENCIES}) set(SOURCE ${SOURCE} PARENT_SCOPE) target_link_libraries(stasm ${OpenCV_LIBS} ${Qt5Core_QTMAIN_LIBRARIES}) diff --git a/3rdparty/stasm4.0.0/stasm/stasm_lib.cpp b/3rdparty/stasm4.0.0/stasm/stasm_lib.cpp index 9c9a7c1..879f157 100755 --- a/3rdparty/stasm4.0.0/stasm/stasm_lib.cpp +++ b/3rdparty/stasm4.0.0/stasm/stasm_lib.cpp @@ -157,7 +157,6 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto { int returnval = 1; // assume success *foundface = 0; // but assume no face found - CatchOpenCvErrs(); try { CheckStasmInit(); @@ -204,7 +203,6 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto { returnval = 0; // a call was made to Err or a CV_Assert failed } - UncatchOpenCvErrs(); return returnval; } diff --git a/CMakeLists.txt b/CMakeLists.txt index ec046b8..907e780 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,10 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CMAKE_MODULE_PATH "${BR_SHARE_DIR}/cmake" ${CMAKE_MODULE_PATH}) set(PACKAGE_YEAR 2013) -if("${CMAKE_VERSION}" VERSION_GREATER 2.8.10) +if(${CMAKE_VERSION} VERSION_EQUAL 2.8.11) + cmake_policy(SET CMP0020 OLD) +endif() +if(${CMAKE_VERSION} VERSION_GREATER 2.8.11) cmake_policy(SET CMP0020 OLD) endif() diff --git a/openbr/core/bee.cpp b/openbr/core/bee.cpp index 3ec2cef..e1f42b2 100644 --- a/openbr/core/bee.cpp +++ b/openbr/core/bee.cpp @@ -43,9 +43,15 @@ FileList BEE::readSigset(const File &sigset, bool ignoreMetadata) QFile file(sigset.resolved()); bool success; success = file.open(QIODevice::ReadOnly); if (!success) qFatal("Unable to open %s for reading.", qPrintable(sigset)); - success = doc.setContent(&file); if (!success) qFatal("Unable to parse %s.", qPrintable(sigset)); + success = doc.setContent(&file); + file.close(); + if (!success) { + qWarning("Unable to parse %s.", qPrintable(sigset)); + return fileList; + } + QDomElement docElem = doc.documentElement(); if (docElem.nodeName() != "biometric-signature-set") return fileList; diff --git a/openbr/core/core.cpp b/openbr/core/core.cpp index 383ce07..5f97d70 100644 --- a/openbr/core/core.cpp +++ b/openbr/core/core.cpp @@ -167,7 +167,6 @@ struct AlgorithmCore } const float speed = 1000 * Globals->totalSteps / Globals->startTime.elapsed() / std::max(1, abs(Globals->parallelism)); - qDebug() << Globals->startTime.elapsed(); if (!Globals->quiet && (Globals->totalSteps > 1)) fprintf(stderr, "\rSPEED=%.1e SIZE=%.4g FAILURES=%d/%d \n", speed, totalBytes/totalCount, failureCount, totalCount); diff --git a/openbr/core/plot.cpp b/openbr/core/plot.cpp index aaf7a15..0ef731c 100644 --- a/openbr/core/plot.cpp +++ b/openbr/core/plot.cpp @@ -247,8 +247,8 @@ float Evaluate(const Mat &simmat, const Mat &mask, const QString &csv) } // Write FAR/TAR Bar Chart (BC) - lines.append(qPrintable(QString("BC,0.001,%1").arg(QString::number(result = getTAR(operatingPoints, 0.001), 'f', 3)))); - lines.append(qPrintable(QString("BC,0.01,%1").arg(QString::number(getTAR(operatingPoints, 0.01), 'f', 3)))); + lines.append(qPrintable(QString("BC,0.001,%1").arg(QString::number(getTAR(operatingPoints, 0.001), 'f', 3)))); + lines.append(qPrintable(QString("BC,0.01,%1").arg(QString::number(result = getTAR(operatingPoints, 0.01), 'f', 3)))); // Write SD & KDE points = qMin(qMin(Max_Points, genuines.size()), impostors.size()); @@ -269,7 +269,7 @@ float Evaluate(const Mat &simmat, const Mat &mask, const QString &csv) } // Write Cumulative Match Characteristic (CMC) curve - const int Max_Retrieval = 100; + const int Max_Retrieval = 200; const int Report_Retrieval = 5; float reportRetrievalRate = -1; @@ -287,7 +287,7 @@ float Evaluate(const Mat &simmat, const Mat &mask, const QString &csv) } if (!csv.isEmpty()) QtUtils::writeFile(csv, lines); - qDebug("TAR @ FAR = 0.001: %.3f\nRetrieval Rate @ Rank = %d: %.3f", result, Report_Retrieval, reportRetrievalRate); + qDebug("TAR @ FAR = 0.01: %.3f\nRetrieval Rate @ Rank = %d: %.3f", result, Report_Retrieval, reportRetrievalRate); return result; } diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index 0a67fbd..976f971 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -368,7 +368,7 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) QScopedPointer i(Gallery::make(file)); TemplateList newTemplates = i->read(); - // If file is a Format not a Gallery + // If file is a Format not a Gallery (e.g. XML Format vs. XML Gallery) if (newTemplates.isEmpty()) newTemplates.append(file); @@ -1193,6 +1193,33 @@ void Transform::backProject(const TemplateList &dst, TemplateList &src) const futures.waitForFinished(); } +QList Transform::getChildren() const +{ + QList output; + for (int i=0; i < metaObject()->propertyCount(); i++) { + const char * prop_name = metaObject()->property(i).name(); + const QVariant & variant = this->property(prop_name); + + if (variant.canConvert()) + output.append(variant.value()); + if (variant.canConvert >()) + output.append(variant.value >()); + } + return output; +} + +TemplateEvent * Transform::getEvent(const QString & name) +{ + foreach(Transform * child, getChildren()) + { + TemplateEvent * probe = child->getEvent(name); + if (probe) + return probe; + } + + return NULL; +} + /* Distance - public methods */ Distance *Distance::make(QString str, QObject *parent) { diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 759b360..0ecf867 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -1040,6 +1040,21 @@ private: * @{ */ +class TemplateEvent : public QObject +{ + Q_OBJECT + +public: + void pulseSignal(const Template & output) const + { + emit theSignal(output); + } + +signals: + void theSignal(const Template & output) const; +}; + + /*! * \brief Plugin base class for processing a template. * @@ -1144,6 +1159,17 @@ public: */ virtual Transform * smartCopy() { return this;} + /*! + * \brief Recursively retrieve a named event, returns NULL if an event is not found. + */ + virtual TemplateEvent * getEvent(const QString & name); + + /*! + * \brief Get a list of child transforms of this transform, child transforms are considered to be + * any transforms stored as properties of this transform. + */ + QList getChildren() const; + protected: Transform(bool independent = true, bool trainable = true); /*!< \brief Construct a transform. */ inline Transform *make(const QString &description) { return make(description, this); } /*!< \brief Make a subtransform. */ diff --git a/openbr/plugins/crop.cpp b/openbr/plugins/crop.cpp index 6808031..5c3c548 100644 --- a/openbr/plugins/crop.cpp +++ b/openbr/plugins/crop.cpp @@ -71,18 +71,32 @@ BR_REGISTER(Transform, ROITransform) * \ingroup transforms * \brief Resize the template * \author Josh Klontz \cite jklontz + * \note Method: Area should be used for shrinking an image, Cubic for slow but accurate enlargment, Bilin for fast enlargement. */ class ResizeTransform : public UntrainableTransform { Q_OBJECT + Q_ENUMS(Method) + +public: + /*!< */ + enum Method { Near = INTER_NEAREST, + Area = INTER_AREA, + Bilin = INTER_LINEAR, + Cubic = INTER_CUBIC, + Lanczo = INTER_LANCZOS4}; + +private: Q_PROPERTY(int rows READ get_rows WRITE set_rows RESET reset_rows STORED false) Q_PROPERTY(int columns READ get_columns WRITE set_columns RESET reset_columns STORED false) + Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false) BR_PROPERTY(int, rows, -1) BR_PROPERTY(int, columns, -1) + BR_PROPERTY(Method, method, Bilin) void project(const Template &src, Template &dst) const { - resize(src, dst, Size((columns == -1) ? src.m().cols*rows/src.m().rows : columns, rows)); + resize(src, dst, Size((columns == -1) ? src.m().cols*rows/src.m().rows : columns, rows), 0, 0, method); } }; diff --git a/openbr/plugins/distance.cpp b/openbr/plugins/distance.cpp index 8781197..7f0cf71 100644 --- a/openbr/plugins/distance.cpp +++ b/openbr/plugins/distance.cpp @@ -298,6 +298,33 @@ class IdenticalDistance : public Distance BR_REGISTER(Distance, IdenticalDistance) -} // namespace br +/*! + * \ingroup distances + * \brief Online distance metric to attenuate match scores across multiple frames + * \author Brendan klare \cite bklare + */ +class OnlineDistance : public Distance +{ + Q_OBJECT + Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) + Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false) + BR_PROPERTY(br::Distance*, distance, NULL) + BR_PROPERTY(float, alpha, 0.1f); + + mutable QHash scoreHash; + mutable QMutex mutex; + + float compare(const Template &target, const Template &query) const + { + float currentScore = distance->compare(target, query); + + QMutexLocker mutexLocker(&mutex); + return scoreHash[target.file.name] = (1.0- alpha) * scoreHash[target.file.name] + alpha * currentScore; + } +}; + +BR_REGISTER(Distance, OnlineDistance) + +} // namespace br #include "distance.moc" diff --git a/openbr/plugins/format.cpp b/openbr/plugins/format.cpp index 1dabd73..816b7f2 100644 --- a/openbr/plugins/format.cpp +++ b/openbr/plugins/format.cpp @@ -660,7 +660,7 @@ class xmlFormat : public Format QFile f(fileName); if (!f.open(QIODevice::ReadOnly)) qFatal("Unable to open %s for reading.", qPrintable(file.flat())); - if (!doc.setContent(&f)) qFatal("Unable to parse %s.", qPrintable(file.flat())); + if (!doc.setContent(&f)) qWarning("Unable to parse %s.", qPrintable(file.flat())); f.close(); QDomElement docElem = doc.documentElement(); diff --git a/openbr/plugins/gui.cpp b/openbr/plugins/gui.cpp index b0000b8..ac26d00 100644 --- a/openbr/plugins/gui.cpp +++ b/openbr/plugins/gui.cpp @@ -220,16 +220,16 @@ signals: BR_REGISTER(Transform, ShowTransform) -class FPSSynch : public TimeVaryingTransform +class FPSLimit : public TimeVaryingTransform { Q_OBJECT Q_PROPERTY(int targetFPS READ get_targetFPS WRITE set_targetFPS RESET reset_targetFPS STORED false) BR_PROPERTY(int, targetFPS, 30) public: - FPSSynch() : TimeVaryingTransform(false, false) {} + FPSLimit() : TimeVaryingTransform(false, false) {} - ~FPSSynch() {} + ~FPSLimit() {} void train(const TemplateList &data) { (void) data; } @@ -237,16 +237,18 @@ public: void projectUpdate(const TemplateList &src, TemplateList &dst) { dst = src; - qint64 time_delta = timer.elapsed(); + qint64 current_time = timer.elapsed(); + qint64 target_time = last_time + target_wait; + qint64 wait_time = target_time - current_time; - qint64 wait_time = target_wait - time_delta; - timer.start(); + last_time = current_time; if (wait_time < 0) { return; } QThread::msleep(wait_time); + last_time = timer.elapsed(); } void finalize(TemplateList & output) @@ -256,15 +258,17 @@ public: void init() { - target_wait = 1000 / targetFPS; + target_wait = 1000.0 / targetFPS; timer.start(); + last_time = timer.elapsed(); } protected: QElapsedTimer timer; qint64 target_wait; + qint64 last_time; }; -BR_REGISTER(Transform, FPSSynch) +BR_REGISTER(Transform, FPSLimit) class FPSCalc : public TimeVaryingTransform { diff --git a/openbr/plugins/landmarks.cpp b/openbr/plugins/landmarks.cpp index a178e33..b9fed85 100644 --- a/openbr/plugins/landmarks.cpp +++ b/openbr/plugins/landmarks.cpp @@ -21,6 +21,9 @@ class ProcrustesTransform : public Transform { Q_OBJECT + Q_PROPERTY(bool warp READ get_warp WRITE set_warp RESET reset_warp STORED false) + BR_PROPERTY(bool, warp, true) + Eigen::MatrixXf meanShape; void train(const TemplateList &data) @@ -30,23 +33,33 @@ class ProcrustesTransform : public Transform // Normalize all sets of points foreach (br::Template datum, data) { QList points = datum.file.points(); + QList rects = datum.file.rects(); + + if (points.empty() || rects.empty()) continue; - if (points.empty()) continue; + // 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()); - cv::Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector()); + // Center shape at origin + Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector()); for (int i = 0; i < points.size(); i++) points[i] -= QPointF(mean[0],mean[1]); + // Remove scale component float norm = cv::norm(OpenCVUtils::toPoints(points).toVector().toStdVector()); for (int i = 0; i < points.size(); i++) points[i] /= norm; normalizedPoints.append(points); } - // Determine mean shape - Eigen::MatrixXf shapeBuffer(normalizedPoints[0].size(), 2); + if (normalizedPoints.empty()) qFatal("Unable to calculate normalized points"); - for (int i = 0; i < normalizedPoints[0].size(); i++) { + // Determine mean shape, assuming all shapes contain the same number of points + meanShape = Eigen::MatrixXf(normalizedPoints[0].size(), 2); + for (int i = 0; i < normalizedPoints[0].size(); i++) { double x = 0; double y = 0; @@ -58,39 +71,58 @@ class ProcrustesTransform : public Transform x /= (double)normalizedPoints.size(); y /= (double)normalizedPoints.size(); - shapeBuffer(i,0) = x; - shapeBuffer(i,1) = y; + meanShape(i,0) = x; + meanShape(i,1) = y; } - - meanShape = shapeBuffer; } void project(const Template &src, Template &dst) const { QList points = src.file.points(); + QList rects = src.file.rects(); - cv::Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector()); + if (points.empty() || rects.empty()) { + dst = src; + qWarning("Procrustes alignment failed because points or rects are empty."); + return; + } + + // 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()); + + Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector()); for (int i = 0; i < points.size(); i++) points[i] -= QPointF(mean[0],mean[1]); + Eigen::MatrixXf srcMat(points.size(), 2); float norm = cv::norm(OpenCVUtils::toPoints(points).toVector().toStdVector()); - Eigen::MatrixXf srcPoints(points.size(), 2); - for (int i = 0; i < points.size(); i++) { - srcPoints(i,0) = points[i].x()/(norm/150.)+50; - srcPoints(i,1) = points[i].y()/(norm/150.)+50; + points[i] /= norm; + srcMat(i,0) = points[i].x(); + srcMat(i,1) = points[i].y(); } - Eigen::JacobiSVD svd(srcPoints.transpose()*meanShape, Eigen::ComputeThinU | Eigen::ComputeThinV); - + Eigen::JacobiSVD svd(srcMat.transpose()*meanShape, Eigen::ComputeThinU | Eigen::ComputeThinV); Eigen::MatrixXf R = svd.matrixU()*svd.matrixV().transpose(); - Eigen::MatrixXf dstPoints = srcPoints*R; - - points.clear(); + dst = src; - for (int i = 0; i < dstPoints.rows(); i++) points.append(QPointF(dstPoints(i,0),dstPoints(i,1))); + 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.appendPoints(points); + 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 @@ -109,60 +141,202 @@ BR_REGISTER(Transform, ProcrustesTransform) /*! * \ingroup transforms - * \brief Wraps STASM key point detector + * \brief Creates a Delaunay triangulation based on a set of points * \author Scott Klum \cite sklum */ -class DelauneyTransform : public UntrainableTransform +class DelaunayTransform : public UntrainableTransform { Q_OBJECT + Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) + Q_PROPERTY(QString widthCrop READ get_widthCrop WRITE set_widthCrop RESET reset_widthCrop STORED false) + Q_PROPERTY(QString heightCrop READ get_heightCrop WRITE set_heightCrop RESET reset_heightCrop 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(QString, widthCrop, QString()) + BR_PROPERTY(QString, heightCrop, QString()) + BR_PROPERTY(bool, warp, true) BR_PROPERTY(bool, draw, false) void project(const Template &src, Template &dst) const { - dst = src; - Subdiv2D subdiv(Rect(0,0,src.m().cols,src.m().rows)); - foreach(const cv::Point2f& point, OpenCVUtils::toPoints(src.file.points())) subdiv.insert(point); + QList points = src.file.points(); + QList rects = src.file.rects(); + + if (points.empty() || rects.empty()) { + dst = src; + qWarning("Delauney triangulation failed because points or rects are empty."); + return; + } + + // 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()); + + 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) { + dst = src; + qWarning("Delauney triangulation failed because points lie on boundary."); + return; + } + subdiv.insert(OpenCVUtils::toPoint(points[i])); + } vector triangleList; subdiv.getTriangleList(triangleList); - vector pt(3); - Scalar delaunay_color(0, 0, 0); + QList< QList > validTriangles; + + for (size_t i = 0; i < triangleList.size(); ++i) { + + 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])); + + 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]); + } + + dst.m() = src.m().clone(); if (draw) { - int count = 0; - for(size_t i = 0; i < triangleList.size(); ++i) { - Vec6f t = triangleList[i]; - - pt[0] = Point(cvRound(t[0]), cvRound(t[1])); - pt[1] = Point(cvRound(t[2]), cvRound(t[3])); - pt[2] = Point(cvRound(t[4]), cvRound(t[5])); - - bool inside = true; - for (int i = 0; i < 3; i++) { - if(pt[i].x > dst.m().cols || pt[i].y > dst.m().rows || pt[i].x <= 0 || pt[i].y <= 0) { - inside = false; - } + 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); + } + } + + 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"); + + cv::Scalar mean(2); + mean[0] = src.file.get("Procrustes_mean_0"); + mean[1] = src.file.get("Procrustes_mean_1"); + + float norm = src.file.get("Procrustes_norm"); + + dst.m() = Mat::zeros(src.m().rows,src.m().cols,src.m().type()); + + QList mappedPoints; + + for (int i = 0; i < validTriangles.size(); i++) { + Eigen::MatrixXf srcMat(validTriangles[i].size(), 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; } - if (inside) { - count++; - //qDebug() << count << pt[0] << pt[1] << pt[2] << "Area" << contourArea(pt); - line(dst.m(), pt[0], pt[1], delaunay_color, 1); - line(dst.m(), pt[1], pt[2], delaunay_color, 1); - line(dst.m(), pt[2], pt[0], delaunay_color, 1); + + Eigen::MatrixXf dstMat = srcMat*R; + + Point2f srcPoints[3]; + for (int j = 0; j < 3; j++) srcPoints[j] = 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); + dstPoints[j] = warpedPoint; + mappedPoints.append(warpedPoint); + } + + Mat buffer(src.m().rows,src.m().cols,src.m().type()); + + warpAffine(src.m(), buffer, getAffineTransform(srcPoints, dstPoints), Size(src.m().cols,src.m().rows)); + + Mat mask = Mat::zeros(src.m().rows, src.m().cols, CV_8UC1); + Point maskPoints[1][3]; + maskPoints[0][0] = dstPoints[0]; + maskPoints[0][1] = dstPoints[1]; + maskPoints[0][2] = dstPoints[2]; + const Point* ppt = { maskPoints[0] }; + + fillConvexPoly(mask, ppt, 3, Scalar(255,255,255), 8); + + Mat output(src.m().rows,src.m().cols,src.m().type()); + + // Optimization needed + if (i > 0) { + Mat overlap; + bitwise_and(dst.m(),mask,overlap); + for (int j = 0; j < overlap.rows; j++) { + for (int k = 0; k < overlap.cols; k++) { + if (overlap.at(k,j) != 0) { + mask.at(k,j) = 0; + } + } + } } + + bitwise_and(buffer,mask,output); + + dst.m() += output; } + + Rect boundingBox = boundingRect(mappedPoints.toVector().toStdVector()); + + boundingBox.x += boundingBox.width * QtUtils::toPoint(widthCrop).x(); + boundingBox.y += boundingBox.height * QtUtils::toPoint(heightCrop).x(); + boundingBox.width *= 1-QtUtils::toPoint(widthCrop).y(); + boundingBox.height *= 1-QtUtils::toPoint(heightCrop).y(); + + dst.m() = Mat(dst.m(), boundingBox); } } }; -BR_REGISTER(Transform, DelauneyTransform) +BR_REGISTER(Transform, DelaunayTransform) + +/*! + * \ingroup transforms + * \brief Computes the mean of a set of templates. + * \note Suitable for visualization only as it sets every projected template to the mean template. + * \author Scott Klum \cite sklum + */ +class MeanTransform : public Transform +{ + Q_OBJECT + + Mat mean; + + void train(const TemplateList &data) + { + mean = Mat::zeros(data[0].m().rows,data[0].m().cols,CV_32F); + + for (int i = 0; i < data.size(); i++) { + Mat converted; + data[i].m().convertTo(converted, CV_32F); + mean += converted; + } + + mean /= data.size(); + } + + void project(const Template &src, Template &dst) const + { + dst = src; + dst.m() = mean; + } + +}; + +BR_REGISTER(Transform, MeanTransform) } // namespace br diff --git a/openbr/plugins/misc.cpp b/openbr/plugins/misc.cpp index f872f1a..f0b1fbb 100644 --- a/openbr/plugins/misc.cpp +++ b/openbr/plugins/misc.cpp @@ -35,6 +35,7 @@ class OpenTransform : public UntrainableMetaTransform void project(const Template &src, Template &dst) const { + if (!src.isEmpty()) { dst = src; return; } if (Globals->verbose) qDebug("Opening %s", qPrintable(src.file.flat())); dst.file = src.file; foreach (const File &file, src.file.split()) { @@ -561,6 +562,28 @@ class ExpandRectTransform : public UntrainableTransform BR_REGISTER(Transform, ExpandRectTransform) + +class EventTransform : public UntrainableMetaTransform +{ + Q_OBJECT + Q_PROPERTY(QString eventName READ get_eventName WRITE set_eventName RESET reset_eventName STORED false) + BR_PROPERTY(QString, eventName, "") + + TemplateEvent event; + + void project(const Template &src, Template &dst) const + { + dst = src; + event.pulseSignal(dst); + } + + TemplateEvent * getEvent(const QString & name) + { + return name == eventName ? &event : NULL; + } +}; +BR_REGISTER(Transform, EventTransform) + } #include "misc.moc" diff --git a/openbr/plugins/pp5.cpp b/openbr/plugins/pp5.cpp index e79ee92..5c5ebfd 100644 --- a/openbr/plugins/pp5.cpp +++ b/openbr/plugins/pp5.cpp @@ -97,8 +97,8 @@ struct PP5Context static void createRawImage(const cv::Mat &src, ppr_raw_image_type &dst) { - if (!src.isContinuous()) qFatal("PP5Context::createRawImage requires continuous data."); - if (src.channels() == 3) ppr_raw_image_create(&dst, src.cols, src.rows, PPR_RAW_IMAGE_BGR24); + if (!src.isContinuous()) qFatal("PP5Context::createRawImage requires continuous data."); + else if (src.channels() == 3) ppr_raw_image_create(&dst, src.cols, src.rows, PPR_RAW_IMAGE_BGR24); else if (src.channels() == 1) ppr_raw_image_create(&dst, src.cols, src.rows, PPR_RAW_IMAGE_GRAY8); else qFatal("PP5Context::createRawImage invalid channel count."); memcpy(dst.data, src.data, src.channels()*src.rows*src.cols); @@ -241,41 +241,43 @@ class PP5EnrollTransform : public UntrainableMetaTransform PP5Context *context = contexts.acquire(); foreach(const Template & src, srcList) { - ppr_raw_image_type raw_image; - PP5Context::createRawImage(src, raw_image); - ppr_image_type image; - TRY(ppr_create_image(raw_image, &image)) - ppr_face_list_type face_list; - TRY(ppr_detect_faces(context->context, image, &face_list)) - - for (int i=0; icontext, face, &extractable)) - if (!extractable && !detectOnly) continue; - - cv::Mat m; - if (detectOnly) { - m = src; - } else { - TRY(ppr_extract_face_template(context->context, image, &face)) - context->createMat(face, m); - } - Template dst; - dst.file = src.file; - - dst.file.append(PP5Context::toMetadata(face)); - dst += m; - dstList.append(dst); - - // Found a face, nothing else to do (if we aren't trying to find multiple faces). - if (!Globals->enrollAll) - break; + if (!src.isEmpty()) { + ppr_raw_image_type raw_image; + PP5Context::createRawImage(src, raw_image); + ppr_image_type image; + TRY(ppr_create_image(raw_image, &image)) + ppr_face_list_type face_list; + TRY(ppr_detect_faces(context->context, image, &face_list)) + + for (int i=0; icontext, face, &extractable)) + if (!extractable && !detectOnly) continue; + + cv::Mat m; + if (detectOnly) { + m = src; + } else { + TRY(ppr_extract_face_template(context->context, image, &face)) + context->createMat(face, m); + } + Template dst; + dst.file = src.file; + + dst.file.append(PP5Context::toMetadata(face)); + dst += m; + dstList.append(dst); + + // Found a face, nothing else to do (if we aren't trying to find multiple faces). + if (!Globals->enrollAll) + break; + } + + ppr_free_face_list(face_list); + ppr_free_image(image); + ppr_raw_image_free(raw_image); } - - ppr_free_face_list(face_list); - ppr_free_image(image); - ppr_raw_image_free(raw_image); } // No faces were detected, output something with FTE set. @@ -305,10 +307,14 @@ class PP5CompareDistance : public Distance float compare(const Template &target, const Template &query) const { - (void) target; - (void) query; - qFatal("Compare single templates should never be called!"); - return 0; + TemplateList targetList; + targetList.append(target); + TemplateList queryList; + queryList.append(query); + MatrixOutput *score = MatrixOutput::make(targetList.files(), queryList.files()); + compare(targetList, queryList, score); + return score->data.at(0); + } void compare(const TemplateList &target, const TemplateList &query, Output *output) const diff --git a/openbr/plugins/regions.cpp b/openbr/plugins/regions.cpp index 26d2dc0..020f3e1 100644 --- a/openbr/plugins/regions.cpp +++ b/openbr/plugins/regions.cpp @@ -95,8 +95,9 @@ class CatTransform : public UntrainableMetaTransform for (int i=0; i offsets(partitions, 0); for (int i=0; i maxY) maxY = src.file.points()[index].y(); points.append(src.file.points()[index]); } + else qFatal("Incorrect indices"); } double width = maxX-minX; @@ -238,12 +240,15 @@ class RectFromPointsTransform : public UntrainableTransform double height = maxY-minY; double deltaHeight = width/aspectRatio - height; - height += deltaHeight; + height += deltaHeight; dst.file.setPoints(points); if (crop) dst.m() = src.m()(Rect(std::max(0.0, minX - deltaWidth/2.0), std::max(0.0, minY - deltaHeight/2.0), std::min((double)src.m().cols, width), std::min((double)src.m().rows, height))); - else dst.m() = src.m(); + else { + dst.file.appendRect(QRectF(std::max(0.0, minX - deltaWidth/2.0), std::max(0.0, minY - deltaHeight/2.0), std::min((double)src.m().cols, width), std::min((double)src.m().rows, height))); + dst.m() = src.m(); + } } }; diff --git a/openbr/plugins/register.cpp b/openbr/plugins/register.cpp index dd88b06..9ff9422 100644 --- a/openbr/plugins/register.cpp +++ b/openbr/plugins/register.cpp @@ -28,10 +28,22 @@ namespace br * \ingroup transforms * \brief Performs a two or three point registration. * \author Josh Klontz \cite jklontz + * \note Method: Area should be used for shrinking an image, Cubic for slow but accurate enlargment, Bilin for fast enlargement. */ class AffineTransform : public UntrainableTransform { Q_OBJECT + Q_ENUMS(Method) + +public: + /*!< */ + enum Method { Near = INTER_NEAREST, + Area = INTER_AREA, + Bilin = INTER_LINEAR, + Cubic = INTER_CUBIC, + Lanczo = INTER_LANCZOS4}; + +private: Q_PROPERTY(int width READ get_width WRITE set_width RESET reset_width STORED false) Q_PROPERTY(int height READ get_height WRITE set_height RESET reset_height STORED false) Q_PROPERTY(float x1 READ get_x1 WRITE set_x1 RESET reset_x1 STORED false) @@ -40,6 +52,7 @@ class AffineTransform : public UntrainableTransform Q_PROPERTY(float y2 READ get_y2 WRITE set_y2 RESET reset_y2 STORED false) Q_PROPERTY(float x3 READ get_x3 WRITE set_x3 RESET reset_x3 STORED false) Q_PROPERTY(float y3 READ get_y3 WRITE set_y3 RESET reset_y3 STORED false) + Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false) BR_PROPERTY(int, width, 64) BR_PROPERTY(int, height, 64) BR_PROPERTY(float, x1, 0) @@ -48,6 +61,7 @@ class AffineTransform : public UntrainableTransform BR_PROPERTY(float, y2, -1) BR_PROPERTY(float, x3, -1) BR_PROPERTY(float, y3, -1) + BR_PROPERTY(Method, method, Bilin) static Point2f getThirdAffinePoint(const Point2f &a, const Point2f &b) { @@ -72,9 +86,6 @@ class AffineTransform : public UntrainableTransform (src.file.contains("Affine_2") || twoPoints)) { srcPoints[0] = OpenCVUtils::toPoint(src.file.get("Affine_0")); srcPoints[1] = OpenCVUtils::toPoint(src.file.get("Affine_1")); - - dst.file.set("Affine_0", OpenCVUtils::fromPoint(dstPoints[0])); - dst.file.set("Affine_1", OpenCVUtils::fromPoint(dstPoints[1])); if (!twoPoints) srcPoints[2] = OpenCVUtils::toPoint(src.file.get("Affine_2")); } else { const QList landmarks = OpenCVUtils::toPoints(src.file.points()); @@ -89,13 +100,12 @@ class AffineTransform : public UntrainableTransform dst.file.set("Affine_0", OpenCVUtils::fromPoint(landmarks[0])); dst.file.set("Affine_1", OpenCVUtils::fromPoint(landmarks[1])); - if (!twoPoints) dst.file.set("Affine_2", OpenCVUtils::fromPoint(landmarks[2])); } } if (twoPoints) srcPoints[2] = getThirdAffinePoint(srcPoints[0], srcPoints[1]); - warpAffine(src, dst, getAffineTransform(srcPoints, dstPoints), Size(width, height)); + warpAffine(src, dst, getAffineTransform(srcPoints, dstPoints), Size(width, height), method); } }; diff --git a/openbr/plugins/stasm4.cpp b/openbr/plugins/stasm4.cpp index 3191edf..810903c 100644 --- a/openbr/plugins/stasm4.cpp +++ b/openbr/plugins/stasm4.cpp @@ -38,8 +38,8 @@ class StasmInitializer : public Initializer { Globals->abbreviations.insert("RectFromStasmEyes","RectFromPoints([29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],0.125,6.0)+Resize(44,164)"); Globals->abbreviations.insert("RectFromStasmBrow","RectFromPoints([17, 18, 19, 20, 21, 22, 23, 24],0.15,6)+Resize(28,132)"); - Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.25)+Resize(60,60)"); - Globals->abbreviations.insert("RectFromStasmMouth","RectFromPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76],0.3,3.0)+Resize(28,68)"); + Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.25)"); + Globals->abbreviations.insert("RectFromStasmMouth","RectFromPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76],0.3,2.5)"); } }; diff --git a/openbr/plugins/stream.cpp b/openbr/plugins/stream.cpp index 9bebf3c..5470f51 100644 --- a/openbr/plugins/stream.cpp +++ b/openbr/plugins/stream.cpp @@ -241,7 +241,7 @@ public: } virtual void close() = 0; - virtual bool open(Template & output) = 0; + virtual bool open(Template & output, int start_index=0) = 0; virtual bool isOpen() = 0; virtual bool getNext(FrameData & input) = 0; @@ -262,13 +262,13 @@ class VideoDataSource : public DataSource public: VideoDataSource(int maxFrames) : DataSource(maxFrames) {} - bool open(Template &input) + bool open(Template &input, int start_index=0) { final_frame = -1; last_issued = -2; last_received = -3; - next_idx = 0; + next_idx = start_index; basis = input; bool is_int = false; int anInt = input.file.name.toInt(&is_int); @@ -336,11 +336,11 @@ public: } bool data_ok; - bool open(Template &input) + bool open(Template &input, int start_index=0) { basis = input; current_idx = 0; - next_sequence = 0; + next_sequence = start_index; final_frame = -1; last_issued = -2; last_received = -3; @@ -406,22 +406,31 @@ public: } } - bool open(Template & input) + bool open(TemplateList & input) + { + currentIdx = 0; + templates = input; + + return open(templates[currentIdx]); + } + + bool open(Template & input, int start_index=0) { close(); final_frame = -1; last_issued = -2; last_received = -3; + next_frame = start_index; // Input has no matrices? Its probably a video that hasn't been loaded yet if (input.empty()) { actualSource = new VideoDataSource(0); - actualSource->open(input); + actualSource->open(input, next_frame); } else { // create frame dealer actualSource = new TemplateDataSource(0); - actualSource->open(input); + actualSource->open(input, next_frame); } if (!isOpen()) { delete actualSource; @@ -434,10 +443,31 @@ public: bool isOpen() { return !actualSource ? false : actualSource->isOpen(); } protected: + int currentIdx; + int next_frame; + TemplateList templates; DataSource * actualSource; bool getNext(FrameData & output) { - return actualSource->getNext(output); + bool res = actualSource->getNext(output); + if (res) { + next_frame = output.sequenceNumber+1; + return true; + } + + while(!res) { + currentIdx++; + + if (currentIdx >= templates.size()) + return false; + bool open_res = open(templates[currentIdx], next_frame); + if (!open_res) + return false; + res = actualSource->getNext(output); + } + + next_frame = output.sequenceNumber+1; + return res; } }; @@ -796,11 +826,9 @@ public: // start processing void projectUpdate(const TemplateList & src, TemplateList & dst) { - if (src.size() != 1) - qFatal("Expected single template input to stream"); - dst = src; - bool res = readStage->dataSource.open(dst[0]); + + bool res = readStage->dataSource.open(dst); if (!res) return; QThreadPool::globalInstance()->releaseThread(); diff --git a/share/openbr/cmake/InstallDependencies.cmake b/share/openbr/cmake/InstallDependencies.cmake index e29b011..74e05e5 100644 --- a/share/openbr/cmake/InstallDependencies.cmake +++ b/share/openbr/cmake/InstallDependencies.cmake @@ -10,7 +10,8 @@ function(install_opencv_library lib) if(NOT MSVC) set(BR_INSTALL_DEPENDENCIES_PREFIX "lib") endif() - install(FILES ${OpenCV_DIR}/bin/${BR_INSTALL_DEPENDENCIES_PREFIX}${lib}${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}${BR_INSTALL_DEPENDENCIES_SUFFIX}.dll DESTINATION bin) + list(GET OpenCV_LIB_DIR 0 cv_lib_stripped) + install(FILES ${cv_lib_stripped}/../bin/${BR_INSTALL_DEPENDENCIES_PREFIX}${lib}${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}${BR_INSTALL_DEPENDENCIES_SUFFIX}.dll DESTINATION bin) else() set(OpenCV_LIB_DIR "/usr/local/lib") install(FILES ${OpenCV_LIB_DIR}/lib${lib}.${OpenCV_VERSION_MAJOR}.${OpenCV_VERSION_MINOR}.${OpenCV_VERSION_PATCH}${CMAKE_SHARED_LIBRARY_SUFFIX} DESTINATION lib)