From 8b1dd0029b1f1e6d6952c5df0ddbfa01e88bc728 Mon Sep 17 00:00:00 2001 From: Jordan Cheney Date: Tue, 12 May 2015 20:02:07 -0400 Subject: [PATCH] Progress towards better organization --- openbr/core/boost.cpp | 167 +---------------------------------------------------------------------------------------------------------------------------------------------------------------------- openbr/core/boost.h | 6 +++--- openbr/core/cascade.cpp | 3 +++ openbr/plugins/classification/boostedforest.cpp | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- openbr/plugins/classification/cascade.cpp | 36 +++++++++++++++--------------------- openbr/plugins/imgproc/slidingwindow.cpp | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 263 insertions(+), 201 deletions(-) diff --git a/openbr/core/boost.cpp b/openbr/core/boost.cpp index 16ed736..252d480 100644 --- a/openbr/core/boost.cpp +++ b/openbr/core/boost.cpp @@ -178,21 +178,6 @@ CascadeBoostParams::CascadeBoostParams( int _boostType, use_surrogates = use_1se_rule = truncate_pruned_tree = false; } -void CascadeBoostParams::write( FileStorage &fs ) const -{ - string boostTypeStr = boost_type == CvBoost::DISCRETE ? CC_DISCRETE_BOOST : - boost_type == CvBoost::REAL ? CC_REAL_BOOST : - boost_type == CvBoost::LOGIT ? CC_LOGIT_BOOST : - boost_type == CvBoost::GENTLE ? CC_GENTLE_BOOST : string(); - CV_Assert( !boostTypeStr.empty() ); - fs << CC_BOOST_TYPE << boostTypeStr; - fs << CC_MINHITRATE << minHitRate; - fs << CC_MAXFALSEALARM << maxFalseAlarm; - fs << CC_TRIM_RATE << weight_trim_rate; - fs << CC_MAX_DEPTH << max_depth; - fs << CC_WEAK_COUNT << weak_count; -} - //---------------------------- CascadeBoostTrainData ----------------------------- CvDTreeNode* CascadeBoostTrainData::subsample_data( const CvMat* _subsample_idx ) @@ -826,139 +811,6 @@ CvDTreeNode* CascadeBoostTree::predict( int sampleIdx ) const return node; } -/* -static void writeRecursive(FileStorage &fs, CvDTreeNode *node, int maxCatCount) -{ - bool hasChildren = node->left ? true : false; - fs << "hasChildren" << hasChildren; - - if (!hasChildren) // Write the leaf value - fs << "value" << node->value; // value of the node. Only relevant for leaf nodes - else { // Write the splitting information and then the children - if (maxCatCount > 0) { - fs << "subset" << "[:"; - for (int i = 0; i < ((maxCatCount + 31) / 32); i++) - fs << node->split->subset[i]; // subset to split on (categorical features) - fs << "]"; - } else { - fs << "threshold" << node->split->ord.c; // threshold to split on (ordered features) - } - - fs << "feature_idx" << node->split->var_idx; // feature idx of node - - fs << "left" << "{"; writeRecursive(fs, node->left, maxCatCount); fs << "}"; // write left child - fs << "right" << "{"; writeRecursive(fs, node->right, maxCatCount); fs << "}"; // write right child - } -} - -void CascadeBoostTree::write(FileStorage &fs) -{ - fs << "{"; - writeRecursive(fs, root, ((CascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount()); - fs << "}"; -} - -static void readRecursive(const FileNode &fn, CvDTreeNode *node, CvDTreeTrainData *data) -{ - bool hasChildren = (int)fn["hasChildren"]; - - if (!hasChildren) - node->value = (float)fn["value"]; - else { - int maxCatCount = ((CascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount(); - if (maxCatCount > 0) { - node->split = data->new_split_cat(0, 0); - FileNode subset_node = fn["subset"]; FileNodeIterator subset_it = subset_node.begin(); - for (int i = 0; i < (maxCatCount + 31) / 32; i++, ++subset_it) - node->split->subset[i] = (int)*subset_it; - } else { - float threshold = (float)fn["threshold"]; - node->split = data->new_split_ord(0, threshold, 0, 0, 0); - } - - node->split->var_idx = (int)fn["feature_idx"]; - - CvDTreeNode *leftChild = data->new_node(node, 0, 0, 0); - node->left = leftChild; - readRecursive(fn["left"], leftChild, data); - - CvDTreeNode *rightChild = data->new_node(node, 0, 0, 0); - node->right = rightChild; - readRecursive(fn["right"], rightChild, data); - } -} - -void CascadeBoostTree::read(const FileNode &fn, CvBoost* _ensemble, CvDTreeTrainData* _data) -{ - clear(); - data = _data; - ensemble = _ensemble; - pruned_tree_idx = 0; - - root = data->new_node(0, 0, 0, 0); - readRecursive(fn, root, data); -}*/ - -void CascadeBoostTree::write(FileStorage &fs) -{ - int maxCatCount = ((CascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount(); - int subsetN = (maxCatCount + 31)/32; - queue internalNodesQueue; - int size = (int)pow( 2.f, (float)ensemble->get_params().max_depth); - Ptr leafVals = new float[size]; - int leafValIdx = 0; - int internalNodeIdx = 1; - CvDTreeNode* tempNode; - - CV_DbgAssert( root ); - internalNodesQueue.push( root ); - - fs << "{"; - fs << CC_INTERNAL_NODES << "[:"; - while (!internalNodesQueue.empty()) - { - tempNode = internalNodesQueue.front(); - CV_Assert( tempNode->left ); - if ( !tempNode->left->left && !tempNode->left->right) // left node is leaf - { - leafVals[-leafValIdx] = (float)tempNode->left->value; - fs << leafValIdx-- ; - } - else - { - internalNodesQueue.push( tempNode->left ); - fs << internalNodeIdx++; - } - CV_Assert( tempNode->right ); - if ( !tempNode->right->left && !tempNode->right->right) // right node is leaf - { - leafVals[-leafValIdx] = (float)tempNode->right->value; - fs << leafValIdx--; - } - else - { - internalNodesQueue.push( tempNode->right ); - fs << internalNodeIdx++; - } - int fidx = tempNode->split->var_idx; - - fs << fidx; - if ( !maxCatCount ) - fs << tempNode->split->ord.c; - else - for( int i = 0; i < subsetN; i++ ) - fs << tempNode->split->subset[i]; - internalNodesQueue.pop(); - } - fs << "]"; // CC_INTERNAL_NODES - - fs << CC_LEAF_VALUES << "[:"; - for (int ni = 0; ni < -leafValIdx; ni++) - fs << leafVals[ni]; - fs << "]"; // CC_LEAF_VALUES - fs << "}"; -} - void CascadeBoostTree::split_node_data( CvDTreeNode* node ) { int n = node->sample_count, nl, nr, scount = data->sample_count; @@ -1214,6 +1066,7 @@ bool CascadeBoost::train( const FeatureEvaluator* _featureEvaluator, break; } + trees.append(tree); cvSeqPush( weak, &tree ); update_weights( tree ); trim_weights(); @@ -1541,21 +1394,3 @@ bool CascadeBoost::isErrDesired() return falseAlarm <= maxFalseAlarm; } - -void CascadeBoost::write(FileStorage &fs) const -{ -// char cmnt[30]; - CascadeBoostTree* weakTree; - fs << CC_WEAK_COUNT << weak->total; - fs << CC_STAGE_THRESHOLD << threshold; - fs << CC_WEAK_CLASSIFIERS << "["; - for( int wi = 0; wi < weak->total; wi++) - { - /*sprintf( cmnt, "tree %i", wi ); - cvWriteComment( fs, cmnt, 0 );*/ - weakTree = *((CascadeBoostTree**) cvGetSeqElem( weak, wi )); - weakTree->write(fs); - } - fs << "]"; -} - diff --git a/openbr/core/boost.h b/openbr/core/boost.h index 136143a..e92b688 100644 --- a/openbr/core/boost.h +++ b/openbr/core/boost.h @@ -77,7 +77,6 @@ struct CascadeBoostParams : CvBoostParams CascadeBoostParams(int _boostType, float _minHitRate, float _maxFalseAlarm, double _weightTrimRate, int _maxDepth, int _maxWeakCount); virtual ~CascadeBoostParams() {} - void write( cv::FileStorage &fs ) const; }; struct CascadeBoostTrainData : CvDTreeTrainData @@ -113,7 +112,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); @@ -128,13 +126,15 @@ public: virtual float predict( int sampleIdx, bool returnSum = false ) const; float getThreshold() const { return threshold; } - void write(cv::FileStorage &fs) const; + const QList getTrees() const { return trees; } protected: virtual bool set_params(const CvBoostParams& _params); virtual void update_weights(CvBoostTree* tree); virtual bool isErrDesired(); + QList trees; + 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 fc4bb09..015570e 100644 --- a/openbr/plugins/classification/boostedforest.cpp +++ b/openbr/plugins/classification/boostedforest.cpp @@ -6,6 +6,86 @@ using namespace cv; namespace br { +struct Node +{ + Node() : left(NULL), right(NULL) {} + + int featureIdx; + float threshold; // for ordered features only + QList subset; // for categorical features only + float value; // for leaf nodes only + Node *left; + Node *right; +}; + +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; + } +} + +static void writeRecursive(FileStorage &fs, const Node *node, int maxCatCount) +{ + bool hasChildren = node->left ? true : false; + fs << "hasChildren" << hasChildren; + + if (!hasChildren) // Write the leaf value + fs << "value" << node->value; // value of the node. + else { // Write the splitting information and then the children + if (maxCatCount > 1) { + fs << "subset" << "["; + for (int i = 0; i < ((maxCatCount + 31) / 32); i++) + fs << node->subset[i]; // subset to split on (categorical features) + fs << "]"; + } else { + fs << "threshold" << node->threshold; // threshold to split on (ordered features) + } + + fs << "feature_idx" << node->featureIdx; // feature idx of node + + fs << "left" << "{"; writeRecursive(fs, node->left, maxCatCount); fs << "}"; // write left child + fs << "right" << "{"; writeRecursive(fs, node->right, maxCatCount); fs << "}"; // write right child + } +} + +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); + } +} + class BoostedForestClassifier : public Classifier { Q_OBJECT @@ -24,27 +104,55 @@ class BoostedForestClassifier : public Classifier BR_PROPERTY(int, maxDepth, 1) BR_PROPERTY(int, maxWeakCount, 100) - CascadeBoost *boost; - FeatureEvaluator *featureEvaluator; + QList weakClassifiers; + float threshold; void train(const QList &images, const QList &labels) { CascadeBoostParams params(CvBoost::GENTLE, minTAR, maxFAR, trimRate, maxDepth, maxWeakCount); - featureEvaluator = new FeatureEvaluator; - featureEvaluator->init(representation, images.size()); + FeatureEvaluator featureEvaluator; + featureEvaluator.init(representation, images.size()); for (int i = 0; i < images.size(); i++) - featureEvaluator->setImage(images[i], labels[i], i); + featureEvaluator.setImage(images[i], labels[i], i); + + CascadeBoost boost; + boost.train(&featureEvaluator, images.size(), 1024, 1024, params); - boost = new CascadeBoost; - boost->train(featureEvaluator, images.size(), 1024, 1024, params); + // Convert into simpler, cleaner cascade after training + threshold = boost.getThreshold(); + + foreach (const CvBoostTree *tree, boost.getTrees()) { + Node *root = new Node; + buildTreeRecursive(root, tree->get_root(), representation->maxCatCount()); + weakClassifiers.append(root); + } } float classify(const Mat &image) const { - featureEvaluator->setImage(image, 0, 0); - return boost->predict(0); + 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); + node = val < node->threshold ? node->left : node->right; + } + } + sum += node->value; + } + + if (sum < threshold) + return -std::abs(sum); + return std::abs(sum); } int numFeatures() const @@ -64,7 +172,31 @@ class BoostedForestClassifier : public Classifier void write(FileStorage &fs) const { - boost->write(fs); + fs << "numWeak" << weakClassifiers.size(); + fs << "stageThreshold" << threshold; + fs << "weakClassifiers" << "["; + foreach (const Node *root, weakClassifiers) { + fs << "{"; + writeRecursive(fs, root, representation->maxCatCount()); + fs << "}"; + } + fs << "]"; + } + + 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); + } } }; diff --git a/openbr/plugins/classification/cascade.cpp b/openbr/plugins/classification/cascade.cpp index ec00c67..43a34bd 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; @@ -155,10 +157,17 @@ class CascadeClassifier : public Classifier float classify(const Mat &image) const { - foreach (const Classifier *stage, stages) - if (stage->classify(image) == 0.0f) - 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,21 +187,6 @@ class CascadeClassifier : public Classifier void write(FileStorage &fs) const { - fs << CC_STAGE_TYPE << CC_BOOST; - fs << CC_FEATURE_TYPE << CC_LBP; - fs << CC_HEIGHT << 24; - fs << CC_WIDTH << 24; - - CascadeBoostParams stageParams(CvBoost::GINI, 0.999, 0.5, 0.95, 1, 200); - fs << CC_STAGE_PARAMS << "{"; stageParams.write( fs ); fs << "}"; - - fs << CC_FEATURE_PARAMS << "{"; - fs << CC_MAX_CAT_COUNT << stages.first()->maxCatCount(); - fs << CC_FEATURE_SIZE << 1; - fs << "}"; - - fs << CC_STAGE_NUM << stages.size(); - fs << CC_STAGES << "["; foreach (const Classifier *stage, stages) { fs << "{"; @@ -212,7 +206,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); @@ -228,7 +222,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); + } + } + } } }; -- libgit2 0.21.4