OpenCV4 实现背景分离的详细步骤(背景减法模型)

 更新时间:2021年09月16日 14:47:23   作者:RayChiu757374816  
背景分离(BS)是一种通过使用静态相机来生成前景掩码(即包含属于场景中的移动对象像素的二进制图像)的常用技术,本文给大家介绍OpenCV4 实现背景分离的详细步骤,需要的朋友可以参考下

定义:

背景分离,又称背景减法模型。

背景分离(BS)是一种通过使用静态相机来生成前景掩码(即包含属于场景中的移动对象像素的二进制图像)的常用技术。

顾名思义,BS计算前景掩码,在当前帧与背景模型之间执行减法运算,其中包含场景的静态部分,或者更一般而言,考虑到所观察场景的特征,可以将其视为背景的所有内容。

背景建模包括两个主要步骤:

1. 背景初始化;

2. 背景更新。

初步,计算背景的初始模型,而在第二步中,更新模型以适应场景中可能的变化。

OpenCV中三个背景分离的重要函数

BackgroundSubtractorMOG()

这是一个以混合高斯模型为基础的前景/背景分割算法。

它使用 K(K=3 或 5)个高斯分布混合对背景像素进行建模。使用这些颜色(在整个视频中)存在时间的长短作为混合的权重。背景的颜色一般持续的时间最长,而且更加静止。

在 x,y平面上一个像素就是一个像素,没有分布,但是背景建模是基于时间序列的,因此每一个像素点所在的位置在整个时间序列中就会有很多值,从而构成一个分布

使用函数时先用函数:CV2.createBackgroundSubtractorMOG() 创建一个背景对象。这个函数有些可选参数,比如要进行建模场景的时间长度,高斯混合成分的数量,阈值等。将他们全部设置为默认值。然后在整个视频中我们是需要使用backgroundsubtractor.apply() 就可得到前景的掩模了,移动的物体会被标记为白色,背景会被标记为黑色的,前景的掩模就是白色的了。

不过目前这个方法已经被弃用了,OpenCV中也没有了相关函数的API。

BackgroundSubtractorMOG2

这个也是以高斯混合模型为基础的背景/前景分割算法。这个算法的一个特点是它为每 一个像素选择一个合适数目的高斯分布。(上一个方法中我们使用是 K 高斯分布),这样就会对由于亮度等发生变化引起的场景变化产生更好的适应。

和前面一样我们需要创建一个背景对象。但在这里我们我们可以选择是否检测阴影。如果 detectShadows = True(默认值),它就会检测并将影子标记出来,但是这样做会降低处理速度。影子会被标记为灰色。

BackgroundSubtractorMOG2算法的两个改进点:

  1.  -阴影检测
  2. -速度快了一倍

BackgroundSubtractorGMG

此算法结合了静态背景图像估计和每个像素的贝叶斯分割。它使用前面很少的图像(默认为前 120 帧)进行背景建模。使用了概率前景估计算法(使用贝叶斯估计鉴定前景)。这是一种自适应的估计,新观察到的 对象比旧的对象具有更高的权重,从而对光照变化产生适应。一些形态学操作 如开运算闭运算等被用来除去不需要的噪音,在前几帧图像中你会得到一个黑色窗口,对结果进行形态学开运算对与去除噪声很有帮助。

不过同样的,这个方法目前也是不再用了。

BackgroundSubtractorKNN

KNN作为大名鼎鼎的机器学习算法,其用在背景分离应用中也是得心应手,但是在此不对KNN作过多的解释。

C++实现:

#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
#include <iostream>
#include <sstream>
using namespace cv;
using namespace std;
 
const int HISTORY_NUM = 7;// 14;// 历史信息帧数
const int nKNN = 3;// KNN聚类后判断为背景的阈值
const float defaultDist2Threshold = 20.0f;// 灰度聚类阈值
 
