diff --git a/openbr/core/boost.cpp b/openbr/core/boost.cpp index abf4de2..6a76366 100644 --- a/openbr/core/boost.cpp +++ b/openbr/core/boost.cpp @@ -806,6 +806,7 @@ CvDTreeNode* CascadeBoostTree::predict( int sampleIdx ) const return node; } +<<<<<<< HEAD static void writeRecursive(FileStorage &fs, CvDTreeNode *node, int maxCatCount) { @@ -880,6 +881,8 @@ void CascadeBoostTree::read(const FileNode &fn, CvBoost* _ensemble, CvDTreeTrain } */ +======= +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e void CascadeBoostTree::split_node_data( CvDTreeNode* node ) { int n = node->sample_count, nl, nr, scount = data->sample_count; @@ -1135,7 +1138,11 @@ bool CascadeBoost::train( const FeatureEvaluator* _featureEvaluator, break; } +<<<<<<< HEAD classifiers.append(tree); +======= + trees.append(tree); +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e cvSeqPush( weak, &tree ); update_weights( tree ); trim_weights(); @@ -1463,4 +1470,7 @@ bool CascadeBoost::isErrDesired() return falseAlarm <= maxFalseAlarm; } +<<<<<<< HEAD +======= +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e diff --git a/openbr/core/boost.h b/openbr/core/boost.h index d0d9635..39fa827 100644 --- a/openbr/core/boost.h +++ b/openbr/core/boost.h @@ -111,7 +111,6 @@ class CascadeBoostTree : public CvBoostTree { public: virtual CvDTreeNode* predict(int sampleIdx) const; - void write(cv::FileStorage &fs); protected: virtual void split_node_data(CvDTreeNode* n); @@ -126,14 +125,22 @@ public: virtual float predict( int sampleIdx, bool returnSum = false ) const; float getThreshold() const { return threshold; } +<<<<<<< HEAD QList getClassifiers() const { return classifiers; } +======= + const QList getTrees() const { return trees; } +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e protected: virtual bool set_params(const CvBoostParams& _params); virtual void update_weights(CvBoostTree* tree); virtual bool isErrDesired(); +<<<<<<< HEAD QList classifiers; +======= + QList trees; +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e float threshold; float minHitRate, maxFalseAlarm; diff --git a/openbr/core/cascade.cpp b/openbr/core/cascade.cpp index 84be00d..b5f7107 100644 --- a/openbr/core/cascade.cpp +++ b/openbr/core/cascade.cpp @@ -124,6 +124,7 @@ void br::groupRectangles(vector& rectList, vector& rejectLevels, vect static void loadRecursive(const FileNode &fn, _CascadeClassifier::Node *node, int maxCatCount) { bool hasChildren = (int)fn["hasChildren"]; + if (hasChildren) { if (maxCatCount > 1) { FileNode subset_fn = fn["subset"]; @@ -158,6 +159,7 @@ bool _CascadeClassifier::load(const string& filename) // load stages FileNode stages_fn = root["stages"]; + if( stages_fn.empty() ) return false; @@ -168,6 +170,7 @@ bool _CascadeClassifier::load(const string& filename) stage.threshold = (float)stage_fn["stageThreshold"] - THRESHOLD_EPS; FileNode nodes_fn = stage_fn["weakClassifiers"]; + if(nodes_fn.empty()) return false; diff --git a/openbr/plugins/classification/boostedforest.cpp b/openbr/plugins/classification/boostedforest.cpp index 7aade36..b16a361 100644 --- a/openbr/plugins/classification/boostedforest.cpp +++ b/openbr/plugins/classification/boostedforest.cpp @@ -10,16 +10,24 @@ struct Node { Node() : left(NULL), right(NULL) {} +<<<<<<< HEAD float value; float threshold; // For ordered features QList subset; // For categorical features int featureIdx; +======= + int featureIdx; + float threshold; // for ordered features only + QList subset; // for categorical features only + float value; // for leaf nodes only +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e Node *left; Node *right; }; +<<<<<<< HEAD static void buildTreeRecursive(Node *node, const CvDTreeNode *cv_node, int maxCatCount) { if (!cv_node->left) // Write the leaf value @@ -37,6 +45,26 @@ static void buildTreeRecursive(Node *node, const CvDTreeNode *cv_node, int maxCa buildTreeRecursive(node->left, cv_node->left, maxCatCount); node->right = new Node; buildTreeRecursive(node->right, cv_node->right, maxCatCount); +======= +static void buildTreeRecursive(Node *node, const CvDTreeNode *tree_node, int maxCatCount) +{ + if (tree_node->left) { + if (maxCatCount > 1) { + for (int i = 0; i < (maxCatCount + 31)/32; i++) + node->subset.append(tree_node->split->subset[i]); + } else { + node->threshold = tree_node->split->ord.c; + } + + node->featureIdx = tree_node->split->var_idx; + + node->left = new Node; + buildTreeRecursive(node->left, tree_node->left, maxCatCount); + node->right = new Node; + buildTreeRecursive(node->right, tree_node->right, maxCatCount); + } else { + node->value = tree_node->value; +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e } } @@ -46,10 +74,17 @@ static void writeRecursive(FileStorage &fs, const Node *node, int maxCatCount) fs << "hasChildren" << hasChildren; if (!hasChildren) // Write the leaf value +<<<<<<< HEAD fs << "value" << node->value; // value of the node. Only relevant for leaf nodes else { // Write the splitting information and then the children if (maxCatCount > 1) { fs << "subset" << "[:"; +======= + fs << "value" << node->value; // value of the node. + else { // Write the splitting information and then the children + if (maxCatCount > 1) { + fs << "subset" << "["; +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e for (int i = 0; i < ((maxCatCount + 31) / 32); i++) fs << node->subset[i]; // subset to split on (categorical features) fs << "]"; @@ -64,6 +99,32 @@ static void writeRecursive(FileStorage &fs, const Node *node, int maxCatCount) } } +<<<<<<< HEAD +======= +static void readRecursive(const FileNode &fn, Node *node, int maxCatCount) +{ + bool hasChildren = (int)fn["hasChildren"]; + if (!hasChildren) { + node->value = (float)fn["value"]; + } else { + if (maxCatCount > 1) { + FileNode subset_fn = fn["subset"]; + for (FileNodeIterator subset_it = subset_fn.begin(); subset_it != subset_fn.end(); ++subset_it) + node->subset.append((int)*subset_it); + } else { + node->threshold = (float)fn["threshold"]; + } + + node->featureIdx = (int)fn["feature_idx"]; + + node->left = new Node; + readRecursive(fn["left"], node->left, maxCatCount); + node->right = new Node; + readRecursive(fn["right"], node->right, maxCatCount); + } +} + +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e class BoostedForestClassifier : public Classifier { Q_OBJECT @@ -97,10 +158,18 @@ class BoostedForestClassifier : public Classifier CascadeBoost boost; boost.train(&featureEvaluator, images.size(), 2048, 2048, params); +<<<<<<< HEAD threshold = boost.getThreshold(); foreach (const CvBoostTree *tree, boost.getClassifiers()) { +======= + + // Convert into simpler, cleaner cascade after training + threshold = boost.getThreshold(); + + foreach (const CvBoostTree *tree, boost.getTrees()) { +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e Node *root = new Node; buildTreeRecursive(root, tree->get_root(), representation->maxCatCount()); weakClassifiers.append(root); @@ -109,6 +178,7 @@ class BoostedForestClassifier : public Classifier float classify(const Mat &image) const { +<<<<<<< HEAD float sum = 0; foreach (const Node *root, weakClassifiers) { const Node *node = root; @@ -119,12 +189,33 @@ class BoostedForestClassifier : public Classifier node = (node->subset[c >> 5] & (1 << (c & 31))) ? node->left : node->right; } else { float val = representation->evaluate(image, node->featureIdx); +======= + Mat pp; + representation->preprocess(image, pp); + + float sum = 0; + + foreach (const Node *node, weakClassifiers) { + while (node->left) { + if (representation->maxCatCount() > 1) { + int c = (int)representation->evaluate(pp, node->featureIdx); + node = (node->subset[c >> 5] & (1 << (c & 31))) ? node->left : node->right; + } else { + double val = representation->evaluate(pp, node->featureIdx); +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e node = val < node->threshold ? node->left : node->right; } } sum += node->value; } +<<<<<<< HEAD return sum < threshold - FLT_EPSILON ? 0.0 : 1.0; +======= + + if (sum < threshold) + return 0.0f; //-std::abs(sum); + return 1.0f; //std::abs(sum); +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e } int numFeatures() const @@ -144,7 +235,11 @@ class BoostedForestClassifier : public Classifier void write(FileStorage &fs) const { +<<<<<<< HEAD fs << "weakCount" << weakClassifiers.size(); +======= + fs << "numWeak" << weakClassifiers.size(); +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e fs << "stageThreshold" << threshold; fs << "weakClassifiers" << "["; foreach (const Node *root, weakClassifiers) { @@ -153,6 +248,25 @@ class BoostedForestClassifier : public Classifier fs << "}"; } fs << "]"; +<<<<<<< HEAD +======= + } + + void read(const FileNode &node) + { + weakClassifiers.reserve((int)node["numWeak"]); + threshold = (float)node["stageThreshold"]; + + FileNode weaks_fn = node["weakClassifiers"]; + for (FileNodeIterator weaks_it = weaks_fn.begin(); weaks_it != weaks_fn.end(); ++weaks_it) { + FileNode weak_fn = *weaks_it; + + Node *root = new Node; + readRecursive(weak_fn, root, representation->maxCatCount()); + + weakClassifiers.append(root); + } +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e } }; diff --git a/openbr/plugins/classification/cascade.cpp b/openbr/plugins/classification/cascade.cpp index 0baec84..5e2b520 100644 --- a/openbr/plugins/classification/cascade.cpp +++ b/openbr/plugins/classification/cascade.cpp @@ -112,12 +112,14 @@ class CascadeClassifier : public Classifier Q_PROPERTY(int numPos READ get_numPos WRITE set_numPos RESET reset_numPos STORED false) Q_PROPERTY(int numNegs READ get_numNegs WRITE set_numNegs RESET reset_numNegs STORED false) Q_PROPERTY(float maxFAR READ get_maxFAR WRITE set_maxFAR RESET reset_maxFAR STORED false) + Q_PROPERTY(bool ROCMode READ get_ROCMode WRITE set_ROCMode RESET reset_ROCMode STORED false) BR_PROPERTY(QString, stageDescription, "") BR_PROPERTY(int, numStages, 20) BR_PROPERTY(int, numPos, 1000) BR_PROPERTY(int, numNegs, 1000) BR_PROPERTY(float, maxFAR, pow(0.5, numStages)) + BR_PROPERTY(bool, ROCMode, false) QList stages; @@ -156,9 +158,21 @@ class CascadeClassifier : public Classifier float classify(const Mat &image) const { foreach (const Classifier *stage, stages) - if (stage->classify(image) == 0.0f) + if (stage->classify(image) == 0) return 0.0f; return 1.0f; + + /*if (stages.size() == 0) // special case for empty cascade + return 1.0f; + + float result = 0.0f; + for (int stageIdx = 0; stageIdx < stages.size(); stageIdx++) { + result = stages[stageIdx]->classify(image); + + if (result < 0) + return stageIdx > (stages.size() - 4) ? stageIdx * result : 0.0f; + } + return std::abs(stages.size() * result);*/ } int numFeatures() const @@ -178,9 +192,13 @@ class CascadeClassifier : public Classifier void write(FileStorage &fs) const { +<<<<<<< HEAD fs << "stageCount" << stages.size(); fs << "stages" << "["; +======= + fs << CC_STAGES << "["; +>>>>>>> 4fab7f69ddc82d6ba40a73fc6233e3cc9871473e foreach (const Classifier *stage, stages) { fs << "{"; stage->write(fs); @@ -199,7 +217,7 @@ private: if (!imgHandler.getPos(pos)) qFatal("Cannot get another positive sample!"); - if (classify(pos) == 1.0f) { + if (classify(pos) > 0.0f) { printf("POS current samples: %d\r", images.size()); images.append(pos); labels.append(1.0f); @@ -215,7 +233,7 @@ private: if (!imgHandler.getNeg(neg)) qFatal("Cannot get another negative sample!"); - if (classify(neg) == 1.0f) { + if (classify(neg) > 0.0f) { printf("NEG current samples: %d\r", images.size() - posCount); images.append(neg); labels.append(0.0f); diff --git a/openbr/plugins/imgproc/slidingwindow.cpp b/openbr/plugins/imgproc/slidingwindow.cpp index 76eef9e..dd8a9fe 100644 --- a/openbr/plugins/imgproc/slidingwindow.cpp +++ b/openbr/plugins/imgproc/slidingwindow.cpp @@ -21,6 +21,8 @@ #include #include +#include +#include using namespace cv; @@ -39,11 +41,23 @@ class SlidingWindowTransform : public Transform Q_OBJECT Q_PROPERTY(br::Classifier *classifier READ get_classifier WRITE set_classifier RESET reset_classifier STORED false) + Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) + Q_PROPERTY(int maxSize READ get_maxSize WRITE set_maxSize RESET reset_maxSize STORED false) + Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) + Q_PROPERTY(int minNeighbors READ get_minNeighbors WRITE set_minNeighbors RESET reset_minNeighbors STORED false) + Q_PROPERTY(float eps READ get_eps WRITE set_eps RESET reset_eps STORED false) + Q_PROPERTY(QString cascadeDir READ get_cascadeDir WRITE set_cascadeDir RESET reset_cascadeDir STORED false) Q_PROPERTY(QString vecFile READ get_vecFile WRITE set_vecFile RESET reset_vecFile STORED false) Q_PROPERTY(QString negFile READ get_negFile WRITE set_negFile RESET reset_negFile STORED false) BR_PROPERTY(br::Classifier *, classifier, NULL) + BR_PROPERTY(int, minSize, 24) + BR_PROPERTY(int, maxSize, -1) + BR_PROPERTY(float, scaleFactor, 1.2) + BR_PROPERTY(int, minNeighbors, 5) + BR_PROPERTY(float, eps, 0.2) + BR_PROPERTY(QString, cascadeDir, "") BR_PROPERTY(QString, vecFile, "vec.vec") BR_PROPERTY(QString, negFile, "neg.txt") @@ -162,7 +176,91 @@ class SlidingWindowTransform : public Transform void project(const Template &src, Template &dst) const { - (void)src; (void)dst; + TemplateList temp; + project(TemplateList() << src, temp); + if (!temp.isEmpty()) dst = temp.first(); + } + + void project(const TemplateList &src, TemplateList &dst) const + { + foreach (const Template &t, src) { + const bool enrollAll = t.file.getBool("enrollAll"); + + // Mirror the behavior of ExpandTransform in the special case + // of an empty template. + if (t.empty() && !enrollAll) { + dst.append(t); + continue; + } + + for (int i = 0; i < t.size(); i++) { + Mat image; + OpenCVUtils::cvtUChar(t[i], image); + + std::vector rects; + std::vector rejectLevels; + std::vector levelWeights; + + Size minObjectSize(minSize, minSize); + Size maxObjectSize(maxSize, maxSize); + if (maxObjectSize.height == 0 || maxObjectSize.width == 0) + maxObjectSize = image.size(); + + Mat imageBuffer(image.rows + 1, image.cols + 1, CV_8U); + + for (double factor = 1; ; factor *= scaleFactor) { + Size originalWindowSize = classifier->windowSize(); + + Size windowSize(cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) ); + Size scaledImageSize(cvRound(image.cols/factor ), cvRound(image.rows/factor)); + Size processingRectSize(scaledImageSize.width - originalWindowSize.width, scaledImageSize.height - originalWindowSize.height); + + if (processingRectSize.width <= 0 || processingRectSize.height <= 0) + break; + if (windowSize.width > maxObjectSize.width || windowSize.height > maxObjectSize.height) + break; + if (windowSize.width < minObjectSize.width || windowSize.height < minObjectSize.height) + continue; + + Mat scaledImage(scaledImageSize, CV_8U, imageBuffer.data); + resize(image, scaledImage, scaledImageSize, 0, 0, CV_INTER_LINEAR); + + int yStep = factor > 2. ? 1 : 2; + for (int y = 0; y < processingRectSize.height; y += yStep) { + for (int x = 0; x < processingRectSize.width; x += yStep) { + Mat window = scaledImage(Rect(Point(x, y), classifier->windowSize())).clone(); + + float result = classifier->classify(window); + + if (result > 0) { + rects.push_back(Rect(cvRound(x*factor), cvRound(y*factor), windowSize.width, windowSize.height)); + rejectLevels.push_back(1); + levelWeights.push_back(result); + } + if (result == 0) + x += yStep; + } + } + } + + groupRectangles(rects, rejectLevels, levelWeights, minNeighbors, eps); + + if (!enrollAll && rects.empty()) + rects.push_back(Rect(0, 0, image.cols, image.rows)); + + for (size_t j = 0; j < rects.size(); j++) { + Template u(t.file, image); + if (rejectLevels.size() > j) + u.file.set("Confidence", rejectLevels[j]*levelWeights[j]); + else + u.file.set("Confidence", 1); + const QRectF rect = OpenCVUtils::fromRect(rects[j]); + u.file.appendRect(rect); + u.file.set("Face", rect); + dst.append(u); + } + } + } } };