diff --git a/app/br/br.cpp b/app/br/br.cpp index e8742eb..0f8ac51 100644 --- a/app/br/br.cpp +++ b/app/br/br.cpp @@ -230,10 +230,6 @@ int main(int argc, char *argv[]) { br_initialize(argc, argv); - // Do argument execution in another thread so this main thread can run an event loop. - // When adding fakeMain to the global thread pool we also increment maxThreadCount so - // that while this thread waits on parallel work to complete, - // the parallel work can make use of all available CPU threads. FakeMain *fakeMain = new FakeMain(argc, argv); QThreadPool::globalInstance()->start(fakeMain); QCoreApplication::exec(); diff --git a/openbr/core/qtutils.h b/openbr/core/qtutils.h index f6f6062..4ad4743 100644 --- a/openbr/core/qtutils.h +++ b/openbr/core/qtutils.h @@ -72,13 +72,6 @@ namespace QtUtils /**** Variant Utilities ****/ QString toString(const QVariant &variant); - - inline void releaseAndWait(QFutureSynchronizer & futures) - { - QThreadPool::globalInstance()->releaseThread(); - futures.waitForFinished(); - QThreadPool::globalInstance()->reserveThread(); - } } #endif // __QTUTILS_H diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index 5404e11..2161b1c 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -876,6 +876,9 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath) } Globals->sdkPath = sdkPath; + // Empirical evidence suggests an extra thread helps achieve full CPU utilization + QThreadPool::globalInstance()->releaseThread(); + // Trigger registered initializers QList< QSharedPointer > initializers = Factory::makeAll(); foreach (const QSharedPointer &initializer, initializers) @@ -884,6 +887,9 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath) void br::Context::finalize() { + // Undo the 'releaseThread()' in 'initialize()' + QThreadPool::globalInstance()->reserveThread(); + // Trigger registered finalizers QList< QSharedPointer > initializers = Factory::makeAll(); foreach (const QSharedPointer &initializer, initializers) @@ -1181,7 +1187,7 @@ private: if (Globals->parallelism) futures.addFuture(QtConcurrent::run(_train, transforms[i], &templatesList[i])); else _train (transforms[i], &templatesList[i]); } - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } void project(const Template &src, Template &dst) const @@ -1295,10 +1301,7 @@ void Transform::project(const TemplateList &src, TemplateList &dst) const // There are certain conditions where we should process the templates in serial, // but generally we'd prefer to process them in parallel. - if ((src.size() < 2) || - (QThreadPool::globalInstance()->activeThreadCount() >= QThreadPool::globalInstance()->maxThreadCount()) || - (Globals->parallelism == 0)) { - + if ((src.size() < 2) || (Globals->parallelism == 0)) { foreach (const Template &t, src) { dst.append(Template()); _project(this, &t, &dst.last()); @@ -1309,7 +1312,7 @@ void Transform::project(const TemplateList &src, TemplateList &dst) const QFutureSynchronizer futures; for (int i=0; iparallelism) futures.addFuture(QtConcurrent::run(_backProject, this, &dst[i], &src[i])); else _backProject (this, &dst[i], &src[i]); - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } /* Distance - public methods */ @@ -1370,7 +1373,7 @@ void Distance::compare(const TemplateList &target, const TemplateList &query, Ou if (Globals->parallelism) futures.addFuture(QtConcurrent::run(this, &Distance::compareBlock, targets, queries, output, targetOffset, queryOffset)); else compareBlock (targets, queries, output, targetOffset, queryOffset); } - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } QList Distance::compare(const TemplateList &targets, const Template &query) const diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 71787da..010eea8 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -304,6 +304,7 @@ struct Template : public QList Template() {} Template(const File &_file) : file(_file) {} /*!< \brief Initialize #file. */ Template(const File &_file, const cv::Mat &mat) : file(_file) { append(mat); } /*!< \brief Initialize #file and append a matrix. */ + Template(const File &_file, const QList &mats) : file(_file) { append(mats); } /*!< \brief Initialize #file and append matricies. */ Template(const cv::Mat &mat) { append(mat); } /*!< \brief Append a matrix. */ inline const cv::Mat &m() const { static const cv::Mat NullMatrix; diff --git a/openbr/plugins/distance.cpp b/openbr/plugins/distance.cpp index ae7bf22..c3ef877 100644 --- a/openbr/plugins/distance.cpp +++ b/openbr/plugins/distance.cpp @@ -160,7 +160,7 @@ class PipeDistance : public Distance foreach (br::Distance *distance, distances) if (Globals->parallelism) futures.addFuture(QtConcurrent::run(distance, &Distance::train, data)); else distance->train(data); - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } float compare(const Template &a, const Template &b) const diff --git a/openbr/plugins/integral.cpp b/openbr/plugins/integral.cpp index 43fe4f8..d6a54b1 100644 --- a/openbr/plugins/integral.cpp +++ b/openbr/plugins/integral.cpp @@ -122,6 +122,147 @@ BR_REGISTER(Transform, IntegralSamplerTransform) /*! * \ingroup transforms + * \brief Construct template in a recursive decent manner. + * \author Josh Klontz \cite jklontz + */ +class RecursiveIntegralSamplerTransform : public Transform +{ + Q_OBJECT + Q_PROPERTY(int scales READ get_scales WRITE set_scales RESET reset_scales STORED false) + Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) + Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform) + BR_PROPERTY(int, scales, 6) + BR_PROPERTY(float, scaleFactor, 2) + BR_PROPERTY(int, minSize, 8) + BR_PROPERTY(br::Transform*, transform, NULL) + + Transform *subTransform; + + typedef Eigen::Map< const Eigen::Matrix > InputDescriptor; + typedef Eigen::Map< Eigen::Matrix > OutputDescriptor; + typedef Eigen::Map< const Eigen::Matrix > SecondOrderInputDescriptor; + + void init() + { + if (scales >= 2) { + File subFile = file; + subFile.set("scales", scales-1); + subTransform = make(subFile.flat()); + } else { + subTransform = NULL; + } + } + + static void integralHistogram(const Mat &src, const int x, const int y, const int rows, const int columns, Mat &dst, int index) + { + const int channels = src.channels(); + OutputDescriptor(dst.ptr(index), channels, 1) = + ( InputDescriptor(src.ptr(y+rows, x+columns), channels, 1) + - InputDescriptor(src.ptr(y, x+columns), channels, 1) + - InputDescriptor(src.ptr(y+rows, x), channels, 1) + + InputDescriptor(src.ptr(y, x), channels, 1)).cast()/(rows*columns); + } + + void computeDescriptor(const Mat &src, Mat &dst) const + { + const int channels = src.channels(); + dst = Mat(7, channels, CV_32FC1); + const int rows = src.rows-1; // Integral images have an extra row and column + const int columns = src.cols-1; + integralHistogram(src, 0, 0, rows, columns, dst, 0); + + Mat tmp(4, channels, CV_32FC1); + integralHistogram(src, 0, 0, rows/2, columns/2, tmp, 0); + integralHistogram(src, 0, columns/2, rows/2, columns/2, tmp, 1); + integralHistogram(src, rows/2, 0, rows/2, columns/2, tmp, 2); + integralHistogram(src, rows/2, columns/2, rows/2, columns/2, tmp, 3); + OutputDescriptor(dst.ptr(1), channels, 1) = (SecondOrderInputDescriptor(tmp.ptr(0), channels, 1) - SecondOrderInputDescriptor(tmp.ptr(1), channels, 1))/4.f; + OutputDescriptor(dst.ptr(2), channels, 1) = (SecondOrderInputDescriptor(tmp.ptr(1), channels, 1) - SecondOrderInputDescriptor(tmp.ptr(3), channels, 1))/4.f; + OutputDescriptor(dst.ptr(3), channels, 1) = (SecondOrderInputDescriptor(tmp.ptr(3), channels, 1) - SecondOrderInputDescriptor(tmp.ptr(2), channels, 1))/4.f; + OutputDescriptor(dst.ptr(4), channels, 1) = (SecondOrderInputDescriptor(tmp.ptr(2), channels, 1) - SecondOrderInputDescriptor(tmp.ptr(0), channels, 1))/4.f; + OutputDescriptor(dst.ptr(5), channels, 1) = (SecondOrderInputDescriptor(tmp.ptr(0), channels, 1) - SecondOrderInputDescriptor(tmp.ptr(3), channels, 1))/4.f; + OutputDescriptor(dst.ptr(6), channels, 1) = (SecondOrderInputDescriptor(tmp.ptr(1), channels, 1) - SecondOrderInputDescriptor(tmp.ptr(2), channels, 1))/4.f; + } + + Template subdivide(const Template &src) const + { + // Integral images have an extra row and column + int subWidth = (src.m().cols-1) / scaleFactor + 1; + int subHeight = (src.m().rows-1) / scaleFactor + 1; + return Template(src.file, QList() << Mat(src, Rect(0, 0, subWidth, subHeight)) + << Mat(src, Rect(src.m().cols-subWidth, 0, subWidth, subHeight)) + << Mat(src, Rect(0, src.m().rows-subHeight, subWidth, subHeight)) + << Mat(src, Rect(src.m().cols-subWidth, src.m().rows-subHeight, subWidth, subHeight))); + } + + bool canSubdivide(const Template &t) const + { + // Integral images have an extra row and column + const int subWidth = (t.m().cols-1) / scaleFactor; + const int subHeight = (t.m().rows-1) / scaleFactor; + return ((subWidth >= minSize) && (subHeight >= minSize)); + } + + void train(const TemplateList &src) + { + if (subTransform != NULL) { + TemplateList subSrc; subSrc.reserve(src.size()); + foreach (const Template &t, src) + if (canSubdivide(t)) + subSrc.append(subdivide(t)); + + if (subSrc.isEmpty()) { + delete subTransform; + subTransform = NULL; + } else { + subTransform->train(subSrc); + } + } + + TemplateList dst; dst.reserve(src.size()); + foreach (const Template &t, src) { + Template u(t.file); + computeDescriptor(t, u); + dst.append(u); + } + transform->train(dst); + } + + void project(const Template &src, Template &dst) const + { + computeDescriptor(src, dst); + transform->project(dst, dst); + + if ((subTransform != NULL) && canSubdivide(src)) { + Template subDst; + subTransform->project(subdivide(src), subDst); + dst.append(subDst); + } + } + + void store(QDataStream &stream) const + { + transform->store(stream); + stream << (subTransform == NULL); + if (subTransform != NULL) + subTransform->store(stream); + } + + void load(QDataStream &stream) + { + transform->load(stream); + bool hasSubTransform; + stream >> hasSubTransform; + if (hasSubTransform) subTransform->load(stream); + else { delete subTransform; subTransform = NULL; } + } +}; + +BR_REGISTER(Transform, RecursiveIntegralSamplerTransform) + +/*! + * \ingroup transforms * \brief Computes magnitude and/or angle of image. * \author Josh Klontz \cite jklontz */ diff --git a/openbr/plugins/meta.cpp b/openbr/plugins/meta.cpp index 34f5f0d..4c7eee1 100644 --- a/openbr/plugins/meta.cpp +++ b/openbr/plugins/meta.cpp @@ -111,7 +111,7 @@ class PipeTransform : public CompositeTransform for (int j=0; jparallelism) futures.addFuture(QtConcurrent::run(this, &PipeTransform::_projectPartial, ©[j], i, nextTrainableTransform)); else _projectPartial( ©[j], i, nextTrainableTransform); - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); i = nextTrainableTransform; } } @@ -293,7 +293,7 @@ class ForkTransform : public CompositeTransform if (Globals->parallelism) futures.addFuture(QtConcurrent::run(_train, transforms[i], &data)); else _train (transforms[i], &data); } - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } void backProject(const Template &dst, Template &src) const {Transform::backProject(dst, src);} @@ -648,7 +648,7 @@ public: if (Globals->parallelism) futures.addFuture(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i])); else _projectList( transform, &input_buffer[i], &output_buffer[i]); } - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); for (int i=0; iparallelism) futures.addFuture(QtConcurrent::run(&BayesianQuantizationDistance::computeLogLikelihood, data.col(i), templateLabels, &loglikelihoods.data()[i*256])); else computeLogLikelihood( data.col(i), templateLabels, &loglikelihoods.data()[i*256]); - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } float compare(const Template &a, const Template &b) const @@ -259,10 +259,14 @@ class ProductQuantizationDistance : public Distance { float distance = 0; for (int i=0; i(aData); + aData += sizeof(quint16); + bData += sizeof(quint16); + + const float *lut = (const float*)ProductQuantizationLUTs[index].data; for (int j=0; j centers; public: ProductQuantizationTransform() { + if (ProductQuantizationLUTs.size() > std::numeric_limits::max()) + qFatal("Out of LUT space!"); // Unlikely + + static QMutex mutex; + QMutexLocker locker(&mutex); index = ProductQuantizationLUTs.size(); ProductQuantizationLUTs.append(Mat()); } @@ -416,7 +425,7 @@ private: if (Globals->parallelism) futures.addFuture(QtConcurrent::run(this, &ProductQuantizationTransform::_train, subdata[i], labels, &subluts[i], ¢ers[i])); else _train (subdata[i], labels, &subluts[i], ¢ers[i]); } - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } int getIndex(const Mat &m, const Mat ¢er) const @@ -438,19 +447,21 @@ private: Mat m = src.m().reshape(1, 1); const int step = getStep(m.cols); const int offset = getOffset(m.cols); - dst = Mat(1, getDims(m.cols), CV_8UC1); - for (int i=0; i(0,i) = getIndex(m.colRange(max(0, i*step-offset), (i+1)*step-offset), centers[i]); + const int dims = getDims(m.cols); + dst = Mat(1, sizeof(quint16)+dims, CV_8UC1); + memcpy(dst.m().data, &index, sizeof(quint16)); + for (int i=0; i(0,sizeof(quint16)+i) = getIndex(m.colRange(max(0, i*step-offset), (i+1)*step-offset), centers[i]); } void store(QDataStream &stream) const { - stream << centers << ProductQuantizationLUTs[index]; + stream << index << centers << ProductQuantizationLUTs[index]; } void load(QDataStream &stream) { - stream >> centers >> ProductQuantizationLUTs[index]; + stream >> index >> centers >> ProductQuantizationLUTs[index]; } }; diff --git a/openbr/plugins/quantize2.cpp b/openbr/plugins/quantize2.cpp index 48bfe11..07be309 100644 --- a/openbr/plugins/quantize2.cpp +++ b/openbr/plugins/quantize2.cpp @@ -21,47 +21,54 @@ class BayesianQuantizationTransform : public Transform Q_OBJECT QVector thresholds; + static void computeThresholdsRecursive(const QVector &cumulativeGenuines, const QVector &cumulativeImpostors, + float *thresholds, const int thresholdIndex) + { +// const int totalGenuines = cumulativeGenuines.last()-cumulativeGenuines.first(); +// const int totalImpostors = cumulativeImpostors.last()-cumulativeImpostors.first(); + + int low = 0; + int high = cumulativeGenuines.size()-1; + int index = cumulativeGenuines.size()/2; + + while ((index != low) && (index != high)) { + index = (high - low)/2; +// const float logLikelihoodLow = (float(cumulativeGenuines[index]-cumulativeGenuines.first())/totalGenuines)/ +// (float(cumulativeImpostors[index]-cumulativeImpostors.first())/totalImpostors); +// const float logLikelihoodHigh = (float(cumulativeGenuines.last()-cumulativeGenuines[index])/totalGenuines)/ +// (float(cumulativeImpostors.last()-cumulativeImpostors[index])/totalImpostors); + + } + + computeThresholdsRecursive(cumulativeGenuines.mid(0,index), cumulativeImpostors.mid(0,index), thresholds, thresholdIndex); + computeThresholdsRecursive(cumulativeGenuines.mid(index), cumulativeImpostors.mid(index), thresholds, thresholdIndex); + } + static void computeThresholds(const Mat &data, const QList &labels, float *thresholds) { const QList vals = OpenCVUtils::matrixToVector(data); if (vals.size() != labels.size()) qFatal("Logic error."); - QList genuineScores; genuineScores.reserve(vals.size()); - QList impostorScores; impostorScores.reserve(vals.size()*vals.size()/2); + typedef QPair LabeledScore; + QList labeledScores; labeledScores.reserve(vals.size()); for (int i=0; i cumulativeGenuines(labeledScores.size()); + QVector cumulativeImpostors(labeledScores.size()); + cumulativeGenuines[0] = (labeledScores.first().second ? 1 : 0); + cumulativeImpostors[0] = (labeledScores.first().second ? 0 : 1); + for (int i=1; i futures; for (int i=0; iparallelism) futures.addFuture(QtConcurrent::run(&BayesianQuantizationTransform::computeThresholds, data.col(i), labels, &thresholds.data()[i*256])); else computeThresholds( data.col(i), labels, &thresholds.data()[i*256]); futures.waitForFinished(); } diff --git a/openbr/plugins/validate.cpp b/openbr/plugins/validate.cpp index 89ec7b8..61193e4 100644 --- a/openbr/plugins/validate.cpp +++ b/openbr/plugins/validate.cpp @@ -49,7 +49,7 @@ class CrossValidateTransform : public MetaTransform if (Globals->parallelism) futures.addFuture(QtConcurrent::run(transforms[i], &Transform::train, partitionedData)); else transforms[i]->train(partitionedData); } - QtUtils::releaseAndWait(futures); + futures.waitForFinished(); } void project(const Template &src, Template &dst) const