struct PixelHistory
{
	unsigned char* gray;// 历史灰度值
	unsigned char* IsBG;// 对应灰度值的前景/背景判断,1代表判断为背景,0代表判断为前景
};
 
 
int main()
{
	PixelHistory* framePixelHistory = NULL;// 记录一帧图像中每个像素点的历史信息
	cv::Mat frame, FGMask, FGMask_KNN;
	int keyboard = 0;
	int rows, cols;
	rows = cols = 0;
	bool InitFlag = false;
	int frameCnt = 0;
	int gray = 0;
	VideoCapture capture("768X576.avi");
	Ptr<BackgroundSubtractorKNN> pBackgroundKnn =
		createBackgroundSubtractorKNN();
	pBackgroundKnn->setHistory(200);
	pBackgroundKnn->setDist2Threshold(600);
	pBackgroundKnn->setShadowThreshold(0.5);
 
	while ((char)keyboard != 'q' && (char)keyboard != 27)
	{
		// 读取当前帧
		if (!capture.read(frame))
			exit(EXIT_FAILURE);
 
		cvtColor(frame, frame, COLOR_BGR2GRAY);
		if (!InitFlag)
		{
			// 初始化一些变量
			rows = frame.rows;
			cols = frame.cols;
			FGMask.create(rows, cols, CV_8UC1);// 输出图像初始化
 
			// framePixelHistory分配空间
			framePixelHistory = (PixelHistory*)malloc(rows * cols * sizeof(PixelHistory));
			for (int i = 0; i < rows * cols; i++)
			{
				framePixelHistory[i].gray = (unsigned char*)malloc(HISTORY_NUM * sizeof(unsigned char));
				framePixelHistory[i].IsBG = (unsigned char*)malloc(HISTORY_NUM * sizeof(unsigned char));
				memset(framePixelHistory[i].gray, 0, HISTORY_NUM * sizeof(unsigned char));
				memset(framePixelHistory[i].IsBG, 0, HISTORY_NUM * sizeof(unsigned char));
			}
 
			InitFlag = true;
		}
		if (InitFlag)
		{
			FGMask.setTo(Scalar(255));
			for (int i = 0; i < rows; i++)
			{
				for (int j = 0; j < cols; j++)
				{
					gray = frame.at<unsigned char>(i, j);
					int fit = 0;
					int fit_bg = 0;
					// 比较确定前景/背景
					for (int n = 0; n < HISTORY_NUM; n++)
					{
						if (fabs(gray - framePixelHistory[i * cols + j].gray[n]) < defaultDist2Threshold)// 灰度差别是否位于设定阈值内
						{
							fit++;
							if (framePixelHistory[i * cols + j].IsBG[n])// 历史信息对应点之前被判断为背景
							{
								fit_bg++;
							}
						}
					}
					if (fit_bg >= nKNN)// 当前点判断为背景
					{
						FGMask.at<unsigned char>(i, j) = 0;
					}
					// 更新历史值
					int index = frameCnt % HISTORY_NUM;
					framePixelHistory[i * cols + j].gray[index] = gray;
					framePixelHistory[i * cols + j].IsBG[index] = fit >= nKNN ? 1 : 0;// 当前点作为背景点存入历史信息
 
				}
			}
		}
 
		pBackgroundKnn->apply(frame, FGMask_KNN);
		imshow("Frame", frame);
		imshow("FGMask", FGMask);
		imshow("FGMask_KNN", FGMask_KNN);
 
		keyboard = waitKey(30);
		frameCnt++;
	}
	capture.release();
 
	return 0;
 
}

python实现:

import cv2
 
cap=cv2.VideoCapture('./768x576.avi')
 
fgbg = cv2.createBackgroundSubtractorKNN()
 
while (1):
    ret, frame = cap.read()
 
    fgmask = fgbg.apply(frame)
 
    cv2.imshow('frame', fgmask)
    k = cv2.waitKey(100) & 0xff
    if k == 27:
        break
 
cap.release()
cv2.destroyAllWindows()

