diff --git a/openbr/core/boost.cpp b/openbr/core/boost.cpp index b8dcc3b..bbc0997 100644 --- a/openbr/core/boost.cpp +++ b/openbr/core/boost.cpp @@ -1158,6 +1158,7 @@ float CascadeBoost::predict( int sampleIdx, bool returnSum ) const { CV_Assert( weak ); double sum = 0; + CvSeqReader reader; cvStartReadSeq( weak, &reader ); cvSetSeqReaderPos( &reader, 0 ); @@ -1167,6 +1168,7 @@ float CascadeBoost::predict( int sampleIdx, bool returnSum ) const CV_READ_SEQ_ELEM( wtree, reader ); sum += ((CascadeBoostTree*)wtree)->predict(sampleIdx)->value; } + if( !returnSum ) sum = sum < threshold - CV_THRESHOLD_EPS ? 0.0 : 1.0; return (float)sum; diff --git a/openbr/core/cascade.cpp b/openbr/core/cascade.cpp index fa6d477..cc568b1 100644 --- a/openbr/core/cascade.cpp +++ b/openbr/core/cascade.cpp @@ -130,10 +130,12 @@ bool BrCascadeClassifier::train(const string _cascadeDirName, numStages = _numStages; imgReader.create(_posImages, _negImages, winSize); + Representation *representation = Representation::make("MBLBP(24,24)", NULL); + stageParams = new CascadeBoostParams; *stageParams = _stageParams; featureEvaluator = new FeatureEvaluator; - featureEvaluator->init(numPos + numNeg, winSize); + featureEvaluator->init(representation, numPos + numNeg); stageClassifiers.reserve( numStages ); double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) / diff --git a/openbr/core/features.cpp b/openbr/core/features.cpp index 132a0a5..fe77263 100644 --- a/openbr/core/features.cpp +++ b/openbr/core/features.cpp @@ -5,65 +5,22 @@ using namespace br; //------------------------------------- FeatureEvaluator --------------------------------------- -void FeatureEvaluator::init(int _maxSampleCount, Size _winSize ) +void FeatureEvaluator::init(Representation *_representation, int _maxSampleCount) { - CV_Assert(_maxSampleCount > 0); - winSize = _winSize; - numFeatures = 0; - data.create((int)_maxSampleCount, (_winSize.width + 1) * (_winSize.height + 1), CV_32SC1); + representation = _representation; + data.create((int)_maxSampleCount, representation->postWindowSize().area(), CV_32SC1); cls.create( (int)_maxSampleCount, 1, CV_32FC1 ); - - maxCatCount = 256; - featSize = 1; - - generateFeatures(); } void FeatureEvaluator::setImage(const Mat &img, uchar clsLabel, int idx) { - CV_Assert(img.cols == winSize.width); - CV_Assert(img.rows == winSize.height); - CV_Assert(idx < cls.rows); cls.ptr(idx)[0] = clsLabel; - Mat integralImg(winSize.height + 1, winSize.width + 1, data.type(), data.ptr(idx)); - integral(img, integralImg); -} - -void FeatureEvaluator::writeFeatures(FileStorage &fs, const Mat &featureMap) const -{ - _writeFeatures( features, fs, featureMap ); -} -void FeatureEvaluator::generateFeatures() -{ - int offset = winSize.width + 1; - for (int x = 0; x < winSize.width; x++) - for (int y = 0; y < winSize.height; y++) - for (int w = 1; w <= winSize.width / 3; w++) - for (int h = 1; h <= winSize.height / 3; h++) - if ((x+3*w <= winSize.width) && (y+3*h <= winSize.height)) - features.push_back(Feature(offset, x, y, w, h )); - numFeatures = (int)features.size(); + Mat integralImg(representation->postWindowSize(), data.type(), data.ptr(idx)); + representation->preprocess(img, integralImg); } -FeatureEvaluator::Feature::Feature() -{ - rect = cvRect(0, 0, 0, 0); -} - -FeatureEvaluator::Feature::Feature( int offset, int x, int y, int _blockWidth, int _blockHeight ) -{ - Rect tr = rect = cvRect(x, y, _blockWidth, _blockHeight); - CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr, offset ) - tr.x += 2*rect.width; - CV_SUM_OFFSETS( p[2], p[3], p[6], p[7], tr, offset ) - tr.y +=2*rect.height; - CV_SUM_OFFSETS( p[10], p[11], p[14], p[15], tr, offset ) - tr.x -= 2*rect.width; - CV_SUM_OFFSETS( p[8], p[9], p[12], p[13], tr, offset ) -} - -void FeatureEvaluator::Feature::write(FileStorage &fs) const +void FeatureEvaluator::writeFeatures(FileStorage &fs, const Mat &featureMap) const { - fs << CC_RECT << "[:" << rect.x << rect.y << rect.width << rect.height << "]"; + representation->write(fs, featureMap); } diff --git a/openbr/core/features.h b/openbr/core/features.h index 263b0f4..c21c80d 100644 --- a/openbr/core/features.h +++ b/openbr/core/features.h @@ -48,90 +48,28 @@ #define TIME( arg ) (time( arg )) #endif -#define CV_SUM_OFFSETS( p0, p1, p2, p3, rect, step ) \ - /* (x, y) */ \ - (p0) = (rect).x + (step) * (rect).y; \ - /* (x + w, y) */ \ - (p1) = (rect).x + (rect).width + (step) * (rect).y; \ - /* (x + w, y) */ \ - (p2) = (rect).x + (step) * ((rect).y + (rect).height); \ - /* (x + w, y + h) */ \ - (p3) = (rect).x + (rect).width + (step) * ((rect).y + (rect).height); - namespace br { -template -void _writeFeatures( const std::vector features, cv::FileStorage &fs, const cv::Mat& featureMap ) -{ - fs << CC_FEATURES << "["; - const cv::Mat_& featureMap_ = (const cv::Mat_&)featureMap; - for ( int fi = 0; fi < featureMap.cols; fi++ ) - if ( featureMap_(0, fi) >= 0 ) - { - fs << "{"; - features[fi].write( fs ); - fs << "}"; - } - fs << "]"; -} - class FeatureEvaluator { public: ~FeatureEvaluator() {} - void init(int _maxSampleCount, cv::Size _winSize); + void init(Representation *_representation, int _maxSampleCount); void setImage(const cv::Mat& img, uchar clsLabel, int idx); void writeFeatures(cv::FileStorage &fs, const cv::Mat& featureMap) const; - float operator()(int featureIdx, int sampleIdx) const { return (float)features[featureIdx].calc(data, sampleIdx); } + float operator()(int featureIdx, int sampleIdx) const { return representation->evaluate(data.row(sampleIdx), featureIdx); } - int getNumFeatures() const { return numFeatures; } - int getMaxCatCount() const { return maxCatCount; } - int getFeatureSize() const { return featSize; } + int getNumFeatures() const { return representation->numFeatures(); } + int getMaxCatCount() const { return representation->maxCatCount(); } + int getFeatureSize() const { return 1; } const cv::Mat& getCls() const { return cls; } float getCls(int si) const { return cls.at(si, 0); } -protected: - void generateFeatures(); - - class Feature - { - public: - Feature(); - Feature( int offset, int x, int y, int _block_w, int _block_h ); - uchar calc( const cv::Mat &data, int y ) const; - void write( cv::FileStorage &fs ) const; - - cv::Rect rect; - int p[16]; - }; - std::vector features; - cv::Mat data, cls; - - int npos, nneg; - int numFeatures; - int maxCatCount; // 0 in case of numerical features - int featSize; // 1 in case of simple features (HAAR, LBP) and N_BINS(9)*N_CELLS(4) in case of Dalal's HOG features - - cv::Size winSize; + Representation *representation; }; -inline uchar FeatureEvaluator::Feature::calc(const cv::Mat &data, int y) const -{ - const int* ptr = data.ptr(y); - int cval = ptr[p[5]] - ptr[p[6]] - ptr[p[9]] + ptr[p[10]]; - - return (uchar)((ptr[p[0]] - ptr[p[1]] - ptr[p[4]] + ptr[p[5]] >= cval ? 128 : 0) | // 0 - (ptr[p[1]] - ptr[p[2]] - ptr[p[5]] + ptr[p[6]] >= cval ? 64 : 0) | // 1 - (ptr[p[2]] - ptr[p[3]] - ptr[p[6]] + ptr[p[7]] >= cval ? 32 : 0) | // 2 - (ptr[p[6]] - ptr[p[7]] - ptr[p[10]] + ptr[p[11]] >= cval ? 16 : 0) | // 5 - (ptr[p[10]] - ptr[p[11]] - ptr[p[14]] + ptr[p[15]] >= cval ? 8 : 0) | // 8 - (ptr[p[9]] - ptr[p[10]] - ptr[p[13]] + ptr[p[14]] >= cval ? 4 : 0) | // 7 - (ptr[p[8]] - ptr[p[9]] - ptr[p[12]] + ptr[p[13]] >= cval ? 2 : 0) | // 6 - (ptr[p[4]] - ptr[p[5]] - ptr[p[8]] + ptr[p[9]] >= cval ? 1 : 0)); // 3 -} - } // namespace br #endif // FEATURE_H diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index bce5790..438679c 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -1395,18 +1395,27 @@ class BR_EXPORT Representation : public Object { Q_OBJECT + Q_PROPERTY(int winWidth READ get_winWidth WRITE set_winWidth RESET reset_winWidth STORED false) + Q_PROPERTY(int winHeight READ get_winHeight WRITE set_winHeight RESET reset_winHeight STORED false) + BR_PROPERTY(int, winWidth, 24) + BR_PROPERTY(int, winHeight, 24) + public: virtual ~Representation() {} static Representation *make(QString str, QObject *parent); /*!< \brief Make a representation from a string. */ - virtual cv::Mat preprocess(const cv::Mat &image) const { return image; } + virtual void preprocess(const cv::Mat &src, cv::Mat &dst) const { dst = src; } virtual void train(const QList &images, const QList &labels) { (void) images; (void)labels; } + + virtual float evaluate(const cv::Mat &image, int idx) const = 0; // By convention, an empty indices list will result in all feature responses being calculated // and returned. virtual cv::Mat evaluate(const cv::Mat &image, const QList &indices = QList()) const = 0; + virtual cv::Size preWindowSize() const = 0; // window size before preprocessing + virtual cv::Size postWindowSize() const = 0; // window size after preprocessing virtual int numFeatures() const = 0; - virtual cv::Size windowSize() const = 0; + virtual int maxCatCount() const = 0; // Temporary for OpenCV compatibility virtual void write( cv::FileStorage &fs, const cv::Mat &featureMap ) { (void)fs; (void)featureMap; } @@ -1421,12 +1430,20 @@ public: static Classifier *make(QString str, QObject *parent); /*!< \brief Make a classifier from a string. */ - virtual Classifier *clone() const { return Factory::make("." + description(false)); } - virtual void train(const QList &images, const QList &labels) = 0; // By convention, classify should return a value normalized such that the threshold is 0. Negative values // can be interpreted as a negative classification and positive values as a positive classification. virtual float classify(const cv::Mat &image) const = 0; + + // Slots for representation + virtual cv::Size windowSize() const = 0; + + // OpenCV compatibility + virtual int numFeatures() const = 0; + virtual int maxCatCount() const = 0; + virtual void getUsedFeatures(cv::Mat &featureMap) const { (void)featureMap; return; } + virtual void write(cv::FileStorage &fs, const cv::Mat &featureMap) const { (void)fs; (void)featureMap; } + virtual void writeFeatures(cv::FileStorage &fs, const cv::Mat &featureMap) const { (void)fs; (void)featureMap; } }; /*! diff --git a/openbr/plugins/classification/boostedforest.cpp b/openbr/plugins/classification/boostedforest.cpp index 9a98992..8e0e0a0 100644 --- a/openbr/plugins/classification/boostedforest.cpp +++ b/openbr/plugins/classification/boostedforest.cpp @@ -11,12 +11,14 @@ class BoostedForestClassifier : public Classifier { Q_OBJECT + Q_PROPERTY(br::Representation *representation READ get_representation WRITE set_representation RESET reset_representation STORED false) Q_PROPERTY(float minTAR READ get_minTAR WRITE set_minTAR RESET reset_minTAR STORED false) Q_PROPERTY(float maxFAR READ get_maxFAR WRITE set_maxFAR RESET reset_maxFAR STORED false) Q_PROPERTY(float trimRate READ get_trimRate WRITE set_trimRate RESET reset_trimRate STORED false) Q_PROPERTY(int maxDepth READ get_maxDepth WRITE set_maxDepth RESET reset_maxDepth STORED false) Q_PROPERTY(int maxWeakCount READ get_maxWeakCount WRITE set_maxWeakCount RESET reset_maxWeakCount STORED false) + BR_PROPERTY(br::Representation *, representation, NULL) BR_PROPERTY(float, minTAR, 0.995) BR_PROPERTY(float, maxFAR, 0.5) BR_PROPERTY(float, trimRate, 0.95) @@ -24,16 +26,56 @@ class BoostedForestClassifier : public Classifier BR_PROPERTY(int, maxWeakCount, 100) CascadeBoost *boost; + FeatureEvaluator *featureEvaluator; void train(const QList &images, const QList &labels) { - (void)images; (void)labels; + CascadeBoostParams params(CvBoost::GENTLE, minTAR, maxFAR, trimRate, maxDepth, maxWeakCount); + + featureEvaluator = new FeatureEvaluator; + featureEvaluator->init(representation, images.size()); + + for (int i = 0; i < images.size(); i++) + featureEvaluator->setImage(images[i], labels[i], i); + + boost = new CascadeBoost; + boost->train(featureEvaluator, images.size(), 1024, 1024, params); } float classify(const Mat &image) const { - (void) image; - return 0.; + featureEvaluator->setImage(image, 0, 0); + return boost->predict(0); + } + + int numFeatures() const + { + return representation->numFeatures(); + } + + int maxCatCount() const + { + return representation->maxCatCount(); + } + + Size windowSize() const + { + return representation->preWindowSize(); + } + + void getUsedFeatures(Mat &featureMap) const + { + boost->markUsedFeaturesInMap(featureMap); + } + + void write(FileStorage &fs, const Mat &featureMap) const + { + boost->write(fs, featureMap); + } + + void writeFeatures(FileStorage &fs, const Mat &featureMap) const + { + featureEvaluator->writeFeatures(fs, featureMap); } }; diff --git a/openbr/plugins/classification/cascade.cpp b/openbr/plugins/classification/cascade.cpp index 4006403..c4b6f3f 100644 --- a/openbr/plugins/classification/cascade.cpp +++ b/openbr/plugins/classification/cascade.cpp @@ -1,22 +1,135 @@ #include -#include +#include +#include using namespace cv; namespace br { +struct ImageHandler +{ + bool create( const QList &_posImages, const QList &_negImages, cv::Size _winSize ) + { + posImages = _posImages; + negImages = _negImages; + winSize = _winSize; + + posIdx = negIdx = 0; + + src.create( 0, 0 , CV_8UC1 ); + img.create( 0, 0, CV_8UC1 ); + point = offset = Point( 0, 0 ); + scale = 1.0F; + scaleFactor = 1.4142135623730950488016887242097F; + stepFactor = 0.5F; + round = 0; + + return true; + } + + void restart() { posIdx = 0; } + + int numPos() const { return posImages.size(); } + int numNeg() const { return negImages.size(); } + + bool nextNeg() + { + Point _offset = Point(0,0); + size_t count = negImages.size(); + for (size_t i = 0; i < count; i++) { + src = negImages[negIdx++]; + if( src.empty() ) + continue; + round += negIdx / count; + round = round % (winSize.width * winSize.height); + negIdx %= count; + + _offset.x = std::min( (int)round % winSize.width, src.cols - winSize.width ); + _offset.y = std::min( (int)round / winSize.width, src.rows - winSize.height ); + if( !src.empty() && src.type() == CV_8UC1 && _offset.x >= 0 && _offset.y >= 0 ) + break; + } + + if( src.empty() ) + return false; // no appropriate image + point = offset = _offset; + scale = max( ((float)winSize.width + point.x) / ((float)src.cols), + ((float)winSize.height + point.y) / ((float)src.rows) ); + + Size sz( (int)(scale*src.cols + 0.5F), (int)(scale*src.rows + 0.5F) ); + resize( src, img, sz ); + return true; + } + + bool getNeg(cv::Mat &_img) + { + if( img.empty() ) + if ( !nextNeg() ) + return false; + + Mat mat( winSize.height, winSize.width, CV_8UC1, + (void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step ); + mat.copyTo(_img); + + if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols ) + point.x += (int)(stepFactor * winSize.width); + else + { + point.x = offset.x; + if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows ) + point.y += (int)(stepFactor * winSize.height); + else + { + point.y = offset.y; + scale *= scaleFactor; + if( scale <= 1.0F ) + resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ) ); + else + { + if ( !nextNeg() ) + return false; + } + } + } + return true; + } + + bool getPos(cv::Mat &_img) + { + if (posIdx >= posImages.size()) + return false; + + posImages[posIdx++].copyTo(_img); + return true; + } + + QList posImages, negImages; + + int posIdx, negIdx; + + cv::Mat src, img; + cv::Point offset, point; + float scale; + float scaleFactor; + float stepFactor; + size_t round; + cv::Size winSize; +}; + class _CascadeClassifier : public Classifier { Q_OBJECT - Q_PROPERTY(br::Classifier *stage READ get_stage WRITE set_stage RESET reset_stage STORED false) + Q_PROPERTY(QString stageDescription READ get_stageDescription WRITE set_stageDescription RESET reset_stageDescription STORED false) Q_PROPERTY(int numStages READ get_numStages WRITE set_numStages RESET reset_numStages STORED false) + 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) - BR_PROPERTY(br::Classifier *, stage, NULL) + 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)) @@ -28,127 +141,132 @@ class _CascadeClassifier : public Classifier for (int i = 0; i < images.size(); i++) labels[i] == 1 ? posImages.append(images[i]) : negImages.append(images[i]); - QList trainingImages; - QList trainingLabels; + ImageHandler imgHandler; + imgHandler.create(posImages, negImages, Size(24, 24)); + stages.reserve(numStages); for (int i = 0; i < numStages; i++) { - float currFAR = updateTrainingSet(posImages, negImages, trainingImages, trainingLabels); + qDebug() << "===== TRAINING" << i << "stage ====="; + qDebug() << " trainingImages; + QList trainingLabels; + + float currFAR = fillTrainingSet(imgHandler, trainingImages, trainingLabels); if (currFAR < maxFAR) { qDebug() << "FAR is below required level! Terminating early"; return; } - Classifier *next_stage = stage->clone(); + Classifier *next_stage = Classifier::make(stageDescription, NULL); next_stage->train(trainingImages, trainingLabels); stages.append(next_stage); + + qDebug() << "END>"; } } float classify(const Mat &image) const { - (void) image; - return 0.; + foreach (const Classifier *stage, stages) + if (stage->classify(image) == 0.0f) + return 0.0f; + return 1.0f; } - float updateTrainingSet(const QList &posImages, const QList &negImages, QList &trainingImages, QList &trainingLabels) + int numFeatures() const { - trainingImages.clear(); - trainingLabels.clear(); + return stages.first()->numFeatures(); + } - foreach (const Mat &pos, posImages) { - if (classify(pos) > 0) { - trainingImages.append(pos); - trainingLabels.append(1.); - } - } + int maxCatCount() const + { + return stages.first()->maxCatCount(); + } - NegFinder finder(negImages, Size(24, 24)); - int totalNegs = 0, passedNegs = 0; - while (true) { - totalNegs++; - Mat neg = finder.get(); - if (classify(neg) > 0) { - trainingImages.append(neg); - trainingLabels.append(0.); - passedNegs++; - } + cv::Size windowSize() const + { + return stages.first()->windowSize(); + } - if (passedNegs >= numNegs) - return passedNegs / (float)totalNegs; - } + void getUsedFeatures(Mat &featureMap) const + { + foreach (const Classifier *stage, stages) + stage->getUsedFeatures(featureMap); } -private: - struct NegFinder + void write(FileStorage &fs, const Mat &featureMap) const { - NegFinder(const QList &_negs, Size _winSize) - { - negs = _negs; - winSize = _winSize; - - negIdx = round = 0; - img.create( 0, 0, CV_8UC1 ); - point = offset = Point( 0, 0 ); - scale = 1.0F; - scaleFactor = 1.4142135623730950488016887242097F; - stepFactor = 0.5F; - } + fs << CC_STAGE_TYPE << CC_BOOST; + fs << CC_FEATURE_TYPE << CC_LBP; + fs << CC_HEIGHT << 24; + fs << CC_WIDTH << 24; - void _next() - { - src = negs[negIdx++]; + 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(); + + char cmnt[30]; + int i = 0; + fs << CC_STAGES << "["; + foreach (const Classifier *stage, stages) { + sprintf( cmnt, "stage %d", i ); + cvWriteComment( fs.fs, cmnt, 0 ); + fs << "{"; + stage->write(fs, featureMap); + fs << "}"; + } + fs << "]"; + } - round += negIdx / negs.size(); - round %= (winSize.width * winSize.height); - negIdx %= negs.size(); + void writeFeatures(FileStorage &fs, const Mat& featureMap) const + { + stages.first()->writeFeatures(fs, featureMap); + } - point = offset = Point(std::min(round % winSize.width, src.cols - winSize.width), - std::min(round / winSize.width, src.rows - winSize.height)); +private: + float fillTrainingSet(ImageHandler &imgHandler, QList &images, QList &labels) + { + imgHandler.restart(); - scale = max(((float)winSize.width + point.x) / ((float)src.cols), - ((float)winSize.height + point.y) / ((float)src.rows)); + while (images.size() < numPos) { + Mat pos(imgHandler.winSize, CV_8UC1); + if (!imgHandler.getPos(pos)) + qFatal("Cannot get another positive sample!"); - Size sz((int)(scale*src.cols + 0.5F), (int)(scale*src.rows + 0.5F)); - resize(src, img, sz); + if (classify(pos) == 1.0f) { + images.append(pos); + labels.append(1.0f); + } } - Mat get() - { - if (img.empty()) - _next(); - - Mat neg(winSize, CV_8UC1); - Mat m(winSize.height, winSize.width, CV_8UC1, (void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step); - m.copyTo(neg); - - if ((int)(point.x + (1.0F + stepFactor ) * winSize.width) < img.cols) - point.x += (int)(stepFactor * winSize.width); - else { - point.x = offset.x; - if ((int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows) - point.y += (int)(stepFactor * winSize.height); - else { - point.y = offset.y; - scale *= scaleFactor; - if( scale <= 1.0F ) - resize(src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows))); - else - _next(); - } + int posCount = images.size(); + qDebug() << "POS count : consumed " << posCount << ":" << imgHandler.posIdx; + + int passedNegs = 0; + while ((images.size() - posCount) < numNegs) { + Mat neg(imgHandler.winSize, CV_8UC1); + if (!imgHandler.getNeg(neg)) + qFatal("Cannot get another negative sample!"); + + if (classify(neg) == 1.0f) { + images.append(neg); + labels.append(0.0f); } - return neg; + passedNegs++; } - QList negs; - int negIdx, round; - Mat src, img; - float scale; - float scaleFactor; - float stepFactor; - Size winSize; - Point offset, point; - }; + double acceptanceRatio = (images.size() - posCount) / (double)passedNegs; + qDebug() << "NEG count : acceptanceRatio " << images.size() - posCount << ":" << acceptanceRatio; + return acceptanceRatio; + } }; BR_REGISTER(Classifier, _CascadeClassifier) diff --git a/openbr/plugins/imgproc/slidingwindow.cpp b/openbr/plugins/imgproc/slidingwindow.cpp index 24ce263..7d32f81 100644 --- a/openbr/plugins/imgproc/slidingwindow.cpp +++ b/openbr/plugins/imgproc/slidingwindow.cpp @@ -26,63 +26,27 @@ using namespace cv; namespace br { -/* -class CascadeResourceMaker : public ResourceMaker -{ - QString file; - -public: - CascadeResourceMaker(const QString &model) - { - file = Globals->sdkPath + "/share/openbr/models/"; - if (model == "Ear") file += "haarcascades/haarcascade_ear.xml"; - else if (model == "Eye") file += "haarcascades/haarcascade_eye_tree_eyeglasses.xml"; - else if (model == "FrontalFace") file += "haarcascades/haarcascade_frontalface_alt2.xml"; - else if (model == "ProfileFace") file += "haarcascades/haarcascade_profileface.xml"; - else { - // Create a directory for trainable cascades - file += "openbrcascades/"+model+"/cascade.xml"; - QFile touchFile(file); - QtUtils::touchDir(touchFile); - } - } -private: - CascadeClassifier *make() const - { - CascadeClassifier *cascade = new CascadeClassifier(); - if (!cascade->load(file.toStdString())) - qFatal("Failed to load: %s", qPrintable(file)); - return cascade; - } -}; -*/ /*! * \ingroup transforms * \brief Applies a classifier to a sliding window. * Discards negative detections. * \author Jordan Cheney \cite JordanCheney */ - /* + class SlidingWindowTransform : public Transform { Q_OBJECT Q_PROPERTY(br::Classifier *classifier READ get_classifier WRITE set_classifier RESET reset_classifier 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) - 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) BR_PROPERTY(br::Classifier *, classifier, NULL) + BR_PROPERTY(QString, cascadeDir, "") BR_PROPERTY(QString, vecFile, "vec.vec") BR_PROPERTY(QString, negFile, "neg.txt") - BR_PROPERTY(int, minSize, 20) - BR_PROPERTY(int, maxSize, -1) - BR_PROPERTY(float, scaleFactor, 1.2) - - Resource cascadeResource; QList getPos() { @@ -159,9 +123,7 @@ class SlidingWindowTransform : public Transform void init() { - cascadeResource.setResourceMaker(new CascadeResourceMaker(model)); - if (model == "Ear" || model == "Eye" || model == "FrontalFace" || model == "ProfileFace") - this->trainable = false; + cascadeDir = Globals->sdkPath + "/share/openbr/models/openbrcascades/" + cascadeDir; } void train(const TemplateList &_data) @@ -183,118 +145,38 @@ class SlidingWindowTransform : public Transform } classifier->train(images, labels); - } - void project(const Template &src, Template &dst) const - { - TemplateList temp; - project(TemplateList() << src, temp); - if (!temp.isEmpty()) dst = temp.first(); + // save the cascade + std::string filename = cascadeDir.toStdString() + "/cascade.xml"; + FileStorage fs(filename, FileStorage::WRITE ); + + if ( !fs.isOpened() ) + return; + + fs << FileStorage::getDefaultObjectName(filename) << "{"; + + Mat featureMap(1, classifier->numFeatures(), CV_32SC1); + featureMap.setTo(Scalar(-1)); + + classifier->getUsedFeatures(featureMap); + + for (int fi = 0, idx = 0; fi < classifier->numFeatures(); fi++) + if (featureMap.at(0, fi) >= 0) + featureMap.ptr(0)[fi] = idx++; + + classifier->write(fs, featureMap); + classifier->writeFeatures(fs, featureMap); + + fs << "}"; } - void project(const TemplateList &src, TemplateList &dst) const + void project(const Template &src, Template &dst) const { - CascadeClassifier *cascade = cascadeResource.acquire(); - 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 rects; - QList levels; - QList weights; - - for (double factor = 1; ; factor *= 1.2) { - Size originalWindowSize = Size(winWidth, winHeight); - - Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) ); - Size scaledImageSize( cvRound( m.cols/factor ), cvRound( m.rows/factor ) ); - Size processingRectSize( scaledImageSize.width - originalWindowSize.width, scaledImageSize.height - originalWindowSize.height ); - - if( processingRectSize.width <= 0 || processingRectSize.height <= 0 ) - break; - if( windowSize.width > maxSize.width || windowSize.height > maxSize.height ) - break; - if( windowSize.width < minSize || windowSize.height < minSize ) - continue; - - Mat scaledImage( scaledImageSize, CV_8U, imageBuffer.data ); - resize( m, scaledImage, scaledImageSize, 0, 0, CV_INTER_LINEAR ); - - int yStep = factor > 2. ? 1 : 2; - int stripCount, stripSize; - - const int PTS_PER_THREAD = 1000; - stripCount = ((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep + PTS_PER_THREAD/2)/PTS_PER_THREAD; - stripCount = std::min(std::max(stripCount, 1), 100); - stripSize = (((processingRectSize.height + stripCount - 1)/stripCount + yStep-1)/yStep)*yStep; - - if( !cascade->setImage( scaledImage ) ) - qFatal("Can't set an image. Don't know why"); - - for( int y = 0; y < min(stripCount * stripSize, processingRectSize.height); y += yStep ) { - for( int x = 0; x < processingRectSize.width; x += yStep ) { - double gypWeight; - int result = cascade->runAt(cascade->featureEvaluator, Point(x, y), gypWeight); - - if (ROCMode) { - if (result == 1) - result = -(int)cascade->data.stages.size(); - if (cascade->data.stages.size() + result < 4 ) { - rects.append(Rect(cvRound(x*factor), cvRound(y*factor), windowSize.width, windowSize.height)); - levels.append(-result); - weights.append(gypWeight); - } - } - else if( result > 0 ) { - rects.append(Rect(cvRound(x*factor), cvRound(y*factor), windowSize.width, windowSize.height)); - } - if( result == 0 ) - x += yStep; - } - } - } - - if (ROCMode) - groupRectangles(rects, levels, weights, minNeighbors, 0.2); - else - groupRectangles(rects, minNeighbors, 0.2); - - if (!enrollAll && rects.empty()) - rects.append(Rect(0, 0, m.cols, m.rows)); - - for (int j = 0; j < rects.size(); j++) { - Template u(t.file, m); - if (levels.size() > j) - u.file.set("Confidence", levels[j]*weights[j]); - else - u.file.set("Confidence", 1); - const QRectF rect = OpenCVUtils::fromRect(rects[j]); - u.file.appendRect(rect); - u.file.set(model, rect); - dst.append(u); - } - } - } - cascadeResource.release(cascade); + (void)src; (void)dst; } }; BR_REGISTER(Transform, SlidingWindowTransform) -*/ } // namespace br diff --git a/openbr/plugins/representation/mblbp.cpp b/openbr/plugins/representation/mblbp.cpp index 5fc7f0e..7934560 100644 --- a/openbr/plugins/representation/mblbp.cpp +++ b/openbr/plugins/representation/mblbp.cpp @@ -22,11 +22,6 @@ class MBLBPRepresentation : public Representation { Q_OBJECT - Q_PROPERTY(int winWidth READ get_winWidth WRITE set_winWidth RESET reset_winWidth STORED false) - Q_PROPERTY(int winHeight READ get_winHeight WRITE set_winHeight RESET reset_winHeight STORED false) - BR_PROPERTY(int, winWidth, 24) - BR_PROPERTY(int, winHeight, 24) - void init() { int offset = winWidth + 1; @@ -38,11 +33,14 @@ class MBLBPRepresentation : public Representation features.append(Feature(offset, x, y, w, h ) ); } - Mat preprocess(const Mat &image) const + void preprocess(const Mat &src, Mat &dst) const + { + integral(src, dst); + } + + float evaluate(const Mat &image, int idx) const { - Mat integralImage; - integral(image, integralImage); - return integralImage; + return (float)features[idx].calc(image); } Mat evaluate(const Mat &image, const QList &indices) const @@ -55,7 +53,9 @@ class MBLBPRepresentation : public Representation void write(FileStorage &fs, const Mat &featureMap); int numFeatures() const { return features.size(); } - Size windowSize() const { return Size(winWidth + 1, winHeight + 1); } + Size preWindowSize() const { return Size(winWidth, winHeight); } + Size postWindowSize() const { return Size(winWidth + 1, winHeight + 1); } + int maxCatCount() const { return 256; } struct Feature { @@ -100,17 +100,17 @@ MBLBPRepresentation::Feature::Feature( int offset, int x, int y, int _blockWidth inline uchar MBLBPRepresentation::Feature::calc(const Mat &img) const { - const int* psum = img.ptr(); - int cval = psum[p[5]] - psum[p[6]] - psum[p[9]] + psum[p[10]]; - - return (uchar)((psum[p[0]] - psum[p[1]] - psum[p[4]] + psum[p[5]] >= cval ? 128 : 0) | // 0 - (psum[p[1]] - psum[p[2]] - psum[p[5]] + psum[p[6]] >= cval ? 64 : 0) | // 1 - (psum[p[2]] - psum[p[3]] - psum[p[6]] + psum[p[7]] >= cval ? 32 : 0) | // 2 - (psum[p[6]] - psum[p[7]] - psum[p[10]] + psum[p[11]] >= cval ? 16 : 0) | // 5 - (psum[p[10]] - psum[p[11]] - psum[p[14]] + psum[p[15]] >= cval ? 8 : 0) | // 8 - (psum[p[9]] - psum[p[10]] - psum[p[13]] + psum[p[14]] >= cval ? 4 : 0) | // 7 - (psum[p[8]] - psum[p[9]] - psum[p[12]] + psum[p[13]] >= cval ? 2 : 0) | // 6 - (psum[p[4]] - psum[p[5]] - psum[p[8]] + psum[p[9]] >= cval ? 1 : 0)); // 3 + const int* ptr = img.ptr(); + int cval = ptr[p[5]] - ptr[p[6]] - ptr[p[9]] + ptr[p[10]]; + + return (uchar)((ptr[p[0]] - ptr[p[1]] - ptr[p[4]] + ptr[p[5]] >= cval ? 128 : 0) | // 0 + (ptr[p[1]] - ptr[p[2]] - ptr[p[5]] + ptr[p[6]] >= cval ? 64 : 0) | // 1 + (ptr[p[2]] - ptr[p[3]] - ptr[p[6]] + ptr[p[7]] >= cval ? 32 : 0) | // 2 + (ptr[p[6]] - ptr[p[7]] - ptr[p[10]] + ptr[p[11]] >= cval ? 16 : 0) | // 5 + (ptr[p[10]] - ptr[p[11]] - ptr[p[14]] + ptr[p[15]] >= cval ? 8 : 0) | // 8 + (ptr[p[9]] - ptr[p[10]] - ptr[p[13]] + ptr[p[14]] >= cval ? 4 : 0) | // 7 + (ptr[p[8]] - ptr[p[9]] - ptr[p[12]] + ptr[p[13]] >= cval ? 2 : 0) | // 6 + (ptr[p[4]] - ptr[p[5]] - ptr[p[8]] + ptr[p[9]] >= cval ? 1 : 0)); // 3 } } // namespace br