当前位置:   article > 正文

运动目标检测

运动目标检测

运动目标检测

简介

&emsp 运动目标检测是指在序列图像中检测出变化区域并将运动目标从背景图像中提取出来。通常情况下,目标分类、跟踪和行为理解等后处理过程仅仅考虑图像中对应于运动目标的像素区域,因此运动目标的正确检测与分割对于后期处理非常重要然而,由于场景的动态变化,如天气、光照、阴影及杂乱背景干扰等的影响,使得运动目标的检测与分割变得相当困难。根据摄像头是否保持静止,运动检测分为静态背景和运运动目标检测是指在序列图像中检测出变化区域并将运动目标从背景图像中提取出来。通常情况下,目标分类、跟踪和行为理解等后处理过程仅仅考虑图像中对应于运动目标的像素区域,因此运动目标的正确检测与分割对于后期处理非常重要然而,由于场景的动态变化,如天气、光照、阴影及杂乱背景干扰等的影响,使得运动目标的检测与分割变得相当困难。根据摄像头是否保持静止,运动检测分为静态背景和运动背景两类。大多数视频监控系统是摄像头固定的,因此静态背景下运动目标检测算法受到广泛关注,常用的方法有帧差法、光流法、背景减除法等。

帧差法

帧差法是最为常用的运动目标检测和分割方法之一,基本原理就是在图像序列相邻两帧或三帧间采用基于像素的时间差分通过闭值化来提取出图像中的运动区域。首先,将相邻帧图像对应像素值相减得到差分图像,然后对差分图像二值化,在环境亮度变化不大的情况下,如果对应像素值变化小于事先确定的阂值时,可以认为此处为背景像素:如果图像区域的像素值变化很大,可以认为这是由于图像中运动物体引起的,将这些区域标记为前景像素,利用标记的像素区域可以确定运动目标在图像中的位置。由于相邻两帧间的时间间隔非常短,用前一帧图像作为当前帧的背景模型具有较好的实时性,其背景不积累,且更新速度快、算法简单、计算量小。算法的不足在于对环境噪声较为敏感,闽值的选择相当关键,选择过低不足以抑制图像中的噪声,过高则忽略了图像中有用的变化。对于比较大的、颜色一致的运动目标,有可能在目标内部产生空洞,无法完整地提取运动目标。它仅仅适应于相机静止的情况。

