OpenCV实战——提取视频中的前景对象

news2025/1/11 14:59:53

OpenCV实战——提取视频中的前景对象

    • 0. 前言
    • 1. 提取视频中的前景对象
    • 2. 混合高斯方法
    • 3. 完整代码
    • 相关链接

0. 前言

当固定摄像机观察场景时,背景基本保持不变。在这种情况下,我们真正感兴趣的目标是场景中的移动物体。为了提取这些前景物体,我们需要建立一个背景模型,然后将背景模型与当前帧进行比较,检测前景物体,前景提取是智能监控应用中的基本步骤。

1. 提取视频中的前景对象

如果我们拥有场景的背景图像(即不包含前景对象的帧)可供使用,那么通过简单的图像差异提取当前帧的前景:

cv::absdiff(backgroundImage,currentImage,foreground);

将差异足够高的像素视为前景像素。但是,大多数情况下,背景图像并不容易获得,实际上,很难保证给定图像中不存在前景对象。此外,背景场景通常会随着时间而变化,例如光照条件发生了变化或者背景中添加或移除了对象。
因此,有必要动态构建背景场景的模型,可以通过观察场景一段时间完成。我们假设,大多数情况下,背景的每个像素位置都是可见的,那么简单地计算所有观察值的平均值可能是一个很好的策略。然而,由于多种原因,这并不可行。首先,这将需要在计算背景之前存储大量图像;其次,当我们累积图像来计算平均图像时,无法完成前景提取;同时,无法确定何时以及应该累积多少图像来计算可用的背景模型;此外,前景对象图像会对平均背景的计算产生影响。
更好的策略是构建动态背景模型,这可以通过计算移动平均 (moving average) 来实现。这是一种计算最新接收值的时间信号平均值的方法,如果 p t p_t pt 是给定时间 t t t 的像素值, μ t − 1 μ_{t-1} μt1 是当前平均值,则使用以下公式更新该平均值:
u t = ( 1 − α ) μ t − 1 + α p t u_t=(1-\alpha)\mu_{t-1}+\alpha p_t ut=(1α)μt1+αpt
α α α 表示学习率,它定义了当前值对当前估计平均值的影响。该值越大,运行平均值适应观测值变化的速度就越快。要构建背景模型,只需计算传入帧的每个像素的运行平均值。判断像素是否为前景像素,取决于当前图像和背景模型之间的差异。

(1) 构建类 BGFGSegmentor,使用移动平均值学习背景模型,并通过减法提取前景对象。所需的属性如下:

class BGFGSegmentor : public FrameProcessor {
    cv::Mat gray;           // 灰度图像
    cv::Mat background;     // 累积背景
    cv::Mat backImage;      // 当前背景图像
    cv::Mat foreground;     // 前景图像
    double learningRate;    // 学习率
    int threshold;          // 阈值

(2) 当前帧与背景模型进行比较,然后更新模型:

    public:
        BGFGSegmentor() : threshold(10), learningRate(0.01) {}
        // 设置阈值
        void setThreshold(int t) {
            threshold= t;
        }
        // 设置学习率
        void setLearningRate(double r) {
            learningRate= r;
        }
        // processing method
        void process(cv:: Mat &frame, cv:: Mat &output) {
            // 转换为灰度图像
            cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); 
            // 初始化背景图像
            if (background.empty())
                gray.convertTo(background, CV_32F);
            background.convertTo(backImage,CV_8U);
            // 计算当前图像与背景图像间的差异
            cv::absdiff(backImage,gray,foreground);
            // 对前景图像应用阈值
            cv::threshold(foreground,output,threshold,255,cv::THRESH_BINARY_INV);
            // 累积背景
            cv::accumulateWeighted(gray, background, 
                                // alpha*gray + (1-alpha)*background
                                learningRate,       // alpha 
                                output);            // mask
        }

(3) 使用视频处理框架,构建前景提取程序:

int main () {
    // 创建视频处理实例
    VideoProcessor processor;
    BGFGSegmentor segmentor;
    segmentor.setThreshold(25);
    // 打开视频文件
    processor.setInput("example.avi");
    processor.setFrameProcessor(&segmentor);
    // 显示视频
    processor.displayOutput("Extracted Foreground");
    processor.setDelay(1000./processor.getFrameRate());
    processor.run();
    cv::waitKey();
}

