帧差法、光流法、背景减除法
运动目标检测是指在序列图像中检测出变化区域并将运动目标从背景图像中提取出来。通常情况下,目标分类、跟踪和行为理解等后处理过程仅仅考虑图像中对应于运动目标的像素区域,因此运动目标的正确检测与分割对于后期处理非常重要然而,由于场景的动态变化,如天气、光照、阴影及杂乱背景干扰等的影响,使得运动目标的检测与分割变得相当困难。根据摄像头是否保持静止,运动检测分为静态背景和运运动目标检测是指在序列图像中检测出变化区域并将运动目标从背景图像中提取出来。通常情况下,目标分类、跟踪和行为理解等后处理过程仅仅考虑图像中对应于运动目标的像素区域,因此运动目标的正确检测与分割对于后期处理非常重要然而,由于场景的动态变化,如天气、光照、阴影及杂乱背景干扰等的影响,使得运动目标的检测与分割变得相当困难。根据摄像头是否保持静止,运动检测分为静态背景和运动背景两类。大多数视频监控系统是摄像头固定的,因此静态背景下运动目标检测算法受到广泛关注,常用的方法有帧差法、光流法、背景减除法等。
(l)帧差法
帧差法是最为常用的运动目标检测和分割方法之一,基本原理就是在图像序列相邻两帧或三帧间采用基于像素的时间差分通过闭值化来提取出图像中的运动区域。首先,将相邻帧图像对应像素值相减得到差分图像,然后对差分图像二值化,在环境亮度变化不大的情况下,如果对应像素值变化小于事先确定的阂值时,可以认为此处为背景像素:如果图像区域的像素值变化很大,可以认为这是由于图像中运动物体引起的,将这些区域标记为前景像素,利用标记的像素区域可以确定运动目标在图像中的位置。由于相邻两帧间的时间间隔非常短,用前一帧图像作为当前帧的背景模型具有较好的实时性,其背景不积累,且更新速度快、算法简单、计算量小。算法的不足在于对环境噪声较为敏感,闽值的选择相当关键,选择过低不足以抑制图像中的噪声,过高则忽略了图像中有用的变化。对于比较大的、颜色一致的运动目标,有可能在目标内部产生空洞,无法完整地提取运动目标。
(2)光流法
光流法的主要任务就是计算光流场,即在适当的平滑性约束条件下,根据图像序列的时空梯度估算运动场,通过分析运动场的变化对运动目标和场景进行检测与分割。通常有基于全局光流场和特征点光流场两种方法。最经典的全局光流场计算方法是L-K(Lueas&Kanada)法和H-S(Hom&Schunck)法,得到全局光流场后通过比较运动目标与背景之间的运动差异对运动目标进行光流分割,缺点是计算量大。特征点光流法通过特征匹配求特征点处的流速,具有计算量小、快速灵活的特点,但稀疏的光流场很难精确地提取运动目标的形状。总的来说,光流法不需要预先知道场景的任何信息,就能够检测到运动对象,可处理背景运动的情况,但噪声、多光源、阴影和遮挡等因素会对光流场分布的计算结果造成严重影响;而且光流法计算复杂,很难实现实时处理。
(3)背景减除法
背景减除法是一种有效的运动对象检测算法,基本思想是利用背景的参数模型来近似背景图像的像素值,将当前帧与背景图像进行差分比较实现对运动区域的检测,其中区别较大的像素区域被认为是运动区域,而区别较小的像素区域被认为是背景区域。背景减除法必须要有背景图像,并且背景图像必须是随着光照或外部环境的变化而实时更新的,因此背景减除法的关键是背景建模及其更新。针对如何建立对于不同场景的动态变化均具有自适应性的背景模型,减少动态场景变化对运动分割的影响,研究人员已提出了许多背景建模算法,但总的来讲可以概括为非回归递推和回归递推两类。非回归背景建模算法是动态的利用从某一时刻开始到当前一段时间内存储的新近观测数据作为样本来进行背景建模。非回归背景建模方法有最简单的帧间差分、中值滤波方法、Toyama等利用缓存的样本像素来估计背景模型的线性滤波器、Elg~al等提出的利用一段时间的历史数据来计算背景像素密度的非参数模型等。回归算法在背景估计中无需维持保存背景估计帧的缓冲区,它们是通过回归的方式基于输入的每一帧图像来更新某个时刻的背景模型。这类方法包括广泛应用的线性卡尔曼滤波法、Stauffe:与Grimson提出的混合高斯模型等

