1. 程式人生 > >cvpr讀書筆記[3]:traincascade與AdaBoost的opencv實現框架

cvpr讀書筆記[3]:traincascade與AdaBoost的opencv實現框架

本節研究traincascade的opencv實現. 涉及的原始碼位於: sources\apps\traincascade traincascade實現 sources\modules\ml opencv machine learning部分 sources\data\vec_files\trainingfaces_24-24.vec 正樣本 http://blog.csdn.net/njzhujinhua/article/details/38377191
【1】Cascade框架 首先從main入手
int main( int argc, char* argv[] )
{
    CvCascadeClassifier classifier;
    string cascadeDirName, vecName, bgName;
    int numPos = 2000;
    int numNeg = 1000;
    int numStages = 20;
    int precalcValBufSize = 256,
        precalcIdxBufSize = 256;
    bool baseFormatSave = false;

    CvCascadeParams cascadeParams;
    CvCascadeBoostParams stageParams;
    Ptr<CvFeatureParams> featureParams[] = { Ptr<CvFeatureParams>(new CvHaarFeatureParams),
                                             Ptr<CvFeatureParams>(new CvLBPFeatureParams),
                                             Ptr<CvFeatureParams>(new CvHOGFeatureParams)
                                           };
    int fc = sizeof(featureParams)/sizeof(featureParams[0]);
//略
    classifier.train( cascadeDirName,
                      vecName,
                      bgName,
                      numPos, numNeg,
                      precalcValBufSize, precalcIdxBufSize,
                      numStages,
                      cascadeParams,
                      *featureParams[cascadeParams.featureType],
                      stageParams,
                      baseFormatSave );
    return 0;
}

其中的CvCascadeParams 繼承自ml的CvParams, CvCascadeBoostParams 繼承自ml的CvBoostParams後者則是CvDTreeParams的子類.  在CvCascadeBoostParams 中將boosttype設為了GENTLE,一類比較高效的AdaBoost train的各引數: cascadeDirName, 表示訓練結果輸出目錄 vecName, 正樣本的vec檔案,由 opencv_createsamples 生成。正樣本可以由包含待檢測物體的一張圖片生成,也可由一系列標記好的影象生成。 bgName, 背景影象的描述檔案,檔案中包含一系列的影象檔名,這些影象將被隨機選作物體的背景 numPos, numNeg, 正負樣本的個數 precalcValBufSize, 快取大小,用於儲存預先計算的特徵值(feature values),單位為MB。 precalcIdxBufSize 快取大小,用於儲存預先計算的特徵索引(feature indices),單位為MB。記憶體越大,訓練時間越短。 numStages, 訓練的分類器的級數 cascadeParams, 級聯引數,除了預設值外,還可以通過引數指定. 其中stageType智慧取值BOOST, featureType則支援haar,LBP,LOG *featureParams[cascadeParams.featureType], 根據fratureType確定具體使用的FeatureParams stageParams, boost分類器的引數,      -bt指定boosttype,取值
         DAB=Discrete AdaBoost
         RAB = Real AdaBoost,
         LB = LogitBoost,
         GAB = Gentle AdaBoost,預設為GENTLE AdaBoost
     -minHitRate
     分類器的每一級最小檢測率, 預設0.995。總的檢測率大約為 min_hit_rate^number_of_stages。
     -maxFalseAlarmRate
     分類器的每一級允許最大FPR,預設0.5。總的為 max_false_alarm_rate^number_of_stages.
     -weightTrimRate
     樣本權重按大小序累計超過此致的樣本保留進入下一輪訓練. 預設0.95。 見CvBoost::trim_weights
     -maxDepth
     弱分類器樹最大的深度。預設是1,是二叉樹(stumps),只使用一個特徵。
     -maxWeakCount
     每一級中的弱分類器的最大數目。預設100
baseFormatSave 這個引數僅在使用Haar特徵時有效。如果指定這個引數,那麼級聯分類器將以老的格式儲存。 CvCascadeClassifier::train()
概述了整個Cascade的執行過程。包括訓練前的初始化,各Stage的強分類器間的樣本集更新及強分類器訓練都可看到其蹤影,最顯眼的還是其中的Stage訓練的for大迴圈。
bool CvCascadeClassifier::train(...)
{
...
讀取正負樣本
    if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )
    {
        cout << "Image reader can not be created from -vec " << _posFilename<< " and -bg " << _negFilename << "." << endl;
        return false;
    }
在指定data目錄中讀取已訓練過的stagexml檔案
    if ( !load( dirName ) )
    {
檔案不存在則執行初始化過程,
        cascadeParams = _cascadeParams;
具體特徵型別的建立與初始化
        featureParams = CvFeatureParams::create(cascadeParams.featureType);
        featureParams->init(_featureParams);
        stageParams = new CvCascadeBoostParams;
        *stageParams = _stageParams; 還是採用main函式裡面獲取的命令列個引數取值
特徵計算器
        featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);
