opencv_traincascade_fillPassedSamples源碼分析

對於opencv traincascade 讀取負樣本圖片bg的方式不是很瞭解,爲了更好的理解和使用opencv traincascade ,對這個很繞的fillpassedsamples源碼進行分析。

最開始看源碼覺得這個讀取負樣本圖片的程序繞來繞去,結構非常不清晰。後來才發現代碼寫成那樣是爲了避免讀取過程中的出現的各種問題。不過爲了方便我現階段的使用,還是有可以改動的地方。

要點如下:
1.正樣本讀取很簡單,讀取生成的vec文件,而且每個訓練stage需要讀取設定數量numPos的正樣本文件,否則退出訓練。所以要根據vec文件樣本數量、nunStages和maxHiRate設置正確的的numPos。
2.負樣本圖片不含有正樣本圖片,負樣本尺寸需大於或等於採樣框winsize
3.負樣本圖片讀取流程爲:
step1 根據樣本描述文件的順序讀取一幅負樣本圖片,然後馬上縮小到winsize到2winsize(不是指面積,指單個維度的倍數)之間的尺度(得到一個縮小系數)。
step2 進行滑動負樣本採樣,採樣一幅圖結束即根據放大係數放大圖片,進行下一輪滑動負樣本採樣。
step3 讀取樣本描述文件的下一幅圖片。
step4 樣本描述文件中所有負樣本圖片讀取完之後如果樣本數不達要求會重新再讀取一遍,但是此時(然後馬上縮小到winsize到2
winsize)這個尺度會發生改變,進而導致不同輪次負樣本圖片讀取到的負樣本有區別。
4.儘量不要訓練時讀取負樣本文件圖片多個輪次(這個輪次最多隻能winsize.height*winsize.width,否則成爲重複讀取),因爲與前一輪的樣本區別實際上比較小。
5.爲了方便得知輪次,bool CvCascadeImageReader::NegReader::nextImg()函數中我加了一個語句。
round += last / count;//整型除整型,讀取到末位圖片round才+1.記錄遍歷次數
if(last / count==1)
printf(" current round: %d\r", round);//空格是必須的

函數說明:

//讀取正負樣本
//其中正樣本從生成的vec文件中讀取
//負樣本從負樣本背景圖片中讀取
//負樣本背景圖片必須大於或等於樣本圖片得尺寸,如果大於樣本圖片,會繼續縮放滑動採樣
//在級聯分類器的每一個訓練stage,會重新讀取正樣本圖片與負樣本圖片
//並丟棄,之前階段訓練的stage已經正確分類的正樣本與負樣本圖片
//所以讀取的正樣本圖片會增加,有效負樣本的讀取會越來越慢
int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, double minimumAcceptanceRatio, int64& consumed )
{
    int getcount = 0;
    Mat img(cascadeParams.winSize, CV_8UC1);
    for( int i = first; i < first + count; i++ )
    {
        for( ; ; )
        {
            if( consumed != 0 && ((double)getcount+1)/(double)(int64)consumed <= minimumAcceptanceRatio )
                return getcount;
			//讀取樣本
            bool isGetImg = isPositive ? imgReader.getPos( img ) :
                                           imgReader.getNeg( img );
            if( !isGetImg )
                return getcount;
            consumed++;
			//用之前的stage給當前樣本分類並紀錄
            featureEvaluator->setImage( img, isPositive ? 1 : 0, i );
			//如果分類錯誤加入當前樣本集,用於進行下一輪訓練
            if( predict( i ) == 1 )
            {
                getcount++;
                printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount);
                break;
            }
        }
    }
    return getcount;
}


//負樣本讀取 預定義變量
CvCascadeImageReader::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;//滑動步長與樣本width之比
}
//讀取負樣本圖片描述文件
//得到負樣本圖片路徑



bool CvCascadeImageReader::NegReader::nextImg()
{
	//讀取文件圖片,並進行縮小,賦給img
	//文件圖片按文檔的圖片列表讀取完後會進行再一遍讀取,但是讀取方式會有所不同,與之前一遍的縮放係數、滑動採樣偏移值會有所不同
	//對遍歷次數進行記錄,round
	//對讀取的圖片次序進行記錄
	
    Point _offset = Point(0,0);//滑動採樣偏移值
    size_t count = imgFilenames.size();//文件圖片數量

	//對所有文件圖片進行遍歷,直到讀取到正常的圖片, !src.empty() && src.type() == CV_8UC1 && _offset.x >= 0 && _offset.y >= 0
    for( size_t i = 0; i < count; i++ )
    {
        src = imread( imgFilenames[last++], 0 );//src,讀取的第last個文件圖片
        if( src.empty() ){
            last %= count;//可能遍歷多遍,last需爲正常值,不能越界
            continue;
        }
        round += last / count;//整型除整型,讀取到末位圖片round才+1.記錄遍歷次數
        round = round % (winSize.width * winSize.height);//winSize.width * winSize.height的遍歷次數之內所得到的相同文件圖片的縮放圖片纔有區別,故round有界
        last %= count;//可能遍歷多遍,last需爲正常值,不能越界
		//不同的遍歷次數。 _offset.x會有區別,_offset偏移限於winSize內
        _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;
	//_offset的區別會影響 第一次縮小系數,即每個文件圖片第一個縮放圖片的尺寸。尺寸從winsize到2winsize
    scale = max( ((float)winSize.width + point.x) / ((float)src.cols),
                 ((float)winSize.height + point.y) / ((float)src.rows) );
	//縮放src到img
    Size sz( (int)(scale*src.cols + 0.5F), (int)(scale*src.rows + 0.5F) );
    resize( src, img, sz, 0, 0, INTER_LINEAR_EXACT );
    return true;
}

bool CvCascadeImageReader::NegReader::get( Mat& _img )
{
	//_img爲讀取的樣本,img爲當前縮放圖片,src爲當前文件圖片
	//src:負樣本集合中原圖的灰度圖
	//img:src經過縮放得到的下采樣圖片,用於滑動採樣得到樣本_img
	//每一次讀取的當前文件圖片,第一個縮放圖片都在nextImg()縮放到最小,在此函數中縮放更新只需要放大
    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構造函數截取樣本圖片 point決定滑動採樣方式
    Mat mat( winSize.height, winSize.width, CV_8UC1,
        (void*)(img.ptr(point.y) + point.x * img.elemSize()), img.step );
    mat.copyTo(_img);//樣本返回給輸入引用
	//從圖片 左上到右下 的採樣
	//列到達邊界則根據offset.x更新,未到達邊界則繼續滑動
    if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols )
        point.x += (int)(stepFactor * winSize.width);
    else
    {
        point.x = offset.x;////列到達邊界則根據offset.x更新
		////行到達邊界則根據offset.y更新,未到達邊界則繼續滑動
        if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows )
            point.y += (int)(stepFactor * winSize.height);
        else
        {
            point.y = offset.y;
			//行需要更新使說明 圖片以滑動採樣完, 需要更新縮放 得到新的縮放圖片 
            scale *= scaleFactor;//NegReader::NegReader()中賦值爲1.414
            if( scale <= 1.0F )//放大, 如果放大到原圖像界限的大小則此 原圖片已採樣完。需要讀取新的文件圖片
                resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ), 0, 0, INTER_LINEAR_EXACT );
            else
            {
                if ( !nextImg() )
                    return false;
            }
        }
    }
    return true;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章