监控视频前景提取算法总结

文章目录

  • 单高斯算法视频前景提取
      • (1)模型初始化
      • (2)更新参数并检测
        • 代码主要函数解释
      • 算法实现代码(opencv4.4.0+VS2019)
  • VIBE算法视频前景提取
      • (1)建立背景模型
      • (2)前景目标检测
      • (3)背景模型更新
      • 算法实现代码(opencv4.4.0+VS2019)
  • 混合高斯模型视频前景提取
        • 代码主要函数解释
      • 算法实现代码(opencv4.4.0+VS2019)
  • VIBE+算法视频前景提取
  • SIFT算法
      • 算法实现代码(opencv4.4.0+VS2019)
  • 基于SIFT算法的视频前景提取
      • 算法实现代码(opencv4.4.0+VS2019)
  • 基于Canny的VIBE+算法提取视频前景
      • 算法实现代码(opencv4.4.0+VS2019)

单高斯算法视频前景提取

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

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

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

(1)模型初始化

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

(2)更新参数并检测

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

F B t ( x , y ) = { 0 , ∣ I ( x , y , t ) − μ ( x , y , t − 1 ) ∣ < λ × σ ( x , y , t − 1 ) 1 , e l s e 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} FBt(x,y)={0,I(x,y,t)μ(x,y,t1)<λ×σ(x,y,t1)1,else

模型的更新采用如下公式:
{ μ ( x , y , t ) = ( 1 − α ) × μ ( x , y , 0 ) ) + α × μ ( x , y , t ) σ 2 ( x , y , t ) = ( 1 − α ) × σ 2 ( x , y , t − 1 ) ) + α × [ 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} μ(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)
其中,参数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   
using namespace cv;
using namespace std;
const int Train = 100;
int main(int argc, char* argv[])
	{
Ptr 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> 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> 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 ) = { v 1 , v 2 , . . . , v n } M(x)=\{v_1,v_2,...,v_n\} M(x)={v1,v2,...,vn}
​ 式中,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 , i f # ( S R ( v ( x ) ) ∩ M ( x ) ) < # m i n 0 , e l s e F(x)=\begin{cases}1,if\#(S_R(v(x))\cap M(x))<\#_{min}\\ 0,else \end{cases} F(x)={1,if#(SR(v(x))M(x))<#min0,else

# ( S R ( v ( x ) ) ∩ V t ( t ) ) = { 1 , i f d i s t ( v t ( x ) , v ( x ) ) < R 0 , e l s e \#(S_R(v(x))\cap V_t(t))=\begin{cases}1,if dist(v_t(x),v(x))#(SR(v(x))Vt(t))={1,ifdist(vt(x),v(x))<R0,else

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

(3)背景模型更新

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

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

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

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

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

