基于平均背景法的背景提取。
首先我们得明白,一幅图像中什么属于背景什么属于前景。我们简单的可以这么理解,前景一般是会动的物体,而背景一般是不会动的物体。我们可以以此为依据,从而辨别简单的前景和背景。不会动的物体,我们可以认为在一个很长的时间段内,它的像素值几乎都是保持一个数的。那么我们可以取若干张图片将其对应点的像素大小相加,然后再求均值,我们即可以认为这个是我们所需要的背景。可以用以下公式来表示:
那我们怎么去区分前景呢?
因为前景是运动的物体,我们可以用差分的形式去表现一个前景,也就是用当前图片的像素值减去上一张图片对应的像素值,得到的结果再累加求均值。
这里用的是平均差分但是并等价于标准差,但是差分计算速度更快。
代码实现:
#include<iostream>
#include<opencv.hpp>
using namespace std;
using namespace cv;
//平均背景提取法
//3通道图片 float
//IavgF 累计背景图片的均值
//IdiffF 累计绝对值帧的均值
//IprevF 当前帧的前一帧
Mat IavgF, IdiffF, IprevF, IhiF, IlowF;
Mat tmp, tmp2;//背景图
//单通道 float
vector<Mat> Igry(3);
vector<Mat> Ilow(3);
vector<Mat> Ihi(3);
Mat maskt;//掩模
float Icount;
//为所有变量申请内存,由于定义的都是全局变量,故只需要申请栈上的空间即可
void AllocationImage(const Mat &I) {
Size sz = I.size();
IavgF = Mat::zeros(sz, CV_32FC3);
IdiffF = Mat::zeros(sz, CV_32FC3);
IprevF = Mat::zeros(sz, CV_32FC3);
IhiF = Mat::zeros(sz, CV_32FC3);
IlowF = Mat::zeros(sz, CV_32FC3);
Icount = 0.0000001; //最好浮点数别定义为0.0
tmp = Mat::zeros(sz, CV_32FC3);
tmp2= Mat::zeros(sz, CV_32FC3);
maskt= Mat::zeros(sz, CV_32FC1);
}
//计算累计背景图片和累计绝对值帧(一种比学习图片标准差更快的方式)平均差分
void accmulateBackground(Mat&I) {
static int first = 1;
I.convertTo(tmp, CV_32F);
if (!first) {
IavgF += tmp;//累计背景图片
absdiff(tmp, IprevF, tmp2);//计算帧间差的绝对值
IdiffF += tmp2;//累计绝对值帧
Icount += 1.0;//计算总共加了多少图片。
}
first = 0;
IprevF = tmp;
}
至此我们只是得到了前景和背景的大概描述,但是我们怎么去真正定义前景和背景呢?
我们定义:
当然参数是可以根据实际情况调整的。
//我们把高于平均值的n倍平均绝对差(可以理解为累计差分的平均值,实际计算也是这么回事)理解为前景
void setHighThreshold(float high) {
IhiF = IavgF + (IdiffF*high);
split(IhiF, Ihi);
}
//我们把低于平均值的n倍平均绝对差(可以理解为累计差分的平均值,实际计算也是这么回事)理解为背景
void setLowThreshold(float low) {
IlowF = IavgF - (IdiffF*low);
split(IlowF, Ilow);
}
//为什么可以这么理解呢?我们可以这么理解,作为前景,一般是运动的物体,所以一般来说差分的结果是比较大的。
//计算每个像素的均值和偏移值(平均绝对差)
void createModelsfromStats() {
IavgF *= 1 / Icount; //计算累计背景图片的均值
IdiffF *= 1 / Icount;//累计绝对值帧的均值
//为了使累计绝对值帧的均值不全为0,给每个元素都加1
IdiffF += Scalar(1.0, 1.0, 1.0);
setHighThreshold(7.0);
setLowThreshold(6.0);
}
为此我们得到了前景和背景的阈值,我们用此阈值来获取前景的掩模:
//为此我们进行前景和背景的分割 之前得到的前景与背景作为判别一个图像前景和背景的阈值
void backgroundDiff(Mat & I,Mat & mask) {
I.convertTo(tmp, CV_32F);
split(tmp, Igry);
//通道1
inRange(Igry[0], Ilow[0], Ihi[0], mask);
//2
inRange(Igry[1], Ilow[1], Ihi[1], maskt);
mask = cv::min(mask, maskt);
//3
inRange(Igry[2], Ilow[2], Ihi[2], maskt);
mask = cv::min(mask, maskt);
mask = 255 - mask;
}
获取到每个通道的掩模后,我们选择那个共有的部分。
下面就是准备测试
- 读取30-1000帧的图像作为训练数据
- 获取到前景和背景的阈值
- 获取掩模
- 用掩模去修改调整图像
int main()
{
VideoCapture cap;
cap.open(1);
Mat frame;
cap >> frame;
AllocationImage(frame);//初始化
//我们先利用摄像头记录30-1000帧的图像进行训练
int i = 0;
while (i<500)
{
cap >> frame;
if (!frame.data) {
break;
}
accmulateBackground(frame);
/* imshow("frame", frame);
if (waitKey(1) == 27) {
break;
}*/
i++;
}
destroyAllWindows();
cout << "训练完毕" << endl;
createModelsfromStats();
Mat Mymask;
while (true)
{
cap >> frame;
if (!frame.data) {
break;
}
backgroundDiff(frame, Mymask);//分离前景和背景得到掩模
split(frame, Igry);
Igry[0] = cv::max(Mymask, Igry[0]);
Igry[1] = cv::max(Mymask, Igry[1]);
Igry[2] = cv::max(Mymask, Igry[2]);
merge(Igry, frame);
imshow("frame", frame);
if (waitKey(1) == 27) {
break;
}
}
return 0;
}
完整代码为:
//作者:dwy
//日期:2019/07/14
//用途:测试背景提取:平均背景法和码书背景提取法
#include<iostream>
#include<opencv.hpp>
using namespace std;
using namespace cv;
//平均背景提取法
//3通道图片 float
//IavgF 累计背景图片的均值
//IdiffF 累计绝对值帧的均值
//IprevF 当前帧的前一帧
Mat IavgF, IdiffF, IprevF, IhiF, IlowF;
Mat tmp, tmp2;//背景图
//单通道 float
vector<Mat> Igry(3);
vector<Mat> Ilow(3);
vector<Mat> Ihi(3);
Mat maskt;//掩模
float Icount;
//为所有变量申请内存,由于定义的都是全局变量,故只需要申请栈上的空间即可
void AllocationImage(const Mat &I) {
Size sz = I.size();
IavgF = Mat::zeros(sz, CV_32FC3);
IdiffF = Mat::zeros(sz, CV_32FC3);
IprevF = Mat::zeros(sz, CV_32FC3);
IhiF = Mat::zeros(sz, CV_32FC3);
IlowF = Mat::zeros(sz, CV_32FC3);
Icount = 0.0000001; //最好浮点数别定义为0.0
tmp = Mat::zeros(sz, CV_32FC3);
tmp2= Mat::zeros(sz, CV_32FC3);
maskt= Mat::zeros(sz, CV_32FC1);
}
//计算累计背景图片和累计绝对值帧(一种比学习图片标准差更快的方式)平均差分
void accmulateBackground(Mat&I) {
static int first = 1;
I.convertTo(tmp, CV_32F);
if (!first) {
IavgF += tmp;//累计背景图片
absdiff(tmp, IprevF, tmp2);//计算帧间差的绝对值
IdiffF += tmp2;//累计绝对值帧
Icount += 1.0;//计算总共加了多少图片。
}
first = 0;
IprevF = tmp;
}
//我们把高于平均值的n倍平均绝对差(可以理解为累计差分的平均值,实际计算也是这么回事)理解为前景
void setHighThreshold(float high) {
IhiF = IavgF + (IdiffF*high);
split(IhiF, Ihi);
}
//我们把低于平均值的n倍平均绝对差(可以理解为累计差分的平均值,实际计算也是这么回事)理解为背景
void setLowThreshold(float low) {
IlowF = IavgF - (IdiffF*low);
split(IlowF, Ilow);
}
//为什么可以这么理解呢?我们可以这么理解,作为前景,一般是运动的物体,所以一般来说差分的结果是比较大的。
//计算每个像素的均值和偏移值(平均绝对差)
void createModelsfromStats() {
IavgF *= 1 / Icount; //计算累计背景图片的均值
IdiffF *= 1 / Icount;//累计绝对值帧的均值
//为了使累计绝对值帧的均值不全为0,给每个元素都加1
IdiffF += Scalar(1.0, 1.0, 1.0);
setHighThreshold(7.0);
setLowThreshold(6.0);
}
//为此我们进行前景和背景的分割 之前得到的前景与背景作为判别一个图像前景和背景的阈值
void backgroundDiff(Mat & I,Mat & mask) {
I.convertTo(tmp, CV_32F);
split(tmp, Igry);
//通道1
inRange(Igry[0], Ilow[0], Ihi[0], mask);
//2
inRange(Igry[1], Ilow[1], Ihi[1], maskt);
mask = cv::min(mask, maskt);
//3
inRange(Igry[2], Ilow[2], Ihi[2], maskt);
mask = cv::min(mask, maskt);
mask = 255 - mask;
}
int main()
{
VideoCapture cap;
cap.open(1);
Mat frame;
cap >> frame;
AllocationImage(frame);//初始化
//我们先利用摄像头记录30-1000帧的图像进行训练
int i = 0;
while (i<500)
{
cap >> frame;
if (!frame.data) {
break;
}
accmulateBackground(frame);
/* imshow("frame", frame);
if (waitKey(1) == 27) {
break;
}*/
i++;
}
destroyAllWindows();
cout << "训练完毕" << endl;
createModelsfromStats();
Mat Mymask;
while (true)
{
cap >> frame;
if (!frame.data) {
break;
}
backgroundDiff(frame, Mymask);//分离前景和背景得到掩模
split(frame, Igry);
Igry[0] = cv::max(Mymask, Igry[0]);
Igry[1] = cv::max(Mymask, Igry[1]);
Igry[2] = cv::max(Mymask, Igry[2]);
merge(Igry, frame);
imshow("frame", frame);
if (waitKey(1) == 27) {
break;
}
}
return 0;
}
效果为:
这只是背景提取最基础的,当然只是给大家提供一个思路,想法,不至于看到较难的方法无从下手