OpenCV 人臉檢測自學(3)
- 從Opencv教程上才發現下面的話。要是早點看到就好了,就不用看haartraining了,不過話說haartraining的網上的資料還是有不少的,但是traincascade就比較少了,所以只能自己硬著頭皮看程式碼了。在程式的使用上跟haartraining差不多,程式碼流程部分在這記錄下以後慢慢補充。
”OpenCV中有兩個程式可以訓練級聯分類器: opencv_haartraining 和opencv_traincascade 。 opencv_traincascade 是一個新程式,根據OpenCV 2.x API 用C++ 編寫。這二者主要的區別是opencv_traincascade 支援
Haar[Viola2001] 和 LBP[Liao2007] (Local Binary Patterns) 兩種特徵,並易於增加其他的特徵。與Haar特徵相比,LBP特徵是整數特徵,因此訓練和檢測過程都會比Haar特徵快幾倍。LBP和Haar特徵用於檢測的準確率,是依賴訓練過程中的訓練資料的質量和訓練引數。訓練一個與基於Haar特徵同樣準確度的LBP的分類器是可能的。“
下面按照我覺得需要理解的部分進行分析。
1. 總流程
Traincascade.cpp中呼叫cvCascadeClassifier的train函式進行訓練。
- bool CvCascadeClassifier::train( const String _cascadeDirName,//工作目錄
- const String _posFilename,//pos.vec
- const String _negFilename,//neg.txt
- int _numPos, int _numNeg,//5000,3000
- int _precalcValBufSize, int _precalcIdxBufSize,
- int _numStages,
- const CvCascadeParams& _cascadeParams,//BOOST, HAAR/LBP,width,height
- const CvFeatureParams& _featureParams,//裡面的引數好像是跟上面選的是Haar還是LBP有關,具體還不懂
- const CvCascadeBoostParams& _stageParams,//GAB,minHitRate,maxFalseAlarmRate,weightTrimRate,maxDepth,maxWeakCount
- bool baseFormatSave )
- {
- if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )//讀取正負樣本的資訊
- if ( !load( dirName ) )
- {
- cascadeParams = _cascadeParams;//表明cascade是不是用boost,分類器基於的特徵型別是Haar還是LBP,正樣本的大小是多少
- featureParams = CvFeatureParams::create(cascadeParams.featureType);//根據featureType來決定返回的是new 一個CvHaarFeatureParams還是CvLBPFeatureParams
- featureParams->init(_featureParams);
- stageParams = new CvCascadeBoostParams;
- *stageParams = _stageParams;//這裡填寫了APP指定的GAB,minHitRate,maxFalseAlarmRate,weightTrimRate,maxDepth,maxWeakCount,個人認為都是一個訓練強分類器需要的一些引數
- featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);//根據featureType來決定返回的是new 一個CvHaarEvaluator還是CvLBPEvaluator
- featureEvaluator->init( (CvFeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize );//把featureEvaluator的裡面有關feature的指標都開闢記憶體,例如sum,title,cls等,然後根據image的長寬呼叫基類的init和子類的generateFeatrues來計算所有的Haar/LBP的features的形狀存到vector<Feature> features中
- stageClassifiers.reserve( numStages );
- }
- double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) /
- (double)stageParams->max_depth;//需要達到的FA的值
- double tempLeafFARate;
- for( int i = startNumStages; i < numStages; i++ )
- {
- cout << endl << "===== TRAINING " << i << "-stage =====" << endl;
- cout << "<BEGIN" << endl;
- if ( !updateTrainingSet( tempLeafFARate ) )//從正負樣本集合裡挑選出numPos+numNeg個樣本到集合CvCascadeImageReader imgReader中,個人感覺很多時間耗在這裡,因為這裡對每一個樣本要進行predict,就是說利用當前強分類器已構建的弱分類器進行判斷是正樣本還是負樣本,只有被判斷是正樣本的情況才被加到TrainingSet中(第一次的時候當然是預設都是正樣本)。所以如果i比較大的時候,在構建負樣本的TrainingSet就特別費時。並且把積分影象等計算出放到evaluator的img中。
- if( tempLeafFARate <= requiredLeafFARate )//check下是否可以中止訓練
- CvCascadeBoost* tempStage = new CvCascadeBoost;
- tempStage->train( (CvFeatureEvaluator*)featureEvaluator,
- curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
- *((CvCascadeBoostParams*)stageParams) );//訓練啦啦啦啦啦,訓練一個強分類器
- stageClassifiers.push_back( tempStage );//訓練完了一個強分類器新增到結果中
- cout << "END>" << endl;
- //儲存params.xml檔案
- //儲存stage0.xml,stage1.xml,stage2.xml。。。檔案
- }//for i [startNumStages, nStage]
- save( dirName + CC_CASCADE_FILENAME, baseFormatSave );//儲存cascade.xml檔案
- return true;
- }
2.關於特徵,以LBP為例(Haar特徵網上例子比較多了)
2.1 LBP特徵的形狀,位置,數目的計算,都是在CvLBPEvaluator.cpp中
- void CvLBPEvaluator::init(const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize)
- {
- CV_Assert( _maxSampleCount > 0);
- sum.create((int)_maxSampleCount, (_winSize.width + 1) * (_winSize.height + 1), CV_32SC1);//LBP需要用到積分圖,_maxSampleCount是指所有正負樣本的數量和
- CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize );//呼叫基類的init
- }
- void CvFeatureEvaluator::init(const CvFeatureParams *_featureParams,
- int _maxSampleCount, Size _winSize )
- {
- CV_Assert(_maxSampleCount > 0);
- featureParams = (CvFeatureParams *)_featureParams;
- winSize = _winSize;
- numFeatures = 0;
- cls.create( (int)_maxSampleCount, 1, CV_32FC1 );//表示樣本的正負情況,所以就一列
- generateFeatures();//虛擬函式,呼叫子類的實現,這裡是計算feature的位置啊,大小啊等資料,還包括多少個feature等資訊,因為img的大小固定後這些東西就可以計算出來了所以先算出來為好。
- }
- void CvLBPEvaluator::generateFeatures()//把所有可能的LBPfeature的位置大小啥的都計算出來。
- {
- 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++ )//因為它要用3個block by 3個block的情況,所以w這除以3
- 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();
- }
- /************************************************************************/
- /*
- 個人理解這裡LBP的Feature是指的是liao的文章的Fig.1(b)。是9x9MB-LBP operator
- CvLBPEvaluator::Feature::Feature( int offset, int x, int y, int _blockWidth, int _blockHeight )
- {
- Rect tr = rect = cvRect(x, y, _blockWidth, _blockHeight);//我奇怪文章裡講到的block是正方形啊,為啥這裡w和h可以不一樣呢?
- CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr, offset )//這裡按照長方形tr來計算p[0],p[1], p[4], p[5]相對於影象左上角的offset,以便以後對每個影象求積分的直接拿這4個數作為下標就很方便的得到積分值。
- 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 )
- }
2.2 LBP特徵的值的計算
這部分是跟載入正負樣本資料到訓練集合中結合到一起的,也就是CvCascadeClassifier::updateTrainingSet函式,這個函式還返回一個這次載入的負樣本的FA值,來決定是否退出訓練。
- bool CvCascadeClassifier::updateTrainingSet( double& acceptanceRatio)
- {
- int64 posConsumed = 0, negConsumed = 0;
- imgReader.restart();
- int posCount = fillPassedSamples( 0, numPos, true, posConsumed );//這個fillPassSample就是之前說過的對輸入樣本只要被當前的強分類器判斷為正樣本的意思
- 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 );//這裡就是之前說的認為耗時的地方。這裡針對負樣本,負樣本的集合一般圖片大小大於正樣本的大小,如果是負樣本的話,函式會去扣一塊跟正樣本大小相同的區域下來送到訓練集合中。
- curNumSamples = posCount + negCount;
- acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed );
- cout << "NEG count : acceptanceRatio " << negCount << " : " << acceptanceRatio << endl;
- return true;
- }
- int CvCascadeClassifier::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 );//讀一個影象資料到img中,個人覺得需要注意的是這裡它不是說每次呼叫getNeg的時候就是讀下一個負樣本影象,在getNeg裡面它會對一個負樣本進行縮放操作來扣下來目標大小的影象。
- if( !isGetImg )
- return getcount;
- consumed++;
- featureEvaluator->setImage( img, isPositive ? 1 : 0, i );//計算這個img的積分圖等資訊
- if( predict( i ) == 1.0F )//這個predict應該是利用之前已有的weak 分類器來判斷是否為正樣本,所以對於isPositive為False的情況下,會一直去尋找第i個負樣本作為setImage
- {
- getcount++;
- break;
- }
- }
- }
- return getcount;
- }
- bool getNeg(Mat &_img) { return negReader.get( _img ); }
- bool getPos(Mat &_img) { return posReader.get( _img ); }
- bool CvCascadeImageReader::NegReader::get( Mat& _img )
- {
- if( img.empty() )
- if ( !nextImg() )//nextImg根據neg.txt的內容讀取下一個負樣本圖片的內容
- return false;
- Mat mat( winSize.height, winSize.width, CV_8UC1,
- (void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step );//這裡是把mat指向計算的img的data的那個地方,最後一個引數指明src的寬,從而mat是相當於從src上的那個位置扣下來的width x height的大小的圖片。
- 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;//這裡把之前縮小後的scale(w_neg/w_pos)乘以sqrt(2)
- if( scale <= 1.0F )//然後比較下這個scale有沒有達到原始影象(沒縮放的時候)的大小,要是沒有的話就按乘以sqrt(2)的方式放大img
- resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ) );
- else
- {
- if ( !nextImg() )//在這把下一個負樣本的image給read進來
- return false;
- }
- }
- }
- return true;
- }
- void CvLBPEvaluator::setImage(const Mat &img, uchar clsLabel, int idx)//計算積分影象
- {
- CV_DbgAssert( !sum.empty() );
- CvFeatureEvaluator::setImage( img, clsLabel, idx );
- Mat innSum(winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr<int>((int)idx));
- integral( img, innSum );//計算這個讀出來的img的積分影象放到sum的第idx位置中
- }
- bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,//包含了sum,tilted,特徵的位置等資訊
- int _numSamples,
- int _precalcValBufSize, int _precalcIdxBufSize,
- const CvCascadeBoostParams& _params )
- {
- CV_Assert( !data );
- clear();
- data = new CvCascadeBoostTrainData( _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
- {
- CvCascadeBoostTree* tree = new CvCascadeBoostTree;
- 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) );
- data->is_classifier = true;
- data->free_train_data();
- return true;
- }
相關文章
- opencv視訊人臉檢測OpenCV
- 視訊人臉檢測——OpenCV版(三)OpenCV
- 圖片人臉檢測——OpenCV版(二)OpenCV
- 基於opencv實現簡單人臉檢測OpenCV
- OpenCV檢測篇(一)——貓臉檢測OpenCV
- OpenCv人臉檢測技術-(實現抖音特效-給人臉戴上墨鏡)OpenCV特效
- 人臉檢測識別,人臉檢測,人臉識別,離線檢測,C#原始碼C#原始碼
- 主題:人臉檢測原理及示例(OpenCV+Python)OpenCVPython
- 如何用OpenCV在Python中實現人臉檢測OpenCVPython
- 人臉檢測 二
- 3分鐘內實現人臉檢測
- [計算機視覺]人臉應用:人臉檢測、人臉對比、五官檢測、眨眼檢測、活體檢測、疲勞檢測計算機視覺
- 目標檢測 YOLO v3 訓練 人臉檢測模型YOLO模型
- 人臉活體檢測
- 前端人臉檢測指南前端
- 人臉檢測的harr檢測函式函式
- 人臉檢測(detection)與人臉校準(alignment)
- OpenCV&Qt學習之四——OpenCV 實現人臉檢測與相關知識整理OpenCVQT
- opencv 人臉識別OpenCV
- OpenCV — 人臉識別OpenCV
- 人臉活體檢測人臉識別:眨眼+張口
- 在Python中使用OpenCV進行人臉檢測PythonOpenCV
- 人眼疲勞檢測之opencv人眼檢測xml說明OpenCVXML
- iOS 人臉關鍵點檢測iOS
- Android人臉檢測介紹Android
- FaceDetector 人臉檢測追蹤demo
- 【火爐煉AI】機器學習052-OpenCV構建人臉鼻子眼睛檢測器AI機器學習OpenCV
- 人臉識別之人臉檢測的重要性
- IOS人臉識別開發入門教程--人臉檢測篇iOS
- 無需人臉檢測,即可實時,6自由度3維人臉姿態估計方法
- Python人臉識別微笑檢測Python
- JavaScript人臉檢測的實現方法JavaScript
- Android API 人臉檢測(Face Detect)AndroidAPI
- Java版人臉檢測詳解上篇:執行環境的Docker映象(CentOS+JDK+OpenCV)JavaDockerCentOSJDKOpenCV
- 從零玩轉人臉識別之RGB人臉活體檢測
- openCV實戰專案--人臉考勤OpenCV
- canvas+face-api人臉實時檢測CanvasAPI
- 人臉識別檢測專案實戰