OpenCV2帧间差分检测运动目标

对于一个固定的场景(背景),我们感兴趣的是在场景中运动的物体(前景)。前景提取是在智能监控应用中的一个基础步骤。
帧间差分:在运动目标检测中,简单来说,就是背景与当前帧之间的差异。数字图像可以表示成一个矩阵,矩阵中每一个元素叫一个像素点。帧间差分=绝对值(背景-当前帧图像)。
怎么确定前景?
我们取帧间差分足够大的像素点,将这些像素点作为前景像素。

基于帧间差分的运动目标的主要问题:
背景建模的好坏直接影响检测的效果,由于背景是随时间改变的。如果不对背景进行更新,可想而知,检测效果不会好。所以,第一个问题就是,背景如何建模?
另外,反射光(如运动物体在大理石地面的倒影)对运动目标检测也会造成一定影响。那么,怎样处理反射光?
除了这两个问题还有其他许多问题,想了解研究的童鞋可以看看这方面的论文。

准备

在opencv2中,下面的代码实现帧间差分。

cv::absdiff(backImage, //背景
gray, //当前帧
foreground//前景(未加工前)
);

帧间差分后,通过阈值,更加精确的标识出前景物体。

//阈值,前景(黑,0),背景(白,255)
cv::threshold(foreground, //前景(未加工前)
output,//前景(阈值后)
threshold, //阈值
255, cv::THRESH_BINARY_INV);

针对背景建模问题,我们采用动态建模。每一帧通过加权和更新背景,公式如下:
背景=(1-a)背景+a*第t帧图像(去除前景)
参数a时学习率,如a=0.001。当第t帧时,通过将当前的背景和第t帧图像(除掉前景像素点)加权和,获得新的背景,用来检测第t+1帧前景。

//对背景加(通过掩模去除前景像素)
cv::accumulateWeighted(gray, //第t帧图像
 background, //背景
  learningRate,  //学习率
  output //第t帧前景,所有像素为0(黑)的点不参加运算。
  );
  

到此,核心的部分完成。

示例

程序使用了策略模式。策略模式说白了,就是将算法都封装在类中,面向对象的思想,方便扩展和重用。
本程序没有直接写在在一个源文件,而是在以期写好的一些类的基础上进行的。总之,建议用面向对象的方式写程序,掌握策略模式、控制器模式、单件、MVC模式。
VideoProcess类处理每一帧,FrameProcessor抽象类,用来继承,定义自己的帧处理函数。

源.cpp

#include"BGFGSegmentor.h"

int main()
{
	//类对象
	VideoProcess processor;
	BGFGSegmentor sG;
	//设置阈值
	sG.setThreshold(30);
	//处理的视频
	processor.setInput("D:/MatlabWork/classroom.avi");
	//设置帧处理函数
	processor.setFrameProcessor(&sG);
	processor.displayOut("前景");
	processor.displayInput("输入");
	//显示帧时的延迟
	processor.setDelay(1000. / processor.getFrameRate());
	//计算检测耗时
	double time = cv::getTickCount();
	processor.run();
	time = cv::getTickCount() - time;
	std::cout << time / cv::getTickFrequency() <<"秒"<< std::endl;
	cv::waitKey(0);
}

VideoProcess.h

#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\core\core.hpp>
#include<iostream>
class FrameProcessor 
{
public:
	// 帧处理函数(纯虚函数),用来继承
	virtual void process(cv::Mat &input, cv::Mat &output) = 0;
};