利用图像减法函数实现(python版本):

import cv2
import time
"""
背景减法
"""
cap = cv2.VideoCapture("./768x576.avi")
 
_, first_frame = cap.read()
first_gray = cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY)
first_gray = cv2.GaussianBlur(first_gray, (5, 5), 0)
cv2.imshow("First frame", first_frame)
cv2.imwrite('first_frame.jpg', first_frame)
 
count_frame = 0
start = time.time()
 
while True:
    count_frame += 1
    _, frame = cap.read()
    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray_frame = cv2.GaussianBlur(gray_frame, (5, 5), 0)
 
    difference = cv2.absdiff(first_gray, gray_frame)
    _, difference = cv2.threshold(difference, 25, 255, cv2.THRESH_BINARY)
 
    cv2.imshow("Frame", frame)
    cv2.imshow("difference", difference)
 
    key = cv2.waitKey(30)
    if key == 27:
        break
 
    end = time.time()
    print("time %.2f s" % (end-start))
    print(count_frame)
 
cap.release()
cv2.destroyAllWindows()

到此这篇关于OpenCV4 实现背景分离、背景减法模型的文章就介绍到这了,更多相关OpenCV4 实现背景分离内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解C++17中类模板参数推导的使用

    详解C++17中类模板参数推导的使用

    自C++17起就通过使用类模板参数推导,只要编译器能根据初始值推导出所有模板参数,那么就可以不指明参数,下面我们就来看看C++17中类模板参数推导的具体使用吧
    2024-03-03
  • C语言中读取时间日期的基本方法

    C语言中读取时间日期的基本方法

    这篇文章主要介绍了C语言中读取时间日期的基本方法,分别是time()函数和gmtime()函数的使用,注意返回值的区别,需要的朋友可以参考下
    2015-08-08
  • C语言中获取和改变目录的相关函数总结

    C语言中获取和改变目录的相关函数总结

    这篇文章主要介绍了C语言中获取和改变目录的相关函数总结,包括getcwd()函数和chdir()函数以及chroot()函数的使用方法,需要的朋友可以参考下
    2015-09-09
  • C++实现完整功能的通讯录管理系统详解

    C++实现完整功能的通讯录管理系统详解

    来了来了,通讯录管理系统踏着七彩祥云飞来了,结合前面的结构体知识和分文件编写方法,我总结并码了一个带菜单的通讯录管理系统,在这篇文章中将会提到C的清空屏幕函数,嵌套结构体具体实现,简单且充实,跟着我的思路,可以很清晰的解决这个项目
    2022-05-05
  • 浅谈关于C++memory_order的理解

    浅谈关于C++memory_order的理解

    这篇文章主要介绍了浅谈关于C++memory_order的理解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • C++详解实现Stack方法

    C++详解实现Stack方法

    C++ Stack(堆栈)是一个容器类的改编,为程序员提供了堆栈的全部功能,也就是说实现了一个先进后出(FILO)的数据结构
    2022-06-06
  • C语言热门考点结构体与内存对齐详解

    C语言热门考点结构体与内存对齐详解

    在掌握基本的结构体使用后,我们在面试和大型比赛中常常会遇到一个热门考点:结构体内存对齐,也就是计算结构体大小。接下来请跟着笔者一起来学习这块知识点吧
    2021-10-10
  • C++中对象的常引用总结

    C++中对象的常引用总结

    以下是对C++中对象的常引用进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • C++模板的特化超详细精讲

    C++模板的特化超详细精讲

    最近我学习了C++中的模板相关知识,模板是泛型编程的基础,十分重要。所以特意整理出来一篇文章供我们一起复习和学习
    2022-08-08
  • C C++算法题解LeetCode1408数组中的字符串匹配

    C C++算法题解LeetCode1408数组中的字符串匹配

    这篇文章主要为大家介绍了C C++算法题解LeetCode1408数组中的字符串匹配示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10

最新评论