精华内容
下载资源
问答
  • 混合高斯模型运动前景提取的matlab程序,效果还不错这样有利于处理期间感兴趣前景提取的matlab程序,利用帧差法实现的视屏前景提取前景提取的matlab程序,利用帧差法实现的视屏前景提取
  • 复杂场景下的运动前景提取是计算机视觉研究领域的研究重点。 为解决复杂场景中的前景目标提取问题,提出一种应用于复杂变化场景中的基于混合高斯模型的自适应前景提取方法。该方法可以对视频帧中每个像素的高斯分布...
  • 包括常用的前景提取算法,有GMM,VIBE,帧差法等,方便实用
  • 前景提取算法调研

    千次阅读 2018-09-26 11:16:48
    前景提取算法调研 总结: 前景提取算法,主要是用于在摄像监控中,从视频流数据中剔除不需要分析的背景部分,从而提升算法对视频分析的效果。在前景提取中主要分为两大类,一个是静态的前景提取:即摄像头是固定不动...

    前景提取算法调研
    总结:

    前景提取算法,主要是用于在摄像监控中,从视频流数据中剔除不需要分析的背景部分,从而提升算法对视频分析的效果。在前景提取中主要分为两大类,一个是静态的前景提取:即摄像头是固定不动的相对的背景基本是不变化的,另一个时动态前景提取:即摄像头是追踪某个运动的物体背景是随着追踪的物体进行变化的。在本文中,主要是分析静态前景提取的方法。

    前景提取的主要有三大难点:1)视频流因光线等因素导致的背景缓慢的变化,例如天空飘过一朵云导致光线变暗2)视频流中的突发情况,例如在原本开灯的场景,突然把灯关掉3)ghost,例如:在视频初期就有个车子在摄像头中,会默认车子为背景,而把车子移动之后的地方视为前景。

    前景提取方法主要分为三大类:背景建模,帧差法,光流法。

    背景建模

    高斯建模:分为单高斯模型,混合高斯模型,以及滑动高斯平均。三者的主要思想都是一样的,其中,最初的为单高斯模型,混合高斯模型是在建模的时候使用混合高斯,滑动高斯模型是在混合高斯模型的基础上增加了平均值的计算。

    主要思想:对于每一个像素点,都假定如果是背景的话,会服从一个高斯分布的变化,即其变化的概率分布应该是满足高斯模型的,因此以上一帧为基础,构建对每一个像素构建其高斯模型,然后在计算下一帧这个像素的值在此情况下是背景的概率。

    混合高斯模型,只是假设是服从混合高斯的分布。

    滑动高斯平均,是表明在某一端时刻服从一个混合高斯分布,存在一个滑动平移的过程。

    混合高斯模型具体建模过程

    对场景中所有像素点进行高斯建模,第N个像素的模型如下:
    p(xN)=j=1Kwjη(xN;θj)p(\bm{x}_N)=\sum_{j=1}^Kw_j\eta(\bm{x}_N;\theta_j)
    其中wkw_k是第k个高斯模型的权重。
    η(x;θk)=η(xN;μk,Σk)\eta(\bm{x};\theta_k)=\eta(\bm{x}_N;\mu_k,\Sigma_k)
    因为假设,输入的红,绿,蓝三色之间关系是独立的,所以
    Σk=σ2kI\Sigma_k=\sigma^k_2I
    对K个高斯模型按照w/σw/\sigma进行排序,ww高代表着该模型的置信度越大,σ\sigma低代表着模型的波动越小。
    设置一个最小的衡量权重T,如果前b的wkw_k的权重之和大于T,则取前b个为背景模型,具体公式如下:
    B=argminb(k=1bwk>T)B=argmin_b(\sum^b_{k=1} w_k > T)
    模型各个参数的更新:
    wk,t=(1α)wk,t1+α(Mk,t)w_{k,t} =(1-\alpha)w_{k,t-1}+\alpha(M_{k,t})
    如果第k个像素与模型匹配,M_{k,t}=1不然为0
    匹配的定义为:若像素的值在2.5倍的标准差之内,则认定为匹配(原文中用的是match)
    对于一个新的观测到的像素,模型的均值和方差变化情况如下:
    μt=(1ρ)μt1+ρXt\mu_t=(1-\rho)\mu_{t-1}+\rho X_t
    σt2=(1ρ)σt12+ρ\sigma^2_t=(1-\rho)\sigma^2_{t-1}+\rho
    ρ=αη(Xtμk,σk)\rho=\alpha\eta(X_t|\mu_k,\sigma_k)

    展开全文
  • 前景提取的matlab程序,利用帧差法实现的视屏前景提取前景提取的matlab程序,利用帧差法实现的视屏前景提取
  • 复杂背景下监控视频的前景提取
  • 前景提取技术研究,郑迪昕,,前景提取旨在对图片中的前景背景进行区分,从而达到提取前景,去除背景的功能。本文研究并实现了近年来可以应用到前景提取的各项
  • 智能化视频监控系统的研究逐渐成为近年来计算机视觉领域研究的一个重要方向,而视频中的前景提取是智能监控技术中的一个至关重要的课题,也是研究者最感兴趣的问题之一。视频中的前景提取涉及到诸如人工智能、图像...
  • GPU混合高斯前景提取

    2017-07-26 22:49:44
    cuda编程实现混合高斯前景提取算法,达到了实时性的要求。
  • 图像前景提取技术研究
  • 假肢视觉下基于前景提取方法的目标识别
  • 文章目录单高斯算法视频前景提取(1)模型初始化(2)更新参数并检测代码主要函数解释算法实现代码(opencv4.4.0+VS2019)VIBE算法视频前景提取(1)建立背景模型(2)前景目标检测(3)背景模型更新算法实现代码...

    单高斯算法视频前景提取

    在静态背景下的视频前景提取

    单高斯模型 SGM(Single Gaussian background model)[4]是一种图像处理背景提取的处理方法,适用于背景单一不变的场合。对于视频图像中任意一个像素点,将每个像素点的变化看作是不断产生像素点的随机过程,在时间轴 上像素值属于离散分布。在任意时刻 ,各像素值可以表示为:
    {I(x,y,z),1it}={X1,X2,...,Xt} \{I(x,y,z),1\leq i\leq t\}=\{X_1,X_2,...,X_t\}
    式中,I(x,y,i) 表示第i 帧图像中(x,y)点处的像素值。根据高斯建模原理,上式中各点均符合高斯分布:
    P(Xi)=12πσe(xiμ)2σ2,XiI(x,y,i) P(X_i)=\frac{1}{\sqrt{2\pi\sigma}}{e^{\frac{(x_i-\mu)}{2\sigma^2}}},X_i\in I(x,y,i)
    式中,u和sigma分别表示 时刻高斯分布的均值和标准方差,Xt是t时刻的像素值,

    用单高斯模型进行运动检测的基本过程包括:模型的初始化、更新参数并检测两个步骤。

    (1)模型初始化

    模型的初始化即对每个像素位置上对应的高斯模型参数进行初始化,初始化采用如下公式完成
    {μ(x,y,0=I(x,y,0))σ2(x,y,0)=init2σ(x,y,0)=init \begin{cases} \mu(x,y,0=I(x,y,0))\\ \sigma^2(x,y,0)=init_2\\ \sigma(x,y,0)=init \end{cases}
    其中,I(x,y,i)是视频第一帧图像(x,y)位置的像素值,init为常数,一般为20。

    (2)更新参数并检测

    每读入一张新的图片,判断新图片中对应点像素是否在高斯模型描述的范围中,如是,则判断改点处为背景,否则,判断为前景。则可得

    FBt(x,y)={0,I(x,y,t)μ(x,y,t1)<λ×σ(x,y,t1)1,else FB_t(x,y)=\begin{cases} 0,|I(x,y,t)-\mu(x,y,t-1)|<\lambda\times\sigma(x,y,t-1)\\ 1,else \end{cases}

    模型的更新采用如下公式:
    {μ(x,y,t)=(1α)×μ(x,y,0))+α×μ(x,y,t)σ2(x,y,t)=(1α)×σ2(x,y,t1))+α×[I(x,y,t)μ(x,y,t)]2σ(x,y,0)=σ2(x,y,t) \begin{cases} \mu(x,y,t)=(1-\alpha)\times\mu(x,y,0))+\alpha\times\mu(x,y,t)\\ \sigma^2(x,y,t)=(1-\alpha)\times\sigma^2(x,y,t-1))+\alpha\times[I(x,y,t)-\mu(x,y,t)]^2\\ \sigma(x,y,0)=\sqrt{\sigma^2(x,y,t)} \end{cases}
    其中,参数alpha表示更新率,取值在0到1之间,本文取值0.05。

    代码主要函数解释

    BackgroundSubtractorMOG2()//高斯模型
    dilate()//膨胀
    erode()//侵蚀
    

    https://blog.csdn.net/zangle260/article/details/52981008

    算法实现代码(opencv4.4.0+VS2019)

    #include "opencv2/opencv.hpp"  
    #include <vector>  
    using namespace cv;
    using namespace std;
    const int Train = 100;
    int main(int argc, char* argv[])
    	{
    Ptr<BackgroundSubtractorMOG2> mog = createBackgroundSubtractorMOG2(100, 25, false);
    		//bgsubtractor->setVarThreshold(20);
    		Mat foreGround;
    		Mat backGround;
    		int trainCounter = 0;
    		bool dynamicDetect = true;
    		namedWindow("src", WINDOW_AUTOSIZE);
    		namedWindow("foreground", WINDOW_AUTOSIZE);
    		VideoCapture capture;
    		capture.open("C:\\waterSurface\\input.avi");    //输入视频名称
    		if (!capture.isOpened())
    		{
    			cout << "No camera or video input!\n" << endl;
    			return -1;
    		}
    		Mat src;
    		bool stop = false;
    		while (!stop)
    		{
    			capture >> src;
    			if (src.empty())
    				break;
    			if (dynamicDetect)
    			{
    				mog->apply(src, foreGround, 0.005);
    				//图像处理过程
    				medianBlur(foreGround, foreGround, 3);
    				dilate(foreGround, foreGround, Mat(), Point(-1, -1), 3);
    				erode(foreGround, foreGround, Mat(), Point(-1, -1), 6);
    				dilate(foreGround, foreGround, Mat(), Point(-1, -1), 3);
    				imshow("foreground", foreGround);
    				if (trainCounter < Train)//训练期间所得结果为不准确结果,不应作为后续
    				{
    					Mat findc;
    					foreGround.copyTo(findc);
    					vector<vector<Point>> contours;
    		cv::findContours(findc, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    
    					//targets.clear();
    					const int maxArea = 800;
    					size_t s = contours.size();
    					for (size_t i = 0; i < s; i++)
    					{
    						double area = abs(contourArea(contours[i]));
    						if (area > maxArea)
    						{
    							Rect mr = boundingRect(Mat(contours[i]));
    							rectangle(src, mr, Scalar(0, 0, 255), 2, 8, 0);
    							//targets.push_back(mr);
    						}
    					}
    					//string text;					
    					char text[50];
    					sprintf_s(text, "background training -%d- ...", trainCounter);
    				putText(src, text, Point(50, 50), 3, 1, Scalar(0, 255, 255), 2, 8, false);
    					//delete[] text;
    				}
    				else
    				{
    					//detects.clear();
    					Mat findc;
    					foreGround.copyTo(findc);
    					vector<vector<Point>> contours;
    		cv::findContours(findc, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    					const int maxArea = 500;
    					size_t s = contours.size();
    					RNG rng;
    						for (size_t i = 0; i < s; i++)
    						{
    							double area = abs(contourArea(contours[i]));
    							if (area > maxArea)
    							{
    Scalar sca_color = Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
    								Rect mr = boundingRect(Mat(contours[i]));
    								rectangle(src, mr, sca_color, 2, 8, 0);
    								//可以对动态目标进行相应操作
    							}
    						}
    				}
    				trainCounter++;
    			}
    			imshow("src", src);
    			if (waitKey(30) == 27) //Esc键退出    
    			{
    				stop = true;
    			}
    		}
    		system("pause");
    		return 0;
    	}
    

    VIBE算法视频前景提取

    https://blog.csdn.net/tiandijun/article/details/50499708

    在静态背景下的视频前景提取

    ​ VIBE是一种像素级视频背景建模或前景检测的算法,效果优于所熟知的几种算法,对硬件内存占用也少。 VIBE是一种像素级的背景建模、前景检测算法,该算法主要不同之处是背景模型的更新策略,随机选择需要替换的像素的样本,随机选择邻域像素进行更新。在无法确定像素变化的模型时,随机的更新策略,在一定程度上可以模拟像素变化的不确定性。

    ​ VIBE相比于其他方法它有很多的不同和优点。具体的思想就是为每个像素点存储了一个样本集,样本集中采样值就是该像素点过去的像素值和其邻居点的像素值,然后将每一个新的像素值和样本集进行比较来判断是否属于背景点。VIBE 算法主要包括建立背景模型、前景目标检测与背景模型更新三个部分。

    (1)建立背景模型

    ​ 为图像的每个像素点分配了含有N个样本的背景库,假设一个像素与其邻域像素的像素值在时间轴上服从相似的概率分布。从像素Xt的8邻域中等概率地采样并填充到背景库中从而建立背景模型。

    定义v(x)为灰度图上位于x点处的像素值,vi为选取的样本。像素v(x)对应的模型为:
    M(x)={v1,v2,...,vn} M(x)=\{v_1,v_2,...,v_n\}
    ​ 式中,vi是在像素v(x) 的八邻域NG(x)中随机地选取一个像素作为 vi,一共选取N次;如此一来便建立了含有 N个样本的背景库,并得到了背景 模型 M(x)。

    初始化是建立背景模型的过程,一般的检测算法需要一定长度的视频序列学习完成,影响了检测的实时性,而且当视频画面突然变化时,重新学习背景模型需要较长时间。

    优点:不仅减少了背景模型建立的过程,还可以处理背景突然变化的情况,当检测到背景突然变化明显时,只需要舍弃原始的模型,重新利用变化后的首帧图像建立背景模型。

    缺点:由于可能采用了运动物体的像素初始化样本集,容易引入拖影(Ghost)区域。

    (2)前景目标检测

    通过比较待检测单元与背景库内单元的差值是否在预设的阈值范围内来判断像素点是否属于背景。

    如下图1所示,定义一个以v(x)为中心,以R为半径的球体S(v(x)),S(v(x))表示所有v(x)与距离小于R的点的集合,用M(x)落在球体S(v(x))的样本个数#来描述v(x)与背景模型M(x)的相似度。对于给定阈值#min,如果#<#min,则v(x)为前景。前景二值图 F(x)表示为:
    F(x)={1,if#(SR(v(x))M(x))<#min0,else F(x)=\begin{cases}1,if\#(S_R(v(x))\cap M(x))<\#_{min}\\ 0,else \end{cases}

    #(SR(v(x))Vt(t))={1,ifdist(vt(x),v(x))<R0,else \#(S_R(v(x))\cap V_t(t))=\begin{cases}1,if dist(v_t(x),v(x))<R\\ 0,else \end{cases}

    式中,dist(.)表示计算M(x)与v(x)中n个样本之间的欧式距离。#(.)表示欧式 距离小于R的个数,如果#(.)小于给定阈值#min,则x点判定为前景点,否则为背景点。N=20,R=20。

    (3)背景模型更新

    通过使用待检测单元对背景库进行持续更新。

    使用被判定为背景的像素按一定概率如 1/phi来更新背景库,phi为二次抽样时间因子;

    当更新当前像素背景库时通过均匀概率分布来随机选择需要被丢弃的样本,从而有效的保存了有用样本并使得无用的样本不会长时间留在背景库中。样 本在 t到 t+dt时间段内留在样本库的概率等于
    P(t,t+dt)=(N1N)(tdt)t P(t,t+d_t)=(\frac{N-1}{N})^{(t-d_t)-t}
    由此可见,每个像素被保存在背景库内的概率是按指数递减的;

    在当前像素背景库被更新后,随机更新 8 邻域像素的一个背景库。

    算法实现代码(opencv4.4.0+VS2019)

    //ViBe.h
    #pragma once    
    #include  <iostream>    
    #include "opencv2/opencv.hpp"   
    
    #define NUM_SAMPLES 20      //每个像素点的样本个数    
    #define MIN_MATCHES 2       //#min指数    
    #define RADIUS 20         //Sqthere半径    
    #define SUBSAMPLE_FACTOR 16 //子采样概率,决定背景更新的概率  
    using namespace cv;
    using namespace std;
    class ViBe_BGS
    {
    public:
        ViBe_BGS(void);  //构造函数  
        ~ViBe_BGS(void);  //析构函数,对开辟的内存做必要的清理工作  
        void init(const Mat _image);   //初始化    
        void processFirstFrame(const Mat _image); //利用第一帧进行建模   
        void testAndUpdate(const Mat _image);  //判断前景与背景,并进行背景跟新   
        Mat getMask(void) { return m_mask; };  //得到前景  
    private:
        Mat m_samples[NUM_SAMPLES];  //每一帧图像的每一个像素的样本集  
        Mat m_foregroundMatchCount;  //统计像素被判断为前景的次数,便于更新  
        Mat m_mask;  //前景提取后的一帧图像  
    };
    
    
    //ViBe.hpp
    #include <opencv2/opencv.hpp>    
    #include <iostream>    
    #include "ViBe.h"    
    using namespace std;
    using namespace cv;
    int c_xoff[9] = { -1,  0,  1, -1, 1, -1, 0, 1, 0 };  //x的邻居点,9宫格  
    int c_yoff[9] = { -1,  0,  1, -1, 1, -1, 0, 1, 0 };  //y的邻居点    
    ViBe_BGS::ViBe_BGS(void)
    {
    }
    ViBe_BGS::~ViBe_BGS(void)
    {
    }
    /**************** Assign space and init ***************************/
    void ViBe_BGS::init(const Mat _image)  //成员函数初始化  
    {
    for (int i = 0; i < NUM_SAMPLES; i++) //可以这样理解,针对一帧图像,建立了20帧的样本集  
        {
            m_samples[i] = Mat::zeros(_image.size(), CV_8UC1);  //针对每一帧样本集的每一个像素初始化为8位无符号0,单通道  
        }
        m_mask = Mat::zeros(_image.size(), CV_8UC1); //初始化   
        m_foregroundMatchCount = Mat::zeros(_image.size(), CV_8UC1);  //每一个像素被判断为前景的次数,初始化  
    }
    /**************** Init model from first frame ********************/
    void ViBe_BGS::processFirstFrame(const Mat _image)
    {
        RNG rng;    //随机数产生器                                      
        int row, col;
    
        for (int i = 0; i < _image.rows; i++)                     //指针实现
        {
            for (int j = 0; j < _image.cols; j++)
            {
                for (int k = 0; k < NUM_SAMPLES; k++)
                {
                    uchar* pSampRow = m_samples[k].ptr<uchar>(i);
                    int random = rng.uniform(0, 9);  //随机产生0-9的随机数,主要用于定位中心像素的邻域像素  
    
                    row = i + c_yoff[random]; //定位中心像素的邻域像素   
                    if (row < 0)   //下面四句主要用于判断是否超出边界  
                        row = 0;
                    if (row >= _image.rows)
                        row = _image.rows - 1;
                    const uchar* pImgRow = _image.ptr<uchar>(row);
                    col = j + c_xoff[random];
                    if (col < 0)    //下面四句主要用于判断是否超出边界  
                        col = 0;
                    if (col >= _image.cols)
                        col = _image.cols - 1;
                    pSampRow[j] = pImgRow[col];
                }
            }
        }
    }
    /**************** Test a new frame and update model ********************/
    void ViBe_BGS::testAndUpdate(const Mat _image)
    {
        RNG rng;
        for (int i = 0; i < _image.rows; i++)
        {
            uchar* pForegroundMatchRow = m_foregroundMatchCount.ptr<uchar>(i);
            uchar* pMaskRow = m_mask.ptr<uchar>(i);
            const uchar* pImgRow = _image.ptr<uchar>(i);
            for (int j = 0; j < _image.cols; j++)
            {
                int matches(0), count(0);
                float dist;
                uchar* pSampContRow = m_samples[count].ptr<uchar>(i);
                while (matches < MIN_MATCHES && count < NUM_SAMPLES) //逐个像素判断,当匹配个数大于阀值MIN_MATCHES,或整个样本集遍历完成跳出  
                {
                    dist = abs(/*m_samples[count].at<uchar>(i, j)*/pSampContRow[j] - /*_image.at<uchar>(i, j)*/pImgRow[j]); //当前帧像素值与样本集中的值做差,取绝对值   
                    if (dist < RADIUS)  //当绝对值小于阀值是,表示当前帧像素与样本值中的相似  
                        matches++;
                    count++;  //取样本值的下一个元素作比较  
                }
                if (matches >= MIN_MATCHES)  //匹配个数大于阀值MIN_MATCHES个数时,表示作为背景  
                {
                    // It is a background pixel    
                    pForegroundMatchRow[j] = 0; //被检测为前景的个数赋值为0  
    
                    pMaskRow[j] = 0;      //该像素点值也为0 ,如果一个像素是背景点,那么它有 1 / defaultSubsamplingFactor 的概率去更新自己的模型样本值    
                    int random = rng.uniform(0, SUBSAMPLE_FACTOR);   //以1 / defaultSubsamplingFactor概率跟新背景  
    
    
                    uchar* pSampRandRowi = m_samples[random].ptr<uchar>(i);
                    if (random == 0)
                    {
                        random = rng.uniform(0, NUM_SAMPLES);
                        pSampRandRowi[j] = pImgRow[j];
                        //m_samples[random].at<uchar>(i, j) = _image.at<uchar>(i, j);
                    }
    
                    // 同时也有 1 / defaultSubsamplingFactor 的概率去更新它的邻居点的模型样本值    
                    random = rng.uniform(0, SUBSAMPLE_FACTOR);
                    if (random == 0)
                    {
                        int row, col;
                        random = rng.uniform(0, 9);
                        row = i + c_yoff[random];
                        if (row < 0)   //下面四句主要用于判断是否超出边界  
                            row = 0;
                        if (row >= _image.rows)
                            row = _image.rows - 1;
    
                        random = rng.uniform(0, 9);
                        col = j + c_xoff[random];
                        if (col < 0)   //下面四句主要用于判断是否超出边界  
                            col = 0;
                        if (col >= _image.cols)
                            col = _image.cols - 1;
                        uchar* pSampRandRowrow = m_samples[random].ptr<uchar>(row);
                        random = rng.uniform(0, NUM_SAMPLES);
                       
                        pSampRandRowrow[col] = pImgRow[j];
                    }
                }
    
                else  //匹配个数小于阀值MIN_MATCHES个数时,表示作为前景  
                {
                    // It is a foreground pixel    
                    pForegroundMatchRow[j]++;                  //检测为前景的个数加1  
                                                        // Set background pixel to 255    
                    pMaskRow[j] = 255;       //前景点用白色(255)表示    
                    if (pForegroundMatchRow[j] > 50)
                    {
                        int random = rng.uniform(0, SUBSAMPLE_FACTOR);
                        if (random == 0)
                        {
                            random = rng.uniform(0, NUM_SAMPLES);
                            uchar* pSampRandRowi = m_samples[random].ptr<uchar>(i);
                            pSampRandRowi[j] = pImgRow[j];
                        }
                    }
                }
            }
        }
    }
    

    主函数

    #include  < opencv2/opencv.hpp >  
    #include "ViBe.h"    
    #include < iostream>    
    #include < cstdio>    
    #include< stdlib.h>  
    using namespace cv;
    using namespace std;
    int main(int argc, char* argv[])
    {
        namedWindow("mask", WINDOW_AUTOSIZE);
        namedWindow("input", WINDOW_AUTOSIZE);
        Mat frame, gray, mask;
        VideoCapture capture;
        capture.open("input.avi");    //输入视频路径
        if (!capture.isOpened())
        {
            cout << "No camera or video input!\n" << endl;
            return -1;
        }
    
        ViBe_BGS Vibe_Bgs; //定义一个背景差分对象  
        int count = 0; //帧计数器,统计为第几帧   
        while (1)
        {
            count++;
            capture >> frame;
            if (frame.empty())
                break;
            cvtColor(frame, gray, COLOR_BGR2GRAY); //转化为灰度图像   
            if (count == 1)  //若为第一帧  
            {
                Vibe_Bgs.init(gray);
                Vibe_Bgs.processFirstFrame(gray); //背景模型初始化   
                cout << " Training GMM complete!" << endl;
            }
            else
            {
                double t = getTickCount();
                Vibe_Bgs.testAndUpdate(gray);
                mask = Vibe_Bgs.getMask();    //计算前景
                morphologyEx(mask, mask, MORPH_OPEN, Mat());   //形态学处理消除前景图像中的小噪声,这里用的开运算   
                imshow("mask", mask);
                t = (getTickCount() - t) / getTickFrequency();
                cout << "imgpro time" << t << endl;
            }
            imshow("input", frame);
            if (waitKey(10) == 'q')    //键盘键入q,则停止运行,退出程序
                break;
        }
        system("pause");
        return 0;
    }
    

    混合高斯模型视频前景提取

    ​ 在动态背景下的视频前景提取

    图像中每个像素点的值(或特征)短时间内都是围绕与某一中心值一定距离内分布,通常,中心值可以用均值来代替,距离呢可以用方差来代替。这种分布呢是有规律的,根据统计定律,如果数据点足够多的话,是可以说这些点呈正态分布,也称为高斯分布(取名高斯,大概是因为很多地方都用这个名字吧)。根据这个特点,如果像素点的值偏离中心值较远,那么,这个像素值属于前景,如果像素点的值偏离中心值很近(在一定方差范围内),那么可以说这个点属于背景。理论上,如果不存在任何干扰的话,是可以准确区分前景和背景的。但是,现实往往不尽如人意,如果画面中光线变化的话,这个高斯分布的中心位置是会改变的。

    混合高斯模型指这个像素点值存在多个中心位置,如来回摆动的树叶,波光粼粼的水面,闪烁的显示器,图像中特征边缘位置的抖动等,这些都会引起某个像素点会在多个中心位置聚集大量的点,每个位置便会产生一个高斯分布,四个以上的高斯分布其实并不常见,这便是混合高斯模型的由来。混合高斯背景建模主要用来解决背景像素点具有多峰特性的场合,如在智能交通场景中,解决视频画面抖动带来的干扰。

    针对光线变化的问题,混合高斯模型通过比较当前像素点的值与高斯分布中心位置,选择一定的加权系数对当前高斯分布的中心位置进行更新,以便于适应缓慢的光线变化。

    此外,混合高斯模型尤其适合于检测缓慢移动的物体,因为背景已是一个高斯分布,如果车停下来,等到聚集一定的前景数据便会形成一个新的高斯分布,停下来的车也会便是背景。但是如果车缓慢行驶的话,是很难在短时间内形成一个新的高斯分布,也就是应用混合高斯分布很容易检测缓慢行驶的车辆。

    混合高斯背景建模的优点

    每个像素的运动变化特性是不一样的,这才是混合高斯模型具有优势的主要原因。普通的二值化目标分割,整个画面采用同一阈值,无论这个阈值是固定的,还是自适应的,都存在本质性的缺陷,属于有缺陷的分割,这种算法带有不稳定性,无论怎么调整分割参数,也解决不了根本性的问题。因为,画面中每个部分,其实分割阈值是不一样的。一定得建立统计学的方法,进行局部分割,而混合高斯模型正是采用局部分割的一种算法。

    混合高斯背景建模方法评价

    从理论上来说,混合高斯背景建模真是一种较为完美的背景分割方法。但是由于像素值在光线干扰下在中心位置停留时间较短,围绕这一中心位置波动的数据准确地来说并不属于高斯分布,因此也就无法准确地确定这些数据的中心值及方差,造成实际分割的效果并不完美。

    https://blog.csdn.net/lin_limin/article/details/81048411

    代码主要函数解释

    createBackgroundSubtractorMOG2()
    

    https://blog.csdn.net/abc20002929/article/details/43247425

    算法实现代码(opencv4.4.0+VS2019)

    #include "core/core.hpp"    
    #include "highgui/highgui.hpp"    
    #include "imgproc/imgproc.hpp"
    #include "video/tracking.hpp"
    #include <opencv2/video/background_segm.hpp>
    #include <iostream>    
    #include <numeric>
    #include <vector>
    using namespace cv;
    using namespace std;
    
    struct GaussianDistribution
    {
    	float w;// 权重
    	float u;// 期望
    	float sigma;// 标准差
    };
    
    struct PixelGMM
    {
    	int num;// 高斯模型最大数量
    	int index;// 当前使用的高斯模型数量
    	GaussianDistribution* gd;// 高斯模型数组指针
    };
    bool modelInit = false;
    const int GAUSSIAN_MODULE_NUMS = 5;// 模型数量
    const float ALPHA = 0.005;// 学习速率
    const float SIGMA = 30;// 标准差初始值
    const float WEIGHT = 0.05;// 高斯模型权重初始值
    const float T = 0.7;// 有效高斯分布阈值
    int rows, cols;
    PixelGMM* ppgmm;
    
    int main()
    {
    	Mat image;
    	Mat imageGray, imageFG, imageMog;
    	Ptr<BackgroundSubtractorMOG2> mog = createBackgroundSubtractorMOG2(100, 25, false);
    
    	// 打开视频文件
    	VideoCapture cap("F:\\fountain\\Fountain.avi");
    	if (!cap.isOpened())
    	{
    		cout << "cannot open avi file" << endl;
    		return -1;
    	}
    
    	double fps = cap.get(CAP_PROP_FPS);// 获取图像帧率
    	int pauseTime = (int)(1000.f / fps);
    	namedWindow("video");
    
    	while (1)
    	{
    		if (!cap.read(image))
    			break;
    
    		cvtColor(image, imageGray, COLOR_BGR2GRAY);// 转化为灰度图处理
    		mog->apply(imageGray, imageFG, 0.005);
    		
    
    		// 初始化各个像素的高斯分布
    		if (!modelInit)
    		{
    
    			/* 高斯分布参数分配空间*/
    			rows = image.rows;
    			cols = image.cols;
    			ppgmm = (PixelGMM*)malloc(rows * cols * sizeof(PixelGMM));
    			for (int i = 0; i < rows * cols; i++)
    			{
    				ppgmm[i].num = GAUSSIAN_MODULE_NUMS;
    				ppgmm[i].index = 0;
    				ppgmm[i].gd = (GaussianDistribution*)malloc(GAUSSIAN_MODULE_NUMS * sizeof(GaussianDistribution));
    			}
    
    			/* 初始化高斯分布参数 */
    
    			for (int i = 0; i < rows; i++)
    			{
    				for (int j = 0; j < cols; j++)
    				{
    					for (int n = 0; n < GAUSSIAN_MODULE_NUMS; n++)
    					{
    						ppgmm[i * cols + j].gd[n].w = 0;
    						ppgmm[i * cols + j].gd[n].u = 0;
    						ppgmm[i * cols + j].gd[n].sigma = SIGMA;
    
    					}
    				}
    			}
    
    			imageFG.create(rows, cols, CV_8UC1);
    			modelInit = true;
    		}
    		{
    			// 结果图像初始化为全黑
    			imageFG.setTo(Scalar(0));
    
    			for (int i = 0; i < rows; i++)
    			{
    				for (int j = 0; j < cols; j++)
    				{
    					int kHit = -1;// 匹配高斯模型索引
    					int gray = imageGray.at<unsigned char>(i, j);
    					//判断是否属于当前高斯模型
    					for (int m = 0; m < ppgmm[i * cols + j].index; m++)
    					{
    						if (fabs(gray - ppgmm[i * cols + j].gd[m].u) < 2.5 * ppgmm[i * cols + j].gd[m].sigma)// 满足分布
    						{
    							// 更新该高斯分布的标准差、期望、权值,标准差变小,权值增加
    							if (ppgmm[i * cols + j].gd[m].w > 1)
    								ppgmm[i * cols + j].gd[m].w = 1;
    							else
    ppgmm[i * cols + j].gd[m].w = (1 - ALPHA) * ppgmm[i * cols + j].gd[m].w + ALPHA * 1;
    ppgmm[i * cols + j].gd[m].u = (1 - ALPHA) * ppgmm[i * cols + j].gd[m].u + ALPHA * gray;
    							if (ppgmm[i * cols + j].gd[m].sigma < SIGMA / 2)
    								ppgmm[i * cols + j].gd[m].sigma = SIGMA;
    							else
    ppgmm[i * cols + j].gd[m].sigma = sqrt((1 - ALPHA) * ppgmm[i * cols + j].gd[m].sigma
    * ppgmm[i * cols + j].gd[m].sigma + ALPHA * (gray - ppgmm[i * cols + j].gd[m].u)
    * (gray - ppgmm[i * cols + j].gd[m].u));// 若同一高斯分布被重复匹配多次,其标准差会一直下降,这里需要设置最低阈值
    
    // 根据w/sigma降序排序
    int n;
    for (n = m - 1; n >= 0; n--)
    {
        if (ppgmm[i * cols + j].gd[n].w / ppgmm[i * cols + j].gd[n].sigma <= ppgmm[i * cols + j].gd[n + 1].w / ppgmm[i * cols + j].gd[n + 1].sigma)
    	{
    			float temp;
    			temp = ppgmm[i * cols + j].gd[n].sigma;
    		ppgmm[i * cols + j].gd[n].sigma = ppgmm[i * cols + j].gd[n + 1].sigma;
    		    ppgmm[i * cols + j].gd[n + 1].sigma = temp;
    			temp = ppgmm[i * cols + j].gd[n].u;
    			ppgmm[i * cols + j].gd[n].u = ppgmm[i * cols + j].gd[n + 1].u;
    			ppgmm[i * cols + j].gd[n + 1].u = temp;
    			temp = ppgmm[i * cols + j].gd[n].w;
    			ppgmm[i * cols + j].gd[n].w = ppgmm[i * cols + j].gd[n + 1].w;
    			ppgmm[i * cols + j].gd[n + 1].w = temp;
    
    		}
    		else
    		{
    			break;
    		}
    }
    			kHit = n + 1;// 匹配高斯分布的最终索引
    			break;
    		}
    			else
    			{
    			// 没有被匹配到的高斯分布权重降低
    			ppgmm[i * cols + j].gd[m].w *= (1 - ALPHA);
    			}
    		}
                   // 增加新的高斯分布,属于前景
    		if (kHit == -1)
    	  {
    						// 需要去除影响最小的高斯分布
    		if (ppgmm[i * cols + j].index == GAUSSIAN_MODULE_NUMS)
    			{
    	           ppgmm[i * cols + j].gd[ppgmm[i * cols + j].index - 1].sigma = SIGMA;
    				ppgmm[i * cols + j].gd[ppgmm[i * cols + j].index - 1].u = gray;
    				ppgmm[i * cols + j].gd[ppgmm[i * cols + j].index - 1].w = WEIGHT;
    
    			}
    				else
    			{
    		       // 增加新的高斯分布
    			    ppgmm[i * cols + j].gd[ppgmm[i * cols + j].index].sigma = SIGMA;
    				ppgmm[i * cols + j].gd[ppgmm[i * cols + j].index].u = gray;
    				ppgmm[i * cols + j].gd[ppgmm[i * cols + j].index].w = WEIGHT;
    				ppgmm[i * cols + j].index++;
    						}
    					}
    
    			// 高斯分布权值归一化
    			float weightSum = 0;
    			for (int n = 0; n < ppgmm[i * cols + j].index; n++)
    			{
    				weightSum += ppgmm[i * cols + j].gd[n].w;
    			}
    			float weightScale = 1.f / weightSum;
    
    			// 根据T得到有效高斯分布的截止索引
    			weightSum = 0;
    			int kForeground = -1;
    			for (int n = 0; n < ppgmm[i * cols + j].index; n++)
    			{
    				ppgmm[i * cols + j].gd[n].w *= weightScale;
    				weightSum += ppgmm[i * cols + j].gd[n].w;
    				if (weightSum > T && kForeground < 0)
    			{
    				kForeground = n + 1;
    			}
    					}
    
    					// 属于前景点的判断条件
    					if ((kHit > 0 && kHit >= kForeground) || kHit == -1)
    					{
    						imageFG.at<unsigned char>(i, j) = 255;
    
    					}
    				}
    
    			}
    		}
    
    		//imshow("imageMog", imageMog);
    		imshow("video", imageGray);
    		imshow("imageFG", imageFG);
    		waitKey(10);
    	}
    	system("pause");
    
    	return 0;
    }
    

    VIBE+算法视频前景提取

    在动态背景下的视频前景提取

    https://blog.csdn.net/tiandijun/article/details/50499708

    #ifndef ORIGINALVIBE_H
    #define ORIGINALVIBE_H 
    #include<opencv2\core\core.hpp>
    #include<opencv2\imgproc\imgproc.hpp> 
    #include<opencv2\highgui\highgui.hpp> 
    #include<vector>
    using namespace cv;
    class OriginalVibe
    {
    	public: //构造函数
    		OriginalVibe(){};
    		OriginalVibe(int _numberSamples, int _minMatch, int _distanceThreshold, int _updateFactor, int _neighborWidth, int _neighborHeight); 
    		~OriginalVibe(){}; 
    		int distanceL1(const Vec3b &src1, const Vec3b &src2);
    		float distanceL2(const Vec3b &src1, const Vec3b &src2); //操作成员变量
    		void setUpdateFactor(int _updateFactor); //灰度图像
    		void originalVibe_Init_GRAY(const Mat &firstFrame);
    		void originalVibe_ClassifyAndUpdate_GRAY(const Mat &frame,OutputArray &_segmentation); //RGB 三通道 
    		void originalVibe_Init_BGR(const Mat & firstFrame);
    		void originalVibe_ClassifyAndUpdate_BGR(const Mat &frame,OutputArray &_segmentation);
    		int minMatch; //BGR 的距离计算 //背景模型 
    		int numberSamples;
    		std::vector<Mat> backgroundModel; //像素点的分类判断的
    		int distanceThreshold; //背景模型更新概率 
    		int updateFactor; //8-领域(3 x 3) 
    		int neighborWidth;
    		int neighborHeight;
    };
    #endif
    
     #include"Vibe.h" 
    #include<iostream>
    #include<vector>
    
    using namespace std;
    //const unsigned char OriginalVibe::BACK_GROUND = 0; //const unsigned char OriginalVibe::FORE_GROUND = 255; 
    //前景和背景分割 //const unsigned char BACK_GROUND = 0; //const unsigned char FORE_GROUND = 255; 
    OriginalVibe::OriginalVibe(int _numberSamples, int _minMatch, int _distanceThreshold, int _updateFactor, int _neighborWidth, int _neighborHeight) { numberSamples = _numberSamples; minMatch = _minMatch; distanceThreshold = _distanceThreshold; updateFactor = _updateFactor; neighborWidth = _neighborWidth; neighborHeight = _neighborHeight; }//操作成员变量
    void OriginalVibe::setUpdateFactor(int _updateFactor)
    {
    	this->updateFactor = _updateFactor; 
    }//第一种方法:最原始的 vibe 灰度通道
    void OriginalVibe::originalVibe_Init_GRAY(const Mat& firstFrame) 
    {
    	int height = firstFrame.rows;
    	int width = firstFrame.cols; //背景模型分配内存 
    	backgroundModel.clear(); 
    	for(int index = 0;index < this->numberSamples;index++)
        {
    		backgroundModel.push_back(Mat::zeros(height,width,CV_8UC1)); 
        }
    	//随机数 
    	RNG rng;
    	int cshift;
    	int rshift; 
    	for(int r = 0;r < height ;r++) 
        {
    		for(int c = 0;c < width ; c++) 
    		{ 
                if( c < neighborWidth/2 || c > width - neighborWidth/2 -1|| r < neighborHeight/2 || r > height - neighborHeight/2 -1)
                {
    			/*随机数的生成方式有很多种*/ 
    			/*cshift = randu<int>()%neighborWidth - neighborWidth/2; rshift = randu<int>()%neighborHeight - neighborHeight/2; */ 
    			cshift = rand()%neighborWidth - neighborWidth/2;
    			rshift = rand()%neighborHeight - neighborHeight/2;
    			for(std::vector<Mat>::iterator it = backgroundModel.begin();
    				it != backgroundModel.end();it++) 
                {
    				for(;;) 
                    { 
    					/*cshift = rng.uniform(-neighborWidth/2,neighborWidth/2 + 1); rshift = rng.uniform(-neighborHeight/2,neighborHeight/2 +1 ); */ 
    					cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2;
    					rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2;
    					if(!(cshift == 0 && rshift==0))
    						break;
    				}
    				if(c + cshift < 0 || c + cshift >=width)
    					cshift *= -1; 
    				if(r + rshift < 0 || r + rshift >= height) rshift *= -1;
    				(*it).at<uchar>(r,c) = firstFrame.at<uchar>(r+rshift,c+cshift); 
    			}
    		}else {
    			for(std::vector<Mat>::iterator it = backgroundModel.begin();
    				it != backgroundModel.end();
    				it++) { 
    				for(;;) { /*cshift = rng.uniform(-neighborWidth/2,neighborWidth/2 + 1); rshift = rng.uniform(-neighborHeight/2,neighborHeight/2 +1 ); */
    					cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2;
    					rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; 
    					if(!(cshift == 0 && rshift == 0))
    						break; 
    				}(*it).at<uchar>(r,c) = firstFrame.at<uchar>(r+rshift,c+cshift);
    			}
    		}
    		}
    	}
    }
    void OriginalVibe::originalVibe_ClassifyAndUpdate_GRAY(const Mat &frame,OutputArray &_segmentation) 
    { 
    	int width = frame.cols;
    	int height = frame.rows;
    	int rshift;
    	int cshift;
    	_segmentation.create(frame.size(), CV_8UC1); 
    	Mat segmentation = _segmentation.getMat();
    	RNG rng; for (int r = 0; r < height; r++) 
        {
    		for (int c = 0; c < width; c++)
            {
    			int count = 0;
    			unsigned char pixel = frame.at<uchar>(r, c); 
    			//让 pixel 和背景模板中 backgroundModel 进行比较 
    			for(std::vector<Mat>::iterator it = backgroundModel.begin();
    				it != backgroundModel.end();
    				it++) { 
                    if( abs( int(pixel) - int( (*it).at<uchar>(r,c)) ) < (this->distanceThreshold) )
                    {
    				count++;
    				//循环到一定阶段,判断 count 的值是否大于 minMatch,更新 背景模型
    				if( count >= this->minMatch)
                    {
    					int random = rng.uniform(0,this->updateFactor);
    					if(random == 0) {
    						int updateIndex = rng.uniform(0,this->numberSamples);
    						backgroundModel[updateIndex].at<uchar>(r,c) = pixel;
    				}
    					random = rng.uniform(0,this->updateFactor); 
    					if(random == 0) { if(c < neighborWidth/2 || c > width - neighborWidth/2-1 || r < neighborHeight/2 || r > height - neighborHeight/2-1)
                        { 
    						for(;;) 
                            { 
    							cshift = abs(randu<int>()%neighborWidth) - neighborWidth / 2; rshift = abs(randu<int>() % neighborHeight) - neighborHeight / 2; if (!(cshift == 0 && rshift == 0)) break;
    						}if (c + cshift < 0 || c + cshift >= width) cshift *= -1; if (r + rshift < 0 || r + rshift >= height) rshift *= -1; int updateIndex = rng.uniform(0, this->numberSamples); backgroundModel[updateIndex].at<uchar>(r + rshift, c + cshift) = pixel;
    					}
    					else { for (;;) 
                        { 
    					cshift = abs(randu<int>() % neighborWidth) - neighborWidth / 2; 
    					rshift = abs(randu<int>() % neighborHeight) - neighborHeight / 2;
    						if (!(cshift == 0 && rshift == 0))
    							break;
    					}int updateIndex = rng.uniform(0, this->numberSamples);
    			backgroundModel[updateIndex].at<uchar>(r + rshift, c + cshift) = pixel;
    					}
    					}segmentation.at<uchar>(r, c) = 0;
    					break;
    				}
    			}
    			}if (count < this->minMatch)
    				segmentation.at<uchar>(r, c) = 255;
    		}
    	}
    }
    //第三种方法:BGR 通道
    void OriginalVibe::originalVibe_Init_BGR(const Mat & fristFrame)
    { 
    	int height = fristFrame.rows; int width = fristFrame.cols;
    	//背景模型分配内存
    	backgroundModel.clear();
    	for(int index = 0;index < this->numberSamples;index++) 
        { 
    		backgroundModel.push_back( Mat::zeros(height,width,CV_8UC3) );
    	}//随机数
    	RNG rng;
    	int cshift; 
    	int rshift; 
    	for(int r =0 ; r < height; r++) 
        {
    		for(int c = 0;c < width ;c++) 
            {
    			if( c < neighborWidth/2 || c > width - neighborWidth/2 -1|| r < neighborHeight/2 || r > height - neighborHeight/2 -1 )
                { 
    				/*初始化背景模型:开始 */
    				for(vector<Mat>::iterator iter = backgroundModel.begin(); 
    					iter != backgroundModel.end();iter++)
                    { 
    					for(;;) 
                        { 
    						cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2;
    						rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2;
    						if(!(cshift == 0 && rshift==0)) 
    							break;
    					}
    					if(c + cshift < 0 || c + cshift >=width)
    						cshift *= -1; if (r + rshift < 0 || r + rshift >= height) rshift *= -1; (*iter).at<Vec3b>(r, c) = fristFrame.at<Vec3b>(r + rshift, c + cshift);
    				}
    			}
                /*初始化背景模型:结束*/ else { /*******初始化背景模型:开始******/ 
    				for (vector<Mat>::iterator iter = backgroundModel.begin(); 
    					iter != backgroundModel.end(); iter++)
                    { 
    					for (;;)
                        { 
    					cshift = abs(randu<int>() % neighborWidth) - neighborWidth / 2;
    					rshift = abs(randu<int>() % neighborHeight) - neighborHeight / 2;
    						if (!(cshift == 0 && rshift == 0)) break;
    					}
    			(*iter).at<Vec3b>(r, c) = fristFrame.at<Vec3b>(r + rshift, c + cshift);
    				}/*****初始化背景模型:结束 ******/ }
    		}
    	}
    }
    int OriginalVibe::distanceL1(const Vec3b& src1, const Vec3b& src2) 
    { 
    	return abs(src1[0] - src2[0]) + abs(src1[1] - src2[1]) + abs(src1[2] - src2[2]);
    }
    float OriginalVibe::distanceL2(const Vec3b& src1, const Vec3b& src2) 
    { 
    	return pow(pow(src1[0] - src2[0], 2.0) + pow(src1[1] - src2[1], 2.0) +        pow(src1[2] - src2[2], 2.0), 0.5);
    }
    void OriginalVibe::originalVibe_ClassifyAndUpdate_BGR(const Mat& frame, OutputArray& _segmentation) 
    {
    	//*编号 1
    	int height = frame.rows; 
    	int width = frame.cols;
    	int cshift; 
    	int rshift; 
    	_segmentation.create(frame.size(),CV_8UC1);
    	Mat segmentation = _segmentation.getMat();
    	RNG rng;
    	for(int r =0 ;r < height; r++) 
        {
    		//编号 1-1
    		for(int c = 0;c < width ;c++) 
            {
    			//编号 1-1-1 
    			int count = 0;
    			Vec3b pixel = frame.at<Vec3b>(r,c); 
    			for( vector<Mat>::iterator iter = backgroundModel.begin() ;
    				iter != backgroundModel.end(); 
    				iter++) 
                {
    				//编号 1-1-1-1 
    				if( distanceL1(pixel,(*iter).at<Vec3b>(r,c)) < 4.5*this->distanceThreshold ) 
                    { 
    					count++;
    					if(count >= this->minMatch)
                        {
    						//第一步:更新模型 update /**********开始更新模型*************/
    						int random = rng.uniform(0,this->updateFactor);
    						if(random == 0) 
                            {
    							int updateIndex = rng.uniform(0,this->numberSamples); 
    							backgroundModel[updateIndex].at<Vec3b>(r,c) = pixel;
    						}
    						random = rng.uniform(0,this->updateFactor);
    						if(random == 0)
                            {
    			if (c < neighborWidth / 2 || c > width - neighborWidth / 2 - 1 || r < neighborHeight / 2 || r > height - neighborHeight / 2 - 1)
                { 
    							for (;;) 
                                {
    					cshift = abs(randu<int>() % neighborWidth) - neighborWidth / 2;
    					rshift = abs(randu<int>() % neighborHeight) - neighborHeight / 2;
    								if (!(cshift == 0 && rshift == 0)) break;
    							}
    							if (c + cshift < 0 || c + cshift >= width)
    								cshift *= -1;
    							if (r + rshift < 0 || r + rshift >= height) 
    								rshift *= -1; 
    							int updateIndex = rng.uniform(0, this->numberSamples);
    			backgroundModel[updateIndex].at<Vec3b>(r + rshift, c + cshift) = pixel;
    						}
    						else 
                            { 
    							for (;;)
                                {
    							cshift = abs(rand() % neighborWidth) - neighborWidth / 2;
    					rshift = abs(rand() % neighborHeight) - neighborHeight / 2;
    								if (!(cshift == 0 && rshift == 0))
    									break;
    							}
    			int updateIndex = rng.uniform(0, this->numberSamples); 
    			backgroundModel[updateIndex].at<Vec3b>(r + rshift, c + cshift) = pixel;
    						}/****************************************/
    				                }/**********结束更新模型************ */ //第二步:分类 classify 58
    						segmentation.at<uchar>(r, c) = 0;
    						break;
    					}
    				}
    			}//编号 1-1-1-1
    			if(count < this->minMatch)//classify 
    				segmentation.at<uchar>(r,c) = 255;
    		}//编号 1-1-1 
    	}//编号 1-1
    }//*编号 1
    

    主函数

    #include<opencv2\core\core.hpp>
    #include<opencv2\imgproc\imgproc.hpp> 
    #include<opencv2\highgui\highgui.hpp> 
    #include "Vibe.h" 
    #include<iostream>
    using namespace std; 
    using namespace cv; 
    int main() 
    { 
    	/*视频流的输入*/
    	VideoCapture cap("F:\\cars7\\car7out.avi"); 
    	if(!cap.isOpened())
    		return -1;
    	/*视频帧图像*/
    	Mat frame;
    	/*前景-背景检测及显示窗口*/ 
    	Mat seg; /*创建 vibe 背景建模的对象*/ 
    	OriginalVibe vibe(20,2,20,16,3,3);
    	Mat frameGray;
    	int frame_width = cap.get(CAP_PROP_FRAME_WIDTH); 
    	int frame_height = cap.get(CAP_PROP_FRAME_HEIGHT);
    	int frame_fps = cap.get(CAP_PROP_FPS); 
    	//cv::VideoWriter writer; 
    	//writer = VideoWriter("output.avi", CV_FOURCC('X', 'V', 'I', 'D'), frame_fps, Size(frame_width, frame_height), 1);
    	int number =0; 
    	for(;;) 
    { 
    cap >> frame; 
    	if (!frame.data)
    		break;
    	number++; 
    	char pathsource[20] = "source/sou"; 
    	sprintf_s(pathsource, "source/sou%d.jpg", number);
    	imwrite(pathsource, frame); 
    	if (number == 1) 
        { 
    		vibe.originalVibe_Init_BGR(frame); 
    		imwrite("result/pic1.jpg", frame);
    		continue;
    	}
    else { 
    		vibe.originalVibe_ClassifyAndUpdate_BGR(frame, seg);
    		medianBlur(seg, seg, 5); 
    		imshow("segmentation", seg);
    	}
    	char pathresult[20] = "result/pic"; 
    	sprintf_s(pathresult, "result/pic%d.jpg", number); 
    	imwrite(pathresult, seg);
    	//writer.write(seg); 
    	imshow("frame",frame); 
    	moveWindow("segmentation", 500, 0);
    	moveWindow("frame", 0, 500);
    	if(waitKey(10) >= 0)
    		break;
    	}
    	system("pause");
    	return 0;
    }
    

    SIFT算法

    https://blog.csdn.net/honglin_ren/article/details/36430129(见参考文献)

    算法实现代码(opencv4.4.0+VS2019)

    此处是对图像的角点检测和匹配

    #include <opencv2/opencv.hpp>
    #include <iostream>
    
    int main()
    {
    	cv::Mat imageL = cv::imread("D:\\VS2019\\1.png");
    	cv::Mat imageR = cv::imread("D:\\VS2019\\2.png");
    	cv::Mat image= cv::imread("D:\\VS2019\\3.png");
    
    	//提取特征点方法
    	//SIFT
    	//cv::Ptr<cv::xfeatures2d::SIFT> sift = cv::xfeatures2d::SIFT::create();
    	//ORB
    	//cv::Ptr<cv::ORB> orb = cv::ORB::create();
    	//SURF
    	cv::Ptr<cv::SIFT > sift = cv::SIFT::create();
    
    	//特征点
    	std::vector<cv::KeyPoint> keyPointL, keyPointR,keyPoint1;
    	//单独提取特征点
    	sift->detect(imageL, keyPointL);
    	sift->detect(imageR, keyPointR);
    	sift->detect(image, keyPoint1);
    
    	//画特征点
    	cv::Mat keyPointImageL;
    	cv::Mat keyPointImageR;
    	cv::Mat keyPointImage1;
    	drawKeypoints(imageL, keyPointL, keyPointImageL, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    	drawKeypoints(imageR, keyPointR, keyPointImageR, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    	drawKeypoints(image, keyPoint1, keyPointImage1, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    	//显示窗口
    	//cv::namedWindow("KeyPoints of imageL");
    	//cv::namedWindow("KeyPoints of imageR");
    	cv::namedWindow("KeyPoints of image");
    
    	//显示特征点
    	//cv::imshow("KeyPoints of imageL", keyPointImageL);
    	//cv::imshow("KeyPoints of imageR", keyPointImageR);
    	cv::imshow("KeyPoints of image", keyPointImage1);
    	//特征点匹配
    	cv::Mat despL, despR;
    	//提取特征点并计算特征描述子
    	sift->detectAndCompute(imageL, cv::Mat(), keyPointL, despL);
    	sift->detectAndCompute(imageR, cv::Mat(), keyPointR, despR);
    
    	std::vector<cv::DMatch> matches;
    
    	//如果采用flannBased方法 那么 desp通过orb的到的类型不同需要先转换类型
    	if (despL.type() != CV_32F || despR.type() != CV_32F)
    	{
    		despL.convertTo(despL, CV_32F);
    		despR.convertTo(despR, CV_32F);
    	}
    
    	cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("FlannBased");
    	matcher->match(despL, despR, matches);
    
    	//计算特征点距离的最大值 
    	double maxDist = 0;
    	for (int i = 0; i < despL.rows; i++)
    	{
    		double dist = matches[i].distance;
    		if (dist > maxDist)
    			maxDist = dist;
    	}
    
    	//挑选好的匹配点
    	std::vector< cv::DMatch > good_matches;
    	for (int i = 0; i < despL.rows; i++)
    	{
    		if (matches[i].distance < 0.5 * maxDist)
    		{
    			good_matches.push_back(matches[i]);
    		}
    	}
    	cv::Mat imageOutput;
    	cv::drawMatches(imageL, keyPointL, imageR, keyPointR, good_matches, imageOutput);
    
    	cv::namedWindow("picture of matching");
    	cv::imshow("picture of matching", imageOutput);
    	cv::waitKey(0);
    	return 0;
    }
    

    基于SIFT算法的视频前景提取

    在摄像头抖下视频的前景提取。
    
    基于 SIFT 特征点的隔帧匹配模型建立。 
    
    SIFT(Scale-invariant feature transform)是一种检测局部特征的算法,该算法 通过求一幅图中的特征点(interest points,or corner points)及其有关 scale 和 orientation 的描述子得到特征并进行图像特征点匹配,获得了良好效果,详细解析如下: 
    
    SIFT 特征不只具有尺度不变性,即使改变旋转角度,图像亮度或拍摄视角,仍然能够得到好的检测效果。整个算法分为以下几个部分: 
    

    (1)构建尺度空间

    这是一个初始化操作,尺度空间理论目的是模拟图像数据的多尺度特征。
    

    (2)在不同尺度空间中寻找特征点进行描述,最后匹配是 SIFT 算法的核心。因 此可以将 SIFT 算法概括成以下几个步骤:

    a.搜索特征点; 
    
    b.对提取的特征点进行详细的描述; 
    
    c.对关键点进行匹配。 
    

    (2.1)搜索特征点。

    根据尺度空间理论,建立高斯金字塔与高斯差分金字塔,将高斯差分金字塔 中的每个像素与它同尺度的 8 个相邻点和上下相邻尺度对应的 9×2 个点共 26 个 点比较,得到待修正的特征点集。通过特征点精确定位和去除边缘响应2个环节, 得到修正的特征点集。

    (2.2)特征点描述。

    对特征点的邻域进行梯度计算,使用直方图将 0°到 360°分为 36 个柱,每 10°一个柱,柱高为对应梯度方向的累加值。直方图的峰值即为该特征点的主方向。以特征点为中心取 16×16 像素为临域,再将该邻域分为 4×4 个子区域,在 每个子区域计算 8 个梯度方向的直方图。

    (2.3)隔帧特征点匹配。

    对特征点集采用 KD 树[7]进行隔帧匹配搜索,分别得到最邻近匹配对与次临近匹配对,将最邻近欧式距离与次临近欧式距离进行相比,若比值小于某一设定阈值则认为匹配正确。最后采用 RANSC(random sample consensus)进行进一步提 纯,得到最终的特征点匹配对。这样做可以有效的抑制视频抖动现象的发生。

    算法步骤

    (1)高斯滤波。

    首先通过高斯滤波的方法,对图像进行降噪处理,这样可以降低下一步中 SITF 特征点检测的难度,使得一些视频中的闪烁点不会被检测为特征点进行匹 配,并输出每一帧。

    (2)提取 SIFT 特征点

    接着,对滤波后的图片进行特征点检测,通过定义阈值,检测出每一帧上的 前 N 个关键点。

    (3)隔帧特征点匹配。

    这一步是问题三防抖的关键,通过隔一帧进行特征点匹配,进行放射变换和16 配准的过程,优化视频的连续性,并对每一帧仿射变换后的图片进行边框处理, 去掉因仿射变换而产生的扭曲边界。

    (4)提取

    前景目标。 运用前面所述的改进的 ViBe+模型,提取前景目标。

    (5)去除小连通域。

    最后通过去除小连通区域的方式,对视频进行降噪处理,生成提取前景目标 后的模型

    算法实现代码(opencv4.4.0+VS2019)

    #include <opencv2/opencv.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <iostream>
    #include <sstream>
    #include <string>
    #include<vector>
    #include<algorithm>
    using namespace std;
    using namespace cv;
    void Video_To_Image(string filename, int& framenum, int& wide, int& len); 
    void Image_To_Video(int framenumb, int& wid, int& lenth);
    Mat g_srcImage, g_srcImage1, g_grayImage;
    Mat dst;
    Mat dst1;
    vector<Mat> paths1;
    vector<Mat> paths2;
    long frameToStart = 1;
    long currentFrame = frameToStart;
    int frameToStop = 900;
    
    Mat sift_capture(int argc, Mat argv1, Mat argv2)
    {
    	//VideoCapture capture(0); 
    	//VideoCapture capture(filename); 
    	Mat image01, image02, imgdiff;
    	image01 = argv1;
    	image02 = argv2;
    	//灰度图转换 
    	Mat image1, image2;
    	cvtColor(image01, image1, COLOR_RGB2GRAY);
    	cvtColor(image02, image2, COLOR_RGB2GRAY);
    	//GaussianBlur(image02, image02, Size(3,3), 0); 
    	double time0 = static_cast<double>(getTickCount());//开始计时 
    	//灰度图转换 
    	/*Mat image1, image2;
    	cvtColor(image01, image1, CV_RGB2GRAY);
    	cvtColor(image02, image2, CV_RGB2GRAY);*/
    	//提取特征点 
    	//cv::Ptr<cv::SIFT > siftDetector = cv::SIFT::create();
    	cv::Ptr<cv::SIFT > siftDetector = cv::SIFT::create();
    	//特征点
    	std::vector<cv::KeyPoint> keyPoint1, keyPoint2;
    	//单独提取特征点
    	Mat imageDesc1, imageDesc2;
    	siftDetector->detect(image1, keyPoint1);
    	siftDetector->detect(image2, keyPoint2);
    
    	drawKeypoints(image1, keyPoint1, imageDesc1, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    	drawKeypoints(image2, keyPoint2, imageDesc2, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    
    	siftDetector->detectAndCompute(image1, cv::Mat(), keyPoint1,  imageDesc1);
    	siftDetector->detectAndCompute(image2, cv::Mat(), keyPoint2, imageDesc2);
    
    	//特征点描述,为下边的特征点匹配做准备 
    
    	/*SiftDescriptorExtractor siftDescriptor;;
    	Mat imageDesc1, imageDesc2;
    	siftDescriptor.compute(image1, keyPoint1, imageDesc1);
    	siftDescriptor.compute(image2, keyPoint2, imageDesc2);*/
    
    	//获得匹配特征点,并提取最优配对FlannBasedMatcher matcher; 
    	vector<DMatch> matchePoints;
    	cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("FlannBased");
    	matcher->match(imageDesc1, imageDesc2, matchePoints,Mat());
    	sort(matchePoints.begin(), matchePoints.end()); //特征点排序 
    	//获取排在前 N 个的最优匹配特征点 
    	vector<Point2f> imagePoints1, imagePoints2;
    	if ((int)(matchePoints.size() * 0.25) < 4)
    	{
    		for (int i = 0; i < (int)(matchePoints.size()); i++)
    		{
    			imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
    			imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
    		}
    	}
    	else
    	{
    		for (int i = 0; i < (int)(matchePoints.size() * 0.25); i++)
    		{
    			imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
    			imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
    		}
    	}
    	//获取图像 1 到图像 2 的投影映射矩阵 尺寸为 3*3 
    	Mat homo = findHomography(imagePoints1, imagePoints2, RANSAC);
    	//cout<<"变换矩阵为:\n"<<homo<<endl<<endl; //输出映射矩阵 
    	//图像配准 
    	Mat imageTransform1, imgpeizhun, imgerzhi;
    	warpPerspective(image01, imageTransform1, homo, Size(image02.cols,
    		image02.rows));
    	imshow("经过透视矩阵变换后", imageTransform1);
    	absdiff(image02, imageTransform1, imgpeizhun);
    	imshow("配准 diff", imgpeizhun);
    	threshold(imgpeizhun, imgerzhi, 40, 255.0, THRESH_BINARY);
    	imshow("配准二值化", imgerzhi);
    	//输出所需时间 
    	time0 = ((double)getTickCount() - time0) / getTickFrequency();
    	cout << 1 / time0 << endl;
    	Mat temp, image02temp;
    	float m_BiLi = 0.9;
    	image02temp = image02.clone();
    	cvtColor(imgerzhi, temp, COLOR_RGB2GRAY);
    	//检索连通域 
    	Mat se = getStructuringElement(MORPH_RECT, Size(10, 10));
    	Mat result_temp;
    	//dilate(temp, result_temp, Mat(), Point(-1, -1), 3, BORDER_DEFAULT); 
    	morphologyEx(temp, result_temp, MORPH_DILATE, se);
    	
    	imshow("检测与跟踪", image02temp);
    	//return image02temp; 
    	return temp;
    	//waitKey(20); 
    }
    
    void Video_To_Image(string filename,int &framenum,int &wide,int &len) 
    	{ 
    	cout << "---------------Video_To_Image-----------------" << endl; 
    	cv::VideoCapture capture(filename); 
    	//CvCapture* capture = cvCreateFileCapture(filename); 
    	if (!capture.isOpened()) 
    	{ 
    		cout << "open video error"; 
    	}
    	/*CV_CAP_PROP_POS_MSEC – 视频的当前位置(毫秒) 
    	CV_CAP_PROP_POS_FRAMES – 视频的当前位置(帧) 
    	CV_CAP_PROP_FRAME_WIDTH – 视频流的宽度 
    	CV_CAP_PROP_FRAME_HEIGHT – 视频流的高度 
    	CV_CAP_PROP_FPS – 帧速率(帧 / 秒)*/ 
    	int frame_width = (int)capture.get(CAP_PROP_FRAME_WIDTH); 
    	int frame_height = (int)capture.get(CAP_PROP_FRAME_HEIGHT); 
    	float frame_fps = capture.get(CAP_PROP_FPS); 
    	int frame_number = capture.get(CAP_PROP_FRAME_COUNT);//总帧数 
    	framenum = frame_number; 
    	wide = frame_width; 
    	len = frame_height; 
    	cout << "frame_width is " << frame_width << endl; 
    	cout << "frame_height is " << frame_height << endl; 
    	cout << "frame_fps is " << frame_fps << endl; 
    	int num = 0;//统计帧数 
    	cv::Mat img; 
    	string img_name; 
    	char image_name[20]; 
    	cv::namedWindow("MyVideo", WINDOW_AUTOSIZE); 
    	vector<Mat>single_frame; 
    while (true) 
    //while (num<frame_number) 
    { 
        cv::Mat frame; 
        //从视频中读取一个帧 
        //cvSetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES, num); 
        bool bSuccess = capture.read(frame); 
        if (!bSuccess) 
    { 
        cout << "不能从视频文件读取帧" << endl; 
        break; 
    }
        cv::Mat out_temp; 
        Mat WarpImage; 
        //medianBlur(frame, out_temp, 7); 
         GaussianBlur(frame, out_temp, Size(7, 7), 0, 0); 
    if (num==0) 
     { 
        //Mat *temp=new Mat(); 
        single_frame.push_back(out_temp); 
        single_frame.push_back(out_temp); 
        single_frame.push_back(out_temp);
        //single_frame.push_back(out_temp); 
     }
       else 
    	 { 
        	single_frame.push_back(out_temp); 
     	 }
    		WarpImage = sift_capture(2, single_frame[num + 2], single_frame[num]); 
    	//single_frame.push_back(WarpImage); 
    	sprintf_s(image_name, "%s%d%s", "image", num, ".jpg");//保存的图片名 
    	num++; 
    	img_name = image_name; 
    	imwrite(img_name, WarpImage);//保存保存一帧图片 
    if (cv::waitKey(30) == 27 || num == frame_number) 
    	{ 
       	 cout << "按下 ESC 键" << endl; 
        	break; 
    	} 
    	}
        	capture.release();//这句话貌似不需要 
    	}
       void Image_To_Video(int framenumb , int &wid, int &lenth) 
    { 
         	cout<< "---------------Video_To_Image-----------------"<<endl; 
        	 char image_name[20]; 
    		string s_image_name; 
    		cv::VideoWriter writer; 
    	int isColor = 1;//不知道是干啥用的 
    	int frame_fps = 30; 
    	int frame_width = wid; 
    	int frame_height = lenth; 
    	string video_name = "F:\\建模\\D\\附件2-典型视频\\有晃动\\cars7\\car7out.avi"; 
    	writer= VideoWriter(video_name,VideoWriter::fourcc('X', 'V', 'I', 'D'), frame_fps, 
    	Size(frame_width, frame_height), isColor); 
    	cout << "frame_width is " << frame_width << endl; 
    	cout << "frame_height is " << frame_height << endl; 
    	cout << "frame_fps is " << frame_fps << endl; 
    	cv::namedWindow("image to video", WINDOW_AUTOSIZE); 
    	int num = 2000;//输入的图片总张数 
    	int i = 0; 
    	Mat img; 
    	while (i <= num) 
    { 
    	sprintf_s(image_name, "%s%d%s", "image", i, ".jpg"); 
    	i++; 
    	s_image_name = image_name; 
    	img = imread(s_image_name);//读入图片 
    	if (!img.data)//判断图片调入是否成功 
    	{ 
    			cout << "Could not load image file...\n" << endl; 
    	}
    	imshow("image to video", img); 
    	//写入 
    	writer.write(img); 
    	if (cv::waitKey(30) == 27 || i == framenumb) 
    	{ 
    		cout << "按下 ESC 键" << endl; 
    		break; 
    	} 
    	} 
    }
    
    int main()
    {
    
    	string video_name = "F:\\建模\\D\\附件2-典型视频\\有晃动\\cars7\\input.avi";
    	//注意,使用 string 时,若不用 using namespace std,需要使用 std::string
    	int frame_num=0; 
    	int frame_wid = 0; 
    	int frame_len = 0; 
    	Video_To_Image(video_name, frame_num, frame_wid, frame_len);
    	Image_To_Video(frame_num, frame_wid, frame_len);
    
    	return 0;
    }
    

    基于Canny的VIBE+算法提取视频前景

    摄像头抖动下或动态背景下的视频前景提取

    该算法主要是利用Canny边缘算法将VIBE+产生的鬼影现象消除。

    算法实现代码(opencv4.4.0+VS2019)

    主函数

    #include  < opencv2/opencv.hpp >  
    #include "ViBe.h"    
    #include < iostream>    
    #include < cstdio>    
    #include< stdlib.h>  
    using namespace cv;
    using namespace std;
    
    int main(int argc, char* argv[])
    {
        namedWindow("mask", WINDOW_AUTOSIZE);
        namedWindow("input", WINDOW_AUTOSIZE);
        Mat frame, gray, mask;
    	VideoCapture capture;
        capture.open("F:\\建模\\D\\附件2-典型视频\\不带晃动-静态背景\\pedestrian\\input.avi");    //输入视频名称
    	//VideoCapture capture(0);
    
        if (!capture.isOpened())
        {
            cout << "No camera or video input!\n" << endl;
            return -1;
        }
    
        ViBe_BGS Vibe_Bgs; //定义一个背景差分对象  
        int count = 0; //帧计数器,统计为第几帧   
    
        while (1)
        {
            count++;
            capture >> frame;
            if (frame.empty())
                break;
            cvtColor(frame, gray, COLOR_BGR2GRAY); //转化为灰度图像   
    
            if (count == 1)  //若为第一帧  
            {
                Vibe_Bgs.init(gray);
                Vibe_Bgs.processFirstFrame(gray); //背景模型初始化   
                cout << " Training GMM complete!" << endl;
            }
            else
            {
                double t = getTickCount();
    
                Vibe_Bgs.testAndUpdate(gray);
                mask = Vibe_Bgs.getMask();    //计算前景
                morphologyEx(mask, mask, MORPH_OPEN, Mat());   
                //形态学处理消除前景图像中的小噪声,这里用的开运算 
    	
    			Mat localMaxImage;
    			
    			// 先用使用 3x3内核来降噪
    			blur(gray, localMaxImage, Size(3, 3));
    			bool L2 = false;
    			// 运行Canny算子
    			Canny(localMaxImage, localMaxImage, 100, 200, 3,L2);
    			//使用Canny算子输出的边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中
    
    			Mat aim;
    			bitwise_and(mask, localMaxImage, aim);
    
    			//medianBlur(aim, aim, 3);//去除一些杂质点。
    			Mat outBin2 = aim.clone();
    			Mat element = getStructuringElement(MORPH_ELLIPSE, Size(2, 2));
                    //膨胀变亮形成连通域。
    			//Mat element2 = getStructuringElement(MORPH_ELLIPSE, Size(4, 4));
                    //腐蚀操作断开一些连通域。
    			dilate(aim, aim, element, Point(-1, -1), 5);
    			//erode(aim, aim, element2, Point(-1, -1), 3);
    		
    			imshow("aim", aim);
    			imshow("cannal", localMaxImage);
                imshow("mask", mask);
                t = (getTickCount() - t) / getTickFrequency();
                cout << "imgpro time" << t << endl;
            }
     
            imshow("input", frame);
            moveWindow("mask", 1, 500);
            moveWindow("input", 900, 500);
    		moveWindow("cannal",600, 500);
    		moveWindow("aim", 300, 500);
       
            if (waitKey(10) == 'q')    //键盘键入q,则停止运行,退出程序
                break;
        }
        system("pause");
        return 0;
    }
    //使用Canny算子输出的边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中
    			Mat aim;
    			bitwise_and(mask, localMaxImage, aim);
    	       //medianBlur(aim, aim, 3);//去除一些杂质点。
    			Mat outBin2 = aim.clone();
    			Mat element = getStructuringElement(MORPH_ELLIPSE, Size(2, 2));
                    //膨胀变亮形成连通域。
    			//Mat element2 = getStructuringElement(MORPH_ELLIPSE, Size(4, 4));
                    //腐蚀操作断开一些连通域。
    			dilate(aim, aim, element, Point(-1, -1), 5);
    			//erode(aim, aim, element2, Point(-1, -1), 3);
    			
    
    			imshow("aim", aim);
    			imshow("cannal", localMaxImage);
                imshow("mask", mask);
                t = (getTickCount() - t) / getTickFrequency();
                cout << "imgpro time" << t << endl;
            }
     
            imshow("input", frame);
            moveWindow("mask", 1, 500);
            moveWindow("input", 900, 500);
    		moveWindow("cannal",600, 500);
    		moveWindow("aim", 300, 500);
       
            if (waitKey(10) == 'q')    //键盘键入q,则停止运行,退出程序
                break;
        }
        system("pause");
        return 0;
    }
    

    参考文献:
    [1] viBe算法原理及代码解析
    [2] OpenCV Tutorial
    [3] 详解EM算法和混合高斯算法
    [4] OpenCV背景建模
    [5] SIFT算法原理解析

    展开全文
  • 视频前景提取综述

    千次阅读 2018-04-11 22:08:36
    由于前景背景差异小、运动形变,背景里有快速明暗变化,背景中存在几何变化以及光照等噪声,诸多因素对前景提取效果和质量造成了一定的影响,甚至无法有效地提取到完整前景帧序列。完整的提取前景,也就是不多提取也...

           由于前景背景差异小、运动形变,背景里有快速明暗变化,背景中存在几何变化以及光照等噪声,诸多因素对前景提取效果和质量造成了一定的影响,甚至无法有效地提取到完整前景帧序列。完整的提取前景,也就是不多提取也不少提取,已经成为研究热点和难点。

    常用的运动前景检测和提取方法有帧差法、背景减除法和Vibe法

    一、帧差法

           摄像机采集的视频序列具有连续性的特点。如果场景内没有运动目标,则连续帧的变化很微弱,如果存在运动目标,则连续的帧和帧之间会有明显地变化。由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧或三帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。

    两帧差分法的运算过程如图所示。记视频序列中第n帧和第n−1帧图像为fn和fn−1,两帧对应像素点的灰度值记为fn(x,y)和fn−1(x , y),按照式2.13将两帧图像对应像素点的灰度值进行相减,并取其绝对值,得到差分图像Dn:

    设定阈值T,逐个对像素点进行二值化处理,得到二值化图像Rn'。其中,灰度值为255的点即为前景(运动目标)点,灰度值为0的点即为背景点;对图像Rn'进行连通性分析,最终可得到含有完整运动目标的图像Rn。


    帧间差分法的原理简单,计算量小,能够快速检测出场景中的运动目标。但由实验结果可以看出,帧间差分法检测的目标不完整,内部含有“空洞”,这是因为运动目标在相邻帧之间的位置变化缓慢,目标内部在不同帧图像中相重叠的部分很难检测出来。帧间差分法通常不单独用在目标检测中,往往与其它的检测算法结合使用。

    二、背景减除法


    三、Vibe

    Vibe主要思想是:利用邻域像素在空间分布具有一致性来完成背景模型的初始化,使得当前帧图像的每个像素与其背景模型进行匹配,若判定为背景就随机更新其背景模型。

    主要取自博客和肖碧波,胡伟的一篇期刊整合




    展开全文
  • 混合高斯MOG优化的视频前景提取

    千次下载 热门讨论 2011-11-19 19:03:09
    基于opencv+vs2008开发的混合高斯MOG优化的视频前景提取
  • 图像前景提取

    2018-12-18 09:16:25
    调用OpenCV的GrabCut算法,读入图形后首先利用Numpy的有关调用构建前景和背景模型,之后直接使用GrabCut算法对两个模型进行提取
  • 运动前景提取的matlab程序

    热门讨论 2013-06-20 15:55:34
    运动前景提取的matlab程序,效果还不错
  • OTSU是最大类间方差法,可以用了进行视频的前景提取,效果不错。
  • 前景提取、图像缩放、烟雾识别、图像处理、运动检测、目标圈定
  • http://woshicver.com/FifthSection/4_16_交互式前景提取使用GrabCut算法/ http://woshicver.com/FifthSection/4_15_图像分割与分水岭算法/

    http://woshicver.com/FifthSection/4_16_交互式前景提取使用GrabCut算法/
    http://woshicver.com/FifthSection/4_15_图像分割与分水岭算法/

    展开全文
  • 前景提取.txt

    2019-09-08 19:26:28
    利用c++和opencv实现对图像的前景进行提取,不仅可以对图片,也可以对视频进行处理。
  • 下采样和模糊图像的相位一致性分析,用于前景提取
  • 为了尽可能完整地提取前景,针对视频处理中提取的前景区域出现断层导致单个目标出现分裂的问题,提出了一种改进的自适应阈值前景提取方法。该方法是在非模型法产生背景模型的基础上,通过背景差法获取差分图像,利用...
  • 一种基于双目视觉的快速前景提取方法,朱元杰,于雷,在前景提取方法中,几乎所有方法都只采用图片作为唯一的输入信息。为得到较好的前景区域,算法往往需要人工干预或者提出复杂的模
  • 混合高斯模型十四按前景提取,目标追踪, 使用时候只需将你的视频名字进行更改,创建文件夹, 提取的前景图像会才在文件夹中;
  • 分水岭分割和GrabCut前景提取 文章目录1.分水岭算法图像分割2.使用GrabCut算法进行交互式前景提取 准备工作: import cv2 import numpy as np import matplotlib.pyplot as plt def cv_show(name, img): cv2....
  • 交互式前景提取GrabCut

    2020-04-02 17:26:12
    交互式前景提取GrabCut GrabCut算法的具体实施过程 在图片中定义含有(一个或多个)物体的矩形框 矩形框外的区域被自动认为是“确定背景” 对于用户自定义的矩形区域,可用背景中的数据来区别矩形框区域内的前景和...
  • 目录什么是交互式前景提取grabCut函数提取图像前景使用模板提取图像前景 什么是交互式前景提取 经典的前景提取技术主要使用纹理(颜色)信息,如魔术棒工具,或根据边缘(对比度)信息,如智能剪刀等完成。2004年,...
  • 带式输送机是采矿企业现代化...介绍了该监控系统的总体设计,基于前景提取的基本思路,综合使用帧差法和Otsu方法实现了输送机输送带承载物的有效检测,为基于机器视觉实现针对带式输送机异常情况的智能检测奠定了基础。
  • 视频监控中基于GMM的前景提取的实现,李婷,徐华中,实现了视频监控中对运动前景的自适应的提取,为监控系统中下一步的目标识别和跟踪提供了良好的基础。主要采用了混合高斯模型对监

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,514
精华内容 605
关键字:

前景提取