diff --git a/openbr/core/common.cpp b/openbr/core/common.cpp index c3802e4..d6c445a 100644 --- a/openbr/core/common.cpp +++ b/openbr/core/common.cpp @@ -16,32 +16,21 @@ #include "common.h" #include -#include using namespace std; -static RandomLib::Random g_rand; -static QMutex rngLock; - /**** GLOBAL ****/ void Common::seedRNG() { - QMutexLocker lock(&rngLock); + static QMutex seedControl; + QMutexLocker lock(&seedControl); static bool seeded = false; if (!seeded) { srand(0); // We seed with 0 instead of time(NULL) to have reproducible randomness seeded = true; - g_rand.Reseed(0); } } -double Common::randN() -{ - QMutexLocker lock(&rngLock); - - return g_rand.FloatN(); -} - QList Common::RandSample(int n, int max, int min, bool unique) { QList samples; samples.reserve(n); diff --git a/openbr/core/common.h b/openbr/core/common.h index 843dc96..1a9be82 100644 --- a/openbr/core/common.h +++ b/openbr/core/common.h @@ -220,9 +220,6 @@ double KernelDensityEstimation(const V &vals, double x, double h) return y / (vals.size() * h); } -// Return a random number, uniformly distributed over 0,1 -double randN(); - /*! * \brief Returns a vector of n integers sampled in the range RandSample(int n, const QSet &values, bool unique = false); template QList RandSample(int n, const QList &weights, bool unique = false) { + static bool seeded = false; + if (!seeded) { + srand(time(NULL)); + seeded = true; + } + QList cdf = CumSum(weights); for (int i=0; i samples; samples.reserve(n); while (samples.size() < n) { - T r = randN(); - + T r = (T)rand() / (T)RAND_MAX; for (int j=0; j= cdf[j]) && (r <= cdf[j+1])) { if (!unique || !samples.contains(j)) diff --git a/openbr/core/core.cpp b/openbr/core/core.cpp index a6f68bf..4d4bb01 100644 --- a/openbr/core/core.cpp +++ b/openbr/core/core.cpp @@ -110,11 +110,9 @@ struct AlgorithmCore void store(const QString &model) const { - QtUtils::BlockCompression compressedWrite; - QFile outFile(model); - compressedWrite.setBasis(&outFile); - QDataStream out(&compressedWrite); - compressedWrite.open(QFile::WriteOnly); + // Create stream + QByteArray data; + QDataStream out(&data, QFile::WriteOnly); // Serialize algorithm to stream transform->serialize(out); @@ -133,16 +131,18 @@ struct AlgorithmCore if (mode == TransformCompare) comparison->serialize(out); - compressedWrite.close(); + // Compress and save to file + QtUtils::writeFile(model, data, -1); } void load(const QString &model) { - QtUtils::BlockCompression compressedRead; - QFile inFile(model); - compressedRead.setBasis(&inFile); - QDataStream in(&compressedRead); - compressedRead.open(QFile::ReadOnly); + // Load from file and decompress + QByteArray data; + QtUtils::readFile(model, data, true); + + // Create stream + QDataStream in(&data, QFile::ReadOnly); // Load algorithm transform = QSharedPointer(Transform::deserialize(in)); diff --git a/openbr/core/qtutils.cpp b/openbr/core/qtutils.cpp index fdfd004..0f189b0 100644 --- a/openbr/core/qtutils.cpp +++ b/openbr/core/qtutils.cpp @@ -500,131 +500,6 @@ QString getAbsolutePath(const QString &filename) return QFileInfo(filename).absoluteFilePath(); } -BlockCompression::BlockCompression(QIODevice *_basis) -{ - blockSize = 100000000; - setBasis(_basis); -} - -BlockCompression::BlockCompression() { blockSize = 100000000; }; - - -bool BlockCompression::open(QIODevice::OpenMode mode) -{ - this->setOpenMode(mode); - bool res = basis->open(mode); - - if (!res) - return false; - - blockReader.setDevice(basis); - blockWriter.setDevice(basis); - - if (mode & QIODevice::WriteOnly) { - precompressedBlockWriter = new QBuffer; - precompressedBlockWriter->open(QIODevice::ReadWrite); - } - else if (mode & QIODevice::ReadOnly) { - QByteArray compressedBlock; - blockReader >> compressedBlock; - - decompressedBlock = qUncompress(compressedBlock); - decompressedBlockReader.setBuffer(&decompressedBlock); - decompressedBlockReader.open(QIODevice::ReadOnly); - } - - return true; -} - -void BlockCompression::close() -{ - // flush output buffer - if ((openMode() & QIODevice::WriteOnly) && precompressedBlockWriter) { - QByteArray compressedBlock = qCompress(precompressedBlockWriter->buffer(), -1); - blockWriter << compressedBlock; - } - basis->close(); -} - -void BlockCompression::setBasis(QIODevice *_basis) -{ - basis = _basis; - blockReader.setDevice(basis); - blockWriter.setDevice(basis); -} - -// read from current decompressed block, if out of space, read and decompress another -// block from basis -qint64 BlockCompression::readData(char *data, qint64 remaining) -{ - qint64 read = 0; - while (remaining > 0) { - qint64 single_read = decompressedBlockReader.read(data, remaining); - if (single_read == -1) - qFatal("miss read"); - - remaining -= single_read; - read += single_read; - data += single_read; - - // need a new block - if (remaining > 0) { - QByteArray compressedBlock; - blockReader >> compressedBlock; - if (compressedBlock.size() == 0) { - return read; - } - decompressedBlock = qUncompress(compressedBlock); - - decompressedBlockReader.close(); - decompressedBlockReader.setBuffer(&decompressedBlock); - decompressedBlockReader.open(QIODevice::ReadOnly); - } - } - return blockReader.atEnd() && !basis->isReadable() ? -1 : read; -} - -bool BlockCompression::isSequential() const -{ - return true; -} - -qint64 BlockCompression::writeData(const char *data, qint64 remaining) -{ - qint64 written = 0; - - while (remaining > 0) { - // how much more can be put in this buffer? - qint64 capacity = blockSize - precompressedBlockWriter->pos(); - - // don't try to write beyond capacity - qint64 write_size = qMin(capacity, remaining); - - qint64 singleWrite = precompressedBlockWriter->write(data, write_size); - // ignore the error case here, we consdier basis's failure mode the real - // end case - if (singleWrite == -1) - singleWrite = 0; - - remaining -= singleWrite; - data += singleWrite; - written += singleWrite; - - if (remaining > 0) { - QByteArray compressedBlock = qCompress(precompressedBlockWriter->buffer(), -1); - - if (compressedBlock.size() != 0) - blockWriter << compressedBlock; - - delete precompressedBlockWriter; - precompressedBlockWriter = new QBuffer; - precompressedBlockWriter->open(QIODevice::ReadWrite); - } - } - return basis->isWritable() ? written : -1; -} - - } // namespace QtUtils diff --git a/openbr/core/qtutils.h b/openbr/core/qtutils.h index 257ccd8..e9cec08 100644 --- a/openbr/core/qtutils.h +++ b/openbr/core/qtutils.h @@ -17,7 +17,6 @@ #ifndef QTUTILS_QTUTILS_H #define QTUTILS_QTUTILS_H -#include #include #include #include @@ -94,38 +93,6 @@ namespace QtUtils /**** Rect Utilities ****/ float overlap(const QRectF &r, const QRectF &s); - - - class BlockCompression : public QIODevice - { - public: - BlockCompression(QIODevice *_basis); - BlockCompression(); - int blockSize; - QIODevice *basis; - - bool open(QIODevice::OpenMode mode); - - void close(); - - void setBasis(QIODevice *_basis); - - QDataStream blockReader; - QByteArray decompressedBlock; - QBuffer decompressedBlockReader; - - // read from current decompressed block, if out of space, read and decompress another - // block from basis - qint64 readData(char *data, qint64 remaining); - - bool isSequential() const; - - // write to a QByteArray, when max block sized is reached, compress and write - // it to basis - QBuffer * precompressedBlockWriter; - QDataStream blockWriter; - qint64 writeData(const char *data, qint64 remaining); - }; } #endif // QTUTILS_QTUTILS_H diff --git a/openbr/plugins/algorithms.cpp b/openbr/plugins/algorithms.cpp index 88ecaea..f04319a 100644 --- a/openbr/plugins/algorithms.cpp +++ b/openbr/plugins/algorithms.cpp @@ -31,15 +31,15 @@ class AlgorithmsInitializer : public Initializer void initialize() const { // Face - Globals->abbreviations.insert("FaceRecognition", "FaceDetection+FaceRecognitionRegistration++++SetMetadata(AlgorithmID,-1):Unit(ByteL1)"); - Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand+FaceClassificationRegistration+Expand+++Discard"); - Globals->abbreviations.insert("AgeRegression", "FaceDetection+Expand+FaceClassificationRegistration+Expand+++Discard"); + Globals->abbreviations.insert("FaceRecognition", "FaceDetection+Expand++Expand++++SetMetadata(AlgorithmID,-1):MatchProbability(ByteL1)"); + Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand++Expand+++Discard"); + Globals->abbreviations.insert("AgeRegression", "FaceDetection+Expand++Expand+++Discard"); Globals->abbreviations.insert("FaceQuality", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(64,64,0.25,0.35)+ImageQuality+Cvt(Gray)+DFFS+Discard"); Globals->abbreviations.insert("MedianFace", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)"); Globals->abbreviations.insert("BlurredFaceDetection", "Open+LimitSize(1024)+SkinMask/(Cvt(Gray)+GradientMask)+And+Morph(Erode,16)+LargestConvexArea"); Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)+Expand+ASEFEyes+Draw(inPlace=true)"); Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection+Contract+First+Show+Discard"); - Globals->abbreviations.insert("DownloadFaceRecognition", "Download+Open+ROI+Cvt(Gray)+Cascade(FrontalFace)+FaceRecognitionRegistration++++SetMetadata(AlgorithmID,-1):Unit(ByteL1)"); + Globals->abbreviations.insert("DownloadFaceRecognition", "Download+Open+ROI+Expand+Cvt(Gray)+Cascade(FrontalFace)+Expand++Expand++++SetMetadata(AlgorithmID,-1):MatchProbability(ByteL1)"); Globals->abbreviations.insert("OpenBR", "FaceRecognition"); Globals->abbreviations.insert("GenderEstimation", "GenderClassification"); Globals->abbreviations.insert("AgeEstimation", "AgeRegression"); @@ -50,7 +50,7 @@ class AlgorithmsInitializer : public Initializer // Video Globals->abbreviations.insert("DisplayVideo", "FPSLimit(30)+Show(false,[FrameNumber])+Discard"); Globals->abbreviations.insert("PerFrameDetection", "SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true)+Show(false,[FrameNumber])+Discard"); - Globals->abbreviations.insert("AgeGenderDemo", "SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+FaceClassificationRegistration++/+Discard+RestoreMat(original)+Draw(inPlace=true)+DrawPropertiesPoint([Age,Gender],Affine_0,inPlace=true)+SaveMat(original)+Discard+Contract+RestoreMat(original)+FPSCalc+Show(false,[AvgFPS,Age,Gender])+Discard"); + Globals->abbreviations.insert("AgeGenderDemo", "SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+++/+Discard+RestoreMat(original)+Draw(inPlace=true)+DrawPropertiesPoint([Age,Gender],Affine_0,inPlace=true)+SaveMat(original)+Discard+Contract+RestoreMat(original)+FPSCalc+Show(false,[AvgFPS,Age,Gender])+Discard"); Globals->abbreviations.insert("ShowOpticalFlowField", "SaveMat(original)+AggregateFrames(2)+OpticalFlow(useMagnitude=false)+Grid(100,100)+DrawOpticalFlow+FPSLimit(30)+Show(false)+Discard"); Globals->abbreviations.insert("ShowOpticalFlowMagnitude", "AggregateFrames(2)+OpticalFlow+Normalize(Range,false,0,255)+Cvt(Color)+Draw+FPSLimit(30)+Show(false)+Discard"); Globals->abbreviations.insert("ShowMotionSegmentation", "DropFrames(5)+AggregateFrames(2)+OpticalFlow+CvtUChar+WatershedSegmentation+DrawSegmentation+Draw+FPSLimit(30)+Show(false)+Discard"); @@ -92,11 +92,11 @@ class AlgorithmsInitializer : public Initializer Globals->abbreviations.insert("DenseHOG", "Gradient+RectRegions(8,8,6,6)+Bin(0,360,8)+Hist(8)"); Globals->abbreviations.insert("DenseSIFT", "(Grid(10,10)+SIFTDescriptor(12)+ByRow)"); Globals->abbreviations.insert("DenseSIFT2", "(Grid(5,5)+SIFTDescriptor(12)+ByRow)"); - Globals->abbreviations.insert("FaceRecognitionRegistration", "ASEFEyes+Affine(88,88,0.25,0.35)"); + Globals->abbreviations.insert("FaceRecognitionRegistration", "(ASEFEyes+Affine(88,88,0.25,0.35)+DownsampleTraining(FTE(DFFS),instances=1))"); Globals->abbreviations.insert("FaceRecognitionExtraction", "(Mask+DenseSIFT/DenseLBP+DownsampleTraining(PCA(0.95),instances=1)+Normalize(L2)+Cat)"); Globals->abbreviations.insert("FaceRecognitionEmbedding", "(Dup(12)+RndSubspace(0.05,1)+DownsampleTraining(LDA(0.98),instances=-2)+Cat+DownsampleTraining(PCA(768),instances=1))"); Globals->abbreviations.insert("FaceRecognitionQuantization", "(Normalize(L1)+Quantize)"); - Globals->abbreviations.insert("FaceClassificationRegistration", "ASEFEyes+Affine(56,72,0.33,0.45)"); + Globals->abbreviations.insert("FaceClassificationRegistration", "(ASEFEyes+Affine(56,72,0.33,0.45)+FTE(DFFS))"); Globals->abbreviations.insert("FaceClassificationExtraction", "((Grid(7,7)+SIFTDescriptor(8)+ByRow)/DenseLBP+DownsampleTraining(PCA(0.95),instances=-1, inputVariable=Gender)+Cat)"); Globals->abbreviations.insert("AgeRegressor", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Age)+DownsampleTraining(SVM(RBF,EPS_SVR,inputVariable=Age),instances=100, inputVariable=Age)"); Globals->abbreviations.insert("GenderClassifier", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Gender)+DownsampleTraining(SVM(RBF,C_SVC,inputVariable=Gender),instances=4000, inputVariable=Gender)"); diff --git a/openbr/plugins/cascade.cpp b/openbr/plugins/cascade.cpp index a0b282a..bd13f7f 100644 --- a/openbr/plugins/cascade.cpp +++ b/openbr/plugins/cascade.cpp @@ -252,8 +252,6 @@ class CascadeTransform : public MetaTransform void init() { cascadeResource.setResourceMaker(new CascadeResourceMaker(model)); - if (model == "Ear" || model == "Eye" || model == "FrontalFace" || model == "ProfileFace") - this->trainable = false; } // Train transform diff --git a/openbr/plugins/meta.cpp b/openbr/plugins/meta.cpp index 57888a5..df880d5 100644 --- a/openbr/plugins/meta.cpp +++ b/openbr/plugins/meta.cpp @@ -17,8 +17,6 @@ #include #include #include -#include - #include "openbr_internal.h" #include "openbr/core/common.h" #include "openbr/core/opencvutils.h" @@ -96,15 +94,17 @@ class PipeTransform : public CompositeTransform int i = 0; while (i < transforms.size()) { + fprintf(stderr, "\n%s", qPrintable(transforms[i]->objectName())); + // Conditional statement covers likely case that first transform is untrainable if (transforms[i]->trainable) { - qDebug() << "Training " << transforms[i]->description() << "\n..."; + fprintf(stderr, " training..."); transforms[i]->train(dataLines); } // if the transform is time varying, we can't project it in parallel if (transforms[i]->timeVarying()) { - qDebug() << "Projecting " << transforms[i]->description() << "\n..."; + fprintf(stderr, "\n%s projecting...", qPrintable(transforms[i]->objectName())); for (int j=0; j < dataLines.size();j++) { TemplateList junk; splitFTEs(dataLines[j], junk); @@ -130,16 +130,7 @@ class PipeTransform : public CompositeTransform !transforms[nextTrainableTransform]->timeVarying()) nextTrainableTransform++; - // No more trainable transforms? Don't need any more projects then - if (nextTrainableTransform == transforms.size()) - break; - - fprintf(stderr, "Projecting %s", qPrintable(transforms[i]->description())); - for (int j=i+1; j < nextTrainableTransform; j++) - fprintf(stderr,"+%s", qPrintable(transforms[j]->description())); - fprintf(stderr, "\n...\n"); - fflush(stderr); - + fprintf(stderr, " projecting..."); QFutureSynchronizer futures; for (int j=0; j < dataLines.size(); j++) futures.addFuture(QtConcurrent::run(this, &PipeTransform::_projectPartial, &dataLines[j], i, nextTrainableTransform)); @@ -519,6 +510,7 @@ class LoadStoreTransform : public MetaTransform public: Transform *transform; + QString baseName; LoadStoreTransform() : transform(NULL) {} @@ -548,8 +540,8 @@ private: void init() { if (transform != NULL) return; - if (fileName.isEmpty()) fileName = QRegExp("^[_a-zA-Z0-9]+$").exactMatch(transformString) ? transformString : QtUtils::shortTextHash(transformString); - + if (fileName.isEmpty()) baseName = QRegExp("^[_a-zA-Z0-9]+$").exactMatch(transformString) ? transformString : QtUtils::shortTextHash(transformString); + else baseName = fileName; if (!tryLoad()) transform = make(transformString); else @@ -561,28 +553,19 @@ private: return transform->timeVarying(); } - void train(const QList &data) + void train(const TemplateList &data) { if (QFileInfo(getFileName()).exists()) return; transform->train(data); - qDebug("Storing %s", qPrintable(fileName)); - QtUtils::BlockCompression compressedOut; - QFile fout(fileName); - QtUtils::touchDir(fout); - compressedOut.setBasis(&fout); - - QDataStream stream(&compressedOut); - QString desc = transform->description(); - - if (!compressedOut.open(QFile::WriteOnly)) - qFatal("Failed to open %s for writing.", qPrintable(file)); - - stream << desc; + qDebug("Storing %s", qPrintable(baseName)); + QByteArray byteArray; + QDataStream stream(&byteArray, QFile::WriteOnly); + stream << transform->description(); transform->store(stream); - compressedOut.close(); + QtUtils::writeFile(baseName, byteArray, -1); } void project(const Template &src, Template &dst) const @@ -612,8 +595,8 @@ private: QString getFileName() const { - if (QFileInfo(fileName).exists()) return fileName; - const QString file = Globals->sdkPath + "/share/openbr/models/transforms/" + fileName; + if (QFileInfo(baseName).exists()) return baseName; + const QString file = Globals->sdkPath + "/share/openbr/models/transforms/" + baseName; return QFileInfo(file).exists() ? file : QString(); } @@ -623,19 +606,12 @@ private: if (file.isEmpty()) return false; qDebug("Loading %s", qPrintable(file)); - QFile fin(file); - QtUtils::BlockCompression reader(&fin); - if (!reader.open(QIODevice::ReadOnly)) { - if (QFileInfo(file).exists()) qFatal("Unable to open %s for reading. Check file permissions.", qPrintable(file)); - else qFatal("Unable to open %s for reading. File does not exist.", qPrintable(file)); - } - - QDataStream stream(&reader); + QByteArray data; + QtUtils::readFile(file, data, true); + QDataStream stream(&data, QFile::ReadOnly); stream >> transformString; - transform = Transform::make(transformString); transform->load(stream); - return true; } }; diff --git a/openbr/plugins/quality.cpp b/openbr/plugins/quality.cpp index 11d4bbd..469009e 100644 --- a/openbr/plugins/quality.cpp +++ b/openbr/plugins/quality.cpp @@ -77,12 +77,6 @@ class ImpostorUniquenessMeasureTransform : public Transform BR_REGISTER(Transform, ImpostorUniquenessMeasureTransform) - -float KDEPointer(const QList *scores, double x, double h) -{ - return Common::KernelDensityEstimation(*scores, x, h); -} - /* Kernel Density Estimator */ struct KDE { @@ -91,35 +85,20 @@ struct KDE QList bins; KDE() : min(0), max(1), mean(0), stddev(1) {} - - KDE(const QList &scores, bool trainKDE) + KDE(const QList &scores) { Common::MinMax(scores, &min, &max); Common::MeanStdDev(scores, &mean, &stddev); - - if (!trainKDE) - return; - double h = Common::KernelDensityBandwidth(scores); const int size = 255; bins.reserve(size); - - QFutureSynchronizer futures; - - for (int i=0; i < size; i++) - futures.addFuture(QtConcurrent::run(KDEPointer, &scores, min + (max-min)*i/(size-1), h)); - futures.waitForFinished(); - - foreach(const QFuture & future, futures.futures()) - bins.append(future.result()); + for (int i=0; i::max(); - if (score <= min) return bins.first(); if (score >= max) return bins.last(); const float x = (score-min)/(max-min)*bins.size(); @@ -144,8 +123,8 @@ struct MP { KDE genuine, impostor; MP() {} - MP(const QList &genuineScores, const QList &impostorScores, bool trainKDE) - : genuine(genuineScores, trainKDE), impostor(impostorScores, trainKDE) {} + MP(const QList &genuineScores, const QList &impostorScores) + : genuine(genuineScores), impostor(impostorScores) {} float operator()(float score, bool gaussian = true) const { const float g = genuine(score, gaussian); @@ -186,7 +165,7 @@ class MatchProbabilityDistance : public Distance const QList labels = src.indexProperty(inputVariable); QScopedPointer matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size()))); distance->compare(src, src, matrixOutput.data()); - + QList genuineScores, impostorScores; genuineScores.reserve(labels.size()); impostorScores.reserve(labels.size()*labels.size()); @@ -199,8 +178,8 @@ class MatchProbabilityDistance : public Distance else impostorScores.append(score); } } - - mp = MP(genuineScores, impostorScores, !gaussian); + + mp = MP(genuineScores, impostorScores); } float compare(const Template &target, const Template &query) const diff --git a/share/openbr/models b/share/openbr/models index 85842e6..79938fe 160000 --- a/share/openbr/models +++ b/share/openbr/models @@ -1 +1 @@ -Subproject commit 85842e6da7738e317b9d40d5a395b92cd7b996e1 +Subproject commit 79938fe401faafead086b4711dd0b8f898a7a21e