diff --git a/openbr/core/boost.cpp b/openbr/core/boost.cpp new file mode 100644 index 0000000..0d315e5 --- /dev/null +++ b/openbr/core/boost.cpp @@ -0,0 +1,1680 @@ +#include "boost.h" +#include +#include "cxmisc.h" + +using namespace std; +using namespace br; +using namespace cv; + +static inline double +logRatio( double val ) +{ + const double eps = 1e-5; + + val = max( val, eps ); + val = min( val, 1. - eps ); + return log( val/(1. - val) ); +} + +#define CV_CMP_FLT(i,j) (i < j) +static CV_IMPLEMENT_QSORT_EX( icvSortFlt, float, CV_CMP_FLT, const float* ) + +#define CV_CMP_NUM_IDX(i,j) (aux[i] < aux[j]) +static CV_IMPLEMENT_QSORT_EX( icvSortIntAux, int, CV_CMP_NUM_IDX, const float* ) +static CV_IMPLEMENT_QSORT_EX( icvSortUShAux, unsigned short, CV_CMP_NUM_IDX, const float* ) + +#define CV_THRESHOLD_EPS (0.00001F) + +static const int MinBlockSize = 1 << 16; +static const int BlockSizeDelta = 1 << 10; + +// TODO remove this code duplication with ml/precomp.hpp + +static int CV_CDECL icvCmpIntegers( const void* a, const void* b ) +{ + return *(const int*)a - *(const int*)b; +} + +static CvMat* cvPreprocessIndexArray( const CvMat* idx_arr, int data_arr_size, bool check_for_duplicates=false ) +{ + CvMat* idx = 0; + + CV_FUNCNAME( "cvPreprocessIndexArray" ); + + __BEGIN__; + + int i, idx_total, idx_selected = 0, step, type, prev = INT_MIN, is_sorted = 1; + uchar* srcb = 0; + int* srci = 0; + int* dsti; + + if( !CV_IS_MAT(idx_arr) ) + CV_ERROR( CV_StsBadArg, "Invalid index array" ); + + if( idx_arr->rows != 1 && idx_arr->cols != 1 ) + CV_ERROR( CV_StsBadSize, "the index array must be 1-dimensional" ); + + idx_total = idx_arr->rows + idx_arr->cols - 1; + srcb = idx_arr->data.ptr; + srci = idx_arr->data.i; + + type = CV_MAT_TYPE(idx_arr->type); + step = CV_IS_MAT_CONT(idx_arr->type) ? 1 : idx_arr->step/CV_ELEM_SIZE(type); + + switch( type ) + { + case CV_8UC1: + case CV_8SC1: + // idx_arr is array of 1's and 0's - + // i.e. it is a mask of the selected components + if( idx_total != data_arr_size ) + CV_ERROR( CV_StsUnmatchedSizes, + "Component mask should contain as many elements as the total number of input variables" ); + + for( i = 0; i < idx_total; i++ ) + idx_selected += srcb[i*step] != 0; + + if( idx_selected == 0 ) + CV_ERROR( CV_StsOutOfRange, "No components/input_variables is selected!" ); + + break; + case CV_32SC1: + // idx_arr is array of integer indices of selected components + if( idx_total > data_arr_size ) + CV_ERROR( CV_StsOutOfRange, + "index array may not contain more elements than the total number of input variables" ); + idx_selected = idx_total; + // check if sorted already + for( i = 0; i < idx_total; i++ ) + { + int val = srci[i*step]; + if( val >= prev ) + { + is_sorted = 0; + break; + } + prev = val; + } + break; + default: + CV_ERROR( CV_StsUnsupportedFormat, "Unsupported index array data type " + "(it should be 8uC1, 8sC1 or 32sC1)" ); + } + + CV_CALL( idx = cvCreateMat( 1, idx_selected, CV_32SC1 )); + dsti = idx->data.i; + + if( type < CV_32SC1 ) + { + for( i = 0; i < idx_total; i++ ) + if( srcb[i*step] ) + *dsti++ = i; + } + else + { + for( i = 0; i < idx_total; i++ ) + dsti[i] = srci[i*step]; + + if( !is_sorted ) + qsort( dsti, idx_total, sizeof(dsti[0]), icvCmpIntegers ); + + if( dsti[0] < 0 || dsti[idx_total-1] >= data_arr_size ) + CV_ERROR( CV_StsOutOfRange, "the index array elements are out of range" ); + + if( check_for_duplicates ) + { + for( i = 1; i < idx_total; i++ ) + if( dsti[i] <= dsti[i-1] ) + CV_ERROR( CV_StsBadArg, "There are duplicated index array elements" ); + } + } + + __END__; + + if( cvGetErrStatus() < 0 ) + cvReleaseMat( &idx ); + + return idx; +} + +//----------------------------- CascadeBoostParams ------------------------------------------------- + +CascadeBoostParams::CascadeBoostParams() : minHitRate( 0.995F), maxFalseAlarm( 0.5F ) +{ + boost_type = CvBoost::GENTLE; + use_surrogates = use_1se_rule = truncate_pruned_tree = false; +} + +CascadeBoostParams::CascadeBoostParams( int _boostType, + float _minHitRate, float _maxFalseAlarm, + double _weightTrimRate, int _maxDepth, int _maxWeakCount ) : + CvBoostParams( _boostType, _maxWeakCount, _weightTrimRate, _maxDepth, false, 0 ) +{ + boost_type = CvBoost::GENTLE; + minHitRate = _minHitRate; + maxFalseAlarm = _maxFalseAlarm; + 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; +} + +bool CascadeBoostParams::read( const FileNode &node ) +{ + string boostTypeStr; + FileNode rnode = node[CC_BOOST_TYPE]; + rnode >> boostTypeStr; + boost_type = !boostTypeStr.compare( CC_DISCRETE_BOOST ) ? CvBoost::DISCRETE : + !boostTypeStr.compare( CC_REAL_BOOST ) ? CvBoost::REAL : + !boostTypeStr.compare( CC_LOGIT_BOOST ) ? CvBoost::LOGIT : + !boostTypeStr.compare( CC_GENTLE_BOOST ) ? CvBoost::GENTLE : -1; + if (boost_type == -1) + CV_Error( CV_StsBadArg, "unsupported Boost type" ); + node[CC_MINHITRATE] >> minHitRate; + node[CC_MAXFALSEALARM] >> maxFalseAlarm; + node[CC_TRIM_RATE] >> weight_trim_rate ; + node[CC_MAX_DEPTH] >> max_depth ; + node[CC_WEAK_COUNT] >> weak_count ; + if ( minHitRate <= 0 || minHitRate > 1 || + maxFalseAlarm <= 0 || maxFalseAlarm > 1 || + weight_trim_rate <= 0 || weight_trim_rate > 1 || + max_depth <= 0 || weak_count <= 0 ) + CV_Error( CV_StsBadArg, "bad parameters range"); + return true; +} + +void CascadeBoostParams::printDefaults() const +{ + cout << "--boostParams--" << endl; + cout << " [-bt <{" << CC_DISCRETE_BOOST << ", " + << CC_REAL_BOOST << ", " + << CC_LOGIT_BOOST ", " + << CC_GENTLE_BOOST << "(default)}>]" << endl; + cout << " [-minHitRate = " << minHitRate << ">]" << endl; + cout << " [-maxFalseAlarmRate ]" << endl; + cout << " [-weightTrimRate ]" << endl; + cout << " [-maxDepth ]" << endl; + cout << " [-maxWeakCount ]" << endl; +} + +void CascadeBoostParams::printAttrs() 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() ); + cout << "boostType: " << boostTypeStr << endl; + cout << "minHitRate: " << minHitRate << endl; + cout << "maxFalseAlarmRate: " << maxFalseAlarm << endl; + cout << "weightTrimRate: " << weight_trim_rate << endl; + cout << "maxDepth: " << max_depth << endl; + cout << "maxWeakCount: " << weak_count << endl; +} + +bool CascadeBoostParams::scanAttr( const string prmName, const string val) +{ + bool res = true; + + if( !prmName.compare( "-bt" ) ) + { + boost_type = !val.compare( CC_DISCRETE_BOOST ) ? CvBoost::DISCRETE : + !val.compare( CC_REAL_BOOST ) ? CvBoost::REAL : + !val.compare( CC_LOGIT_BOOST ) ? CvBoost::LOGIT : + !val.compare( CC_GENTLE_BOOST ) ? CvBoost::GENTLE : -1; + if (boost_type == -1) + res = false; + } + else if( !prmName.compare( "-minHitRate" ) ) + { + minHitRate = (float) atof( val.c_str() ); + } + else if( !prmName.compare( "-maxFalseAlarmRate" ) ) + { + maxFalseAlarm = (float) atof( val.c_str() ); + } + else if( !prmName.compare( "-weightTrimRate" ) ) + { + weight_trim_rate = (float) atof( val.c_str() ); + } + else if( !prmName.compare( "-maxDepth" ) ) + { + max_depth = atoi( val.c_str() ); + } + else if( !prmName.compare( "-maxWeakCount" ) ) + { + weak_count = atoi( val.c_str() ); + } + else + res = false; + + return res; +} + +CvDTreeNode* CascadeBoostTrainData::subsample_data( const CvMat* _subsample_idx ) +{ + CvDTreeNode* root = 0; + CvMat* isubsample_idx = 0; + CvMat* subsample_co = 0; + + bool isMakeRootCopy = true; + + if( !data_root ) + CV_Error( CV_StsError, "No training data has been set" ); + + if( _subsample_idx ) + { + CV_Assert( (isubsample_idx = cvPreprocessIndexArray( _subsample_idx, sample_count )) != 0 ); + + if( isubsample_idx->cols + isubsample_idx->rows - 1 == sample_count ) + { + const int* sidx = isubsample_idx->data.i; + for( int i = 0; i < sample_count; i++ ) + { + if( sidx[i] != i ) + { + isMakeRootCopy = false; + break; + } + } + } + else + isMakeRootCopy = false; + } + + if( isMakeRootCopy ) + { + // make a copy of the root node + CvDTreeNode temp; + int i; + root = new_node( 0, 1, 0, 0 ); + temp = *root; + *root = *data_root; + root->num_valid = temp.num_valid; + if( root->num_valid ) + { + for( i = 0; i < var_count; i++ ) + root->num_valid[i] = data_root->num_valid[i]; + } + root->cv_Tn = temp.cv_Tn; + root->cv_node_risk = temp.cv_node_risk; + root->cv_node_error = temp.cv_node_error; + } + else + { + int* sidx = isubsample_idx->data.i; + // co - array of count/offset pairs (to handle duplicated values in _subsample_idx) + int* co, cur_ofs = 0; + int workVarCount = get_work_var_count(); + int count = isubsample_idx->rows + isubsample_idx->cols - 1; + + root = new_node( 0, count, 1, 0 ); + + CV_Assert( (subsample_co = cvCreateMat( 1, sample_count*2, CV_32SC1 )) != 0); + cvZero( subsample_co ); + co = subsample_co->data.i; + for( int i = 0; i < count; i++ ) + co[sidx[i]*2]++; + for( int i = 0; i < sample_count; i++ ) + { + if( co[i*2] ) + { + co[i*2+1] = cur_ofs; + cur_ofs += co[i*2]; + } + else + co[i*2+1] = -1; + } + + cv::AutoBuffer inn_buf(sample_count*(2*sizeof(int) + sizeof(float))); + // subsample ordered variables + for( int vi = 0; vi < numPrecalcIdx; vi++ ) + { + int ci = get_var_type(vi); + CV_Assert( ci < 0 ); + + int *src_idx_buf = (int*)(uchar*)inn_buf; + float *src_val_buf = (float*)(src_idx_buf + sample_count); + int* sample_indices_buf = (int*)(src_val_buf + sample_count); + const int* src_idx = 0; + const float* src_val = 0; + get_ord_var_data( data_root, vi, src_val_buf, src_idx_buf, &src_val, &src_idx, sample_indices_buf ); + + int j = 0, idx, count_i; + int num_valid = data_root->get_num_valid(vi); + CV_Assert( num_valid == sample_count ); + + if (is_buf_16u) + { + unsigned short* udst_idx = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + vi*sample_count + data_root->offset); + for( int i = 0; i < num_valid; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + udst_idx[j] = (unsigned short)cur_ofs; + } + } + else + { + int* idst_idx = buf->data.i + root->buf_idx*get_length_subbuf() + + vi*sample_count + root->offset; + for( int i = 0; i < num_valid; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + idst_idx[j] = cur_ofs; + } + } + } + + // subsample cv_lables + const int* src_lbls = get_cv_labels(data_root, (int*)(uchar*)inn_buf); + if (is_buf_16u) + { + unsigned short* udst = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + (workVarCount-1)*sample_count + root->offset); + for( int i = 0; i < count; i++ ) + udst[i] = (unsigned short)src_lbls[sidx[i]]; + } + else + { + int* idst = buf->data.i + root->buf_idx*get_length_subbuf() + + (workVarCount-1)*sample_count + root->offset; + for( int i = 0; i < count; i++ ) + idst[i] = src_lbls[sidx[i]]; + } + + // subsample sample_indices + const int* sample_idx_src = get_sample_indices(data_root, (int*)(uchar*)inn_buf); + if (is_buf_16u) + { + unsigned short* sample_idx_dst = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + workVarCount*sample_count + root->offset); + for( int i = 0; i < count; i++ ) + sample_idx_dst[i] = (unsigned short)sample_idx_src[sidx[i]]; + } + else + { + int* sample_idx_dst = buf->data.i + root->buf_idx*get_length_subbuf() + + workVarCount*sample_count + root->offset; + for( int i = 0; i < count; i++ ) + sample_idx_dst[i] = sample_idx_src[sidx[i]]; + } + + for( int vi = 0; vi < var_count; vi++ ) + root->set_num_valid(vi, count); + } + + cvReleaseMat( &isubsample_idx ); + cvReleaseMat( &subsample_co ); + + return root; +} + +//---------------------------- CascadeBoostTrainData ----------------------------- + +CascadeBoostTrainData::CascadeBoostTrainData( const FeatureEvaluator* _featureEvaluator, + const CvDTreeParams& _params ) +{ + is_classifier = true; + var_all = var_count = (int)_featureEvaluator->getNumFeatures(); + + featureEvaluator = _featureEvaluator; + shared = true; + set_params( _params ); + max_c_count = MAX( 2, featureEvaluator->getMaxCatCount() ); + var_type = cvCreateMat( 1, var_count + 2, CV_32SC1 ); + if ( featureEvaluator->getMaxCatCount() > 0 ) + { + numPrecalcIdx = 0; + cat_var_count = var_count; + ord_var_count = 0; + for( int vi = 0; vi < var_count; vi++ ) + { + var_type->data.i[vi] = vi; + } + } + else + { + cat_var_count = 0; + ord_var_count = var_count; + for( int vi = 1; vi <= var_count; vi++ ) + { + var_type->data.i[vi-1] = -vi; + } + } + var_type->data.i[var_count] = cat_var_count; + var_type->data.i[var_count+1] = cat_var_count+1; + + int maxSplitSize = cvAlign(sizeof(CvDTreeSplit) + (MAX(0,max_c_count - 33)/32)*sizeof(int),sizeof(void*)); + int treeBlockSize = MAX((int)sizeof(CvDTreeNode)*8, maxSplitSize); + treeBlockSize = MAX(treeBlockSize + BlockSizeDelta, MinBlockSize); + tree_storage = cvCreateMemStorage( treeBlockSize ); + node_heap = cvCreateSet( 0, sizeof(node_heap[0]), sizeof(CvDTreeNode), tree_storage ); + split_heap = cvCreateSet( 0, sizeof(split_heap[0]), maxSplitSize, tree_storage ); +} + +CascadeBoostTrainData::CascadeBoostTrainData( const FeatureEvaluator* _featureEvaluator, + int _numSamples, + int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params ) +{ + setData( _featureEvaluator, _numSamples, _precalcValBufSize, _precalcIdxBufSize, _params ); +} + +void CascadeBoostTrainData::setData( const FeatureEvaluator* _featureEvaluator, + int _numSamples, + int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params ) +{ + int* idst = 0; + unsigned short* udst = 0; + + uint64 effective_buf_size = 0; + int effective_buf_height = 0, effective_buf_width = 0; + + + clear(); + shared = true; + have_labels = true; + have_priors = false; + is_classifier = true; + + rng = &cv::theRNG(); + + set_params( _params ); + + CV_Assert( _featureEvaluator ); + featureEvaluator = _featureEvaluator; + + max_c_count = MAX( 2, featureEvaluator->getMaxCatCount() ); + _resp = featureEvaluator->getCls(); + responses = &_resp; + // TODO: check responses: elements must be 0 or 1 + + if( _precalcValBufSize < 0 || _precalcIdxBufSize < 0) + CV_Error( CV_StsOutOfRange, "_numPrecalcVal and _numPrecalcIdx must be positive or 0" ); + + var_count = var_all = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize(); + sample_count = _numSamples; + + is_buf_16u = false; + if (sample_count < 65536) + is_buf_16u = true; + + numPrecalcVal = min( cvRound((double)_precalcValBufSize*1048576. / (sizeof(float)*sample_count)), var_count ); + numPrecalcIdx = min( cvRound((double)_precalcIdxBufSize*1048576. / + ((is_buf_16u ? sizeof(unsigned short) : sizeof (int))*sample_count)), var_count ); + + assert( numPrecalcIdx >= 0 && numPrecalcVal >= 0 ); + + valCache.create( numPrecalcVal, sample_count, CV_32FC1 ); + var_type = cvCreateMat( 1, var_count + 2, CV_32SC1 ); + if ( featureEvaluator->getMaxCatCount() > 0 ) + { + numPrecalcIdx = 0; + cat_var_count = var_count; + ord_var_count = 0; + for( int vi = 0; vi < var_count; vi++ ) + { + var_type->data.i[vi] = vi; + } + } + else + { + cat_var_count = 0; + ord_var_count = var_count; + for( int vi = 1; vi <= var_count; vi++ ) + { + var_type->data.i[vi-1] = -vi; + } + } + var_type->data.i[var_count] = cat_var_count; + var_type->data.i[var_count+1] = cat_var_count+1; + work_var_count = ( cat_var_count ? 0 : numPrecalcIdx ) + 1/*cv_lables*/; + buf_count = 2; + + buf_size = -1; // the member buf_size is obsolete + + effective_buf_size = (uint64)(work_var_count + 1)*(uint64)sample_count * buf_count; // this is the total size of "CvMat buf" to be allocated + effective_buf_width = sample_count; + effective_buf_height = work_var_count+1; + + if (effective_buf_width >= effective_buf_height) + effective_buf_height *= buf_count; + else + effective_buf_width *= buf_count; + + if ((uint64)effective_buf_width * (uint64)effective_buf_height != effective_buf_size) + { + CV_Error(CV_StsBadArg, "The memory buffer cannot be allocated since its size exceeds integer fields limit"); + } + if ( is_buf_16u ) + buf = cvCreateMat( effective_buf_height, effective_buf_width, CV_16UC1 ); + else + buf = cvCreateMat( effective_buf_height, effective_buf_width, CV_32SC1 ); + + cat_count = cvCreateMat( 1, cat_var_count + 1, CV_32SC1 ); + + // precalculate valCache and set indices in buf + precalculate(); + + // now calculate the maximum size of split, + // create memory storage that will keep nodes and splits of the decision tree + // allocate root node and the buffer for the whole training data + int maxSplitSize = cvAlign(sizeof(CvDTreeSplit) + + (MAX(0,sample_count - 33)/32)*sizeof(int),sizeof(void*)); + int treeBlockSize = MAX((int)sizeof(CvDTreeNode)*8, maxSplitSize); + treeBlockSize = MAX(treeBlockSize + BlockSizeDelta, MinBlockSize); + tree_storage = cvCreateMemStorage( treeBlockSize ); + node_heap = cvCreateSet( 0, sizeof(*node_heap), sizeof(CvDTreeNode), tree_storage ); + + int nvSize = var_count*sizeof(int); + nvSize = cvAlign(MAX( nvSize, (int)sizeof(CvSetElem) ), sizeof(void*)); + int tempBlockSize = nvSize; + tempBlockSize = MAX( tempBlockSize + BlockSizeDelta, MinBlockSize ); + temp_storage = cvCreateMemStorage( tempBlockSize ); + nv_heap = cvCreateSet( 0, sizeof(*nv_heap), nvSize, temp_storage ); + + data_root = new_node( 0, sample_count, 0, 0 ); + + // set sample labels + if (is_buf_16u) + udst = (unsigned short*)(buf->data.s + work_var_count*sample_count); + else + idst = buf->data.i + work_var_count*sample_count; + + for (int si = 0; si < sample_count; si++) + { + if (udst) + udst[si] = (unsigned short)si; + else + idst[si] = si; + } + for( int vi = 0; vi < var_count; vi++ ) + data_root->set_num_valid(vi, sample_count); + for( int vi = 0; vi < cat_var_count; vi++ ) + cat_count->data.i[vi] = max_c_count; + + cat_count->data.i[cat_var_count] = 2; + + maxSplitSize = cvAlign(sizeof(CvDTreeSplit) + + (MAX(0,max_c_count - 33)/32)*sizeof(int),sizeof(void*)); + split_heap = cvCreateSet( 0, sizeof(*split_heap), maxSplitSize, tree_storage ); + + priors = cvCreateMat( 1, get_num_classes(), CV_64F ); + cvSet(priors, cvScalar(1)); + priors_mult = cvCloneMat( priors ); + counts = cvCreateMat( 1, get_num_classes(), CV_32SC1 ); + direction = cvCreateMat( 1, sample_count, CV_8UC1 ); + split_buf = cvCreateMat( 1, sample_count, CV_32SC1 );//TODO: make a pointer +} + +void CascadeBoostTrainData::free_train_data() +{ + CvDTreeTrainData::free_train_data(); + valCache.release(); +} + +const int* CascadeBoostTrainData::get_class_labels( CvDTreeNode* n, int* labelsBuf) +{ + int nodeSampleCount = n->sample_count; + int rStep = CV_IS_MAT_CONT( responses->type ) ? 1 : responses->step / CV_ELEM_SIZE( responses->type ); + + int* sampleIndicesBuf = labelsBuf; // + const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf); + for( int si = 0; si < nodeSampleCount; si++ ) + { + int sidx = sampleIndices[si]; + labelsBuf[si] = (int)responses->data.fl[sidx*rStep]; + } + return labelsBuf; +} + +const int* CascadeBoostTrainData::get_sample_indices( CvDTreeNode* n, int* indicesBuf ) +{ + return CvDTreeTrainData::get_cat_var_data( n, get_work_var_count(), indicesBuf ); +} + +const int* CascadeBoostTrainData::get_cv_labels( CvDTreeNode* n, int* labels_buf ) +{ + return CvDTreeTrainData::get_cat_var_data( n, get_work_var_count() - 1, labels_buf ); +} + +void CascadeBoostTrainData::get_ord_var_data( CvDTreeNode* n, int vi, float* ordValuesBuf, int* sortedIndicesBuf, + const float** ordValues, const int** sortedIndices, int* sampleIndicesBuf ) +{ + int nodeSampleCount = n->sample_count; + const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf); + + if ( vi < numPrecalcIdx ) + { + if( !is_buf_16u ) + *sortedIndices = buf->data.i + n->buf_idx*get_length_subbuf() + vi*sample_count + n->offset; + else + { + const unsigned short* shortIndices = (const unsigned short*)(buf->data.s + n->buf_idx*get_length_subbuf() + + vi*sample_count + n->offset ); + for( int i = 0; i < nodeSampleCount; i++ ) + sortedIndicesBuf[i] = shortIndices[i]; + + *sortedIndices = sortedIndicesBuf; + } + + if( vi < numPrecalcVal ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + int idx = (*sortedIndices)[i]; + idx = sampleIndices[idx]; + ordValuesBuf[i] = valCache.at( vi, idx); + } + } + else + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + int idx = (*sortedIndices)[i]; + idx = sampleIndices[idx]; + ordValuesBuf[i] = (*featureEvaluator)( vi, idx); + } + } + } + else // vi >= numPrecalcIdx + { + cv::AutoBuffer abuf(nodeSampleCount); + float* sampleValues = &abuf[0]; + + if ( vi < numPrecalcVal ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + sortedIndicesBuf[i] = i; + sampleValues[i] = valCache.at( vi, sampleIndices[i] ); + } + } + else + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + sortedIndicesBuf[i] = i; + sampleValues[i] = (*featureEvaluator)( vi, sampleIndices[i]); + } + } + icvSortIntAux( sortedIndicesBuf, nodeSampleCount, &sampleValues[0] ); + for( int i = 0; i < nodeSampleCount; i++ ) + ordValuesBuf[i] = (&sampleValues[0])[sortedIndicesBuf[i]]; + *sortedIndices = sortedIndicesBuf; + } + + *ordValues = ordValuesBuf; +} + +const int* CascadeBoostTrainData::get_cat_var_data( CvDTreeNode* n, int vi, int* catValuesBuf ) +{ + int nodeSampleCount = n->sample_count; + int* sampleIndicesBuf = catValuesBuf; // + const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf); + + if ( vi < numPrecalcVal ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + catValuesBuf[i] = (int) valCache.at( vi, sampleIndices[i]); + } + else + { + if( vi >= numPrecalcVal && vi < var_count ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + catValuesBuf[i] = (int)(*featureEvaluator)( vi, sampleIndices[i] ); + } + else + { + get_cv_labels( n, catValuesBuf ); + } + } + + return catValuesBuf; +} + +float CascadeBoostTrainData::getVarValue( int vi, int si ) +{ + if ( vi < numPrecalcVal && !valCache.empty() ) + return valCache.at( vi, si ); + return (*featureEvaluator)( vi, si ); +} + + +struct FeatureIdxOnlyPrecalc : ParallelLoopBody +{ + FeatureIdxOnlyPrecalc( const FeatureEvaluator* _featureEvaluator, CvMat* _buf, int _sample_count, bool _is_buf_16u ) + { + featureEvaluator = _featureEvaluator; + sample_count = _sample_count; + udst = (unsigned short*)_buf->data.s; + idst = _buf->data.i; + is_buf_16u = _is_buf_16u; + } + void operator()( const Range& range ) const + { + cv::AutoBuffer valCache(sample_count); + float* valCachePtr = (float*)valCache; + for ( int fi = range.start; fi < range.end; fi++) + { + for( int si = 0; si < sample_count; si++ ) + { + valCachePtr[si] = (*featureEvaluator)( fi, si ); + if ( is_buf_16u ) + *(udst + fi*sample_count + si) = (unsigned short)si; + else + *(idst + fi*sample_count + si) = si; + } + if ( is_buf_16u ) + icvSortUShAux( udst + fi*sample_count, sample_count, valCachePtr ); + else + icvSortIntAux( idst + fi*sample_count, sample_count, valCachePtr ); + } + } + const FeatureEvaluator* featureEvaluator; + int sample_count; + int* idst; + unsigned short* udst; + bool is_buf_16u; +}; + +struct FeatureValAndIdxPrecalc : ParallelLoopBody +{ + FeatureValAndIdxPrecalc( const FeatureEvaluator* _featureEvaluator, CvMat* _buf, Mat* _valCache, int _sample_count, bool _is_buf_16u ) + { + featureEvaluator = _featureEvaluator; + valCache = _valCache; + sample_count = _sample_count; + udst = (unsigned short*)_buf->data.s; + idst = _buf->data.i; + is_buf_16u = _is_buf_16u; + } + void operator()( const Range& range ) const + { + for ( int fi = range.start; fi < range.end; fi++) + { + for( int si = 0; si < sample_count; si++ ) + { + valCache->at(fi,si) = (*featureEvaluator)( fi, si ); + if ( is_buf_16u ) + *(udst + fi*sample_count + si) = (unsigned short)si; + else + *(idst + fi*sample_count + si) = si; + } + if ( is_buf_16u ) + icvSortUShAux( udst + fi*sample_count, sample_count, valCache->ptr(fi) ); + else + icvSortIntAux( idst + fi*sample_count, sample_count, valCache->ptr(fi) ); + } + } + const FeatureEvaluator* featureEvaluator; + Mat* valCache; + int sample_count; + int* idst; + unsigned short* udst; + bool is_buf_16u; +}; + +struct FeatureValOnlyPrecalc : ParallelLoopBody +{ + FeatureValOnlyPrecalc( const FeatureEvaluator* _featureEvaluator, Mat* _valCache, int _sample_count ) + { + featureEvaluator = _featureEvaluator; + valCache = _valCache; + sample_count = _sample_count; + } + void operator()( const Range& range ) const + { + for ( int fi = range.start; fi < range.end; fi++) + for( int si = 0; si < sample_count; si++ ) + valCache->at(fi,si) = (*featureEvaluator)( fi, si ); + } + const FeatureEvaluator* featureEvaluator; + Mat* valCache; + int sample_count; +}; + +void CascadeBoostTrainData::precalculate() +{ + int minNum = MIN( numPrecalcVal, numPrecalcIdx); + + double proctime = -TIME( 0 ); + parallel_for_( Range(numPrecalcVal, numPrecalcIdx), + FeatureIdxOnlyPrecalc(featureEvaluator, buf, sample_count, is_buf_16u!=0) ); + parallel_for_( Range(0, minNum), + FeatureValAndIdxPrecalc(featureEvaluator, buf, &valCache, sample_count, is_buf_16u!=0) ); + parallel_for_( Range(minNum, numPrecalcVal), + FeatureValOnlyPrecalc(featureEvaluator, &valCache, sample_count) ); + cout << "Precalculation time: " << (proctime + TIME( 0 )) << endl; +} + +//-------------------------------- CascadeBoostTree ---------------------------------------- + +CvDTreeNode* CascadeBoostTree::predict( int sampleIdx ) const +{ + CvDTreeNode* node = root; + if( !node ) + CV_Error( CV_StsError, "The tree has not been trained yet" ); + + if ( ((CascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount() == 0 ) // ordered + { + while( node->left ) + { + CvDTreeSplit* split = node->split; + float val = ((CascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx ); + node = val <= split->ord.c ? node->left : node->right; + } + } + else // categorical + { + while( node->left ) + { + CvDTreeSplit* split = node->split; + int c = (int)((CascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx ); + node = CV_DTREE_CAT_DIR(c, split->subset) < 0 ? node->left : node->right; + } + } + return node; +} + +void CascadeBoostTree::write( FileStorage &fs, const Mat& featureMap ) +{ + 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; + fidx = featureMap.empty() ? fidx : featureMap.at(0, fidx); + 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::read( const FileNode &node, CvBoost* _ensemble, + CvDTreeTrainData* _data ) +{ + int maxCatCount = ((CascadeBoostTrainData*)_data)->featureEvaluator->getMaxCatCount(); + int subsetN = (maxCatCount + 31)/32; + int step = 3 + ( maxCatCount>0 ? subsetN : 1 ); + + queue internalNodesQueue; + FileNodeIterator internalNodesIt, leafValsuesIt; + CvDTreeNode* prntNode, *cldNode; + + clear(); + data = _data; + ensemble = _ensemble; + pruned_tree_idx = 0; + + // read tree nodes + FileNode rnode = node[CC_INTERNAL_NODES]; + internalNodesIt = rnode.end(); + leafValsuesIt = node[CC_LEAF_VALUES].end(); + internalNodesIt--; leafValsuesIt--; + for( size_t i = 0; i < rnode.size()/step; i++ ) + { + prntNode = data->new_node( 0, 0, 0, 0 ); + if ( maxCatCount > 0 ) + { + prntNode->split = data->new_split_cat( 0, 0 ); + for( int j = subsetN-1; j>=0; j--) + { + *internalNodesIt >> prntNode->split->subset[j]; internalNodesIt--; + } + } + else + { + float split_value; + *internalNodesIt >> split_value; internalNodesIt--; + prntNode->split = data->new_split_ord( 0, split_value, 0, 0, 0); + } + *internalNodesIt >> prntNode->split->var_idx; internalNodesIt--; + int ridx, lidx; + *internalNodesIt >> ridx; internalNodesIt--; + *internalNodesIt >> lidx;internalNodesIt--; + if ( ridx <= 0) + { + prntNode->right = cldNode = data->new_node( 0, 0, 0, 0 ); + *leafValsuesIt >> cldNode->value; leafValsuesIt--; + cldNode->parent = prntNode; + } + else + { + prntNode->right = internalNodesQueue.front(); + prntNode->right->parent = prntNode; + internalNodesQueue.pop(); + } + + if ( lidx <= 0) + { + prntNode->left = cldNode = data->new_node( 0, 0, 0, 0 ); + *leafValsuesIt >> cldNode->value; leafValsuesIt--; + cldNode->parent = prntNode; + } + else + { + prntNode->left = internalNodesQueue.front(); + prntNode->left->parent = prntNode; + internalNodesQueue.pop(); + } + + internalNodesQueue.push( prntNode ); + } + + root = internalNodesQueue.front(); + internalNodesQueue.pop(); +} + +void CascadeBoostTree::split_node_data( CvDTreeNode* node ) +{ + int n = node->sample_count, nl, nr, scount = data->sample_count; + char* dir = (char*)data->direction->data.ptr; + CvDTreeNode *left = 0, *right = 0; + int* newIdx = data->split_buf->data.i; + int newBufIdx = data->get_child_buf_idx( node ); + int workVarCount = data->get_work_var_count(); + CvMat* buf = data->buf; + size_t length_buf_row = data->get_length_subbuf(); + cv::AutoBuffer inn_buf(n*(3*sizeof(int)+sizeof(float))); + int* tempBuf = (int*)(uchar*)inn_buf; + bool splitInputData; + + complete_node_dir(node); + + for( int i = nl = nr = 0; i < n; i++ ) + { + int d = dir[i]; + // initialize new indices for splitting ordered variables + newIdx[i] = (nl & (d-1)) | (nr & -d); // d ? ri : li + nr += d; + nl += d^1; + } + + node->left = left = data->new_node( node, nl, newBufIdx, node->offset ); + node->right = right = data->new_node( node, nr, newBufIdx, node->offset + nl ); + + splitInputData = node->depth + 1 < data->params.max_depth && + (node->left->sample_count > data->params.min_sample_count || + node->right->sample_count > data->params.min_sample_count); + + // split ordered variables, keep both halves sorted. + for( int vi = 0; vi < ((CascadeBoostTrainData*)data)->numPrecalcIdx; vi++ ) + { + int ci = data->get_var_type(vi); + if( ci >= 0 || !splitInputData ) + continue; + + int n1 = node->get_num_valid(vi); + float *src_val_buf = (float*)(tempBuf + n); + int *src_sorted_idx_buf = (int*)(src_val_buf + n); + int *src_sample_idx_buf = src_sorted_idx_buf + n; + const int* src_sorted_idx = 0; + const float* src_val = 0; + data->get_ord_var_data(node, vi, src_val_buf, src_sorted_idx_buf, &src_val, &src_sorted_idx, src_sample_idx_buf); + + for(int i = 0; i < n; i++) + tempBuf[i] = src_sorted_idx[i]; + + if (data->is_buf_16u) + { + ushort *ldst, *rdst; + ldst = (ushort*)(buf->data.s + left->buf_idx*length_buf_row + + vi*scount + left->offset); + rdst = (ushort*)(ldst + nl); + + // split sorted + for( int i = 0; i < n1; i++ ) + { + int idx = tempBuf[i]; + int d = dir[idx]; + idx = newIdx[idx]; + if (d) + { + *rdst = (ushort)idx; + rdst++; + } + else + { + *ldst = (ushort)idx; + ldst++; + } + } + CV_Assert( n1 == n ); + } + else + { + int *ldst, *rdst; + ldst = buf->data.i + left->buf_idx*length_buf_row + + vi*scount + left->offset; + rdst = buf->data.i + right->buf_idx*length_buf_row + + vi*scount + right->offset; + + // split sorted + for( int i = 0; i < n1; i++ ) + { + int idx = tempBuf[i]; + int d = dir[idx]; + idx = newIdx[idx]; + if (d) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + CV_Assert( n1 == n ); + } + } + + // split cv_labels using newIdx relocation table + int *src_lbls_buf = tempBuf + n; + const int* src_lbls = data->get_cv_labels(node, src_lbls_buf); + + for(int i = 0; i < n; i++) + tempBuf[i] = src_lbls[i]; + + if (data->is_buf_16u) + { + unsigned short *ldst = (unsigned short *)(buf->data.s + left->buf_idx*length_buf_row + + (workVarCount-1)*scount + left->offset); + unsigned short *rdst = (unsigned short *)(buf->data.s + right->buf_idx*length_buf_row + + (workVarCount-1)*scount + right->offset); + + for( int i = 0; i < n; i++ ) + { + int idx = tempBuf[i]; + if (dir[i]) + { + *rdst = (unsigned short)idx; + rdst++; + } + else + { + *ldst = (unsigned short)idx; + ldst++; + } + } + + } + else + { + int *ldst = buf->data.i + left->buf_idx*length_buf_row + + (workVarCount-1)*scount + left->offset; + int *rdst = buf->data.i + right->buf_idx*length_buf_row + + (workVarCount-1)*scount + right->offset; + + for( int i = 0; i < n; i++ ) + { + int idx = tempBuf[i]; + if (dir[i]) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + + // split sample indices + int *sampleIdx_src_buf = tempBuf + n; + const int* sampleIdx_src = data->get_sample_indices(node, sampleIdx_src_buf); + + for(int i = 0; i < n; i++) + tempBuf[i] = sampleIdx_src[i]; + + if (data->is_buf_16u) + { + unsigned short* ldst = (unsigned short*)(buf->data.s + left->buf_idx*length_buf_row + + workVarCount*scount + left->offset); + unsigned short* rdst = (unsigned short*)(buf->data.s + right->buf_idx*length_buf_row + + workVarCount*scount + right->offset); + for (int i = 0; i < n; i++) + { + unsigned short idx = (unsigned short)tempBuf[i]; + if (dir[i]) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + else + { + int* ldst = buf->data.i + left->buf_idx*length_buf_row + + workVarCount*scount + left->offset; + int* rdst = buf->data.i + right->buf_idx*length_buf_row + + workVarCount*scount + right->offset; + for (int i = 0; i < n; i++) + { + int idx = tempBuf[i]; + if (dir[i]) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + + for( int vi = 0; vi < data->var_count; vi++ ) + { + left->set_num_valid(vi, (int)(nl)); + right->set_num_valid(vi, (int)(nr)); + } + + // deallocate the parent node data that is not needed anymore + data->free_node_data(node); +} + +static void auxMarkFeaturesInMap( const CvDTreeNode* node, Mat& featureMap) +{ + if ( node && node->split ) + { + featureMap.ptr(0)[node->split->var_idx] = 1; + auxMarkFeaturesInMap( node->left, featureMap ); + auxMarkFeaturesInMap( node->right, featureMap ); + } +} + +void CascadeBoostTree::markFeaturesInMap( Mat& featureMap ) +{ + auxMarkFeaturesInMap( root, featureMap ); +} + +//----------------------------------- CascadeBoost -------------------------------------- + +bool CascadeBoost::train( const FeatureEvaluator* _featureEvaluator, + int _numSamples, + int _precalcValBufSize, int _precalcIdxBufSize, + const CascadeBoostParams& _params ) +{ + bool isTrained = false; + CV_Assert( !data ); + clear(); + + data = new CascadeBoostTrainData( _featureEvaluator, _numSamples, + _precalcValBufSize, _precalcIdxBufSize, _params ); + + CvMemStorage *storage = cvCreateMemStorage(); + weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage ); + storage = 0; + + set_params( _params ); + if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) ) + data->do_responses_copy(); + + update_weights( 0 ); + + cout << "+----+---------+---------+" << endl; + cout << "| N | HR | FA |" << endl; + cout << "+----+---------+---------+" << endl; + + do + { + CascadeBoostTree* tree = new CascadeBoostTree; + if( !tree->train( data, subsample_mask, this ) ) + { + delete tree; + break; + } + + cvSeqPush( weak, &tree ); + update_weights( tree ); + trim_weights(); + if( cvCountNonZero(subsample_mask) == 0 ) + break; + } + while( !isErrDesired() && (weak->total < params.weak_count) ); + + if(weak->total > 0) + { + data->is_classifier = true; + data->free_train_data(); + isTrained = true; + } + else + clear(); + + return isTrained; +} + +float CascadeBoost::predict( int sampleIdx, bool returnSum ) const +{ + CV_Assert( weak ); + double sum = 0; + CvSeqReader reader; + cvStartReadSeq( weak, &reader ); + cvSetSeqReaderPos( &reader, 0 ); + for( int i = 0; i < weak->total; i++ ) + { + CvBoostTree* wtree; + 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; +} + +bool CascadeBoost::set_params( const CvBoostParams& _params ) +{ + minHitRate = ((CascadeBoostParams&)_params).minHitRate; + maxFalseAlarm = ((CascadeBoostParams&)_params).maxFalseAlarm; + return ( ( minHitRate > 0 ) && ( minHitRate < 1) && + ( maxFalseAlarm > 0 ) && ( maxFalseAlarm < 1) && + CvBoost::set_params( _params )); +} + +void CascadeBoost::update_weights( CvBoostTree* tree ) +{ + int n = data->sample_count; + double sumW = 0.; + int step = 0; + float* fdata = 0; + int *sampleIdxBuf; + const int* sampleIdx = 0; + int inn_buf_size = ((params.boost_type == LOGIT) || (params.boost_type == GENTLE) ? n*sizeof(int) : 0) + + ( !tree ? n*sizeof(int) : 0 ); + cv::AutoBuffer inn_buf(inn_buf_size); + uchar* cur_inn_buf_pos = (uchar*)inn_buf; + if ( (params.boost_type == LOGIT) || (params.boost_type == GENTLE) ) + { + step = CV_IS_MAT_CONT(data->responses_copy->type) ? + 1 : data->responses_copy->step / CV_ELEM_SIZE(data->responses_copy->type); + fdata = data->responses_copy->data.fl; + sampleIdxBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(sampleIdxBuf + n); + sampleIdx = data->get_sample_indices( data->data_root, sampleIdxBuf ); + } + CvMat* buf = data->buf; + size_t length_buf_row = data->get_length_subbuf(); + if( !tree ) // before training the first tree, initialize weights and other parameters + { + int* classLabelsBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(classLabelsBuf + n); + const int* classLabels = data->get_class_labels(data->data_root, classLabelsBuf); + // in case of logitboost and gentle adaboost each weak tree is a regression tree, + // so we need to convert class labels to floating-point values + double w0 = 1./n; + double p[2] = { 1, 1 }; + + cvReleaseMat( &orig_response ); + cvReleaseMat( &sum_response ); + cvReleaseMat( &weak_eval ); + cvReleaseMat( &subsample_mask ); + cvReleaseMat( &weights ); + + orig_response = cvCreateMat( 1, n, CV_32S ); + weak_eval = cvCreateMat( 1, n, CV_64F ); + subsample_mask = cvCreateMat( 1, n, CV_8U ); + weights = cvCreateMat( 1, n, CV_64F ); + subtree_weights = cvCreateMat( 1, n + 2, CV_64F ); + + if (data->is_buf_16u) + { + unsigned short* labels = (unsigned short*)(buf->data.s + data->data_root->buf_idx*length_buf_row + + data->data_root->offset + (data->work_var_count-1)*data->sample_count); + for( int i = 0; i < n; i++ ) + { + // save original categorical responses {0,1}, convert them to {-1,1} + orig_response->data.i[i] = classLabels[i]*2 - 1; + // make all the samples active at start. + // later, in trim_weights() deactivate/reactive again some, if need + subsample_mask->data.ptr[i] = (uchar)1; + // make all the initial weights the same. + weights->data.db[i] = w0*p[classLabels[i]]; + // set the labels to find (from within weak tree learning proc) + // the particular sample weight, and where to store the response. + labels[i] = (unsigned short)i; + } + } + else + { + int* labels = buf->data.i + data->data_root->buf_idx*length_buf_row + + data->data_root->offset + (data->work_var_count-1)*data->sample_count; + + for( int i = 0; i < n; i++ ) + { + // save original categorical responses {0,1}, convert them to {-1,1} + orig_response->data.i[i] = classLabels[i]*2 - 1; + subsample_mask->data.ptr[i] = (uchar)1; + weights->data.db[i] = w0*p[classLabels[i]]; + labels[i] = i; + } + } + + if( params.boost_type == LOGIT ) + { + sum_response = cvCreateMat( 1, n, CV_64F ); + + for( int i = 0; i < n; i++ ) + { + sum_response->data.db[i] = 0; + fdata[sampleIdx[i]*step] = orig_response->data.i[i] > 0 ? 2.f : -2.f; + } + + // in case of logitboost each weak tree is a regression tree. + // the target function values are recalculated for each of the trees + data->is_classifier = false; + } + else if( params.boost_type == GENTLE ) + { + for( int i = 0; i < n; i++ ) + fdata[sampleIdx[i]*step] = (float)orig_response->data.i[i]; + + data->is_classifier = false; + } + } + else + { + // at this moment, for all the samples that participated in the training of the most + // recent weak classifier we know the responses. For other samples we need to compute them + if( have_subsample ) + { + // invert the subsample mask + cvXorS( subsample_mask, cvScalar(1.), subsample_mask ); + + // run tree through all the non-processed samples + for( int i = 0; i < n; i++ ) + if( subsample_mask->data.ptr[i] ) + { + weak_eval->data.db[i] = ((CascadeBoostTree*)tree)->predict( i )->value; + } + } + + // now update weights and other parameters for each type of boosting + if( params.boost_type == DISCRETE ) + { + // Discrete AdaBoost: + // weak_eval[i] (=f(x_i)) is in {-1,1} + // err = sum(w_i*(f(x_i) != y_i))/sum(w_i) + // C = log((1-err)/err) + // w_i *= exp(C*(f(x_i) != y_i)) + + double C, err = 0.; + double scale[] = { 1., 0. }; + + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i]; + sumW += w; + err += w*(weak_eval->data.db[i] != orig_response->data.i[i]); + } + + if( sumW != 0 ) + err /= sumW; + C = err = -logRatio( err ); + scale[1] = exp(err); + + sumW = 0; + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i]* + scale[weak_eval->data.db[i] != orig_response->data.i[i]]; + sumW += w; + weights->data.db[i] = w; + } + + tree->scale( C ); + } + else if( params.boost_type == REAL ) + { + // Real AdaBoost: + // weak_eval[i] = f(x_i) = 0.5*log(p(x_i)/(1-p(x_i))), p(x_i)=P(y=1|x_i) + // w_i *= exp(-y_i*f(x_i)) + + for( int i = 0; i < n; i++ ) + weak_eval->data.db[i] *= -orig_response->data.i[i]; + + cvExp( weak_eval, weak_eval ); + + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i]*weak_eval->data.db[i]; + sumW += w; + weights->data.db[i] = w; + } + } + else if( params.boost_type == LOGIT ) + { + // LogitBoost: + // weak_eval[i] = f(x_i) in [-z_max,z_max] + // sum_response = F(x_i). + // F(x_i) += 0.5*f(x_i) + // p(x_i) = exp(F(x_i))/(exp(F(x_i)) + exp(-F(x_i))=1/(1+exp(-2*F(x_i))) + // reuse weak_eval: weak_eval[i] <- p(x_i) + // w_i = p(x_i)*1(1 - p(x_i)) + // z_i = ((y_i+1)/2 - p(x_i))/(p(x_i)*(1 - p(x_i))) + // store z_i to the data->data_root as the new target responses + + const double lbWeightThresh = FLT_EPSILON; + const double lbZMax = 10.; + + for( int i = 0; i < n; i++ ) + { + double s = sum_response->data.db[i] + 0.5*weak_eval->data.db[i]; + sum_response->data.db[i] = s; + weak_eval->data.db[i] = -2*s; + } + + cvExp( weak_eval, weak_eval ); + + for( int i = 0; i < n; i++ ) + { + double p = 1./(1. + weak_eval->data.db[i]); + double w = p*(1 - p), z; + w = MAX( w, lbWeightThresh ); + weights->data.db[i] = w; + sumW += w; + if( orig_response->data.i[i] > 0 ) + { + z = 1./p; + fdata[sampleIdx[i]*step] = (float)min(z, lbZMax); + } + else + { + z = 1./(1-p); + fdata[sampleIdx[i]*step] = (float)-min(z, lbZMax); + } + } + } + else + { + // Gentle AdaBoost: + // weak_eval[i] = f(x_i) in [-1,1] + // w_i *= exp(-y_i*f(x_i)) + assert( params.boost_type == GENTLE ); + + for( int i = 0; i < n; i++ ) + weak_eval->data.db[i] *= -orig_response->data.i[i]; + + cvExp( weak_eval, weak_eval ); + + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i] * weak_eval->data.db[i]; + weights->data.db[i] = w; + sumW += w; + } + } + } + + // renormalize weights + if( sumW > FLT_EPSILON ) + { + sumW = 1./sumW; + for( int i = 0; i < n; ++i ) + weights->data.db[i] *= sumW; + } +} + +bool CascadeBoost::isErrDesired() +{ + int sCount = data->sample_count, + numPos = 0, numNeg = 0, numFalse = 0, numPosTrue = 0; + vector eval(sCount); + + for( int i = 0; i < sCount; i++ ) + if( ((CascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 1.0F ) + eval[numPos++] = predict( i, true ); + icvSortFlt( &eval[0], numPos, 0 ); + int thresholdIdx = (int)((1.0F - minHitRate) * numPos); + threshold = eval[ thresholdIdx ]; + numPosTrue = numPos - thresholdIdx; + for( int i = thresholdIdx - 1; i >= 0; i--) + if ( abs( eval[i] - threshold) < FLT_EPSILON ) + numPosTrue++; + float hitRate = ((float) numPosTrue) / ((float) numPos); + + for( int i = 0; i < sCount; i++ ) + { + if( ((CascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 0.0F ) + { + numNeg++; + if( predict( i ) ) + numFalse++; + } + } + float falseAlarm = ((float) numFalse) / ((float) numNeg); + + cout << "|"; cout.width(4); cout << right << weak->total; + cout << "|"; cout.width(9); cout << right << hitRate; + cout << "|"; cout.width(9); cout << right << falseAlarm; + cout << "|" << endl; + cout << "+----+---------+---------+" << endl; + + return falseAlarm <= maxFalseAlarm; +} + +void CascadeBoost::write( FileStorage &fs, const Mat& featureMap ) 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, featureMap ); + } + fs << "]"; +} + +bool CascadeBoost::read( const FileNode &node, + const FeatureEvaluator* _featureEvaluator, + const CascadeBoostParams& _params ) +{ + CvMemStorage* storage; + clear(); + data = new CascadeBoostTrainData( _featureEvaluator, _params ); + set_params( _params ); + + node[CC_STAGE_THRESHOLD] >> threshold; + FileNode rnode = node[CC_WEAK_CLASSIFIERS]; + + storage = cvCreateMemStorage(); + weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage ); + for( FileNodeIterator it = rnode.begin(); it != rnode.end(); it++ ) + { + CascadeBoostTree* tree = new CascadeBoostTree(); + tree->read( *it, this, data ); + cvSeqPush( weak, &tree ); + } + return true; +} + +void CascadeBoost::markUsedFeaturesInMap( Mat& featureMap ) +{ + for( int wi = 0; wi < weak->total; wi++ ) + { + CascadeBoostTree* weakTree = *((CascadeBoostTree**) cvGetSeqElem( weak, wi )); + weakTree->markFeaturesInMap( featureMap ); + } +} + diff --git a/openbr/core/boost.h b/openbr/core/boost.h new file mode 100644 index 0000000..e39c12a --- /dev/null +++ b/openbr/core/boost.h @@ -0,0 +1,92 @@ +#ifndef _BOOST_H_ +#define _BOOST_H_ + +#include "features.h" +#include "ml.h" + +namespace br +{ + +struct CascadeBoostParams : CvBoostParams +{ + float minHitRate; + float maxFalseAlarm; + + CascadeBoostParams(); + CascadeBoostParams( int _boostType, float _minHitRate, float _maxFalseAlarm, + double _weightTrimRate, int _maxDepth, int _maxWeakCount ); + virtual ~CascadeBoostParams() {} + void write( cv::FileStorage &fs ) const; + bool read( const cv::FileNode &node ); + virtual void printDefaults() const; + virtual void printAttrs() const; + virtual bool scanAttr( const std::string prmName, const std::string val); +}; + +struct CascadeBoostTrainData : CvDTreeTrainData +{ + CascadeBoostTrainData( const FeatureEvaluator* _featureEvaluator, + const CvDTreeParams& _params ); + CascadeBoostTrainData( const FeatureEvaluator* _featureEvaluator, + int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params = CvDTreeParams() ); + virtual void setData( const FeatureEvaluator* _featureEvaluator, + int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params=CvDTreeParams() ); + void precalculate(); + + virtual CvDTreeNode* subsample_data( const CvMat* _subsample_idx ); + + virtual const int* get_class_labels( CvDTreeNode* n, int* labelsBuf ); + virtual const int* get_cv_labels( CvDTreeNode* n, int* labelsBuf); + virtual const int* get_sample_indices( CvDTreeNode* n, int* indicesBuf ); + + virtual void get_ord_var_data( CvDTreeNode* n, int vi, float* ordValuesBuf, int* sortedIndicesBuf, + const float** ordValues, const int** sortedIndices, int* sampleIndicesBuf ); + virtual const int* get_cat_var_data( CvDTreeNode* n, int vi, int* catValuesBuf ); + virtual float getVarValue( int vi, int si ); + virtual void free_train_data(); + + const FeatureEvaluator* featureEvaluator; + cv::Mat valCache; // precalculated feature values (CV_32FC1) + CvMat _resp; // for casting + int numPrecalcVal, numPrecalcIdx; +}; + +class CascadeBoostTree : public CvBoostTree +{ +public: + virtual CvDTreeNode* predict( int sampleIdx ) const; + void write( cv::FileStorage &fs, const cv::Mat& featureMap ); + void read( const cv::FileNode &node, CvBoost* _ensemble, CvDTreeTrainData* _data ); + void markFeaturesInMap( cv::Mat& featureMap ); +protected: + virtual void split_node_data( CvDTreeNode* n ); +}; + +class CascadeBoost : public CvBoost +{ +public: + virtual bool train( const FeatureEvaluator* _featureEvaluator, + int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, + const CascadeBoostParams& _params=CascadeBoostParams() ); + virtual float predict( int sampleIdx, bool returnSum = false ) const; + + float getThreshold() const { return threshold; } + void write( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + bool read( const cv::FileNode &node, const FeatureEvaluator* _featureEvaluator, + const CascadeBoostParams& _params ); + void markUsedFeaturesInMap( cv::Mat& featureMap ); +protected: + virtual bool set_params( const CvBoostParams& _params ); + virtual void update_weights( CvBoostTree* tree ); + virtual bool isErrDesired(); + + float threshold; + float minHitRate, maxFalseAlarm; +}; + +} // namespace br + +#endif + diff --git a/openbr/core/cascade.cpp b/openbr/core/cascade.cpp new file mode 100644 index 0000000..2cf5945 --- /dev/null +++ b/openbr/core/cascade.cpp @@ -0,0 +1,647 @@ +#include "cascade.h" +#include +#include +#include + +using namespace std; +using namespace br; +using namespace cv; + +bool CascadeImageReader::create( const string _posFilename, const string _negFilename, Size _winSize ) +{ + return posReader.create(_posFilename) && negReader.create(_negFilename, _winSize); +} + +CascadeImageReader::NegReader::NegReader() +{ + 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; +} + +bool CascadeImageReader::NegReader::create( const string _filename, Size _winSize ) +{ + string dirname, str; + std::ifstream file(_filename.c_str()); + if ( !file.is_open() ) + return false; + + size_t pos = _filename.rfind('\\'); + char dlmrt = '\\'; + if (pos == string::npos) + { + pos = _filename.rfind('/'); + dlmrt = '/'; + } + dirname = pos == string::npos ? "" : _filename.substr(0, pos) + dlmrt; + while( !file.eof() ) + { + std::getline(file, str); + if (str.empty()) break; + if (str.at(0) == '#' ) continue; /* comment */ + imgFilenames.push_back(dirname + str); + } + file.close(); + + winSize = _winSize; + last = round = 0; + return true; +} + +bool CascadeImageReader::NegReader::nextImg() +{ + Point _offset = Point(0,0); + size_t count = imgFilenames.size(); + for( size_t i = 0; i < count; i++ ) + { + src = imread( imgFilenames[last++], 0 ); + if( src.empty() ) + continue; + round += last / count; + round = round % (winSize.width * winSize.height); + last %= 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 CascadeImageReader::NegReader::get( Mat& _img ) +{ + CV_Assert( !_img.empty() ); + CV_Assert( _img.type() == CV_8UC1 ); + CV_Assert( _img.cols == winSize.width ); + CV_Assert( _img.rows == winSize.height ); + + if( img.empty() ) + if ( !nextImg() ) + 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 ( !nextImg() ) + return false; + } + } + } + return true; +} + +CascadeImageReader::PosReader::PosReader() +{ + file = 0; + vec = 0; +} + +bool CascadeImageReader::PosReader::create( const string _filename ) +{ + if ( file ) + fclose( file ); + file = fopen( _filename.c_str(), "rb" ); + + if( !file ) + return false; + short tmp = 0; + if( fread( &count, sizeof( count ), 1, file ) != 1 || + fread( &vecSize, sizeof( vecSize ), 1, file ) != 1 || + fread( &tmp, sizeof( tmp ), 1, file ) != 1 || + fread( &tmp, sizeof( tmp ), 1, file ) != 1 ) + CV_Error_( CV_StsParseError, ("wrong file format for %s\n", _filename.c_str()) ); + base = sizeof( count ) + sizeof( vecSize ) + 2*sizeof( tmp ); + if( feof( file ) ) + return false; + last = 0; + vec = (short*) cvAlloc( sizeof( *vec ) * vecSize ); + CV_Assert( vec ); + return true; +} + +bool CascadeImageReader::PosReader::get( Mat &_img ) +{ + CV_Assert( _img.rows * _img.cols == vecSize ); + uchar tmp = 0; + size_t elements_read = fread( &tmp, sizeof( tmp ), 1, file ); + if( elements_read != 1 ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. The most possible reason is " + "insufficient count of samples in given vec-file.\n"); + elements_read = fread( vec, sizeof( vec[0] ), vecSize, file ); + if( elements_read != (size_t)(vecSize) ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. Seems that vec-file has incorrect structure.\n"); + + if( feof( file ) || last++ >= count ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. vec-file is over.\n"); + + for( int r = 0; r < _img.rows; r++ ) + { + for( int c = 0; c < _img.cols; c++ ) + _img.ptr(r)[c] = (uchar)vec[r * _img.cols + c]; + } + return true; +} + +void CascadeImageReader::PosReader::restart() +{ + CV_Assert( file ); + last = 0; + fseek( file, base, SEEK_SET ); +} + +CascadeImageReader::PosReader::~PosReader() +{ + if (file) + fclose( file ); + cvFree( &vec ); +} + +// -------------------------------------- Cascade -------------------------------------------- + +static const char* stageTypes[] = { CC_BOOST }; +static const char* featureTypes[] = { CC_LBP, CC_HAAR, CC_HOG, CC_HOGMULTI, CC_NPD }; + +CascadeParams::CascadeParams() : stageType( defaultStageType ), + featureType( defaultFeatureType ), winSize( cvSize(24, 24) ) +{ + name = CC_CASCADE_PARAMS; +} +CascadeParams::CascadeParams( int _stageType, int _featureType ) : stageType( _stageType ), + featureType( _featureType ), winSize( cvSize(24, 24) ) +{ + name = CC_CASCADE_PARAMS; +} + +//---------------------------- CascadeParams -------------------------------------- + +void CascadeParams::write( FileStorage &fs ) const +{ + string stageTypeStr = stageType == BOOST ? CC_BOOST : string(); + CV_Assert( !stageTypeStr.empty() ); + fs << CC_STAGE_TYPE << stageTypeStr; + string featureTypeStr = featureType == FeatureParams::LBP ? CC_HAAR : + 0; + CV_Assert( !stageTypeStr.empty() ); + fs << CC_FEATURE_TYPE << featureTypeStr; + fs << CC_HEIGHT << winSize.height; + fs << CC_WIDTH << winSize.width; +} + +bool CascadeParams::read( const FileNode &node ) +{ + if ( node.empty() ) + return false; + string stageTypeStr, featureTypeStr; + FileNode rnode = node[CC_STAGE_TYPE]; + if ( !rnode.isString() ) + return false; + rnode >> stageTypeStr; + stageType = !stageTypeStr.compare( CC_BOOST ) ? BOOST : -1; + if (stageType == -1) + return false; + rnode = node[CC_FEATURE_TYPE]; + if ( !rnode.isString() ) + return false; + rnode >> featureTypeStr; + featureType = !featureTypeStr.compare( CC_LBP ) ? FeatureParams::LBP : + -1; + if (featureType == -1) + return false; + node[CC_HEIGHT] >> winSize.height; + node[CC_WIDTH] >> winSize.width; + return winSize.height > 0 && winSize.width > 0; +} + +void CascadeParams::printDefaults() const +{ + Params::printDefaults(); + cout << " [-stageType <"; + for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ ) + { + cout << (i ? " | " : "") << stageTypes[i]; + if ( i == defaultStageType ) + cout << "(default)"; + } + cout << ">]" << endl; + + cout << " [-featureType <{"; + for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ ) + { + cout << (i ? ", " : "") << featureTypes[i]; + if ( i == defaultStageType ) + cout << "(default)"; + } + cout << "}>]" << endl; + cout << " [-w ]" << endl; + cout << " [-h ]" << endl; +} + +void CascadeParams::printAttrs() const +{ + cout << "stageType: " << stageTypes[stageType] << endl; + cout << "featureType: " << featureTypes[featureType] << endl; + cout << "sampleWidth: " << winSize.width << endl; + cout << "sampleHeight: " << winSize.height << endl; +} + +bool CascadeParams::scanAttr( const string prmName, const string val ) +{ + bool res = true; + if( !prmName.compare( "-stageType" ) ) + { + for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ ) + if( !val.compare( stageTypes[i] ) ) + stageType = i; + } + else if( !prmName.compare( "-featureType" ) ) + { + for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ ) + if( !val.compare( featureTypes[i] ) ) + featureType = i; + } + else if( !prmName.compare( "-w" ) ) + { + winSize.width = atoi( val.c_str() ); + } + else if( !prmName.compare( "-h" ) ) + { + winSize.height = atoi( val.c_str() ); + } + else + res = false; + return res; +} + +//---------------------------- CascadeClassifier -------------------------------------- + +bool BrCascadeClassifier::train( const string _cascadeDirName, + const string _posFilename, + const string _negFilename, + int _numPos, int _numNeg, + int _precalcValBufSize, int _precalcIdxBufSize, + int _numStages, + const CascadeParams& _cascadeParams, + const FeatureParams& _featureParams, + const CascadeBoostParams& _stageParams, + bool baseFormatSave ) +{ + // Start recording clock ticks for training time output + const clock_t begin_time = clock(); + + if( _cascadeDirName.empty() || _posFilename.empty() || _negFilename.empty() ) + CV_Error( CV_StsBadArg, "_cascadeDirName or _bgfileName or _vecFileName is NULL" ); + + string dirName; + if (_cascadeDirName.find_last_of("/\\") == (_cascadeDirName.length() - 1) ) + dirName = _cascadeDirName; + else + dirName = _cascadeDirName + '/'; + + numPos = _numPos; + numNeg = _numNeg; + numStages = _numStages; + if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) ) + { + cout << "Image reader can not be created from -vec " << _posFilename + << " and -bg " << _negFilename << "." << endl; + return false; + } + if ( !load( dirName ) ) + { + cascadeParams = _cascadeParams; + featureParams = FeatureParams::create(cascadeParams.featureType); + featureParams->init(_featureParams); + stageParams = new CascadeBoostParams; + *stageParams = _stageParams; + featureEvaluator = FeatureEvaluator::create(cascadeParams.featureType); + featureEvaluator->init( (FeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize ); + stageClassifiers.reserve( numStages ); + } + cout << "PARAMETERS:" << endl; + cout << "cascadeDirName: " << _cascadeDirName << endl; + cout << "vecFileName: " << _posFilename << endl; + cout << "bgFileName: " << _negFilename << endl; + cout << "numPos: " << _numPos << endl; + cout << "numNeg: " << _numNeg << endl; + cout << "numStages: " << numStages << endl; + cout << "precalcValBufSize[Mb] : " << _precalcValBufSize << endl; + cout << "precalcIdxBufSize[Mb] : " << _precalcIdxBufSize << endl; + cascadeParams.printAttrs(); + stageParams->printAttrs(); + featureParams->printAttrs(); + + int startNumStages = (int)stageClassifiers.size(); + if ( startNumStages > 1 ) + cout << endl << "Stages 0-" << startNumStages-1 << " are loaded" << endl; + else if ( startNumStages == 1) + cout << endl << "Stage 0 is loaded" << endl; + + double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) / + (double)stageParams->max_depth; + double tempLeafFARate; + + for( int i = startNumStages; i < numStages; i++ ) + { + cout << endl << "===== TRAINING " << i << "-stage =====" << endl; + cout << "train( (FeatureEvaluator*)featureEvaluator, + curNumSamples, _precalcValBufSize, _precalcIdxBufSize, + *((CascadeBoostParams*)stageParams) ); + cout << "END>" << endl; + + if(!isStageTrained) + break; + + stageClassifiers.push_back( tempStage ); + + // save params + if( i == 0) + { + std::string paramsFilename = dirName + CC_PARAMS_FILENAME; + FileStorage fs( paramsFilename, FileStorage::WRITE); + if ( !fs.isOpened() ) + { + cout << "Parameters can not be written, because file " << paramsFilename + << " can not be opened." << endl; + return false; + } + fs << FileStorage::getDefaultObjectName(paramsFilename) << "{"; + writeParams( fs ); + fs << "}"; + } + // save current stage + char buf[10]; + sprintf(buf, "%s%d", "stage", i ); + string stageFilename = dirName + buf + ".xml"; + FileStorage fs( stageFilename, FileStorage::WRITE ); + if ( !fs.isOpened() ) + { + cout << "Current stage can not be written, because file " << stageFilename + << " can not be opened." << endl; + return false; + } + fs << FileStorage::getDefaultObjectName(stageFilename) << "{"; + tempStage->write( fs, Mat() ); + fs << "}"; + + // Output training time up till now + float seconds = float( clock () - begin_time ) / CLOCKS_PER_SEC; + int days = int(seconds) / 60 / 60 / 24; + int hours = (int(seconds) / 60 / 60) % 24; + int minutes = (int(seconds) / 60) % 60; + int seconds_left = int(seconds) % 60; + cout << "Training until now has taken " << days << " days " << hours << " hours " << minutes << " minutes " << seconds_left <<" seconds." << endl; + } + + if(stageClassifiers.size() == 0) + { + cout << "Cascade classifier can't be trained. Check the used training parameters." << endl; + return false; + } + + save( dirName + CC_CASCADE_FILENAME, baseFormatSave ); + + return true; +} + +int BrCascadeClassifier::predict( int sampleIdx ) +{ + CV_DbgAssert( sampleIdx < numPos + numNeg ); + for (vector< Ptr >::iterator it = stageClassifiers.begin(); + it != stageClassifiers.end(); it++ ) + { + if ( (*it)->predict( sampleIdx ) == 0.f ) + return 0; + } + return 1; +} + +bool BrCascadeClassifier::updateTrainingSet( double& acceptanceRatio) +{ + int64 posConsumed = 0, negConsumed = 0; + imgReader.restart(); + + int posCount = fillPassedSamples( 0, numPos, true, posConsumed ); + if( !posCount ) + return false; + cout << "POS count : consumed " << posCount << " : " << (int)posConsumed << endl; + + int proNumNeg = cvRound( ( ((double)numNeg) * ((double)posCount) ) / numPos ); // apply only a fraction of negative samples. double is required since overflow is possible + int negCount = fillPassedSamples( posCount, proNumNeg, false, negConsumed ); + if ( !negCount ) + return false; + + curNumSamples = posCount + negCount; + acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed ); + cout << "NEG count : acceptanceRatio " << negCount << " : " << acceptanceRatio << endl; + return true; +} + +int BrCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, int64& consumed ) +{ + int getcount = 0; + Mat img(cascadeParams.winSize, CV_8UC1); + for( int i = first; i < first + count; i++ ) + { + for( ; ; ) + { + bool isGetImg = isPositive ? imgReader.getPos( img ) : + imgReader.getNeg( img ); + if( !isGetImg ) + return getcount; + consumed++; + + featureEvaluator->setImage( img, isPositive ? 1 : 0, i ); + if( predict( i ) == 1.0F ) + { + getcount++; + printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount); + break; + } + } + } + return getcount; +} + +void BrCascadeClassifier::writeParams( FileStorage &fs ) const +{ + cascadeParams.write( fs ); + fs << CC_STAGE_PARAMS << "{"; stageParams->write( fs ); fs << "}"; + fs << CC_FEATURE_PARAMS << "{"; featureParams->write( fs ); fs << "}"; +} + +void BrCascadeClassifier::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + ((FeatureEvaluator*)((Ptr)featureEvaluator))->writeFeatures( fs, featureMap ); +} + +void BrCascadeClassifier::writeStages( FileStorage &fs, const Mat& featureMap ) const +{ + char cmnt[30]; + int i = 0; + fs << CC_STAGES << "["; + for( vector< Ptr >::const_iterator it = stageClassifiers.begin(); + it != stageClassifiers.end(); it++, i++ ) + { + sprintf( cmnt, "stage %d", i ); + cvWriteComment( fs.fs, cmnt, 0 ); + fs << "{"; + ((CascadeBoost*)((Ptr)*it))->write( fs, featureMap ); + fs << "}"; + } + fs << "]"; +} + +bool BrCascadeClassifier::readParams( const FileNode &node ) +{ + if ( !node.isMap() || !cascadeParams.read( node ) ) + return false; + stageParams = new CascadeBoostParams; + FileNode rnode = node[CC_STAGE_PARAMS]; + if ( !stageParams->read( rnode ) ) + return false; + + featureParams = FeatureParams::create(cascadeParams.featureType); + rnode = node[CC_FEATURE_PARAMS]; + if ( !featureParams->read( rnode ) ) + return false; + return true; +} + +bool BrCascadeClassifier::readStages( const FileNode &node) +{ + FileNode rnode = node[CC_STAGES]; + if (!rnode.empty() || !rnode.isSeq()) + return false; + stageClassifiers.reserve(numStages); + FileNodeIterator it = rnode.begin(); + for( int i = 0; i < min( (int)rnode.size(), numStages ); i++, it++ ) + { + CascadeBoost* tempStage = new CascadeBoost; + if ( !tempStage->read( *it, (FeatureEvaluator *)featureEvaluator, *((CascadeBoostParams*)stageParams) ) ) + { + delete tempStage; + return false; + } + stageClassifiers.push_back(tempStage); + } + return true; +} + +void BrCascadeClassifier::save( const string filename, bool baseFormat ) +{ + FileStorage fs( filename, FileStorage::WRITE ); + + if ( !fs.isOpened() ) + return; + + fs << FileStorage::getDefaultObjectName(filename) << "{"; + if ( !baseFormat ) + { + Mat featureMap; + getUsedFeaturesIdxMap( featureMap ); + writeParams( fs ); + fs << CC_STAGE_NUM << (int)stageClassifiers.size(); + writeStages( fs, featureMap ); + writeFeatures( fs, featureMap ); + } + else + { + qFatal("Old style cascade. Not sure how you got here but it's not supported"); + } + fs << "}"; +} + +bool BrCascadeClassifier::load( const string cascadeDirName ) +{ + FileStorage fs( cascadeDirName + CC_PARAMS_FILENAME, FileStorage::READ ); + if ( !fs.isOpened() ) + return false; + FileNode node = fs.getFirstTopLevelNode(); + if ( !readParams( node ) ) + return false; + + featureEvaluator = FeatureEvaluator::create(cascadeParams.featureType); + featureEvaluator->init( ((FeatureParams*)featureParams), numPos + numNeg, cascadeParams.winSize ); + fs.release(); + + char buf[10]; + for ( int si = 0; si < numStages; si++ ) + { + sprintf( buf, "%s%d", "stage", si); + fs.open( cascadeDirName + buf + ".xml", FileStorage::READ ); + node = fs.getFirstTopLevelNode(); + if ( !fs.isOpened() ) + break; + CascadeBoost *tempStage = new CascadeBoost; + + if ( !tempStage->read( node, (FeatureEvaluator*)featureEvaluator, *((CascadeBoostParams*)stageParams )) ) + { + delete tempStage; + fs.release(); + break; + } + stageClassifiers.push_back(tempStage); + } + return true; +} + +void BrCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap ) +{ + 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++ ) + ((CascadeBoost*)((Ptr)(*it)))->markUsedFeaturesInMap( featureMap ); + + for( int fi = 0, idx = 0; fi < varCount; fi++ ) + if ( featureMap.at(0, fi) >= 0 ) + featureMap.ptr(0)[fi] = idx++; +} + diff --git a/openbr/core/cascade.h b/openbr/core/cascade.h new file mode 100644 index 0000000..86f3f07 --- /dev/null +++ b/openbr/core/cascade.h @@ -0,0 +1,119 @@ +#ifndef CASCADE_H +#define CASCADE_H + +#include +#include +#include "features.h" +#include "boost.h" + +namespace br +{ + +class CascadeImageReader +{ +public: + bool create( const std::string _posFilename, const std::string _negFilename, cv::Size _winSize ); + void restart() { posReader.restart(); } + bool getNeg(cv::Mat &_img) { return negReader.get( _img ); } + bool getPos(cv::Mat &_img) { return posReader.get( _img ); } + +private: + class PosReader + { + public: + PosReader(); + virtual ~PosReader(); + bool create( const std::string _filename ); + bool get( cv::Mat &_img ); + void restart(); + + short* vec; + FILE* file; + int count; + int vecSize; + int last; + int base; + } posReader; + + class NegReader + { + public: + NegReader(); + bool create( const std::string _filename, cv::Size _winSize ); + bool get( cv::Mat& _img ); + bool nextImg(); + + cv::Mat src, img; + std::vector imgFilenames; + cv::Point offset, point; + float scale; + float scaleFactor; + float stepFactor; + size_t last, round; + cv::Size winSize; + } negReader; +}; + +class CascadeParams : public Params +{ +public: + enum { BOOST = 0 }; + static const int defaultStageType = BOOST; + static const int defaultFeatureType = FeatureParams::LBP; + + CascadeParams(); + CascadeParams( int _stageType, int _featureType ); + void write( cv::FileStorage &fs ) const; + bool read( const cv::FileNode &node ); + + void printDefaults() const; + void printAttrs() const; + bool scanAttr( const std::string prmName, const std::string val ); + + int stageType; + int featureType; + cv::Size winSize; +}; + +class BrCascadeClassifier +{ +public: + bool train( const std::string _cascadeDirName, + const std::string _posFilename, + const std::string _negFilename, + int _numPos, int _numNeg, + int _precalcValBufSize, int _precalcIdxBufSize, + int _numStages, + const CascadeParams& _cascadeParams, + const FeatureParams& _featureParams, + const CascadeBoostParams& _stageParams, + bool baseFormatSave = false ); +private: + int predict( int sampleIdx ); + void save( const std::string cascadeDirName, bool baseFormat = false ); + bool load( const std::string cascadeDirName ); + bool updateTrainingSet( double& acceptanceRatio ); + int fillPassedSamples( int first, int count, bool isPositive, int64& consumed ); + + void writeParams( cv::FileStorage &fs ) const; + void writeStages( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + bool readParams( const cv::FileNode &node ); + bool readStages( const cv::FileNode &node ); + + void getUsedFeaturesIdxMap( cv::Mat& featureMap ); + + CascadeParams cascadeParams; + cv::Ptr featureParams; + cv::Ptr stageParams; + + cv::Ptr featureEvaluator; + std::vector< cv::Ptr > stageClassifiers; + CascadeImageReader imgReader; + int numStages, curNumSamples; + int numPos, numNeg; +}; + +} // namespace br + +#endif // CASCADE_H diff --git a/openbr/core/features.cpp b/openbr/core/features.cpp new file mode 100644 index 0000000..e727678 --- /dev/null +++ b/openbr/core/features.cpp @@ -0,0 +1,149 @@ +#include "features.h" + +using namespace cv; +using namespace br; + +//------------------------- Params ----------------------------------------------- + +float calcNormFactor( const Mat& sum, const Mat& sqSum ) +{ + CV_DbgAssert( sum.cols > 3 && sqSum.rows > 3 ); + Rect normrect( 1, 1, sum.cols - 3, sum.rows - 3 ); + size_t p0, p1, p2, p3; + CV_SUM_OFFSETS( p0, p1, p2, p3, normrect, sum.step1() ) + double area = normrect.width * normrect.height; + const int *sp = (const int*)sum.data; + int valSum = sp[p0] - sp[p1] - sp[p2] + sp[p3]; + const double *sqp = (const double *)sqSum.data; + double valSqSum = sqp[p0] - sqp[p1] - sqp[p2] + sqp[p3]; + return (float) sqrt( (double) (area * valSqSum - (double)valSum * valSum) ); +} + +Params::Params() : name( "params" ) {} +void Params::printDefaults() const { std::cout << "--" << name << "--" << endl; } +void Params::printAttrs() const {} +bool Params::scanAttr( const string, const string ) { return false; } + + +//---------------------------- FeatureParams -------------------------------------- + +FeatureParams::FeatureParams() : maxCatCount( 0 ), featSize( 1 ) +{ + name = CC_FEATURE_PARAMS; +} + +void FeatureParams::init( const FeatureParams& fp ) +{ + maxCatCount = fp.maxCatCount; + featSize = fp.featSize; +} + +void FeatureParams::write( FileStorage &fs ) const +{ + fs << CC_MAX_CAT_COUNT << maxCatCount; + fs << CC_FEATURE_SIZE << featSize; +} + +bool FeatureParams::read( const FileNode &node ) +{ + if ( node.empty() ) + return false; + maxCatCount = node[CC_MAX_CAT_COUNT]; + featSize = node[CC_FEATURE_SIZE]; + return ( maxCatCount >= 0 && featSize >= 1 ); +} + +Ptr FeatureParams::create( int featureType ) +{ + return featureType == LBP ? Ptr(new LBPFeatureParams) : + Ptr(); +} + +//------------------------------------- FeatureEvaluator --------------------------------------- + +void FeatureEvaluator::init(const FeatureParams *_featureParams, + int _maxSampleCount, Size _winSize ) +{ + CV_Assert(_maxSampleCount > 0); + featureParams = (FeatureParams *)_featureParams; + winSize = _winSize; + numFeatures = 0; + cls.create( (int)_maxSampleCount, 1, CV_32FC1 ); + 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; +} + +Ptr FeatureEvaluator::create(int type) +{ + return type == FeatureParams::LBP ? Ptr(new LBPEvaluator) : + Ptr(); +} + +// ------------------------------------ LBP ----------------------------------------------- + +LBPFeatureParams::LBPFeatureParams() +{ + maxCatCount = 256; + name = LBPF_NAME; +} + +void LBPEvaluator::init(const FeatureParams *_featureParams, int _maxSampleCount, Size _winSize) +{ + CV_Assert( _maxSampleCount > 0); + sum.create((int)_maxSampleCount, (_winSize.width + 1) * (_winSize.height + 1), CV_32SC1); + FeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize ); +} + +void LBPEvaluator::setImage(const Mat &img, uchar clsLabel, int idx) +{ + CV_DbgAssert( !sum.empty() ); + FeatureEvaluator::setImage( img, clsLabel, idx ); + Mat innSum(winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr((int)idx)); + integral( img, innSum ); +} + +void LBPEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + _writeFeatures( features, fs, featureMap ); +} + +void LBPEvaluator::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(); +} + +LBPEvaluator::Feature::Feature() +{ + rect = cvRect(0, 0, 0, 0); +} + +LBPEvaluator::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 LBPEvaluator::Feature::write(FileStorage &fs) const +{ + fs << CC_RECT << "[:" << rect.x << rect.y << rect.width << rect.height << "]"; +} diff --git a/openbr/core/features.h b/openbr/core/features.h new file mode 100644 index 0000000..94fb020 --- /dev/null +++ b/openbr/core/features.h @@ -0,0 +1,218 @@ +#ifndef FEATURE_H +#define FEATURE_H + +#include +#include "opencv2/imgproc/imgproc.hpp" +#include + +#define CC_CASCADE_FILENAME "cascade.xml" +#define CC_PARAMS_FILENAME "params.xml" + +#define CC_CASCADE_PARAMS "cascadeParams" +#define CC_STAGE_TYPE "stageType" +#define CC_FEATURE_TYPE "featureType" +#define CC_HEIGHT "height" +#define CC_WIDTH "width" + +#define CC_STAGE_NUM "stageNum" +#define CC_STAGES "stages" +#define CC_STAGE_PARAMS "stageParams" + +#define CC_BOOST "BOOST" +#define CC_BOOST_TYPE "boostType" +#define CC_DISCRETE_BOOST "DAB" +#define CC_REAL_BOOST "RAB" +#define CC_LOGIT_BOOST "LB" +#define CC_GENTLE_BOOST "GAB" +#define CC_MINHITRATE "minHitRate" +#define CC_MAXFALSEALARM "maxFalseAlarm" +#define CC_TRIM_RATE "weightTrimRate" +#define CC_MAX_DEPTH "maxDepth" +#define CC_WEAK_COUNT "maxWeakCount" +#define CC_STAGE_THRESHOLD "stageThreshold" +#define CC_WEAK_CLASSIFIERS "weakClassifiers" +#define CC_INTERNAL_NODES "internalNodes" +#define CC_LEAF_VALUES "leafValues" + +#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" +#define CC_MODE_BASIC "BASIC" +#define CC_MODE_CORE "CORE" +#define CC_MODE_ALL "ALL" +#define CC_RECTS "rects" +#define CC_TILTED "tilted" + +#define CC_LBP "LBP" +#define CC_RECT "rect" + +#define CC_HOG "HOG" +#define CC_HOGMULTI "HOGMulti" + +#define CC_NPD "NPD" +#define CC_POINTS "points" +#define CC_POINT "point" + +#ifdef _WIN32 +#define TIME( arg ) (((double) clock()) / CLOCKS_PER_SEC) +#else +#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); + +#define CV_TILTED_OFFSETS( p0, p1, p2, p3, rect, step ) \ + /* (x, y) */ \ + (p0) = (rect).x + (step) * (rect).y; \ + /* (x - h, y + h) */ \ + (p1) = (rect).x - (rect).height + (step) * ((rect).y + (rect).height);\ + /* (x + w, y + w) */ \ + (p2) = (rect).x + (rect).width + (step) * ((rect).y + (rect).width); \ + /* (x + w - h, y + w + h) */ \ + (p3) = (rect).x + (rect).width - (rect).height \ + + (step) * ((rect).y + (rect).width + (rect).height); + +namespace br +{ + +float calcNormFactor( const cv::Mat& sum, const cv::Mat& sqSum ); + +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 Params +{ +public: + Params(); + virtual ~Params() {} + // from|to file + virtual void write( cv::FileStorage &fs ) const = 0; + virtual bool read( const cv::FileNode &node ) = 0; + // from|to screen + virtual void printDefaults() const; + virtual void printAttrs() const; + virtual bool scanAttr( const std::string prmName, const std::string val ); + std::string name; +}; + +class FeatureParams : public Params +{ +public: + enum { LBP = 0 }; + FeatureParams(); + virtual void init( const FeatureParams& fp ); + virtual void write( cv::FileStorage &fs ) const; + virtual bool read( const cv::FileNode &node ); + static cv::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 FeatureEvaluator +{ +public: + virtual ~FeatureEvaluator() {} + virtual void init(const FeatureParams *_featureParams, + int _maxSampleCount, cv::Size _winSize ); + virtual void setImage(const cv::Mat& img, uchar clsLabel, int idx); + virtual void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const = 0; + virtual float operator()(int featureIdx, int sampleIdx) const = 0; + static cv::Ptr create(int type); + + int getNumFeatures() const { return numFeatures; } + int getMaxCatCount() const { return featureParams->maxCatCount; } + int getFeatureSize() const { return featureParams->featSize; } + const cv::Mat& getCls() const { return cls; } + float getCls(int si) const { return cls.at(si, 0); } +protected: + virtual void generateFeatures() = 0; + + int npos, nneg; + int numFeatures; + cv::Size winSize; + FeatureParams *featureParams; + cv::Mat cls; +}; + + +//------------------------- LBP Feature --------------------------------- + +#define LBPF_NAME "lbpFeatureParams" + +struct LBPFeatureParams : FeatureParams +{ + LBPFeatureParams(); + +}; + +class LBPEvaluator : public FeatureEvaluator +{ +public: + virtual ~LBPEvaluator() {} + virtual void init(const FeatureParams *_featureParams, + int _maxSampleCount, cv::Size _winSize ); + virtual void setImage(const cv::Mat& img, uchar clsLabel, int idx); + virtual float operator()(int featureIdx, int sampleIdx) const + { return (float)features[featureIdx].calc( sum, sampleIdx); } + virtual void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const; +protected: + virtual void generateFeatures(); + + class Feature + { + public: + Feature(); + Feature( int offset, int x, int y, int _block_w, int _block_h ); + uchar calc( const cv::Mat& _sum, size_t y ) const; + void write( cv::FileStorage &fs ) const; + + cv::Rect rect; + int p[16]; + }; + std::vector features; + + cv::Mat sum; +}; + +inline uchar LBPEvaluator::Feature::calc(const cv::Mat &_sum, size_t y) const +{ + const int* psum = _sum.ptr((int)y); + 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 +} + +} // namespace br + +#endif // FEATURE_H diff --git a/openbr/plugins/metadata/cascade.cpp b/openbr/plugins/metadata/cascade.cpp index 16935d0..4dee71c 100644 --- a/openbr/plugins/metadata/cascade.cpp +++ b/openbr/plugins/metadata/cascade.cpp @@ -21,139 +21,9 @@ #include #include #include +#include using namespace cv; - -struct TrainParams -{ - QString data; // REQUIRED: Filepath to store trained classifier - QString vec; // REQUIRED: Filepath to store vector of positive samples, default "vector" - QString img; // Filepath to source object image. Either this or info is REQUIRED - QString info; // Description file of source images. Either this or img is REQUIRED - QString bg; // REQUIRED: Filepath to background list file - int num; // Number of samples to generate - int bgcolor; // Background color supplied image (via img) - int bgthresh; // Threshold to determine bgcolor match - bool inv; // Invert colors - bool randinv; // Randomly invert colors - int maxidev; // Max intensity deviation of foreground pixels - double maxxangle; // Maximum rotation angle (X) - double maxyangle; // Maximum rotation angle (Y) - double maxzangle; // Maximum rotation angle (Z) - bool show; // Show generated samples - int w; // REQUIRED: Sample width - int h; // REQUIRED: Sample height - int numPos; // Number of positive samples - int numNeg; // Number of negative samples - int numStages; // Number of stages - int precalcValBufSize; // Precalculated val buffer size in Mb - int precalcIdxBufSize; // Precalculated index buffer size in Mb - bool baseFormatSave; // Save in old format - QString stageType; // Stage type (BOOST) - QString featureType; // Feature type (HAAR, LBP) - QString bt; // Boosted classifier type (DAB, RAB, LB, GAB) - double minHitRate; // Minimal hit rate per stage - double maxFalseAlarmRate; // Max false alarm rate per stage - double weightTrimRate; // Weight for trimming - int maxDepth; // Max weak tree depth - int maxWeakCount; // Max weak tree count per stage - QString mode; // Haar feature mode (BASIC, CORE, ALL) - - TrainParams() - { - num = -1; - maxidev = -1; - maxxangle = -1; - maxyangle = -1; - maxzangle = -1; - w = -1; - h = -1; - numPos = -1; - numNeg = -1; - numStages = -1; - precalcValBufSize = -1; - precalcIdxBufSize = -1; - minHitRate = -1; - maxFalseAlarmRate = -1; - weightTrimRate = -1; - maxDepth = -1; - maxWeakCount = -1; - inv = false; - randinv = false; - show = false; - baseFormatSave = false; - vec = "vector.vec"; - bgcolor = -1; - bgthresh = -1; - } -}; - -static QStringList buildTrainingArgs(const TrainParams ¶ms) -{ - QStringList args; - if (params.data != "") args << "-data" << params.data; - else qFatal("Must specify storage location for cascade"); - if (params.vec != "") args << "-vec" << params.vec; - else qFatal("Must specify location of positive vector"); - if (params.bg != "") args << "-bg" << params.bg; - else qFatal("Must specify negative images"); - if (params.numPos >= 0) args << "-numPos" << QString::number(params.numPos); - if (params.numNeg >= 0) args << "-numNeg" << QString::number(params.numNeg); - if (params.numStages >= 0) args << "-numStages" << QString::number(params.numStages); - if (params.precalcValBufSize >= 0) args << "-precalcValBufSize" << QString::number(params.precalcValBufSize); - if (params.precalcIdxBufSize >= 0) args << "-precalcIdxBufSize" << QString::number(params.precalcIdxBufSize); - if (params.baseFormatSave) args << "-baseFormatSave"; - if (params.stageType != "") args << "-stageType" << params.stageType; - if (params.featureType != "") args << "-featureType" << params.featureType; - if (params.w >= 0) args << "-w" << QString::number(params.w); - else qFatal("Must specify width"); - if (params.h >= 0) args << "-h" << QString::number(params.h); - else qFatal("Must specify height"); - if (params.bt != "") args << "-bt" << params.bt; - if (params.minHitRate >= 0) args << "-minHitRate" << QString::number(params.minHitRate); - if (params.maxFalseAlarmRate >= 0) args << "-maxFalseAlarmRate" << QString::number(params.maxFalseAlarmRate); - if (params.weightTrimRate >= 0) args << "-weightTrimRate" << QString::number(params.weightTrimRate); - if (params.maxDepth >= 0) args << "-maxDepth" << QString::number(params.maxDepth); - if (params.maxWeakCount >= 0) args << "-maxWeakCount" << QString::number(params.maxWeakCount); - if (params.mode != "") args << "-mode" << params.mode; - return args; -} - -static QStringList buildSampleArgs(const TrainParams ¶ms) -{ - QStringList args; - if (params.vec != "") args << "-vec" << params.vec; - else qFatal("Must specify location of positive vector"); - if (params.img != "") args << "-img" << params.img; - else if (params.info != "") args << "-info" << params.info; - else qFatal("Must specify positive images"); - if (params.bg != "") args << "-bg" << params.bg; - if (params.num > 0) args << "-num" << QString::number(params.num); - if (params.bgcolor >=0 ) args << "-bgcolor" << QString::number(params.bgcolor); - if (params.bgthresh >= 0) args << "-bgthresh" << QString::number(params.bgthresh); - if (params.maxidev >= 0) args << "-maxidev" << QString::number(params.maxidev); - if (params.maxxangle >= 0) args << "-maxxangle" << QString::number(params.maxxangle); - if (params.maxyangle >= 0) args << "-maxyangle" << QString::number(params.maxyangle); - if (params.maxzangle >= 0) args << "-maxzangle" << QString::number(params.maxzangle); - if (params.w >= 0) args << "-w" << QString::number(params.w); - if (params.h >= 0) args << "-h" << QString::number(params.h); - if (params.show) args << "-show"; - if (params.inv) args << "-inv"; - if (params.randinv) args << "-randinv"; - return args; -} - -static void genSamples(const TrainParams ¶ms) -{ - const QStringList cmdArgs = buildSampleArgs(params); - QProcess::execute("opencv_createsamples",cmdArgs); -} - -static void trainCascade(const TrainParams ¶ms) -{ - const QStringList cmdArgs = buildTrainingArgs(params); - QProcess::execute("opencv_traincascade", cmdArgs); -} namespace br { @@ -202,50 +72,21 @@ class CascadeTransform : public MetaTransform Q_PROPERTY(int minNeighbors READ get_minNeighbors WRITE set_minNeighbors RESET reset_minNeighbors STORED false) Q_PROPERTY(bool ROCMode READ get_ROCMode WRITE set_ROCMode RESET reset_ROCMode STORED false) - // Training parameters - Q_PROPERTY(int numStages READ get_numStages WRITE set_numStages RESET reset_numStages STORED false) - Q_PROPERTY(int w READ get_w WRITE set_w RESET reset_w STORED false) - Q_PROPERTY(int h READ get_h WRITE set_h RESET reset_h STORED false) - Q_PROPERTY(int numPos READ get_numPos WRITE set_numPos RESET reset_numPos STORED false) - Q_PROPERTY(int numNeg READ get_numNeg WRITE set_numNeg RESET reset_numNeg STORED false) - Q_PROPERTY(int precalcValBufSize READ get_precalcValBufSize WRITE set_precalcValBufSize RESET reset_precalcValBufSize STORED false) - Q_PROPERTY(int precalcIdxBufSize READ get_precalcIdxBufSize WRITE set_precalcIdxBufSize RESET reset_precalcIdxBufSize STORED false) - Q_PROPERTY(double minHitRate READ get_minHitRate WRITE set_minHitRate RESET reset_minHitRate STORED false) - Q_PROPERTY(double maxFalseAlarmRate READ get_maxFalseAlarmRate WRITE set_maxFalseAlarmRate RESET reset_maxFalseAlarmRate STORED false) - Q_PROPERTY(double weightTrimRate READ get_weightTrimRate WRITE set_weightTrimRate RESET reset_weightTrimRate 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) - Q_PROPERTY(QString stageType READ get_stageType WRITE set_stageType RESET reset_stageType STORED false) - Q_PROPERTY(QString featureType READ get_featureType WRITE set_featureType RESET reset_featureType STORED false) - Q_PROPERTY(QString bt READ get_bt WRITE set_bt RESET reset_bt STORED false) - Q_PROPERTY(QString mode READ get_mode WRITE set_mode RESET reset_mode STORED false) - Q_PROPERTY(bool show READ get_show WRITE set_show RESET reset_show STORED false) - Q_PROPERTY(bool baseFormatSave READ get_baseFormatSave WRITE set_baseFormatSave RESET reset_baseFormatSave STORED false) + // Training parameters + 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 numPos READ get_numPos WRITE set_numPos RESET reset_numPos STORED false) + Q_PROPERTY(int numNeg READ get_numNeg WRITE set_numNeg RESET reset_numNeg STORED false) BR_PROPERTY(QString, model, "FrontalFace") BR_PROPERTY(int, minSize, 64) BR_PROPERTY(int, minNeighbors, 5) - BR_PROPERTY(bool, ROCMode, false) - - // Training parameters - Default values provided trigger OpenCV defaults - BR_PROPERTY(int, numStages, -1) - BR_PROPERTY(int, w, -1) - BR_PROPERTY(int, h, -1) - BR_PROPERTY(int, numPos, -1) - BR_PROPERTY(int, numNeg, -1) - BR_PROPERTY(int, precalcValBufSize, -1) - BR_PROPERTY(int, precalcIdxBufSize, -1) - BR_PROPERTY(double, minHitRate, -1) - BR_PROPERTY(double, maxFalseAlarmRate, -1) - BR_PROPERTY(double, weightTrimRate, -1) - BR_PROPERTY(int, maxDepth, -1) - BR_PROPERTY(int, maxWeakCount, -1) - BR_PROPERTY(QString, stageType, "") - BR_PROPERTY(QString, featureType, "") - BR_PROPERTY(QString, bt, "") - BR_PROPERTY(QString, mode, "") - BR_PROPERTY(bool, show, false) - BR_PROPERTY(bool, baseFormatSave, false) + BR_PROPERTY(bool, ROCMode, false) + + BR_PROPERTY(QString, vecFile, "data.vec") + BR_PROPERTY(QString, negFile, "neg.txt") + BR_PROPERTY(int, numPos, 1000) + BR_PROPERTY(int, numNeg, 1000) Resource cascadeResource; @@ -259,115 +100,19 @@ class CascadeTransform : public MetaTransform // Train transform void train(const TemplateList& data) { - // Don't train if we're using OpenCV's prebuilt cascades - if (model == "Ear" || model == "Eye" || model == "FrontalFace" || model == "ProfileFace") - return; - - // Open positive and negative list temporary files - QTemporaryFile posFile; - QTemporaryFile negFile; - - posFile.open(); - negFile.open(); - - QTextStream posStream(&posFile); - QTextStream negStream(&negFile); - - TrainParams params; - - // Fill in from params (param defaults are same as struct defaults, so no checks are needed) - params.numStages = numStages; - params.w = w; - params.h = h; - params.numPos = numPos; - params.numNeg = numNeg; - params.precalcValBufSize = precalcValBufSize; - params.precalcIdxBufSize = precalcIdxBufSize; - params.minHitRate = minHitRate; - params.maxFalseAlarmRate = maxFalseAlarmRate; - params.weightTrimRate = weightTrimRate; - params.maxDepth = maxDepth; - params.maxWeakCount = maxWeakCount; - params.stageType = stageType; - params.featureType = featureType; - params.bt = bt; - params.mode = mode; - params.show = show; - params.baseFormatSave = baseFormatSave; - if (params.w < 0) params.w = minSize; - if (params.h < 0) params.h = minSize; - - int posCount = 0; - int negCount = 0; - - bool buildPos = false; // If true, build positive vector from single image - - const FileList files = data.files(); - - for (int i = 0; i < files.length(); i++) { - File f = files[i]; - if (f.contains("training-set")) { - QString tset = f.get("training-set",QString()).toLower(); - - // Negative samples - if (tset == "neg") { - negStream << f.path() << QDir::separator() << f.fileName() << endl; - negCount++; - // Positive samples for crop/rescale - } else if (tset == "pos") { - QString buffer = ""; - - // Extract rectangles - QList rects = f.rects(); - for (int j = 0; j < rects.size(); j++) { - QRectF r = rects[j]; - buffer += " " + QString::number(r.x()) + " " + QString::number(r.y()) + " " + QString::number(r.width()) + " "+ QString::number(r.height()); - posCount++; - } - - posStream << f.path() << QDir::separator() << f.fileName() << " " << f.rects().length() << " " << buffer << endl; - - // Single positive sample for background removal and overlay on negatives - } else if (tset == "pos-base") { - buildPos = true; - params.img = f.path() + QDir::separator() + f.fileName(); - - // Parse settings (unique to this one tag) - if (f.contains("num")) params.num = f.get("num"); - if (f.contains("bgcolor")) params.bgcolor = f.get("bgcolor"); - if (f.contains("bgthresh")) params.bgthresh =f.get("bgthresh"); - if (f.contains("inv")) params.inv = f.get("inv",false); - if (f.contains("randinv")) params.randinv = f.get("randinv",false); - if (f.contains("maxidev")) params.maxidev = f.get("maxidev"); - if (f.contains("maxxangle")) params.maxxangle = f.get("maxxangle"); - if (f.contains("maxyangle")) params.maxyangle = f.get("maxyangle"); - if (f.contains("maxzangle")) params.maxzangle = f.get("maxzangle"); - } - } - } - - posFile.close(); - negFile.close(); - - // Fill in remaining params conditionally - if (buildPos) { - if (params.numPos < 0) { - if (params.num > 0) params.numPos = params.num*.95; - else params.numPos = 950; - } - } else { - params.info = posFile.fileName(); - if (params.numPos < 0) params.numPos = posCount*.95; - } + (void)data; - if (params.num < 0) params.num = posCount; - if (params.numNeg < 0) params.numNeg = negCount*10; + BrCascadeClassifier classifier; - params.bg = negFile.fileName(); - params.data = Globals->sdkPath + "/share/openbr/models/openbrcascades/" + model + "/cascade.xml"; + CascadeParams cascadeParams(CascadeParams::BOOST, FeatureParams::LBP); + CascadeBoostParams stageParams(CvBoost::GENTLE, 0.999, 0.5, 0.95, 1, 200); + LBPFeatureParams featureParams; - genSamples(params); - trainCascade(params); + QString cascadeDir = Globals->sdkPath + "/share/openbr/models/openbrcascades/" + model; + classifier.train(cascadeDir.toStdString(), + vecFile.toStdString(), negFile.toStdString(), + numPos, numNeg, 1024, 1024, 20, + cascadeParams, featureParams, stageParams); } void project(const Template &src, Template &dst) const