From 78bd2133cc0ff07f15f4d987df4f1538c695cf32 Mon Sep 17 00:00:00 2001 From: Alexey Kazakov Date: Thu, 6 Oct 2011 16:46:03 +0000 Subject: [PATCH] Added HOG features to the traincascade module --- modules/traincascade/CMakeLists.txt | 1 + modules/traincascade/HOGfeatures.cpp | 245 +++++++++++++++++++++++++++ modules/traincascade/HOGfeatures.h | 78 +++++++++ modules/traincascade/boost.cpp | 2 +- modules/traincascade/cascadeclassifier.cpp | 15 +- modules/traincascade/cascadeclassifier.h | 4 + modules/traincascade/features.cpp | 15 +- modules/traincascade/traincascade.cpp | 5 +- modules/traincascade/traincascade_features.h | 4 +- 9 files changed, 356 insertions(+), 13 deletions(-) create mode 100644 modules/traincascade/HOGfeatures.cpp create mode 100644 modules/traincascade/HOGfeatures.h diff --git a/modules/traincascade/CMakeLists.txt b/modules/traincascade/CMakeLists.txt index 21d968a..858f42f 100644 --- a/modules/traincascade/CMakeLists.txt +++ b/modules/traincascade/CMakeLists.txt @@ -22,6 +22,7 @@ set(traincascade_files traincascade.cpp boost.cpp boost.h features.cpp traincascade_features.h haarfeatures.cpp haarfeatures.h lbpfeatures.cpp lbpfeatures.h + HOGfeatures.cpp HOGfeatures.h imagestorage.cpp imagestorage.h) set(the_target opencv_traincascade) diff --git a/modules/traincascade/HOGfeatures.cpp b/modules/traincascade/HOGfeatures.cpp new file mode 100644 index 0000000..68c1943 --- /dev/null +++ b/modules/traincascade/HOGfeatures.cpp @@ -0,0 +1,245 @@ +#include "HOGfeatures.h" +#include "cascadeclassifier.h" + + +CvHOGFeatureParams::CvHOGFeatureParams() +{ + maxCatCount = 0; + name = HOGF_NAME; + featSize = N_BINS * N_CELLS; +} + +void CvHOGEvaluator::init(const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize) +{ + CV_Assert( _maxSampleCount > 0); + int cols = (_winSize.width + 1) * (_winSize.height + 1); + for (int bin = 0; bin < N_BINS; bin++) + { + hist.push_back(Mat(_maxSampleCount, cols, CV_32FC1)); + } + normSum.create( (int)_maxSampleCount, cols, CV_32FC1 ); + CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize ); +} + +void CvHOGEvaluator::setImage(const Mat &img, uchar clsLabel, int idx) +{ + CV_DbgAssert( !hist.empty()); + CvFeatureEvaluator::setImage( img, clsLabel, idx ); + vector integralHist; + for (int bin = 0; bin < N_BINS; bin++) + { + integralHist.push_back( Mat(winSize.height + 1, winSize.width + 1, hist[bin].type(), hist[bin].ptr((int)idx)) ); + } + Mat integralNorm(winSize.height + 1, winSize.width + 1, normSum.type(), normSum.ptr((int)idx)); + integralHistogram(img, integralHist, integralNorm, (int)N_BINS); +} + +//void CvHOGEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +//{ +// _writeFeatures( features, fs, featureMap ); +//} + +void CvHOGEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + int featIdx; + int componentIdx; + const Mat_& featureMap_ = (const Mat_&)featureMap; + fs << FEATURES << "["; + for ( int fi = 0; fi < featureMap.cols; fi++ ) + if ( featureMap_(0, fi) >= 0 ) + { + fs << "{"; + featIdx = fi / getFeatureSize(); + componentIdx = fi % getFeatureSize(); + features[featIdx].write( fs, componentIdx ); + fs << "}"; + } + fs << "]"; +} + +void CvHOGEvaluator::generateFeatures() +{ + int offset = winSize.width + 1; + Size blockStep; + int x, y, t, w, h; + + for (t = 8; t <= winSize.width/2; t+=8) //t = size of a cell. blocksize = 4*cellSize + { + blockStep = Size(4,4); + w = 2*t; //width of a block + h = 2*t; //height of a block + for (x = 0; x <= winSize.width - w; x += blockStep.width) + { + for (y = 0; y <= winSize.height - h; y += blockStep.height) + { + features.push_back(Feature(offset, x, y, t, t)); + } + } + w = 2*t; + h = 4*t; + for (x = 0; x <= winSize.width - w; x += blockStep.width) + { + for (y = 0; y <= winSize.height - h; y += blockStep.height) + { + features.push_back(Feature(offset, x, y, t, 2*t)); + } + } + w = 4*t; + h = 2*t; + for (x = 0; x <= winSize.width - w; x += blockStep.width) + { + for (y = 0; y <= winSize.height - h; y += blockStep.height) + { + features.push_back(Feature(offset, x, y, 2*t, t)); + } + } + } + + numFeatures = (int)features.size(); +} + +CvHOGEvaluator::Feature::Feature() +{ + for (int i = 0; i < N_CELLS; i++) + { + rect[i] = Rect(0, 0, 0, 0); + } +} + +CvHOGEvaluator::Feature::Feature( int offset, int x, int y, int cellW, int cellH ) +{ + rect[0] = Rect(x, y, cellW, cellH); //cell0 + rect[1] = Rect(x+cellW, y, cellW, cellH); //cell1 + rect[2] = Rect(x, y+cellH, cellW, cellH); //cell2 + rect[3] = Rect(x+cellW, y+cellH, cellW, cellH); //cell3 + + for (int i = 0; i < N_CELLS; i++) + { + CV_SUM_OFFSETS(fastRect[i].p0, fastRect[i].p1, fastRect[i].p2, fastRect[i].p3, rect[i], offset); + } +} + +void CvHOGEvaluator::Feature::write(FileStorage &fs) const +{ + fs << CC_RECTS << "["; + for( int i = 0; i < N_CELLS; i++ ) + { + fs << "[:" << rect[i].x << rect[i].y << rect[i].width << rect[i].height << "]"; + } + fs << "]"; +} + +//cell and bin idx writing +//void CvHOGEvaluator::Feature::write(FileStorage &fs, int varIdx) const +//{ +// int featComponent = varIdx % (N_CELLS * N_BINS); +// int cellIdx = featComponent / N_BINS; +// int binIdx = featComponent % N_BINS; +// +// fs << CC_RECTS << "[:" << rect[cellIdx].x << rect[cellIdx].y << +// rect[cellIdx].width << rect[cellIdx].height << binIdx << "]"; +//} + +//cell[0] and featComponent idx writing. By cell[0] it's possible to recover all block +//All block is nessesary for block normalization +void CvHOGEvaluator::Feature::write(FileStorage &fs, int featComponentIdx) const +{ + fs << CC_RECT << "[:" << rect[0].x << rect[0].y << + rect[0].width << rect[0].height << featComponentIdx << "]"; +} + + +void CvHOGEvaluator::integralHistogram(const Mat &img, vector &histogram, Mat &norm, int nbins) const +{ + CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 ); + int x, y, binIdx; + + Size gradSize(img.size()); + Size histSize(histogram[0].size()); + Mat grad(gradSize, CV_32F); + Mat qangle(gradSize, CV_8U); + + AutoBuffer mapbuf(gradSize.width + gradSize.height + 4); + int* xmap = (int*)mapbuf + 1; + int* ymap = xmap + gradSize.width + 2; + + const int borderType = (int)BORDER_REPLICATE; + + for( x = -1; x < gradSize.width + 1; x++ ) + xmap[x] = borderInterpolate(x, gradSize.width, borderType); + for( y = -1; y < gradSize.height + 1; y++ ) + ymap[y] = borderInterpolate(y, gradSize.height, borderType); + + int width = gradSize.width; + AutoBuffer _dbuf(width*4); + float* dbuf = _dbuf; + Mat Dx(1, width, CV_32F, dbuf); + Mat Dy(1, width, CV_32F, dbuf + width); + Mat Mag(1, width, CV_32F, dbuf + width*2); + Mat Angle(1, width, CV_32F, dbuf + width*3); + + float angleScale = (float)(nbins/CV_PI); + + for( y = 0; y < gradSize.height; y++ ) + { + const uchar* currPtr = img.data + img.step*ymap[y]; + const uchar* prevPtr = img.data + img.step*ymap[y-1]; + const uchar* nextPtr = img.data + img.step*ymap[y+1]; + float* gradPtr = (float*)grad.ptr(y); + uchar* qanglePtr = (uchar*)qangle.ptr(y); + + for( x = 0; x < width; x++ ) + { + dbuf[x] = (float)(currPtr[xmap[x+1]] - currPtr[xmap[x-1]]); + dbuf[width + x] = (float)(nextPtr[xmap[x]] - prevPtr[xmap[x]]); + } + cartToPolar( Dx, Dy, Mag, Angle, false ); + for( x = 0; x < width; x++ ) + { + float mag = dbuf[x+width*2]; + float angle = dbuf[x+width*3]; + angle = angle*angleScale - 0.5f; + int bidx = cvFloor(angle); + angle -= bidx; + if( bidx < 0 ) + bidx += nbins; + else if( bidx >= nbins ) + bidx -= nbins; + + qanglePtr[x] = (uchar)bidx; + gradPtr[x] = mag; + } + } + integral(grad, norm, grad.depth()); + + float* histBuf; + const float* magBuf; + const uchar* binsBuf; + + int binsStep = (int)( qangle.step / sizeof(uchar) ); + int histStep = (int)( histogram[0].step / sizeof(float) ); + int magStep = (int)( grad.step / sizeof(float) ); + for( binIdx = 0; binIdx < nbins; binIdx++ ) + { + histBuf = (float*)histogram[binIdx].data; + magBuf = (const float*)grad.data; + binsBuf = (const uchar*)qangle.data; + + memset( histBuf, 0, histSize.width * sizeof(histBuf[0]) ); + histBuf += histStep + 1; + for( y = 0; y < qangle.rows; y++ ) + { + histBuf[-1] = 0.f; + float strSum = 0.f; + for( x = 0; x < qangle.cols; x++ ) + { + if( binsBuf[x] == binIdx ) + strSum += magBuf[x]; + histBuf[x] = histBuf[-histStep + x] + strSum; + } + histBuf += histStep; + binsBuf += binsStep; + magBuf += magStep; + } + } +} diff --git a/modules/traincascade/HOGfeatures.h b/modules/traincascade/HOGfeatures.h new file mode 100644 index 0000000..d6e1b58 --- /dev/null +++ b/modules/traincascade/HOGfeatures.h @@ -0,0 +1,78 @@ +#ifndef _OPENCV_HOGFEATURES_H_ +#define _OPENCV_HOGFEATURES_H_ + +#include "traincascade_features.h" + +//#define TEST_INTHIST_BUILD +//#define TEST_FEAT_CALC + +#define N_BINS 9 +#define N_CELLS 4 + +#define HOGF_NAME "HOGFeatureParams" +struct CvHOGFeatureParams : public CvFeatureParams +{ + CvHOGFeatureParams(); +}; + +class CvHOGEvaluator : public CvFeatureEvaluator +{ +public: + virtual ~CvHOGEvaluator() {} + virtual void init(const CvFeatureParams *_featureParams, + int _maxSampleCount, Size _winSize ); + virtual void setImage(const Mat& img, uchar clsLabel, int idx); + virtual float operator()(int varIdx, int sampleIdx) const; + virtual void writeFeatures( FileStorage &fs, const Mat& featureMap ) const; +protected: + virtual void generateFeatures(); + virtual void integralHistogram(const Mat &img, vector &histogram, Mat &norm, int nbins) const; + class Feature + { + public: + Feature(); + Feature( int offset, int x, int y, int cellW, int cellH ); + float calc( const vector &_hists, const Mat &_normSum, size_t y, int featComponent ) const; + void write( FileStorage &fs ) const; + void write( FileStorage &fs, int varIdx ) const; + + Rect rect[N_CELLS]; //cells + + struct + { + int p0, p1, p2, p3; + } fastRect[N_CELLS]; + }; + vector features; + + Mat normSum; //for nomalization calculation (L1 or L2) + vector hist; +}; + +inline float CvHOGEvaluator::operator()(int varIdx, int sampleIdx) const +{ + int featureIdx = varIdx / (N_BINS * N_CELLS); + int componentIdx = varIdx % (N_BINS * N_CELLS); + //return features[featureIdx].calc( hist, sampleIdx, componentIdx); + return features[featureIdx].calc( hist, normSum, sampleIdx, componentIdx); +} + +inline float CvHOGEvaluator::Feature::calc( const vector& _hists, const Mat& _normSum, size_t y, int featComponent ) const +{ + float normFactor; + float res; + + int binIdx = featComponent % N_BINS; + int cellIdx = featComponent / N_BINS; + + const float *hist = _hists[binIdx].ptr(y); + res = hist[fastRect[cellIdx].p0] - hist[fastRect[cellIdx].p1] - hist[fastRect[cellIdx].p2] + hist[fastRect[cellIdx].p3]; + + const float *normSum = _normSum.ptr(y); + normFactor = (float)(normSum[fastRect[0].p0] - normSum[fastRect[1].p1] - normSum[fastRect[2].p2] + normSum[fastRect[3].p3]); + res = (res > 0.001f) ? ( res / (normFactor + 0.001f) ) : 0.f; //for cutting negative values, which apper due to floating precision + + return res; +} + +#endif // _OPENCV_HOGFEATURES_H_ diff --git a/modules/traincascade/boost.cpp b/modules/traincascade/boost.cpp index 7eb07b2..1ab93e6 100644 --- a/modules/traincascade/boost.cpp +++ b/modules/traincascade/boost.cpp @@ -233,7 +233,7 @@ void CvCascadeBoostTrainData::setData( const CvFeatureEvaluator* _featureEvaluat if( _precalcValBufSize < 0 || _precalcIdxBufSize < 0) CV_Error( CV_StsOutOfRange, "_numPrecalcVal and _numPrecalcIdx must be positive or 0" ); - var_count = var_all = featureEvaluator->getNumFeatures(); + var_count = var_all = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize(); sample_count = _numSamples; is_buf_16u = false; diff --git a/modules/traincascade/cascadeclassifier.cpp b/modules/traincascade/cascadeclassifier.cpp index ee5e8a2..d192dff 100644 --- a/modules/traincascade/cascadeclassifier.cpp +++ b/modules/traincascade/cascadeclassifier.cpp @@ -4,7 +4,7 @@ using namespace std; static const char* stageTypes[] = { CC_BOOST }; -static const char* featureTypes[] = { CC_HAAR, CC_LBP }; +static const char* featureTypes[] = { CC_HAAR, CC_LBP, CC_HOG }; CvCascadeParams::CvCascadeParams() : stageType( defaultStageType ), featureType( defaultFeatureType ), winSize( cvSize(24, 24) ) @@ -25,7 +25,9 @@ void CvCascadeParams::write( FileStorage &fs ) const CV_Assert( !stageTypeStr.empty() ); fs << CC_STAGE_TYPE << stageTypeStr; String featureTypeStr = featureType == CvFeatureParams::HAAR ? CC_HAAR : - featureType == CvFeatureParams::LBP ? CC_LBP : 0; + featureType == CvFeatureParams::LBP ? CC_LBP : + featureType == CvFeatureParams::HOG ? CC_HOG : + 0; CV_Assert( !stageTypeStr.empty() ); fs << CC_FEATURE_TYPE << featureTypeStr; fs << CC_HEIGHT << winSize.height; @@ -49,7 +51,9 @@ bool CvCascadeParams::read( const FileNode &node ) return false; rnode >> featureTypeStr; featureType = !featureTypeStr.compare( CC_HAAR ) ? CvFeatureParams::HAAR : - !featureTypeStr.compare( CC_LBP ) ? CvFeatureParams::LBP : -1; + !featureTypeStr.compare( CC_LBP ) ? CvFeatureParams::LBP : + !featureTypeStr.compare( CC_HOG ) ? CvFeatureParams::HOG : + -1; if (featureType == -1) return false; node[CC_HEIGHT] >> winSize.height; @@ -509,14 +513,15 @@ bool CvCascadeClassifier::load( const String cascadeDirName ) void CvCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap ) { - featureMap.create( 1, featureEvaluator->getNumFeatures(), CV_32SC1 ); + int varCount = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize(); + featureMap.create( 1, varCount, CV_32SC1 ); featureMap.setTo(Scalar(-1)); for( vector< Ptr >::const_iterator it = stageClassifiers.begin(); it != stageClassifiers.end(); it++ ) ((CvCascadeBoost*)((Ptr)(*it)))->markUsedFeaturesInMap( featureMap ); - for( int fi = 0, idx = 0; fi < featureEvaluator->getNumFeatures(); fi++ ) + for( int fi = 0, idx = 0; fi < varCount; fi++ ) if ( featureMap.at(0, fi) >= 0 ) featureMap.ptr(0)[fi] = idx++; } diff --git a/modules/traincascade/cascadeclassifier.h b/modules/traincascade/cascadeclassifier.h index b59fdea..67de534 100644 --- a/modules/traincascade/cascadeclassifier.h +++ b/modules/traincascade/cascadeclassifier.h @@ -5,6 +5,7 @@ #include "traincascade_features.h" #include "haarfeatures.h" #include "lbpfeatures.h" +#include "HOGfeatures.h" //new #include "boost.h" #include "cv.h" #include "cxcore.h" @@ -41,6 +42,7 @@ #define CC_FEATURES FEATURES #define CC_FEATURE_PARAMS "featureParams" #define CC_MAX_CAT_COUNT "maxCatCount" +#define CC_FEATURE_SIZE "featSize" #define CC_HAAR "HAAR" #define CC_MODE "mode" @@ -53,6 +55,8 @@ #define CC_LBP "LBP" #define CC_RECT "rect" +#define CC_HOG "HOG" + #ifdef _WIN32 #define TIME( arg ) (((double) clock()) / CLOCKS_PER_SEC) #else diff --git a/modules/traincascade/features.cpp b/modules/traincascade/features.cpp index 1670ece..c117a99 100644 --- a/modules/traincascade/features.cpp +++ b/modules/traincascade/features.cpp @@ -26,7 +26,7 @@ bool CvParams::scanAttr( const String prmName, const String val ) { return false //---------------------------- FeatureParams -------------------------------------- -CvFeatureParams::CvFeatureParams() : maxCatCount( 0 ) +CvFeatureParams::CvFeatureParams() : maxCatCount( 0 ), featSize( 1 ) { name = CC_FEATURE_PARAMS; } @@ -34,11 +34,13 @@ CvFeatureParams::CvFeatureParams() : maxCatCount( 0 ) void CvFeatureParams::init( const CvFeatureParams& fp ) { maxCatCount = fp.maxCatCount; + featSize = fp.featSize; } void CvFeatureParams::write( FileStorage &fs ) const { fs << CC_MAX_CAT_COUNT << maxCatCount; + fs << CC_FEATURE_SIZE << featSize; } bool CvFeatureParams::read( const FileNode &node ) @@ -46,13 +48,16 @@ bool CvFeatureParams::read( const FileNode &node ) if ( node.empty() ) return false; maxCatCount = node[CC_MAX_CAT_COUNT]; - return maxCatCount >= 0; + featSize = node[CC_FEATURE_SIZE]; + return ( maxCatCount >= 0 && featSize >= 1 ); } Ptr CvFeatureParams::create( int featureType ) { return featureType == HAAR ? Ptr(new CvHaarFeatureParams) : - featureType == LBP ? Ptr(new CvLBPFeatureParams) : Ptr(); + featureType == LBP ? Ptr(new CvLBPFeatureParams) : + featureType == HOG ? Ptr(new CvHOGFeatureParams) : + Ptr(); } //------------------------------------- FeatureEvaluator --------------------------------------- @@ -79,5 +84,7 @@ void CvFeatureEvaluator::setImage(const Mat &img, uchar clsLabel, int idx) Ptr CvFeatureEvaluator::create(int type) { return type == CvFeatureParams::HAAR ? Ptr(new CvHaarEvaluator) : - type == CvFeatureParams::LBP ? Ptr(new CvLBPEvaluator) : Ptr(); + type == CvFeatureParams::LBP ? Ptr(new CvLBPEvaluator) : + type == CvFeatureParams::HOG ? Ptr(new CvHOGEvaluator) : + Ptr(); } diff --git a/modules/traincascade/traincascade.cpp b/modules/traincascade/traincascade.cpp index db1dd00..07dbe3e 100644 --- a/modules/traincascade/traincascade.cpp +++ b/modules/traincascade/traincascade.cpp @@ -17,8 +17,9 @@ int main( int argc, char* argv[] ) CvCascadeParams cascadeParams; CvCascadeBoostParams stageParams; Ptr featureParams[] = { Ptr(new CvHaarFeatureParams), - Ptr(new CvLBPFeatureParams) - }; + Ptr(new CvLBPFeatureParams), + Ptr(new CvHOGFeatureParams) + }; int fc = sizeof(featureParams)/sizeof(featureParams[0]); if( argc == 1 ) { diff --git a/modules/traincascade/traincascade_features.h b/modules/traincascade/traincascade_features.h index 1e03a82..019a4b9 100644 --- a/modules/traincascade/traincascade_features.h +++ b/modules/traincascade/traincascade_features.h @@ -65,13 +65,14 @@ public: class CvFeatureParams : public CvParams { public: - enum { HAAR = 0, LBP = 1 }; + enum { HAAR = 0, LBP = 1, HOG = 2 }; CvFeatureParams(); virtual void init( const CvFeatureParams& fp ); virtual void write( FileStorage &fs ) const; virtual bool read( const FileNode &node ); static Ptr create( int featureType ); 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 }; class CvFeatureEvaluator @@ -87,6 +88,7 @@ public: int getNumFeatures() const { return numFeatures; } int getMaxCatCount() const { return featureParams->maxCatCount; } + int getFeatureSize() const { return featureParams->featSize; } const Mat& getCls() const { return cls; } float getCls(int si) const { return cls.at(si, 0); } protected: -- 2.7.4