特徵的初始化,裡面生成了24*24正樣本區域的162336個特徵.
        featureEvaluator->init( (CvFeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize );
        stageClassifiers.reserve( numStages );
    }
......
列印引數
<span style="color:#339999;">PARAMETERS:
cascadeDirName: .
vecFileName: trainingfaces_24-24.vec
bgFileName: bg.txt
numPos: 100
numNeg: 120
numStages: 20
precalcValBufSize[Mb] : 256
precalcIdxBufSize[Mb] : 256
stageType: BOOST
featureType: HAAR 特徵型別
sampleWidth: 24
sampleHeight: 24
boostType: GAB GENTLE AdaBoost
minHitRate: 0.995
maxFalseAlarmRate: 0.5
weightTrimRate: 0.95
maxDepth: 1 只有一個分支節點的二叉決策樹
maxWeakCount: 100
mode: BASIC</span>

已經從檔案讀取的訓練過的stage
    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 << "<BEGIN" << endl;

         更新stage間的樣本, 要做到訓練新的stage的時候仍保障正負樣本數足夠.
        if ( !updateTrainingSet( tempLeafFARate ) )
        {
            樣本數不夠, 退出訓練
            cout << "Train dataset for temp stage can not be filled. Branch training terminated." << endl;
            break;
        }
        if( tempLeafFARate <= requiredLeafFARate )
        {
              虛警率已經達標 不再繼續訓練
            cout << "Required leaf false alarm rate achieved. Branch training terminated." << endl;
            break;
        }

        CvCascadeBoost* tempStage = new CvCascadeBoost;
cascade中一級的強分類器的訓練過程
        bool isStageTrained = tempStage->train( (CvFeatureEvaluator*)featureEvaluator,
                                                curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
                                                *((CvCascadeBoostParams*)stageParams) );
        cout << "END>" << endl;

        if(!isStageTrained)
            break;

        stageClassifiers.push_back( tempStage );

        //儲存階段性的stage到獨立xml檔案
       ............
    }

    if(stageClassifiers.size() == 0)
    {
        cout << "Cascade classifier can't be trained. Check the used training parameters." << endl;
        return false;
    }
   生成最終的xml檔案
    save( dirName + CC_CASCADE_FILENAME, baseFormatSave );

    return true;
}

在介紹AdaBoost的實現前本應該先介紹下CvCascadeClassifier::updateTrainingSet的, 但因為其中的fillPassedSamples用到了CvCascadeClassifier::predict, 所以這個我們還是後面再講吧。 【2】AdaBoost的訓練過程        AdaBoost由CvCascadeBoost類實現, 其繼承自ml的CvBoost。 呼叫位置在CvCascadeClassifier::train中訓練各Stage時呼叫
        CvCascadeBoost* tempStage = new CvCascadeBoost;
        bool isStageTrained = tempStage->train( (CvFeatureEvaluator*)featureEvaluator,
                                                curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
                                                *((CvCascadeBoostParams*)stageParams) );


 引數含義  (CvFeatureEvaluator*)featureEvaluator, 特徵提取器實現, HAAR LBP HOG  curNumSamples, 正負樣本總數  _precalcValBufSize, _precalcIdxBufSize,前有說明,略  *((CvCascadeBoostParams*)stageParams) Boost實現的一些引數, 包括AdaBoost的型別GENTLE等, 前面已有說明了         在設計實現AdaBoost時最常用的弱分類器是決策樹, 一般使用只含有一個節點的決策樹就足夠了,這就是所謂的stump(樹樁).  演算法描述中得到的強分類器有一系列改變權重的樣本訓練得到的弱分類器按一定係數的線性組合,涉及到我們現在分析的CvCascadeBoost實現便是由一系列CvCascadeBoostTree表示的弱分類器。
bool CvCascadeBoost::train(...)
{
...
    初始化Cascade中該級AdaBoost生成用到的訓練資料。 此段比較耗時,後面分析
    data = new CvCascadeBoostTrainData(...);

    weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage ); //CvSeq* weak 多個弱分類器CvCascadeBoostTree的序列

 初始化第一個弱分類器前的權值
    update_weights( 0 );

    do
    {
     一個弱分類器及其訓練過程
        CvCascadeBoostTree* tree = new CvCascadeBoostTree;
        tree->train( data, subsample_mask, this )
        將訓練好的弱分類器加入Seq中
        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;
}

其中的update_weights最開始引數為0時表示初始化權值為1.0/n 在訓練完一個弱分類器後的update_weights(tree)則表示對權值的更新 void CvCascadeBoost::update_weights( CvBoostTree* tree )在權值更新流程時主要操作如下
<pre name="code" class="cpp">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;
}
其中weak_eval表示弱分類器的係數, 但看實現是迴歸樹,並不同於公式中的\alpha_k=0.5*ln((1-Ek)/Ek). 具體取值的計算還沒搞明白. 上述程式碼第二個for迴圈及其之前描述的是newweight[i]=oldweight[i]*exp(-weak_eval[i]*orig_response[i]) 最後的if是權值歸一化過程,還是與文件描述很一致的.