显示二值前景图像如下:

二值前景图像

通过 cv::accumulateWeighted 函数可以计算图像的运移动平均值,该函数将移动平均值公式应用于图像的每个像素。生成的图像必须是浮点图像,因此我们必须在将背景模型与当前帧进行比较之前将其转换为背景图。可以通过使用简单的阈值绝对差(在 cv::absdiff 后使用 cv::threshold 计算)提取前景图像。随后使用前景图像作为 cv::accumulateWeighted 的掩码,以避免更新前景像素,因为前景图像在前景像素处被定义为 false (即 0),因此前景对象在结果图像中显示为黑色像素。
最后,为简单起见,我们程序构建的背景模型是基于提取的灰度图像帧,计算彩色背景需要在色彩空间中计算移动平均值,主要困难在于确定合适的阈值,以得到更加优秀结果。
上述提取场景中前景物体的方法对于背景相对稳定的简单场景效果很好。然而,在许多情况下,背景场景可能会在某些区域发生波动,从而导致频繁的假前景检测,例如,移动的背景物体(例如风中摇晃的树叶)或炫光效应(例如在水面上反射的阳光)。阴影也会带来一系列问题,因为这些阴影通常被检测为移动对象的一部分。为了解决这些问题,需要引入了更复杂的背景建模方法,例如混合高斯方法。

2. 混合高斯方法

混合高斯方法是在移动平均值的基础上进行改进的算法。首先,该方法为每个像素维护多个移动平均值模型。这样,如果背景像素在两个值之间波动,那么就会存储两个移动平均值。只有当一个新的像素值不属于任何常观察到的模型时,它才会被声明为前景。可以通过使用参数确定模型数量,常用模型数量为 5
其次,不仅要为每个模型维护移动平均值,还要维护维护方差:
σ t 2 = ( 1 − α ) σ t − 1 2 + α ( p t − μ t ) 2 \sigma_t^2=(1-\alpha)\sigma_{t-1}^2+\alpha(p_t-\mu_t)^2 σt2=(1α)σt12+α(ptμt)2
使用计算出的平均值和方差构建高斯模型,可以估计给定像素值属于背景的概率。据此,可以更容易的确定合适的阈值,因为此时阈值表示概率而不是绝对差。因此,在背景值波动较大的区域,需要更大的差异来确定前景对象。
当给定的高斯模型没有被足够频繁地匹配时,则认为它并不是背景模型的一部分。相反,当某个像素值在当前维护的背景模型之外(即前景像素)时,就会创建一个新的高斯模型,如果新模型成为最频繁模型,那么它就会与背景相关联。
该算法显然比简单的背景/前景分割器实现起来更复杂。但在 OpenCV 中可以使用 cv::BackgroundSubtractorMOG 实现,它被定义为通用类 cv::BackgroundSubtractor 的子类:

int main () {
    // 打开视频
    cv::VideoCapture capture("example.avi");
    if (!capture.isOpened()) return 0;
    // 当前视频帧
    cv::Mat frame;
    // 前景二值图像
    cv::Mat foreground;
    // 背景图像
    cv::Mat background;
    cv::namedWindow("Extracted Foreground");
    cv::Ptr<cv::BackgroundSubtractor> ptrMOG = cv::bgsegm::createBackgroundSubtractorMOG();
    bool stop(false);
    while (!stop) {
        if (!capture.read(frame)) break;
        // 升级背景并返回前景
        ptrMOG->apply(frame, foreground, 0.01);
        cv::threshold(foreground, foreground, 128, 255, cv::THRESH_BINARY_INV);
        cv::imshow("Extracted Foreground", foreground);
        if (cv::waitKey(10) >= 0) stop = true;
    }
    cv::waitKey();
}

只需创建类实例并调用,算法将同时更新背景并返回前景图像。需要注意的是,此处的背景模型是根据颜色计算的。在 OpenCV 中,还实现了另一种方法,通过检查观察到的像素变化是否仅仅是由局部亮度变化引起的(如果是,则可能是由于阴影)或是否还包括色度变化来识别阴影,可以使用类 cv::BackgroundSubtractorMOG2 调用该算法。该算法可以动态确定要使用的每个像素的适当高斯模型的数量。可以在多个视频上尝试使用以上方法,以观察不同算法的性能。

