23局部与分割-背景减除
<1>原理介绍:
在很多基础应用中背景检出都是一个非常重要的步骤。例如顾客统计,使用一个静态摄像头来记录进入和离开房间的人数,或者是交通摄像头,需要提取交通工具的信息等。在所有的这些例子中,首先要将人或车单独提取出来。
技术上来说,我们需要从静止的背景中提取移动的前景。如果你有一张背景(仅有背景不含前景)图像,比如没有顾客的房间,没有交通工具的道路等,那就好办了。我们只需要在新的图像中减去背景就可以得到前景对象了。但是在大多数情况下,我们没有这样的(背景)图像,所以我们需要从我们有的图像中提取背景。如果图像中的交通工具还有影子的话,那这个工作就更难了,因为影子也在移动,仅仅使用减法会把影子也当成前景。真是一件很复杂的事情。
1:BackgroundSubtractorMOG
这是一个以混合高斯模型为基础的前景/背景分割算法。它是 P.KadewTraKuPong和 R.Bowden 在 2001 年提出的。它使用 K(K=3 或 5)个高斯分布混合对背景像素进行建模。使用这些颜色(在整个视频中)存在时间的长短作为混合的权重。背景的颜色一般持续的时间最长,而且更加静止。一个像素怎么会有分布呢?在 x,y 平面上一个像素就是一个像素没有分布,但是我们现在讲的背景建模是基于时间序列的,因此每一个像素点所在的位置在整个时间序列中就会有很多值,从而构成一个分布。
在编写代码时,我们需要使用函数: cv2.createBackgroundSubtractorMOG()创建一个背景对象。这个函数有些可选参数,比如要进行建模场景的时间长度,高斯混合成分的数量,阈值等。将他们全部设置为默认值。然后在整个视频中我们是需要使用 backgroundsubtractor.apply() 就可以得到前景的掩模了。
2:BackgroundSubtractorMOG2
这个也是以高斯混合模型为基础的背景/前景分割算法。它是以 2004 年和 2006 年 Z.Zivkovic 的两篇文章为基础的。这个算法的一个特点是它为每一个像素选择一个合适数目的高斯分布。(上一个方法中我们使用是 K 高斯分布)。这样就会对由于亮度等发生变化引起的场景变化产生更好的适应。
和前面一样我们需要创建一个背景对象。但在这里我们我们可以选择是否检测阴影。如果detectShadows = T rue(默认值),它就会检测并将影子标记出来,但是这样做会降低处理速度。影子会被标记为灰色。
3:BackgroundSubtractorGMG
此算法结合了静态背景图像估计和每个像素的贝叶斯分割。这是 2012年Andrew_B.Godbehere, Akihiro_Matsukawa 和 Ken_Goldberg 在文章中提出的。它使用前面很少的图像(默认为前 120 帧)进行背景建模。使用了概率前景估计算法(使用贝叶斯估计鉴定前景)。这是一种自适应的估计,新观察到的
对象比旧的对象具有更高的权重,从而对光照变化产生适应。一些形态学操作如开运算闭运算等被用来除去不需要的噪音。在前几帧图像中你会得到一个黑色窗口。
对结果进行形态学开运算对与去除噪声很有帮助。
<2>具体代码:
python代码:
import numpy as np
import cv2
#BackgroundSubtractorMOG2
#opencv自带的一个视频
cap = cv2.VideoCapture('bg.avi')
#创建一个3*3的椭圆核
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
#创建BackgroundSubtractorMOG2
fgbg = cv2.createBackgroundSubtractorMOG2()
while(1):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
#形态学开运算去噪点
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
#寻找视频中的轮廓
im, contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
#计算各轮廓的周长
perimeter = cv2.arcLength(c,True)
if perimeter > 188:
#找到一个直矩形(不会旋转)
x,y,w,h = cv2.boundingRect(c)
#画出这个矩形
cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('frame',frame)
cv2.imshow('fgmask', fgmask)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
C++代码:
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/background_segm.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdio.h>
#include <cv.h>
#include "opencv2/opencv.hpp"
#include <vector>
using namespace std;
using namespace cv;
//--------------------------------------【全局变量】--------------------------------------
// 描述:定义全局变量
//----------------------------------------------------------------------------------------------
int g_kernel_shape = MORPH_ELLIPSE;
int g_kernel_size = 3;
//--------------------------------------【help( )函数】--------------------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void help()
{
printf("\n\n\t此程序展示了视频前后背景分离的方法,采用cvUpdateBGStatModel()方法.\n"
"\n\n\t程序首先会“学习背景”,然后进行分割。\n"
"\n\n\t可以用过【Space】空格进行功能切换:是否更新背景。\n"
"\n\n\tlearningRate 学习速率,值为0-1,为0时背景不更新,\n"
"\n\n\t为1时逐帧更新,默认为-1,即算法自动更新\n\n");
}
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main(int argc, const char** argv)
{
help();
VideoCapture cap;
bool update_bg_model = true;
//cap.open(0);
cap.open("D:\\opencv310\\opencv\\sources\\samples\\gpu\\768x576.avi");
if (!cap.isOpened())
{
printf("can not open camera or video file\n");
return -1;
}
vector<vector<Point>> contours;//轮廓
vector<Vec4i> hierarchy;//拐点
double contour_length = 0;
Mat kernel = getStructuringElement(g_kernel_shape,Size(g_kernel_size,g_kernel_size));//创建一个3X3椭圆形核
namedWindow("image", WINDOW_AUTOSIZE);
namedWindow("foreground mask", WINDOW_AUTOSIZE);
//namedWindow("foreground image", WINDOW_AUTOSIZE);
//namedWindow("mean background image", WINDOW_AUTOSIZE);
BackgroundSubtractorMOG2 bg_model;//(100, 3, 0.3, 5);
Mat img, fgmask, fgimg;
for (;;)
{
cap >> img;
if (img.empty())
break;
//cvtColor(_img, img, COLOR_BGR2GRAY);
if (fgimg.empty())
fgimg.create(img.size(), img.type());
//更新模型
bg_model(img, fgmask, update_bg_model ? -1 : 0);
//-----------------------------------【获取移动物体的图像】--------------------------------------
// 描述:获取移动物体的图像
//fgimg = Scalar::all(0);
//img.copyTo(fgimg, fgmask);
//-----------------------------------------------------------------------------------------------
//-----------------------------------【获取背景图像】--------------------------------------------
//Mat bgimg;
//bg_model.getBackgroundImage(bgimg);
//-----------------------------------------------------------------------------------------------
morphologyEx(fgmask,fgmask,MORPH_OPEN,kernel);//开操作,去除小区域亮度噪声
findContours(fgmask, contours, hierarchy, CV_RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
//-----------------------------------【绘制轮廓】------------------------------------------------
for (int i = 0; i < contours.size(); i++){
contour_length=arcLength(contours[i], true);
if (contour_length>188) {
Rect rect = boundingRect(contours[i]);
rectangle(img, rect, Scalar(0,255,0), 1);
}
}
//-----------------------------------------------------------------------------------------------
imshow("image", img);
imshow("foreground mask", fgmask);
//imshow("foreground image", fgimg);
//if (!bgimg.empty())
// imshow("mean background image", bgimg);
char k = (char)waitKey(1);
if (k == 27) break;
if (k == ' ')
{
update_bg_model = !update_bg_model;
if (update_bg_model)
printf("\t>背景更新(Background update)已打开:逐帧更新背景\n");
else
printf("\t>背景更新(Background update)已关闭:背景不更新\n");
}
}
cvDestroyAllWindows();
return 0;
}