在这里我们实现以下帧差法,即利用当前帧与下一帧之差来检测运动目标

///运动物体检测——帧差法  
#include"opencv2/opencv.hpp"  
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;  
#include <iostream>  
using namespace std;  
//运动物体检测函数声明  
Mat MoveDetect(Mat temp, Mat frame);  

int main()  
{  

    VideoCapture video(0);
    if(!video.isOpened())
    return -1;

   // VideoCapture video(0);//定义VideoCapture类video  
   // if (!video.isOpened())  //对video进行异常检测  
   // {  
    //    cout << "video open error!" << endl;  
    //    return 0;  
   // }  
    while(1)
{
    int frameCount = video.get(CV_CAP_PROP_FRAME_COUNT);//获取帧数  
    double FPS = video.get(CV_CAP_PROP_FPS);//获取FPS  
    Mat frame;//存储帧  
    Mat temp;//存储前一帧图像  
    Mat result;//存储结果图像  
    for (int i = 0; i < frameCount; i++)  
    {  

        video >> frame;//读帧进frame  
        imshow("frame", frame);  
        if (frame.empty())//对帧进行异常检测  
        {  
            cout << "frame is empty!" << endl;  
            break;  
        }  
        if (i == 0)//如果为第一帧(temp还为空)  
        {  
            result = MoveDetect(frame, frame);//调用MoveDetect()进行运动物体检测,返回值存入result  
        }  
        else//若不是第一帧(temp有值了)  
        {  
            result = MoveDetect(temp, frame);//调用MoveDetect()进行运动物体检测,返回值存入result  

        }  
        imshow("result", result);  
        if (waitKey(1000.0 / FPS) == 27)//按原FPS显示  
        {  
            cout << "ESC退出!" << endl;  
            break;  
        }  
        temp = frame.clone();  
    } 
} 
    return 0;  

}  
Mat MoveDetect(Mat temp, Mat frame)  
{  
    Mat result = frame.clone();  
    //1.将background和frame转为灰度图  
    Mat gray1, gray2;  
    cvtColor(temp, gray1, CV_BGR2GRAY);  
    cvtColor(frame, gray2, CV_BGR2GRAY);  
    //2.将background和frame做差  
    Mat diff;  
    absdiff(gray1, gray2, diff);  
    imshow("diff", diff);  
    //3.对差值图diff_thresh进行阈值化处理  
    Mat diff_thresh;  
    threshold(diff, diff_thresh, 50, 255, CV_THRESH_BINARY);  
    imshow("diff_thresh", diff_thresh);  
    //4.腐蚀  
    Mat kernel_erode = getStructuringElement(MORPH_RECT, Size(3, 3));  
    Mat kernel_dilate = getStructuringElement(MORPH_RECT, Size(18, 18));  
    erode(diff_thresh, diff_thresh, kernel_erode);  
    imshow("erode", diff_thresh);  
    //5.膨胀  
    dilate(diff_thresh, diff_thresh, kernel_dilate);  
    imshow("dilate", diff_thresh);  
    //6.查找轮廓并绘制轮廓  
    vector<vector<Point> > contours;  
    findContours(diff_thresh, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);  
    drawContours(result, contours, -1, Scalar(0, 0, 255), 2);//在result上绘制轮廓  
    //7.查找正外接矩形  
    vector<Rect> boundRect(contours.size());  
    for (int i = 0; i < contours.size(); i++)  
    {  
        boundRect[i] = boundingRect(contours[i]);  
        rectangle(result, boundRect[i], Scalar(0, 255, 0), 2);//在result上绘制正外接矩形  
    }  
    return result;//返回result  
}