3. 完整代码

头文件 (videoprocessor.h) 完整代码参考视频序列处理一节,头文件 (BGFGSegmentor.h) 完整代码如下所示:

#if !defined BGFGSeg
#define BGFGSeg

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include "videoprocessor.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 setLearningRate(double r) {
            learningRate= r;
        }
        // processing method
        void process(cv:: Mat &frame, cv:: Mat &output) {
            // 转换为灰度图像
            cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); 
            // 初始化背景图像
            if (background.empty())
                gray.convertTo(background, CV_32F);
            background.convertTo(backImage,CV_8U);
            // 计算当前图像与背景图像间的差异
            cv::absdiff(backImage,gray,foreground);
            // 对前景图像应用阈值
            cv::threshold(foreground,output,threshold,255,cv::THRESH_BINARY_INV);
            // 累积背景
            cv::accumulateWeighted(gray, background, 
                                // alpha*gray + (1-alpha)*background
                                learningRate,       // alpha 
                                output);            // mask
        }
};

#endif

主函数文件 (foreground.cpp) 完整代码如下所示:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/bgsegm.hpp>

#include "videoprocessor.h"
#include "BGFGSegmentor.h"

int main () {
    // 打开视频
    cv::VideoCapture capture("r3.mp4");
    if (!capture.isOpened()) return 0;
    // 当前视频帧
    cv::Mat frame;
    // 前景二值图像
    cv::Mat foreground;
    // 背景图像
    cv::Mat background;
    cv::namedWindow("Extracted Foreground");
    cv::Ptr<cv::BackgroundSubtractor> ptrMOG = cv::bgsegm::createBackgroundSubtractorMOG();
    bool stop(false);
    while (!stop) {
        if (!capture.read(frame)) break;
        // 升级背景并返回前景
        ptrMOG->apply(frame, foreground, 0.01);
        cv::threshold(foreground, foreground, 128, 255, cv::THRESH_BINARY_INV);
        cv::imshow("Extracted Foreground", foreground);
        if (cv::waitKey(10) >= 0) stop = true;
    }
    cv::waitKey();
    // 创建视频处理实例#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/bgsegm.hpp>

#include "videoprocessor.h"
#include "BGFGSegmentor.h"

int main () {
    // 打开视频
    cv::VideoCapture capture("r3.mp4");
    if (!capture.isOpened()) return 0;
    // 当前视频帧
    cv::Mat frame;
    // 前景二值图像
    cv::Mat foreground;
    // 背景图像
    cv::Mat background;
    cv::namedWindow("Extracted Foreground");
    cv::Ptr<cv::BackgroundSubtractor> ptrMOG = cv::bgsegm::createBackgroundSubtractorMOG();
    bool stop(false);
    while (!stop) {
        if (!capture.read(frame)) break;
        // 升级背景并返回前景
        ptrMOG->apply(frame, foreground, 0.01);
        cv::threshold(foreground, foreground, 128, 255, cv::THRESH_BINARY_INV);
        cv::imshow("Extracted Foreground", foreground);
        if (cv::waitKey(10) >= 0) stop = true;
    }
    cv::waitKey();
    // 创建视频处理实例
    VideoProcessor processor;
    BGFGSegmentor segmentor;
    segmentor.setThreshold(25);
    // 打开视频文件
    processor.setInput("example.avi");
    processor.setFrameProcessor(&segmentor);
    // 显示视频
    processor.displayOutput("Extracted Foreground");
    processor.setDelay(1000./processor.getFrameRate());
    processor.run();
    cv::waitKey();
}
    VideoProcessor processor;
    BGFGSegmentor segmentor;
    segmentor.setThreshold(25);
    // 打开视频文件
    processor.setInput("example.avi");
    processor.setFrameProcessor(&segmentor);
    // 显示视频
    processor.displayOutput("Extracted Foreground");
    processor.setDelay(1000./processor.getFrameRate());
    processor.run();
    cv::waitKey();
}

