VIBE(前景检测)

1、VIBE思想:

为每个像素点存储了一个样本集,样本集中采样值就是该像素点过去的像素值和其邻居点的像素值,然后将每一个新的像素值和样本集进行比较来判断是否属于背景点。

2、VIBE模型初始化

通用的检测算法的初始化,需要一定长度的视频序列来完成,需要耗费数秒时间;VIBE只需要一帧图像即可。ViBe初始化就是填充像素的样本集的过程但是由于在一帧图像中不可能包含像素点的时空分布信息,我们利用了相近像素点拥有相近的时空分布特性,具体来讲就是:对于一个像素点,随机的选择它的邻居点的像素值作为它的模型样本值。这种初始化方法优点是对于噪声的反应比较灵敏,计算量小速度快,可以很快的进行运动物体的检测,缺点是容易引入Ghost区域。

3、VIBE模型更新策略

保守策略+前景点计数

保守策略:如果初始检测为前景像素,那么就一直认为是前景像素

前景点计数:对像素点进行统计,如果被标记为前景像素的次数>阈值,则认为是前景点

借鉴大牛人的代码运行、调试:

main.cpp

// This is based on 
// "VIBE: A POWERFUL RANDOM TECHNIQUE TO ESTIMATE THE BACKGROUND IN VIDEO SEQUENCES"
// by Olivier Barnich and Marc Van Droogenbroeck
// Author : zouxy
// Date   : 2013-4-13
// HomePage : http://blog.csdn.net/zouxy09
// Email  : zouxy09@qq.com

#include "ViBe.h"
#include <opencv2/video/video.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>


using namespace cv;
using namespace std;

int main(int argc, char* argv[])
{
    Mat frame, gray, mask;
    VideoCapture capture("C:\TEST\opencv\car.avi");
    if (!capture.isOpened())
    {
        cout << "No camera or video input!
" << endl;
        return -1;
    }

    ViBe_BGS Vibe_Bgs;
    int count = 0;

    while (1)
    {
        count++;
        capture >> frame;
        if (frame.empty())
            break;
        cvtColor(frame, gray, CV_RGB2GRAY);

        if (count == 1)
        {    
            cout << "row=" << frame.rows << "    " << "cols=" << frame.cols << endl;
            Vibe_Bgs.init(gray);
            Vibe_Bgs.processFirstFrame(gray);
            cout << " Training GMM complete!" << endl;
        }
        else
        {
            Vibe_Bgs.testAndUpdate(gray);
            mask = Vibe_Bgs.getMask();
            morphologyEx(mask, mask, MORPH_OPEN, Mat());
            imshow("mask", mask);
        }

        imshow("input", frame);

        if (cvWaitKey(10) == 'q')
            break;
    }

    return 0;
}

ViBe.cpp

#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的邻居点
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++)
    {
        m_samples[i] = Mat::zeros(_image.size(), CV_8UC1);//初始化每个像素点有20个样本像素点的存储值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++)
            {
                // Random pick up NUM_SAMPLES pixel in neighbourhood to construct the model
                int random = rng.uniform(0, 9);

                row = i + c_yoff[random];//修正随机值
                if (row < 0)
                    row = 0;
                if (row >= _image.rows)
                    row = _image.rows - 1;

                col = j + c_xoff[random];//修正随机值
                if (col < 0)
                    col = 0;
                if (col >= _image.cols)
                    col = _image.cols - 1;

                m_samples[k].at<uchar>(i, j) = _image.at<uchar>(row, col);//随机抽取20次该像素点周围的像素点,做为样本像素点
            }
        }
    }
}

/**************** Test a new frame and update model ********************/
void ViBe_BGS::testAndUpdate(const Mat _image)
{
    RNG rng;

    for (int i = 0; i < _image.rows; i++)
    {
        for (int j = 0; j < _image.cols; j++)
        {
            int matches(0), count(0);
            float dist;

            while (matches < MIN_MATCHES && count < NUM_SAMPLES)//两者都满足的话就继续循环,只要有一个不满足就中断循环
            {
                dist = abs(m_samples[count].at<uchar>(i, j) - _image.at<uchar>(i, j));//20个样本点
                if (dist < RADIUS)
                    matches++;
                count++;
            }

            if (matches >= MIN_MATCHES)
            {
                // It is a background pixel
                m_foregroundMatchCount.at<uchar>(i, j) = 0;//background pixel=0;It is black;

                // Set background pixel to 0
                m_mask.at<uchar>(i, j) = 0;

                // 如果一个像素是背景点,那么它有 1 / defaultSubsamplingFactor 的概率去更新自己的模型样本值
                int random = rng.uniform(0, SUBSAMPLE_FACTOR);
                if (random == 0)
                {
                    random = rng.uniform(0, NUM_SAMPLES);
                    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;

                    random = rng.uniform(0, NUM_SAMPLES);
                    m_samples[random].at<uchar>(row, col) = _image.at<uchar>(i, j);
                }
            }
            else
            {
                // It is a foreground pixel
                m_foregroundMatchCount.at<uchar>(i, j)++;

                // Set background pixel to 255
                m_mask.at<uchar>(i, j) = 255;

                //如果某个像素点连续N次被检测为前景,则认为一块静止区域被误判为运动,将其更新为背景点
                if (m_foregroundMatchCount.at<uchar>(i, j) > 50)
                {
                    int random = rng.uniform(0, SUBSAMPLE_FACTOR);
                    if (random == 0)
                    {
                        random = rng.uniform(0, NUM_SAMPLES);
                        m_samples[random].at<uchar>(i, j) = _image.at<uchar>(i, j);
                    }
                }
            }
        }
    }
}

ViBe.h

#pragma once
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

#define NUM_SAMPLES 20        //每个像素点的样本个数
#define MIN_MATCHES 2        //#min指数
#define RADIUS 20        //Sqthere半径
#define SUBSAMPLE_FACTOR 16    //子采样概率


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;
};

实验测试如上图,存在的问题:

1、在input窗口中并没有A,mask窗口中A是黑色车前几帧造成的,这说明A这部分消失的太慢了,就是模型数据更新的慢了点。也可以说是VIBE的最大缺点,容易进入Ghost区。

2、B 处是白色小车的影子造成的,所以VIBE算法没有做影子消除。

3、D处检测的数据有点乱

还需要做很多优化和测试工作!

原文地址:https://www.cnblogs.com/liang2713020/p/6668906.html