该算法采用邻域像素来创建背景模型,通过比对背景模型和当前输入像素值来检测前景。总的来说就是先背景建模,后前景检测的背景差方法。
具体的思想就是为每个像素点存储了一个样本集,样本集中采样值就是该像素点过去的像素值和其邻居点的像素值,然后将每一个新的像素值和样本集进行比较来判断是否属于背景点。
vibe算法可以分为以下三个步骤。
初始化单帧图像中每个像素点的背景模型。假设每一个像素和其邻域像素的像素值在空域上有相似的分布。基于这种假设,每一个像素模型都可以用其邻域中的像素来表示。为了保证背景模型符合统计学规律,邻域的范围要足够大。
对于一个像素点,随机的选择它的邻居点的像素值作为它的模型样本值。M0(x)=v0(y|y∈NG(x)),t=0初始时刻,NG(x)即为邻居点 。
对后续的图像序列进行前景目标分割操作。
背景模型为每个背景点存储一个样本集,然后每个新的像素值和样本集比较判断是否属于背景。像素点(x,y)的背景模型为,像素值为。按照下面判断该像素值是否为前景。
这里上标r是随机选的;T是预先设置好的阈值。当
满足符合背景#N次时,我们认为像素点 为背景,否则为前景。
ViBe算法的更新在时间和空间上都具有随机性。时间上的随机性。在N个背景模型中随机抽取一个,设为图像Pg。当我们得到新的一帧图像Pt时,如果图像Pt中的x位置对应的像素Pt(x)被判断为背景,则Pg需要被更新。这个抽取的过程体现了时间上的随机性。空间上的随机性。在Pg(x)的八邻域中随机抽取一个像素Pg(r),用Pg(r)的来替换掉Pg(x),这体现了模型更新空间上的随机性。
优点:
缺点:
在背景建模中,由于可能采用了运动物体的像素初始化样本集,容易引入Ghost区域(拖影、鬼影区域)(Ghost区域也就是指当一个原本静止的物体开始运动,背景差检测算法可能会将原来该物体所覆盖的区域错误的检测为运动的,这块区域就成为Ghost,当然原来运动的物体变为静止的也会引入Ghost区域,Ghost区域在检测中必须被尽快的消除。)Ghost区域如下图所示;
阴影前景问题,阴影的存在导致检测出来的运动目标形状不准确,影响后续目标分类、跟踪、识别和分析等其他智能视频处理模块。产生阴影前景问题的根源是:光线被运动目标前景遮挡,投射阴影区的颜色比背景的颜色暗,即阴影和背景颜色值的距离相差较大,背景差分后被误检为运动目标前景。
运动目标不完整问题,运动目标通常可分为非刚性物体和刚性物体,人属于非刚性物体,车属于刚性物体,这两种常见检测对象的检测结果都出现了不完整现象。
Vibe.h头文件
#include
#include
#include
using namespace cv;
using namespace std;
//每个像素点的样本个数默认值
#define DEFAULT_NUM_SAMPLES 20
//#min指数默认值
#define DEFAULT_MIN_MATCHES 2
//Sqthere半径默认值
#define DEFAULT_RADIUS 20
//子采样概率默认值
#define DEFAULT_RANDOM_SAMPLE 16
class ViBe
{
public:
ViBe(int num_sam = DEFAULT_NUM_SAMPLES,
int min_match = DEFAULT_MIN_MATCHES,
int r = DEFAULT_RADIUS,
int rand_sam = DEFAULT_RANDOM_SAMPLE);
~ViBe(void);
//背景模型初始化
void init(Mat img);
//处理第一帧图像
void ProcessFirstFrame(Mat img);
//运行ViBe算法,提取前景区域并更新背景模型样本库
void Run(Mat img);
//获取前景模型二值图像
Mat getFGModel();
//删除样本库
void deleteSamples();
//x的邻居点
int c_xoff[9];
//y的邻居点
int c_yoff[9];
private:
//样本库
unsigned char*** samples;
//前景模型二值图像
Mat FGModel;
//每个像素点的样本个数
int num_samples;
//#min指数
int num_min_matches;
//Sqthere半径
int radius;
//子采样概率
int random_sample;
};
#pragma once
Vibe.cpp
#include"Vibe.h"
/*
构造函数ViBe
参数:
int num_sam:每个像素点的样本个数
int min_match:#min 指数
int r: Sqthere 半径
int rand_sam:子采样概率
*/
ViBe::ViBe(int num_sam, int min_match, int r, int rand_sam)
{
num_samples = num_sam;
num_min_matches = min_match;
radius = r;
random_sample = rand_sam;
int c_off[9] = { -1,0,1,-1,1,-1,0,1,0 };
for (int i = 0; i < 9; i++) {
c_xoff[i] = c_yoff[i] = c_off[i];
}
}
/*析构函数:~ViBe
说明:释放样本库内存
*/
ViBe::~ViBe(void)
{
deleteSamples();
}
/*
函数名init
说明:背景模型初始化
为样本库分配空间
参数:Mat img:源图像
返回值:void
*/
void ViBe::init(Mat img)
{
//动态分配三维数组,samples[][][num_samples]存储前景被连续检测的次数
//
samples = new unsigned char**[img.rows];
for (int i = 0; i < img.rows; i++)
{
samples[i] = new uchar *[img.cols];
for (int j = 0; j < img.cols; j++)
{
//数组中,在num_samples之外多增加的一个值,用于统计该像素点连续成为前景的次数
samples[i][j] = new uchar[num_samples + 1];
for (int k = 0; k < num_samples + 1; k++)
{
//创建样本库是,所有样本全部初始化为0
samples[i][j][k] = 0;
}
}
}
FGModel = Mat::zeros(img.size(), CV_8UC1);
}
/*
函数名 ProcessFirstFrame
说明:处理第一帧图像
读取视频序列第一帧,并随机选取像素点邻域内像素填充样本库,初始化背景模型
参数:
Mat img:源图像
返回值:void
*/
void ViBe::ProcessFirstFrame(Mat img)
{
RNG rng;
int row, col;
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
for (int k = 0; k < num_samples; k++)
{
//随机选择num_samples个邻域像素点,构建背景模型
int random;
random = rng.uniform(0, 9); row = i + c_yoff[random];
random = rng.uniform(0, 9); col = j + c_xoff[random];
//防止选取的像素点越界
if (row < 0)
row = 0;
if (row >= img.rows)
row = img.rows - 1;
if (col < 0)
col = 0;
if (col >= img.cols)
col = img.cols - 1;
//为样本库赋值随机值
samples[i][j][k] = img.at<uchar>(row, col);
}
}
}
}
/*
函数名:Run
说明:运行ViBe算法,提取前景区域并更新背景模型样本库
参数:
Mat img 源图像
返回值:void
*/
void ViBe::Run(Mat img)
{
RNG rng;
int k = 0, dist = 0, matches = 0;
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
//前景提取
//说明:计算当前像素值与样本库的匹配情况
//参数:
//int matches:当前像素值与 样本库中值之差小于阈值范围RADIUS的个数
//int count: 遍历样本库的缓存变量
for (k = 0, matches = 0; matches < num_min_matches && k < num_samples; k++)
{
dist = abs(samples[i][j][k] - img.at<uchar>(i, j));
if (dist < radius)
matches++;
}
//说明:当前像素值与样本库中值匹配次数较高,则认为是背景像素点;
//此时更新前景统计次数、更新前景模型、更新该像素模型样本值、更新该像素点邻域像素点
if (matches >= num_min_matches)
{
//已经认为是背景像素,故该像素前景统计次数置0
samples[i][j][num_samples] = 0;
//该像素点的前景模型像素值置0
FGModel.at<uchar>(i, j) = 0;
}
//说明:当前像素值与样本库中值匹配次数较低,则认为是前景像素点
//此时需要更新前景统计次数,判断更新前景模型
else {
//已经认为是前景像素,故该像素的前景统计次数+1
samples[i][j][num_samples]++;
//该像素点的前景模型像素值置255
FGModel.at<uchar>(i, j) = 255;
//如果某个像素点连续50次被检测为前景,则认为一块静止区域被误判为运动,将其更新为背景点
if (samples[i][j][num_samples] > 50)
{
int random = rng.uniform(0, num_samples);
samples[i][j][random] = img.at<uchar>(i, j);
}
}
//更新模型样本库
if (matches >= num_min_matches)
{
//已经认为该像素是背景像素,那么它有1/φ的概率去更新自己的模型样本值
int random = rng.uniform(0, random_sample);
if (random == 0)
{
random = rng.uniform(0, num_samples);
samples[i][j][random] = img.at<uchar>(i, j);
}
//同时也有1/φ的概率去更新它的邻居点的模型样本值
random = rng.uniform(0, random_sample);
if (random == 0)
{
int row, col;
random = rng.uniform(0, 9); row = i + c_yoff[random];
random = rng.uniform(0, 9); col = j + c_xoff[random];
//防止选取的像素点越界
if (row < 0)
row = 0;
if (row >= img.rows)
row = img.rows - 1;
if (col < 0)
col = 0;
if (col >= img.cols)
col = img.cols - 1;
//为样本库赋值随机值
random = rng.uniform(0, num_samples);
samples[row][col][random] = img.at<uchar>(i, j);
}
}
}
}
}
/*
函数名 :getFGModel
说明:获取前景模型二值图像
返回值:Mat
*/
Mat ViBe::getFGModel()
{
return FGModel;
}
/*
函数名:deletesamples
说明:删除样本库
返回值:void
*/
void ViBe::deleteSamples()
{
delete samples;
}
main.cpp
#include
#include
#include
#include
#include
#include "Vibe.h"
int main(int argc, char* argv[])
{
Mat frame, gray, FGModel;
VideoCapture capture;
capture = VideoCapture("E:/material/yaoyongde/test.avi");
if (!capture.isOpened())
{
capture = VideoCapture("E:/material/yaoyongde/test.avi");
if (!capture.isOpened())
{
capture = VideoCapture("E:/material/yaoyongde/test.avi");
if (!capture.isOpened())
{
cout << "ERROR:Didn't find thid video!" << endl;
return 0;
}
}
}
capture.set(CAP_PROP_FRAME_WIDTH, 160);
capture.set(CAP_PROP_FRAME_HEIGHT, 120);
if (!capture.isOpened())
{
cout << "No camera or video input!" << endl;
return -1;
}
//程序运行时间统计变量
double time;
double start;
ViBe vibe;
bool count = true;
while (1)
{
capture >> frame;
if (frame.empty())
continue;
cvtColor(frame, gray, CV_RGB2GRAY);
if (count)
{
vibe.init(gray);
vibe.ProcessFirstFrame(gray);
cout << "Training ViBe Success." << endl;
count = false;
}
else
{
start = static_cast<double>(getTickCount());
vibe.Run(gray);
time = ((double)getTickCount() - start) / getTickFrequency() * 1000;
cout << "Time of Update ViBe BAckground:" << time << "ms" << endl << endl;
FGModel = vibe.getFGModel();
imshow("FGModel", FGModel);
}
imshow("input", frame);
if (waitKey(25) == 27)
break;
}
return 0;
}