1.基本原理

帧间差分法是一种通过对视频图像序列的连续两帧图像做差分运算获取运动目标轮廓的方法。当监控场景中出现异常目标运动时,相邻两帧图像之间会出现较为明显的差别,两帧相减,求得图像对应位置像素值差的绝对值,判断其是否大于某一阈值,进而分析视频或图像序列的物体运动特性。其数学公式描述如下:

python使用帧差法测速 帧差法算法_ide


D(x,y)为连续两帧图像之间的差分图像,I(t)和I(t-1)分别为t和t-1时刻的图像,T为差分图像二值化时选取的阈值,D(x,y) = 1表示前景,D(x,y) = 0表示背景。

优点:算法实现简单,程序设计复杂度低,运行速度快;动态环境自适应性强,对场景光线变化不敏感。

缺点:“空洞”现象(运动物体内部灰度值相近);“双影”现象(差分图像物体边缘轮廓较粗);不能提取出运动对象的完整区域,仅能提取轮廓;算法效果严重依赖所选取的帧间时间间隔和分割阈值。

1.1 帧间差分自适应

对视频序列中相邻两帧图像进行帧间差分得到运动区域图像,运动区域图形与背景图像进行差分提取出运动目标图像,运动目标图像与阈值比较得到二值化图像。

a)对Ak(i,j) 中每一对相邻两帧图像进行差分处理,获得帧差图像Dk(i,j) :

python使用帧差法测速 帧差法算法_差分_02


b) 将帧差图像Dk(i,j) 与背景图像Bk(i,j)差分提取出运动目标Wk(i,j) :

python使用帧差法测速 帧差法算法_ide_03


c) 将运动目标Wk(i,j)转换为二值图像T(i,j) :

python使用帧差法测速 帧差法算法_帧差法_04


其中:T(i,j) 为二值化输出图像,0、1分别为前景和背景;Th为阈值。

*

1.2 阈值Th的取法

如果阈值Th选择过高,会将运动目标区域严重碎化,如果选择得过低,会引入大量的噪声。因此,提出一个运用当前图像灰度值来确定动态阈值的方法:

a) 求出图像中的最小和最大灰度值, 取其平均值为初始阈值,记为T。

b)根据初始阈值将图像分割成目标和背景两部分,求出两部分的平均灰度值μ1、μ2和两部分的灰度概率a1、a2:

python使用帧差法测速 帧差法算法_python使用帧差法测速_05


c)分割阈值Th为

python使用帧差法测速 帧差法算法_帧差法_06

2 相邻帧间差分法C++、python实现

相邻帧间差分法直接对相邻的两帧图像做差分运算,并取差分运算的绝对值构成移动物体,优点是运算快速,实时性高,缺点是无法应对光照的突变,物体间一般具有空洞。物体的轮廓是“双边”的,并且物体的移动速度越快,双边轮廓现象越粗越明显(这是不是给监控中速度检测提供了一个思路~~),另一个就是物体具有较大的空洞。

#include "core/core.hpp"
#include "highgui/highgui.hpp"
#include "imgproc/imgproc.hpp"
 
using namespace cv;
 
int main(int argc,char *argv[])
{
	VideoCapture videoCap(argv[1]);
	if(!videoCap.isOpened())
	{
		return -1;
	}
	double videoFPS=videoCap.get(CV_CAP_PROP_FPS);  //获取帧率
	double videoPause=1000/videoFPS;
	Mat framePre; //上一帧
	Mat frameNow; //当前帧
	Mat frameDet; //运动物体
	videoCap>>framePre;
	cvtColor(framePre,framePre,CV_RGB2GRAY);	
	while(true)
	{
		videoCap>>frameNow;
		if(frameNow.empty()||waitKey(2500)==27)
		{
			break;
		}
		cvtColor(frameNow,frameNow,CV_RGB2GRAY);
		absdiff(frameNow,framePre,frameDet);
		framePre=frameNow;		
		imshow("Video",frameNow);
		imshow("Detection",frameDet);		
	}
	return 0;
}

Python实现:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
cv.namedWindow('Image')
cap  = cv.VideoCapture('WIN_20180903_16_06_34_Pro.mp4')
formerimg=0
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
plt.grid(True) #添加网格
plt.ion()  #interactive mode on
graylist = list()
i=0
while 1:
    i=i+1
    issuc,img = cap.read()
    img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
    img = img.astype(np.int32)
    finalimg = img-formerimg
    finalimg =finalimg.astype(np.uint8)
    finalimg[finalimg<200]=0
    finalimg[finalimg>200]=255
#            r = img[:,:,0]
    print(len(finalimg[np.where(finalimg==255)]))
    graylist.append(len(finalimg[np.where(finalimg==255)]))
    ax.scatter(i,len(finalimg[np.where(finalimg==255)]),c='r',marker='.')
#            plt.hist(r.flatten(),256)
    intimg = img.astype(np.uint8)
    cv.imshow('Image',intimg)
    formerimg = img
    plt.pause(0.5)
    if cv.waitKey(1) & 0xFF == 'q':
        break
    
cv.destroyAllWindows()
cv.release()
 
plt.plot(range(175),graylist,'-',linewidth=2.0)

3 三帧差法

三帧差法是在相邻帧差法基础上改进的算法,在一定程度上优化了运动物体双边,粗轮廓的现象,相比之下,三帧差法比相邻帧差法更适用于物体移动速度较快的情况,比如道路上车辆的智能监控。相比相邻两帧差法,原始的三帧差法对物体的双边粗轮廓和“鬼影”现象有所改善,比较适合对运动速度较快物体的检测,但是仍然会有空洞出现,并且物体移动速度较慢时容易丢失轮廓。

三帧差法基本实现步骤:

  1. 前两帧图像做灰度差
  2. 当前帧图像与前一帧图像做灰度差
  3. 1和2的结果图像按位做“与”操作
#include "core/core.hpp"
#include "highgui/highgui.hpp"
#include "imgproc/imgproc.hpp"
 
using namespace cv;
 
int main(int argc,char *argv[])
{
	VideoCapture videoCap(argv[1]);
	if(!videoCap.isOpened())
	{
		return -1;
	}
	double videoFPS=videoCap.get(CV_CAP_PROP_FPS);  //获取帧率
	double videoPause=1000/videoFPS;
	Mat framePrePre; //上上一帧
	Mat framePre; //上一帧
	Mat frameNow; //当前帧
	Mat frameDet; //运动物体
	videoCap>>framePrePre;
	videoCap>>framePre;
	cvtColor(framePrePre,framePrePre,CV_RGB2GRAY);	
	cvtColor(framePre,framePre,CV_RGB2GRAY);	
	int save=0;
	while(true)
	{
		videoCap>>frameNow;
		if(frameNow.empty()||waitKey(videoPause)==27)
		{
			break;
		}
		cvtColor(frameNow,frameNow,CV_RGB2GRAY);	
		Mat Det1;
		Mat Det2;
		absdiff(framePrePre,framePre,Det1);  //帧差1
		absdiff(framePre,frameNow,Det2);     //帧差2
		threshold(Det1,Det1,0,255,CV_THRESH_OTSU);  //自适应阈值化
		threshold(Det2,Det2,0,255,CV_THRESH_OTSU);
		Mat element=getStructuringElement(0,Size(3,3));  //膨胀核
		dilate(Det1,Det1,element);    //膨胀
		dilate(Det2,Det2,element);
		bitwise_and(Det1,Det2,frameDet);		
		framePrePre=framePre;		
		framePre=frameNow;		
		imshow("Video",frameNow);
		imshow("Detection",frameDet);
	}
	return 0;
}