在运动目标的前景检测中,GMM的目标是实现对视频帧中的像素进行前景/背景的二分类。通过统计视频图像中各个点的像素值获取背景模型,最后利用背景减除的思想提取出运动目标。

GMM假设在摄像机固定的场景下,在一段足够长的时间区间内,背景目标出现的概率要远高于前景目标。利用监控视频的这一特点,对视频帧上的任意坐标的像素值进行时间方向的统计,为每个坐标分配若干个高斯概率密度函数作为该位置的像素值概率分布模型。

高斯混合背景模型运动目标检测

以图 中用红色标记的点p(x, y)为例,对该点在时间轴上进行像素值的统计,用K个高斯分布描述该位置上的像素值分布Mp。

[M_p={G_1(mu_1,delta^2_1,omega_1),...,G_i(mu_i,delta^2_i,omega_i),...,G_k(mu_k,delta^2_k,omega_k)}
]

混合高斯模型使用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;
}