學習OpenCV範例(二十三)—GMM前景檢測(一)
前一篇部落格中有談論到混合高斯模型GMM,但是隻是在上面的一個小應用,可能沒有很徹底的分析,大部分讀者看起來有點吃力,那麼在這篇微博中就給大家分析一下GMM在前景檢測的原理以及在OpenCV中的運用,當然長篇大論的原理我還是不全部寫出來的,依舊會貼出其他高手的部落格,他們寫的個人覺得已經夠詳細了,再總結已經沒什麼意思了,也總結不出太多的新意,在這裡也是作為記錄,談談自己的小感悟以及執行的結果和函式的使用。
1、原理
如果大家想了解最原汁原味的原理,那還是看原作者的論文吧,如果想了解濃縮版的,和基本的實現步驟,那可以訪問以下幾篇部落格
當然,看完了論文,還會覺得有點雲裡霧裡的感覺,不知道怎麼做到的,怎麼實現,那這時候就得看看原始碼了
有了原理,又提供了函式庫,有了函式介面,那肯定得試一下效果如何
本人也是根據上面提供的程式碼測試了效果,加強了一點註解,感覺不錯。
GMM演算法不同於其他的背景相減方法,因為有時背景也存在部分割槽域的震盪變化,那如果按照一般的前景檢測方法,則會頻繁的檢測到錯誤的前景,GMM演算法則有效的克服了這一點,那是因為GMM演算法有效的做到了以下幾點:
(1)、對每個畫素建立多個高斯模型(即存在多個滑動平均值),那麼背景畫素就可以在多個均值之間波動,而不會被誤判,如果有新的畫素值不屬於其中的一個高斯模型,則認為是前景。
(2)、不僅僅儲存滑動平均值,還儲存了滑動方差,由方差和均值產生了一個高斯模型,於是我們可以獲知某個畫素值屬於哪個高斯模型的概率,如果新畫素不屬於其中的一個高斯模型,則認為是前景。
(3)、增加了學習因子,如果某個模型被擊中的頻率不夠頻繁,那麼權值就會減少,減少到最後把該模型移除,如果一個畫素是前景,那麼新的高斯模型會被建立,剛開始權值較小,但是如果該前景一直不動,不離開,則權重加大,慢慢的和背景融為一體,成為了新的背景。
2、程式碼實現
#include "opencv2/core/core.hpp" #include "opencv2/video/background_segm.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <stdio.h> using namespace std; using namespace cv; //this is a sample for foreground detection functions string src_img_name="WavingTrees/b00"; const char *src_img_name1; Mat img, fgmask, fgimg; int i=-1; char chari[500]; bool pause=false; //第一種gmm,用的是KaewTraKulPong, P. and R. Bowden (2001). //An improved adaptive background mixture model for real-time tracking with shadow detection. BackgroundSubtractorMOG bg_model(200,5,0.7,10); void refineSegments(const Mat& img, Mat& mask, Mat& dst) { int niters = 3; vector<vector<Point> > contours; vector<Vec4i> hierarchy; Mat temp; dilate(mask, temp, Mat(), Point(-1,-1), niters);//膨脹,3*3的element,迭代次數為niters erode(temp, temp, Mat(), Point(-1,-1), niters*2);//腐蝕 dilate(temp, temp, Mat(), Point(-1,-1), niters); findContours( temp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );//找輪廓 dst = Mat::zeros(img.size(), CV_8UC3); if( contours.size() == 0 ) return; // iterate through all the top-level contours, // draw each connected component with its own random color int idx = 0, largestComp = 0; double maxArea = 0; //輪廓模式為CV_RETR_CCOMP,第一層為連通域的外圍邊界,因為我們前景顏色值為255,背景為0 //所以得到的輪廓為外圍邊界,所以我們只需要第一層,hierarchy[idx][0]表示為下一個輪廓的索引 //如果到達最後一個輪廓,則hierarchy[idx][0]=-1; //如需詳細瞭解可訪問網址:http://blog.csdn.net/chenjiazhou12/article/details/22304099 for( ; idx >= 0; idx = hierarchy[idx][0] ) { const vector<Point>& c = contours[idx]; double area = fabs(contourArea(Mat(c))); if( area > maxArea ) { maxArea = area; largestComp = idx;//找出包含面積最大的輪廓 } } Scalar color( 0, 255, 0 ); drawContours( dst, contours, largestComp, color, CV_FILLED, 8, hierarchy ); } int main(int argc, const char** argv) { img=imread("WavingTrees/b00000.bmp"); if(img.empty()) { namedWindow("image",1); namedWindow("foreground image",1); namedWindow("mean background image", 1); } for(;;) { if(!pause) { //讀取圖片檔案 i++; itoa(i,chari,10); if(i<10) { src_img_name+="00"; } else if(i<100) { src_img_name+="0"; } else if(i>283) { i=-1; } src_img_name+=chari; src_img_name+=".bmp"; img=imread(src_img_name); if( img.empty() ) break; //update the model bg_model(img, fgmask,0.005 );//計算前景mask影象,其中輸出fgmask為8-bit二進位制影象,第3個引數為學習速率,如果學習速率為0,則為背景相減法 refineSegments(img, fgmask, fgimg); imshow("image", img); imshow("foreground image", fgimg); src_img_name="WavingTrees/b00"; } char k = (char)waitKey(80); if( k == 27 ) break; if( k == ' ' ) { pause=!pause; } } return 0; }
3、實現結果
圖1、開始背景建模
圖2、前景檢測
圖3、前景檢測
圖4、前景檢測
圖5、後期背景檢測
總結:剛開始時,樹葉都是在搖晃的,所以把樹當成前景,如圖1所示,但當訓練很多副圖片之後,模型慢慢適應了樹葉的晃動,於是能把樹葉也當成背景,如圖5所示,由於人物是不時的插進來,於是一直被認為是前景來處理,所以GMM對背景的震盪變化有較好的處理效果。
4、用到的類或函式
BackgroundSubtractorMOG
建構函式
BackgroundSubtractorMOG::BackgroundSubtractorMOG(int history, int nmixtures, double backgroundRatio, double noiseSigma=0)
history :歷史幀數的長度,如果是預設建構函式,則值為200
nmixtures :高斯模型個數,如果是預設建構函式,則值為5
backgroundRatio :背景門限,如果是預設建構函式,則值為0.7
noiseSigma :噪聲方差,預設為0,如果是預設建構函式,則值為15
void BackgroundSubtractorMOG::operator()(InputArray image, OutputArray fgmask, double learningRate=0)
功能:更新背景模型,返回前景mask
image:輸入圖片
fgmask:輸出前景mask
learningRate:學習速率,如果為0,則為背景相減法