//ViBe.h
#pragma once    
#include      
#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     
#include     
#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(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(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(i);
        uchar* pMaskRow = m_mask.ptr(i);
        const uchar* pImgRow = _image.ptr(i);
        for (int j = 0; j < _image.cols; j++)
        {
            int matches(0), count(0);
            float dist;
            uchar* pSampContRow = m_samples[count].ptr(i);
            while (matches < MIN_MATCHES && count < NUM_SAMPLES) //逐个像素判断,当匹配个数大于阀值MIN_MATCHES,或整个样本集遍历完成跳出  
            {
                dist = abs(/*m_samples[count].at(i, j)*/pSampContRow[j] - /*_image.at(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(i);
                if (random == 0)
                {
                    random = rng.uniform(0, NUM_SAMPLES);
                    pSampRandRowi[j] = pImgRow[j];
                    //m_samples[random].at(i, j) = _image.at(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(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(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 
#include     
#include 
#include 
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 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(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(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
#include 
#include 
#include
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 backgroundModel; //像素点的分类判断的
		int distanceThreshold; //背景模型更新概率 
		int updateFactor; //8-领域(3 x 3) 
		int neighborWidth;
		int neighborHeight;
};
#endif

 #include"Vibe.h" 
#include
#include

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()%neighborWidth - neighborWidth/2; rshift = randu()%neighborHeight - neighborHeight/2; */ 
			cshift = rand()%neighborWidth - neighborWidth/2;
			rshift = rand()%neighborHeight - neighborHeight/2;
			for(std::vector::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()%neighborWidth) - neighborWidth/2;
					rshift = abs(randu()%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(r,c) = firstFrame.at(r+rshift,c+cshift); 
			}
		}else {
			for(std::vector::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()%neighborWidth) - neighborWidth/2;
					rshift = abs(randu()%neighborHeight) - neighborHeight/2; 
					if(!(cshift == 0 && rshift == 0))
						break; 
				}(*it).at(r,c) = firstFrame.at(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(r, c); 
			//让 pixel 和背景模板中 backgroundModel 进行比较 
			for(std::vector::iterator it = backgroundModel.begin();
				it != backgroundModel.end();
				it++) { 
                if( abs( int(pixel) - int( (*it).at(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(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()%neighborWidth) - neighborWidth / 2; rshift = abs(randu() % 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(r + rshift, c + cshift) = pixel;
					}
					else { for (;;) 
                    { 
					cshift = abs(randu() % neighborWidth) - neighborWidth / 2; 
					rshift = abs(randu() % neighborHeight) - neighborHeight / 2;
						if (!(cshift == 0 && rshift == 0))
							break;
					}int updateIndex = rng.uniform(0, this->numberSamples);
			backgroundModel[updateIndex].at(r + rshift, c + cshift) = pixel;
					}
					}segmentation.at(r, c) = 0;
					break;
				}
			}
			}if (count < this->minMatch)
				segmentation.at(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::iterator iter = backgroundModel.begin(); 
					iter != backgroundModel.end();iter++)
                { 
					for(;;) 
                    { 
						cshift = abs(randu()%neighborWidth) - neighborWidth/2;
						rshift = abs(randu()%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(r, c) = fristFrame.at(r + rshift, c + cshift);
				}
			}
            /*初始化背景模型:结束*/ else { /*******初始化背景模型:开始******/ 
				for (vector::iterator iter = backgroundModel.begin(); 
					iter != backgroundModel.end(); iter++)
                { 
					for (;;)
                    { 
					cshift = abs(randu() % neighborWidth) - neighborWidth / 2;
					rshift = abs(randu() % neighborHeight) - neighborHeight / 2;
						if (!(cshift == 0 && rshift == 0)) break;
					}
			(*iter).at(r, c) = fristFrame.at(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(r,c); 
			for( vector::iterator iter = backgroundModel.begin() ;
				iter != backgroundModel.end(); 
				iter++) 
            {
				//编号 1-1-1-1 
				if( distanceL1(pixel,(*iter).at(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(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() % neighborWidth) - neighborWidth / 2;
					rshift = abs(randu() % 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(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(r + rshift, c + cshift) = pixel;
						}/****************************************/
				                }/**********结束更新模型************ */ //第二步:分类 classify 58
						segmentation.at(r, c) = 0;
						break;
					}
				}
			}//编号 1-1-1-1
			if(count < this->minMatch)//classify 
				segmentation.at(r,c) = 255;
		}//编号 1-1-1 
	}//编号 1-1
}//*编号 1

主函数

#include
#include 
#include 
#include "Vibe.h" 
#include
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 
#include 

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 sift = cv::xfeatures2d::SIFT::create();
	//ORB
	//cv::Ptr orb = cv::ORB::create();
	//SURF
	cv::Ptr sift = cv::SIFT::create();

	//特征点
	std::vector 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 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 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 
#include 
#include 
#include 
#include 
#include 
#include
#include
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 paths1;
vector 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(getTickCount());//开始计时 
	//灰度图转换 
	/*Mat image1, image2;
	cvtColor(image01, image1, CV_RGB2GRAY);
	cvtColor(image02, image2, CV_RGB2GRAY);*/
	//提取特征点 
	//cv::Ptr siftDetector = cv::SIFT::create();
	cv::Ptr siftDetector = cv::SIFT::create();
	//特征点
	std::vector 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 matchePoints;
	cv::Ptr matcher = cv::DescriptorMatcher::create("FlannBased");
	matcher->match(imageDesc1, imageDesc2, matchePoints,Mat());
	sort(matchePoints.begin(), matchePoints.end()); //特征点排序 
	//获取排在前 N 个的最优匹配特征点 
	vector 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"<single_frame; 
while (true) 
//while (num

基于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算法原理解析

你可能感兴趣的:(图像处理,计算机视觉,opencv,c++)