相关链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用
OpenCV实战(23)——相机标定
OpenCV实战(24)——相机姿态估计
OpenCV实战(25)——3D场景重建
OpenCV实战(26)——视频序列处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/629767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关系数据模型

1.键的概念 超键&#xff1a;在一个关系中&#xff0c;能唯一标识元组的属性或属性集&#xff08;可能存在多余的属性&#xff09; 侯选建&#xff1a;如果一个属性集能唯一标识元组&#xff08;超键&#xff09;&#xff0c;且又不含有多余的属性&#xff0c;那么这个属性集…

C#,码海拾贝(40)——求解“线性最小二乘问题”的“豪斯荷尔德Householder变换法”之C#源代码

using System; namespace Zhou.CSharp.Algorithm { /// <summary> /// 求解线性方程组的类 LEquations /// 原作 周长发 /// 改编 深度混淆 /// </summary> public static partial class LEquations { /// <summary> /…

6月9日复盘总结|8H30min|7:30-7:50

7:20-8:10 乐词*93 【50min】 8:20-9:20 背书 【1h】 9:20-10:20 词汇笔记 【1h】 10:20-11:00 词汇笔记 【50min】 11:00-12:10 去上马吃饭啦 12:20-13:00 不背单词 【40min】 13:00-13:30 收作业 13:30-14:30 午休♨️ 14:40-15:59 不背单词 *1…

node.js的http模块

http模块 http协议:超文本传输协议&#xff08;互联网应用最广泛的协议之一&#xff09; http请求方法&#xff1a; get获取数据 post新增数据 put/patch更新数据 delete删除数据 head/options/connect/trace URL统一资源定位符&#xff0c;其本身也是一个字符串。 客户端与…

chatgpt赋能python:Python运算简介:从基础运算到高级应用

Python运算简介&#xff1a;从基础运算到高级应用 Python是一种优秀的编程语言&#xff0c;它能够进行各种运算&#xff0c;从简单的加减乘除到高级的科学计算和模拟。在这篇文章中&#xff0c;我们将介绍Python的各种运算方式&#xff0c;并向您展示如何使用它们来进行一些最…

chatgpt赋能python:Python怎么免费用的?

Python 怎么免费用的&#xff1f; Python 是一种高级编程语言&#xff0c;自带简洁优美的语法和强大的开发库。因此&#xff0c;它成为了各种应用程序、网站和服务的主要编程语言之一。如果你对编程语言有些了解&#xff0c;那么你应该知道 Python 很适合开发各类工具、脚本和…

SpringBoot整合MongDB

文章目录 1. MongoDB概述2. MongoDB安装3. MongoDB快速入门3.1 数据库以及表的操作3.2 新增数据3.3 更新数据3.4 删除数据3.5 查询数据3.6 索引3.7 执行计划 4、SpringBoot整合MongoDB4.1 环境搭建4.2 新增文档4.3 查询文档4.4 更新文档4.5 删除文档 人生哪能多如意&#xff0c…

黑马Redis视频教程高级篇(安装Canal)

目录 1、开启MySQL主从 1.1、开启binlog 1.2、设置用户权限 2、安装Canal 2.1、创建网络 2.3、安装Canal 1、开启MySQL主从 Canal是基于MySQL的主从同步功能&#xff0c;因此必须先开启MySQL的主从功能才可以。 这里以之前用Docker运行的mysql为例&#xff1a; 1.1、开…

chatgpt赋能python:如何将Python切换成中文界面

如何将Python切换成中文界面 Python 是一门非常受欢迎的编程语言&#xff0c;有着强大的编程能力和广泛的应用范围。一些朋友可能需要在中文环境下使用 Python&#xff0c;但是默认的 Python 界面是英文的&#xff0c;这给一些初学者带来了不便。本文将介绍如何切换 Python 的…

【操作系统】浅谈 Linux 中的中断机制

【操作系统】浅谈 Linux 中的中断机制 参考资料&#xff1a; [2015 SP] 北京大学 Principles of Operating System 操作系统原理 by 陈向群&#xff08;p7-p10&#xff09; 认认真真的聊聊中断 什么是软中断&#xff1f; 认认真真的聊聊"软"中断 操作系统-x86中断机…

安装nuxt3时一直报错 Error: Failed to download template from registry

一、这个错误提示表明&#xff0c;从GitHub仓库下载模板的请求失败&#xff0c;原因是无法解析raw.githubusercontent.com的地址。这可能是由于网络连接问题或DNS解析问题等原因引起的。 以下是一些可能解决此问题的步骤&#xff1a; 检查网络连接&#xff1a;确保您的网络连接…

JavaScript中的valueOf和toString方法

文章目录 I. 概述A. 引言B. 目的和意义 II. valueOf方法A. 定义和用法B. 默认行为C. 重写valueOf方法 III. toString方法A. 定义和用法B. 默认行为C. 重写toString方法 IV. valueOf VS toStringA. 区别和联系B. 使用场景比较 I. 概述 A. 引言 JavaScript中的valueOf和toStrin…

Docker和Kubernetes部署Spring Boot项目:如何灵活修改配置文件?

大家好&#xff0c;我是G探险者&#xff0c;今天我们来聊一下通过容器化部署的springboot项目&#xff0c;如何灵活的修改配置文件。 有同事问我&#xff0c;我们的springboot项目是容器化部署&#xff0c;使用的是K8S容器编排平台&#xff0c;部署上去后&#xff0c;修改项目…

Linux服务器配置SSH免密码登录后,登录仍提示输入密码(一次真实的问题排查解决记录)

我们知道两台Linux服务器机器之间如果使用ssh命令登录或scp/rsync命令传输文件每一次都需要输入用户名相对应的密码&#xff0c;如果要免密码&#xff0c;则需要对两台Linux服务器机器之间进行SSH互信。 一.SSH介绍 1.SSH互信原理 虽然这是废话&#xff0c;也希望大家了解一…

Rust每日一练(Leetday0026) 最小覆盖子串、组合、子集

目录 76. 最小覆盖子串 Minimum Window Substring &#x1f31f;&#x1f31f;&#x1f31f; 77. 组合 Combinations &#x1f31f;&#x1f31f; 78. 子集 Subsets &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Gola…

HTML table表格详解

一、表格属性 表格属性 用法 border 代表表格边框厚度 width height 表格宽高 align table tr td 设置水平方向对齐方式 默认值left center right cellspacing 单元格到单元格距离 cellpadding 单元格文字到单元格边框距离 bgcolor 表格背景颜色 table tr td 都可以…

Win11 RTX 4090显卡深度学习环境配置(Nvidia显卡驱动、CUDA11.8.0)

Win11 RTX 4090显卡深度学习环境配置&#xff08;Nvidia显卡驱动、CUDA11.8.0&#xff09; 1. 简介2. 安装Anaconda3. 安装Pycharm4. 安装CUDA11.8.04.1 安装4.2 测试4.3 CUDA卸载 5. PyTorch安装5.1 PyTorch安装5.2 测试5.2.1 测试torch&#xff1a;5.2.2 测试CUDA&#xff1a…

chatgpt赋能python:切割字符串的Python技巧及实现方法

切割字符串的Python技巧及实现方法 在Python中操作字符串是比较常见的技巧&#xff0c;而对于需要将字符串切割成不同的元素&#xff0c;Python也提供了相应的方法。 split方法 split方法是Python字符串类中最常用的方法之一&#xff0c;它可以根据给定的分隔符对字符串进行…

学习HCIP的day.11

目录 十一、BGP的属性 1、权重属性 2、本地优先级 3、as-path 4、起源属性 5、MED --多出口的鉴别属性 十二、BGP选路规则 十三、BGP的社团属性 十四、BGP的在MA网络中的下一跳问题 五、BGP的认证 十一、BGP的属性 BGP协议在选路时&#xff0c;先对比属性&#xf…

在Spring Boot项目中连接SQL Server的几种方式

在Spring Boot项目中连接SQL Server 一. 使用Microsoft官方的SQL Server JDBC驱动连接&#xff1a;1. 依赖配置&#xff1a;2. YAML配置&#xff1a; 二. 使用第三方的jTDS驱动连接&#xff1a;1. 依赖配置&#xff1a;2. YAML配置&#xff1a; 三. 使用Microsoft提供的Spring …