class VideoProcess
{
private:
	void(*process)(cv::Mat&, cv::Mat&);
	cv::VideoCapture capture;
	bool callIt;
	std::string Input;
	std::string Output;
	int delay;
	long fnumbers;
	long frameToStop;
	bool stop;
	cv::VideoWriter w;
	std::string outFlie;
	FrameProcessor *frameProcessor;
public:
	VideoProcess() :callIt(true), delay(0), fnumbers(0), stop(false), frameToStop(-1), frameProcessor(0){}
	//设置回调函数
	void setFrameProcessor(void(*P)(cv::Mat&, cv::Mat&))
	{
		frameProcessor = 0;
		process = P;
	}
	void setFrameProcessor(FrameProcessor* frameProcessorPtr)
	{
		process = 0;
		frameProcessor = frameProcessorPtr;
	}
	//得到视频编码类型
	int getCodec(char codec[4]) {

		union
		{
			int value;
			char code[4];
		} returned;

		returned.value = static_cast<int>(capture.get(CV_CAP_PROP_FOURCC));

		codec[0] = returned.code[0];
		codec[1] = returned.code[1];
		codec[2] = returned.code[2];
		codec[3] = returned.code[3];
		return returned.value;
	}
	//设置输出视频文件名
	bool setOutput(const std::string &filename, int codec = 0, double framerate = 0.0, bool isColor = false)
	{
		outFlie = filename;
		if (framerate == 0.0)
		{
			framerate = getFrameRate();
		}
		char c[4];
		if (codec == 0)
		{
			codec = getCodec(c);
		}
		//摄像头
		//return w.open(outFlie, -1, framerate, getFrameSize(), isColor);
		//视频文件
		return w.open(outFlie, codec, framerate, getFrameSize(), isColor);
	}
	//得到帧的尺寸
	cv::Size getFrameSize() {


			// get size of from the capture device
			int w = static_cast<int>(capture.get(CV_CAP_PROP_FRAME_WIDTH));
			int h = static_cast<int>(capture.get(CV_CAP_PROP_FRAME_HEIGHT));
			return cv::Size(w, h);
	}
	//设置输入视频文件
	bool setInput(std::string name)
	{
		fnumbers = 0;
		capture.release();
		return capture.open(name);
	}
	//设置摄像头ID
	bool setInput(int  i)
	{
		fnumbers = 0;
		capture.release();
		return capture.open(i);
	}
	//显示输入帧窗口
	void displayInput(std::string wn)
	{
		Input = wn;
		cv::namedWindow(wn);
	}
	//显示输出帧窗口
	void displayOut(std::string wo)
	{
		Output = wo;
		cv::namedWindow(wo);
	}
	//清空
	void dontDisplay()
	{
		cv::destroyWindow(Input);
		cv::destroyWindow(Output);
		Input.clear();
		Output.clear();
	}
	//判断打开帧状态
	bool isOpened()
	{
		return capture.isOpened();
	}
	//设置停止标记位
	bool isStopped()
	{
		return stop;
	}
	//读取下一帧
	bool readNextFrame(cv::Mat& frame)
	{
		return capture.read(frame);
	}
	//调用回调函数
	void callProcess()
	{
		callIt = true;
	}
	//不调用回调函数
	void dontCallProcess()
	{
		callIt = false;
	}
	//停止
	void stopIt() {

		stop = true;
	}
	//得到帧数
	long getFrameNumber() {
			long f = static_cast<long>(capture.get(CV_CAP_PROP_POS_FRAMES));
			return f;
	}
	//设置延迟
	void setDelay(int d)
	{
		delay = d;
	}
	//帧率
	double getFrameRate()
	{
		double r = capture.get(CV_CAP_PROP_FPS);
		return r;
	}
	//写入视频帧
	void wNextFrame(cv::Mat& frame)
	{
		w.write(frame);
	}
	//run函数
	void run()
	{
		cv::Mat frame;
		cv::Mat output;
		//判断打开
		if (!isOpened())
		{
			return;
		}
		stop = false;
		//循环
		while (!isStopped())
		{
			if (!readNextFrame(frame))
				break;
			if (Input.length() != 0)
			{
				cv::imshow(Input, frame);
			}
			if (callIt)
			{
				if (process)
				{
					process(frame, output);
				}
				else
				{
					frameProcessor->process(frame, output);
				}
				
				fnumbers++;
			}
			else
			{
				output = frame;
			}
			if (outFlie.length() != 0)
			{
				wNextFrame(output);
			}
			if (Output.length() != 0)
			{
				cv::imshow(Output, output);
				if (delay >= 0 && cv::waitKey(delay) >= 0)
				{
					stopIt();
				}
				if (frameToStop >= 0 && getFrameNumber() == frameToStop)
					stopIt();
			}
		}
	}
};

BGFGSegmentor.h

#include"VideoProcess.h"

class BGFGSegmentor :public FrameProcessor
{
	//当前灰度图
	cv::Mat gray;
	//累加的背景
	cv::Mat background;
	//背景图像
	cv::Mat backImage;
	//前景
	cv::Mat foreground;
	//学习率
	double learningRate;
	//阈值
	int threshold;
public:
	BGFGSegmentor() :threshold(10), learningRate(0.01){}
	//设置阈值
	void setThreshold(int T)
	{
		threshold = T;
	}
	//基于帧间差分和动态背景建模的运动目标检测
	void process(cv::Mat& frame, cv::Mat& output)
	{
		cv::cvtColor(frame, gray, CV_BGR2GRAY);
		//初始化背景
		if (background.empty())
		{
			gray.convertTo(background, CV_32F);
		}
		//转换背景为8U格式
		background.convertTo(backImage, CV_8U);
		//计算差值
		cv::absdiff(backImage, gray, foreground);
		//阈值,前景(黑,0),背景(白,255)
		cv::threshold(foreground, output, threshold, 255, cv::THRESH_BINARY_INV);
		//对背景加(通过掩模去除前景像素)
		cv::accumulateWeighted(gray, background, learningRate, output);
	}
};

结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

OpenCV2系列
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值