Bt(i,j)={255,||I(i,j)I(i,j)||<T0,else

例程:

//运动物体检测——帧差法  
#include <opencv2/opencv.hpp> 
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

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

int main() {
    cv::VideoCapture video("F:/test/openCV/forestfireAsms/forestfireAsms/data/forestFireHouse.mp4");
    if (!video.isOpened())
        return -1;
    int frameCount = video.get(CV_CAP_PROP_FRAME_COUNT);//获取帧数
    double FPS = video.get(CV_CAP_PROP_FPS);//获取FPS  
    cv::Mat frame;//存储帧
    cv::Mat temp;//存储前一帧图像  
    cv::Mat result;//存储结果图像  
    for (int i = 0; i < frameCount; i++)
    {
        if (!video.read(frame))
            break;
        cv::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  

        }
        cv::imshow("result", result);
        if (cv::waitKey(1000.0 / FPS) == 27)//按原FPS显示  
        {
            cout << "ESC退出!" << endl;
            break;
        }
        temp = frame.clone();
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

光流法

在非固定摄像头下的视频中,背景也是动态改变的,这种情况下对运动目标的检测就不能采用以上两种方法,而是采用光流法。由于光流包含了目标运动的信息,所以,光流场近似于运动场。通常,视频序列中背景的光流是一致,与运动目标的光流不同,因此,根据光流的不同就可以提取运动目标和背景区域。
如果在t时刻图像点(x,y)的亮度值为I(x,y,t),而光流w=(u,v)在x与y轴的分量uv的表达式如下式(2-4)所示,那么,经dt时间后图像点运动到(x+dx,y+dy),当dt趋向于0时,I值是不变,可得到式(2-5)。

u=dxdt,v=dydt

I(x,y,t)=I(x+dx,y+dy,t+dt)

由泰勒级数展开式,得到光流法的基本约束方程为:
Ixu+Iyv+It=0

为了定解光流( u, v),必须给式(2-6)附加其他约束方程。由于从不同角度引入了不同约束条件,所以,光流的计算方法也是多种多样的,有微分法、区域匹配法等。其中最经典的两种算法为HS(Horn-Schunck)算法与LK(Lucas-Kanade)算法。
光流法的优点是:针对运动的摄像头下的视频,也可以对运动目标与背景的进行提取。但是,在实际应用中,当复杂性与多变性的外界环境不满足光流场的约束条件时,就不能正确的求解出光流场;此外,光流法的算法复杂度高,用于运动检测实时性差。
光流法的主要任务就是计算光流场,即在适当的平滑性约束条件下,根据图像序列的时空梯度估算运动场,通过分析运动场的变化对运动目标和场景进行检测与分割。通常有基于全局光流场和特征点光流场两种方法。最经典的全局光流场计算方法是L-K(Lueas&Kanada)法和H-S(Hom&Schunck)法,得到全局光流场后通过比较运动目标与背景之间的运动差异对运动目标进行光流分割,缺点是计算量大。特征点光流法通过特征匹配求特征点处的流速,具有计算量小、快速灵活的特点,但稀疏的光流场很难精确地提取运动目标的形状。总的来说,光流法不需要预先知道场景的任何信息,就能够检测到运动对象,可处理背景运动的情况,但噪声、多光源、阴影和遮挡等因素会对光流场分布的计算结果造成严重影响;而且光流法计算复杂,很难实现实时处理。

背景减除法

背景减除法是一种有效的运动对象检测算法,基本思想是利用背景的参数模型来近似背景图像的像素值,将当前帧与背景图像进行差分比较实现对运动区域的检测,其中区别较大的像素区域被认为是运动区域,而区别较小的像素区域被认为是背景区域。背景减除法必须要有背景图像,并且背景图像必须是随着光照或外部环境的变化而实时更新的,因此背景减除法的关键是背景建模及其更新。针对如何建立对于不同场景的动态变化均具有自适应性的背景模型,减少动态场景变化对运动分割的影响,研究人员已提出了许多背景建模算法,但总的来讲可以概括为非回归递推和回归递推两类。非回归背景建模算法是动态的利用从某一时刻开始到当前一段时间内存储的新近观测数据作为样本来进行背景建模。非回归背景建模方法有最简单的帧间差分、中值滤波方法、Toyama等利用缓存的样本像素来估计背景模型的线性滤波器、Elg~al等提出的利用一段时间的历史数据来计算背景像素密度的非参数模型等。回归算法在背景估计中无需维持保存背景估计帧的缓冲区,它们是通过回归的方式基于输入的每一帧图像来更新某个时刻的背景模型。这类方法包括广泛应用的线性卡尔曼滤波法、Stauffe:与Grimson提出的混合高斯模型等。它仅仅适应于相机静止的情况。
一般而言,背景减除都包含了以下步骤:
(1)背景初始化
背景初始化是指从视频开始的前N帧图像中“训练”出不包含任何运动目标的背景图像的过程。与背景更新不同的是,背景初始化仅仅完成从视频第一帧到第N帧的背景建立工作。通常,使用一些相对“干净”的视频帧(不包含运动前景)来“训练”出高质量的背景。然而,实际环境中,视频帧往往包含了很多干扰的运动前景。
(2)背景更新
随着时间的推移,往往会发生光照变化、运动的物体停在背景中,而这些变化都会使背景发生变化。所以,背景更新是至关重要的。不同的算法都有着不同的背景更新方法,但这一步骤通常涉及到以下几个问题:
I、更新机制
目前,有全更新、选择更新,以及模糊自适更新三种背景更新方法。全更新是指更新所有的像素,如下式所示:

Bt(i,j)=(1α)Bt(i,j)+αIt(i,j)

其中, α是学习率, Bt It分别表示背景帧和当前视频帧。这种方法的主要缺点是,前一帧的前景将会被更新到背景模型中,这就导致了背景模型更新的不准确。为了解决此问题,一些学者就提出了自适应更新,即针对当前帧的背景和前景区域,采用不同的更新机制,如下所示:
Bt(i,j)={(1α)Bt(i,j)+αIt(i,j),ifIt(i,j)Bt(i,j)(1β)Bt(i,j)+βIt(i,j),else

通常,希望背景像素更多的参与到背景模型中,而前景像素采用较慢的更新机制,所以设置 α值较大, β值较小。特别的,当 β=0的时候,就是去除掉前景元素,只对背景元素进行更新。这样就在一定程度上能够加快背景更新的速率,然而,这种更新机制也存在一定的问题。当前景和背景的提取发生错误时,就可能更新为一个错误的背景模型。模糊自适应更新机制由于考虑到前景和背景提取的不确定性,所以能够较好的解决这一问题,但是缺点是计算量较大。
II、学习率
学习率决定了背景模型变化的快慢,可以是固定的数字,或者动态的数字。
III、更新频率
传统的背景更新是对每一帧都进行背景更新,但背景往往并没有任何显著的变化,所以,有些学者提出了有选择性地进行背景更新的方法。
(3)目标检测。
目标检测就是利用背景模型,将当前帧的像素标注为背景或前景的过程。对于不同的算法,其采用的检测方法也各有不同。
此外,所有背景减除法都涉及特征尺寸选择和特征类型选择的问题。特征尺寸的选择有三种情况:像素、块和集群;而特征类型的选择通常也有三种情况:颜色特征、纹理特征和帧特征等。常用的颜色特征是像素点的灰度值或RGB值,它们是场景中视觉信息的最直观反映,但是两者都极易受到光照变化的影响,所以,在光线变化的环境中,基于灰度值和RGB值的运动目标检测方法都会产生大量的误检。针对颜色特征的不足,研究人员又提出了纹理特征以及帧特征等,并应用到背景减除法上,有效克服了光线变化场景中的问题。显然,选择不同的特征尺寸和特征类型,那么,在特殊情况下(如:光照变化,动态背景等)背景和前景的提取的方式也就不同。
背景减除法优点是:检测的准确性比较高,实时性较好,并且鲁棒性也较好;但是在实际复杂多变的环境中,很难找到理想的背景模型极其更新机制。所以,在近十几年里,众多学者研究出很多背景减除算法。在本文的第三章,将详细介绍各种背景减除算法,并通过实验进行比较分析。
例程:

//运动物体检测——背景减除法  
#include <opencv2/opencv.hpp> 
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

int main()
{

    cv::Mat frame; //frame image
    cv::Mat frImg; //foreground image
    cv::Mat bkImg; //background image

    cv::Mat frameMat; //frame Mat
    cv::Mat frMat;    //foreground image
    cv::Mat bkMat;    //foreground image

    int nFrmNum = 0;

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

    cv::VideoCapture video("F:/test/openCV/forestfireAsms/forestfireAsms/data/forestFireHouse.mp4");
    video = cv::VideoCapture("F:/test/img/data/forestFire/web/FireClips/controlled2.avi");

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

                                   //逐帧读取视频
    while (1)
    {
        if (!video.read(frame))
            break;

        nFrmNum++;

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

            bkImg =cv::Mat(frame.size(),CV_8UC1);
            frImg = cv::Mat(frame.size(), CV_8UC1);
            frameMat = cv::Mat(frame.size(), CV_32FC1);
            bkMat = cv::Mat(frame.size(), CV_32FC1);
            frMat = cv::Mat(frame.size(), CV_32FC1);

            //convert frame into the grayscale image
            cvtColor(frame, bkImg, CV_BGR2GRAY);
            cvtColor(frame, frImg, CV_BGR2GRAY);

            frImg.convertTo(frameMat, CV_32FC1);
            frImg.convertTo(frMat, CV_32FC1);
            frImg.convertTo(frImg, CV_32FC1);
        }
        else
        {
            cvtColor(frame, frImg, CV_BGR2GRAY);
            frImg.convertTo(frameMat, CV_32FC1);
            //先做高斯滤波,以平滑图像
            GaussianBlur(frameMat, frameMat, cv::Size(3,3),3, 3, 0);

            //当前帧跟背景图相减
            absdiff(frameMat, bkMat, frMat);
            //二值化前景图
            threshold(frMat, frImg, 20, 255.0, CV_THRESH_BINARY);

            //进行形态学滤波,去掉噪音 
            cv::Mat kernel_erode = getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
            erode(frImg, frImg, kernel_erode);
            dilate(frImg, frImg, kernel_erode);

            //update the background
            cv::addWeighted(frameMat, 1-0.03, bkMat, 0.03,0, bkMat);
            bkMat.convertTo(bkImg, CV_8UC1);

            cv::imshow("video", frame);
            cv::imshow("background", bkImg);
            cv::imshow("foreground", frImg);
            //如果有按键事件,则跳出循环
            if (waitKey(2) >= 0)
                break;

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

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

程序运行结果为:
这里写图片描述

面临的问题与难点

目前,虽然有大量的运动目标检测算法,但由于实际环境的复杂多变,所以这些算法并不都是十分的健壮。面临的问题与抢占可以归纳总结为以下几个方面:
(1)模型初始化问题:在背景初始化训练时期,由于还没有获得高质量的背景模型,故常常导致运动目标的误检;
(2)伪装现象:一些运动目标可能与背景极其相似,从而导致运动目标无法正确地与背景区分开;
(3)光照变化:分为光线的突变和渐变。背景模型要能够适应白天室外环境中光线的逐渐变化;相应的,背景模型也能够适应突然打开灯光的室内环境。总之,光线的变化将强烈影响背景模型,极有可能导致错误的检测;
(4)前景空洞现象:当运动目标有大量颜色一致的区域时,这些区域的内在变化可能将导致检测不准确,使得前景的一些内部区域被错误判断为背景;
(5)动态背景:最常见的就是树叶的抖动,当然还有水面涟漪、小目标抖动;
(6)突然停滞的运动目标:有些运动物体进入场景后,停在了场景中。显然,这种情况下的运动目标应该被识别为背景;
(7)阴影:能够检测出运动目标的阴影以及背景区域原有的阴影;
(8)噪声干扰:这种情况基本上属于由网络摄像头传输或压缩后的视频图像而引起的数据质量不高;
(9)相机抖动:在一些条件下,风会引起摄像机的抖动;
(10)相机自调节:目前,很多摄像头都具有自动控制的功能,如光照控制、白平衡以及放大缩小等功能。

基于混合高斯模型(GMM)的背景建模

高斯混合模型(Gaussian Mixed Model)指的是多个高斯分布函数的线性组合,理论上GMM可以拟合出任意类型的分布,通常用于解决同一集合下的数据包含多个不同的分布的情况(或者是同一类分布但参数不一样,或者是不同类型的分布,比如正态分布和伯努利分布)。
基于混合高斯模型的背景建模将图像中的每个像素看成是从混合高斯分布样本中采样得到的随机变量,在这些高斯分布中,一些高斯分布表示背景,另一些表示前景(运动物体),根据算法估计出每个像素点属于哪一个高斯模型,进而判断该像素是前景或背景 ;K的选择由计算效率等因素决定,通常情况下,K的取值范围为3~7;设特定像素在时刻t的亮度值为gt,用K个高斯分布表示像素gt的特征,观测概率

p(gt)=k=1Kωkt12πexp((gtμkt)2σkt2)k=1Kωkt=1

下面具体介绍算法步骤。
第一步:模型初始化:确定高斯模型个数 K,初始化模型各个参数
- [均值] 第一个高斯模型的均值取输入视频第一帧对应的像素值,其他设为0
- [方差] 初始方差相等,取值与该视频的动态特性有关
- [权重ω] 在初始化时,一般第一个高斯模型的权重取较大值,其他取较小值
第二步:背景描述:确定描述背景模型的高斯函数;假设所有帧中,背景像素的比例超过阈值T,所有 K个高斯函数按照表达式 ωkt/σkt 排序,比值越大,该高斯函数代表背景的可能性越大,排序后,前B个高斯模型 k=1,2,,B作为背景
B=argminbk=1bωkt>T

第三步:前景判断:对于任意像素,比较是否满足各个高斯模型

lkt={1|gtμk(t1)|λσk(t1)0others

计算该像素满足高斯模型的最小编号,判断该像素是背景或前景。

第四步:模型更新:如果没有找到匹配的高斯模型,将第K个(最后一个)高斯模型用一个新的高斯函数替换,新的高斯函数具有像素点的均值、一个较大的方差和较小的权重

(1){μkt=gtσkt2=2maxkσk(t1)2ωkt=0.5minkωk(t1)

重新归一化各高斯模型的权重,或者采用梯度下降策略,获取第t帧图像,亮度向量为xt,决定哪些高斯函数与之匹配,从中选择最好的,记为l
(2)ωkt={(1η)ωk(t1)klωk(t1)k=l

{ρ=ηN(xt|μl(t1),σl(t1)2)μlt=(1ρ)μl(t1)+ρxtσl(t1)2=(1ρ)σl(t1)2+ρ(xtμl(t1))T(xtμl(t1))

总结如下:
算法:基于混合高斯模型的背景减除
初始化:选择高斯函数数目和学习率η
repeat

  • 获取第t帧图像,亮度向量为xt,决定哪些高斯函数与之匹配,从中选择最好的,记为l,如果找到一个匹配高斯函数l,按式(2)更新所有高斯函数的权重和高斯函数l的参数

    • 如果没有高斯函数匹配像素,丢弃权重最低的高斯函数,用一个按照式(1)构造的新的高斯函数代替
    • 根据高斯函数的权重,确定描述背景模型的高斯函数,判断当前像素是背景还是前景
    • 处理所有像素点,对前景图像使用模糊以及形态学上的膨胀腐蚀组合操作去除小区域,填充大目标中的空洞,获得场景中的运动目标
    • until 所有帧都被处理

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/773057
推荐阅读
相关标签
  

闽ICP备14008679号