背景差分实现运动目标检测

//最简单背景差分法
#include <stdio.h>
#include <cv.h>
#include <highgui.h>

int main( int argc, char** argv )
{
  //声明IplImage指针
  IplImage* pFrame = NULL;
  IplImage* pFrImg = NULL;
  IplImage* pBkImg = NULL;

  CvMat* pFrameMat = NULL;
  CvMat* pFrMat = NULL;
  CvMat* pBkMat = NULL;

  CvCapture* pCapture = NULL;

  int nFrmNum = 0;

  //创建窗口
  cvNamedWindow("video", 1);
  cvNamedWindow("background",1);
  cvNamedWindow("foreground",1);
  //使窗口有序排列
  cvMoveWindow("video", 30, 0);
  cvMoveWindow("background", 360, 0);
  cvMoveWindow("foreground", 690, 0);

  pCapture = cvCaptureFromCAM(0);//从摄像头读入

    //逐帧读取视频
     while(pFrame = cvQueryFrame( pCapture ))
    {
         nFrmNum++;

         //如果是第一帧,需要申请内存,并初始化
         if(nFrmNum == 1)
        {

            pBkImg = cvCreateImage(cvSize(pFrame->width, pFrame->height),
                IPL_DEPTH_8U,1);
           pFrImg = cvCreateImage(cvSize(pFrame->width, pFrame->height), 
                IPL_DEPTH_8U,1);
            pBkMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
           pFrMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
           pFrameMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);

           //转化成单通道图像再处理
           cvCvtColor(pFrame, pBkImg, CV_BGR2GRAY);
           cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY);

           cvConvert(pFrImg, pFrameMat);
           cvConvert(pFrImg, pFrMat);
           cvConvert(pFrImg, pBkMat);
      }
         else
      {
           cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY);
           cvConvert(pFrImg, pFrameMat);
           //先做高斯滤波,以平滑图像
           //cvSmooth(pFrameMat, pFrameMat, CV_GAUSSIAN, 3, 0, 0);

           //当前帧跟背景图相减
           cvAbsDiff(pFrameMat, pBkMat, pFrMat);

           //二值化前景图
           cvThreshold(pFrMat, pFrImg, 60, 255.0, CV_THRESH_BINARY);

           //进行形态学滤波,去掉噪音 
          //cvErode(pFrImg, pFrImg, 0, 1);
           //cvDilate(pFrImg, pFrImg, 0, 1);

           //更新背景
           cvRunningAvg(pFrameMat, pBkMat, 0.003, 0);
           //将背景转化为图像格式,用以显示
           cvConvert(pBkMat, pBkImg);

           //显示图像
         //  pFrImg->origin=1;         //  根据网友意见整改
          // pBkImg->origin=1;
           //cvFlip(pBkImg,NULL,0);    // 也可         

           cvShowImage("video", pFrame);
           cvShowImage("background", pBkImg);
           cvShowImage("foreground", pFrImg);

           //如果有按键事件,则跳出循环
           //此等待也为cvShowImage函数提供时间完成显示
           //等待时间可以根据CPU速度调整

          if( cvWaitKey(2) >= 0 )
             break;

      }  // end of if-else
    } // end of while-loop


     //销毁窗口
     cvDestroyWindow("video");
     cvDestroyWindow("background");
     cvDestroyWindow("foreground");

     //释放图像和矩阵
     cvReleaseImage(&pFrImg);
     cvReleaseImage(&pBkImg);

     cvReleaseMat(&pFrameMat);
     cvReleaseMat(&pFrMat);
     cvReleaseMat(&pBkMat);

     cvReleaseCapture(&pCapture);
     return 0;
}