在運動目標的前景檢測中,GMM的目標是實現對視頻幀中的像素進行前景/背景的二分類。通過統計視頻圖像中各個點的像素值獲取背景模型,最后利用背景減除的思想提取出運動目標。
步驟
GMM假設在攝像機固定的場景下,在一段足夠長的時間區間內,背景目標出現的概率要遠高於前景目標。利用監控視頻的這一特點,對視頻幀上的任意坐標的像素值進行時間方向的統計,為每個坐標分配若干個高斯概率密度函數作為該位置的像素值概率分布模型。
以圖 中用紅色標記的點p(x, y)為例,對該點在時間軸上進行像素值的統計,用K個高斯分布描述該位置上的像素值分布Mp。
混合高斯模型使用K(基本為3到5個)模型來表征圖像中各個像素點的特征,在新一幀圖像獲得后更新混合高斯模型,用當前圖像中的每個像素點與混合高斯模型匹配,如果成功則判定該點為背景點, 否則為前景點。通觀整個高斯模型,他主要是由方差和均值兩個參數決定,,對均值和方差的學習,采取不同的學習機制,將直接影響到模型的穩定性、精確性和收斂性。
建模過程中,我們需要對混合高斯模型中的方差、均值、權值等一些參數初始化,並通過這些參數求出建模所需的數據,如馬茲距離。在初始化過程中,一般我們將方差設置的盡量大些(如15),而權值則盡量小些(如0.001)。 這樣設置是由於初始化的高斯模型是一個並不准確,可能的模型,我們需要不停的縮小他的范圍,更新他的參數值,從而得到最可能的高斯模型,將方差設置大些,就是為了將盡可能多的像素包含到一個模型里面,從而獲得最有可能的模型。
對於Mp中的K個高斯概率密度函數來說,其中不僅包含着背景像素值的分布,也包含對前景像素值的分布情況。對K個高斯分布以權重的大小進行降序排序並篩選出前B個高斯分布作為點p的背景像素值的分布模型Mbk:
步驟
對於點p(x, y),其像素值記為vp,當前的像素值分布模型以及背景模型分別為Mp,Mbk。GMM的模型更新步驟如下:
Step1:計算vp與Mp中任意高斯分布Gi的均值μi的距離,記作di。若di≤Td則轉入Step2,否則轉入Step3;
Step2:判定像素值vp與高斯分布Gi匹配成功,提升Gi的權值並轉入Step4;
Step3:判定像素值vp與高斯分布Gi匹配失敗,降低Gi的權值並返回Step1直到所有分布都完成了與vp的匹配轉入Step5;
Step4:更新Gi的均值和方差並將Mp中的分布按權重降序排序;
Step5:統計Mp中K個高斯分布的權重之和並歸一化所有權值;
Step6:若此時vp的匹配成功數為0,則新增一個高斯分布Gnew,替換掉Mp中權值最小的分布並重新降序排列;
Step7:選出前B個高斯分布作為像素p下一幀的背景模型Mbk。
由上述步驟可以發現,隨着時間的流動,在點p處出現頻率較高的高斯分布的權值會逐漸提升;反之,權值會相應的降低,最后被新的分布替代。而利用每一幀點p處的像素值不斷地對該點的B個背景分布函數進行動態更新,最終得到隨場景光照變化而變化的背景模型。
缺點
GMM算法的缺點來自於其“背景的出現概率遠高於前景”,因此當場景發生顯著變化(如光照突變、相機抖動),算法將產生大量的前景誤檢。當GMM的模型學習速率α無法適應運動目標的速度時,參考背景圖像中有可能出現目標的鬼影,因此學習速率α的選取及自適應是GMM算法的一個研究難點。再加上是以像素為單位進行處理,所以會出現些許噪聲,當部分車體的顏色與背景接近時(如車窗),前景掩膜對應的區域會出現“空洞”。
實現
#ifndef DBM_3rdInclude_H
#define DBM_3rdInclude_H
#include <iostream>
#include <map>
#include <vector>
#endif //DBM_OPENCVINCLUDE_H
#ifndef DBM_OPENCVINCLUDE_H
#define DBM_OPENCVINCLUDE_H
#include <opencv2/core/core.hpp>
#include <opencv2/core/core_c.h>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/highgui/highgui_c.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/video/background_segm.hpp>
#endif //DBM_OPENCVINCLUDE_H
using namespace std;
using namespace cv;
#ifndef DBM_GAUSSIANMUTEXTURE_H
#define DBM_GAUSSIANMUTEXTURE_H
class GaussianMutexture {
private:
vector<Mat> _sort;
public:
void load(vector<Mat> files);
vector<Mat> run(int count = 5, Size siz = Size(3, 3), int startFrame = 0,
int stopFrame = -1, int sharp = MORPH_RECT, int morph = MORPH_OPEN);
};
#endif //DBM_GAUSSIANMUTEXTURE_H
#include "gaussianMutexture.h"
void GaussianMutexture::load(vector<Mat> files) {this->_sort = files;}
vector<Mat> GaussianMutexture::run(int count, Size siz, int startFrame,
int stopFrame, int sharp, int morph) {
int pointer = 0;
map<int, Mat> backgrounds;
if (stopFrame == -1)
stopFrame = this->_sort.size();
#pragma omp parallel for
for (int i = startFrame; i < stopFrame; ++i) {
vector<Mat> readed;
readed.clear();
for (int j = 0; j < count; ++j) {
readed.push_back(this->_sort[i + j]);
}
Mat frame, bgMask_MOG2, bgMask_KNN, background;
Mat kernel = getStructuringElement(sharp, siz);
Ptr<BackgroundSubtractor> ptrMOG2 = createBackgroundSubtractorMOG2();
Ptr<BackgroundSubtractor> ptrKNN = createBackgroundSubtractorKNN();
for (int k = 0; k < readed.size(); k++) {
ptrMOG2->apply(readed[k], bgMask_MOG2);
morphologyEx(bgMask_MOG2, bgMask_MOG2, morph, kernel);
ptrKNN->apply(readed[k], bgMask_KNN);
ptrKNN->getBackgroundImage(background);
}
backgrounds.insert(pair<int, Mat>(i, background));
#ifdef DEBUG
cout << ".";
#endif
}
vector<Mat> backgroundFrame;
for (map<int, Mat>::iterator it = backgrounds.begin(); it != backgrounds.end(); it++)
backgroundFrame.push_back(it->second);
return backgroundFrame;
}