比较背景建模方法并生成视频前景掩膜

比较背景建模方法并生成视频前景掩膜

背景减除用于固定机位场景的运动区域分离。模型收到每帧时更新背景估计,再依据当前帧与背景差异产生前景掩膜。摄像机移动、光照突变、遮挡和动态背景会造成误检;掩膜表示可能运动区域,不是对象语义识别。

视频帧逐步更新 MOG2 或 KNN 背景模型,产生标记运动对象的前景掩膜。
原创流程示意图,由本文编辑绘制。

MOG2 与 KNN

教程演示 BackgroundSubtractorMOG2 和 BackgroundSubtractorKNN。MOG2 用多个高斯分布表示背景变化,KNN 按历史样本邻近程度分类。调用 apply(frame, mask) 更新模型并产出掩膜。工厂默认包括历史窗口、阈值且可检测阴影;对照所用 OpenCV 版本确认默认值。

阴影检测开启时掩膜可包含背景、阴影和前景值,常见为 0、127、255。后续只分析运动对象时要区分阴影和前景。源教程给出 C++、Java、Python 示例,默认媒体为 vtest.avi,算法为 MOG2,也可用 KNN 或图像序列。

帧循环和退出键

每帧先检查读取是否成功,再调用 subtractor 的 apply,显示原帧及掩膜。waitKey(30) 处理 GUI 事件并返回整数按键码。Python 逻辑应为:

ok, frame = capture.read()
if not ok or frame is None: break
mask = subtractor.apply(frame)
cv.imshow("Frame", frame)
cv.imshow("FG Mask", mask)
key = cv.waitKey(30) & 0xFF
if key == ord("q") or key == 27: break

源文 Python 示例把整数返回值和字符串 'q' 比较,永不相等,所以 q 无法退出;需改用 ord('q')。Java 示例用 args[1] == "MOG2" 比较字符串对象引用,应改成 "MOG2".equals(args[1])。C++ 键码本来是整数,按整数比较的思路是正确的。

// Java 字符串按内容比较
boolean useMOG2 = args.length > 1 ? "MOG2".equals(args[1]) : true;

原示例打不开视频时退出码为 0,可能让自动任务误判成功;应报错并返回非零状态。对服务端输入还要限制大小、帧数和处理时间;桌面 imshow 在无头环境不可用,应改为有界输出。

版本与代码修订

来源概述写 OpenCV 3.0 及以上;本页为 OpenCV 4.13.0 文档。构建模块、GUI 后端、媒体格式仍需按实际包验证。本文修复 Python 按键类型、Java 字符串比较和打开失败退出状态;没有编译或执行任一示例。

来源:OpenCV 4.13.0, How to Use Background Subtraction Methods,Domenico Daniele Bloisi。代码按项目根 Apache-2.0 归属;源文图像未复用。

本文补充:核对与维护细节

三种语言版本结构

C++ 版本用 CommandLineParser 读取 input 与 algo,检查帮助参数,选择 createBackgroundSubtractorMOG2 或 createBackgroundSubtractorKNN。Java 版本以参数选择模型,取一帧后用 apply 生成掩膜,并显示 frame 与 FG Mask。Python 版本用 argparse 提供 --input 和 --algo,同样循环读取、apply、imshow 和 waitKey。图像序列也能用 VideoCapture 读取,但文件名格式和路径必须符合 OpenCV 支持方式。

循环退出后应释放 VideoCapture 并销毁窗口;处理特别长的视频时可定期输出指标、检测卡住/损坏帧。GUI 的 waitKey 只读取当前窗口事件;在无头容器使用会失败或无意义。视频帧率不等于算法处理速度,若实时处理落后,应根据场景考虑抽帧、缩放或异步队列,但这些改变也会影响背景适应。

模型参数与输出解释

MOG2 工厂参数包括历史帧数、方差阈值和阴影检测开关;KNN 有历史长度、距离阈值和阴影开关。更长历史让模型变化慢但更稳,过短可能把暂时静止对象迅速吸收为背景;过高/过低阈值会分别漏检或噪声增多。教程用默认值演示,不给适用于所有摄像机的参数。选参数时用包含光照变化、阴影与静止对象的样本集比较误报和漏报。

完整示例:C++

按官方代码结构完整列出,并修复帮助参数继续执行及打开失败仍返回 0 的问题。默认输入 vtest.avi、MOG2;命令参数 --algo=KNN 可选另一模型。逐帧调用 apply 更新模型、叠加帧号并显示原图和掩膜。

#include <iostream>
#include <sstream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
using namespace cv;
using namespace std;
const char* params =
    "{ help h | | Print usage }"
    "{ input | vtest.avi | Path to a video or image sequence }"
    "{ algo | MOG2 | Background subtraction method (KNN, MOG2) }";
int main(int argc, char* argv[]) {
    CommandLineParser parser(argc, argv, params);
    if (parser.has("help")) { parser.printMessage(); return 0; }
    Ptr<BackgroundSubtractor> backSub;
    if (parser.get<String>("algo") == "MOG2")
        backSub = createBackgroundSubtractorMOG2();
    else
        backSub = createBackgroundSubtractorKNN();
    const String input = parser.get<String>("input");
    VideoCapture capture(samples::findFile(input));
    if (!capture.isOpened()) {
        cerr << "Unable to open: " << input << endl;
        return 1; // 修改:输入错误返回非零
    }
    Mat frame, fgMask;
    while (capture.read(frame) && !frame.empty()) {
        backSub->apply(frame, fgMask); // 可传显式 learningRate
        rectangle(frame, Point(10, 2), Point(100, 20),
                  Scalar(255, 255, 255), -1);
        stringstream ss;
        ss << capture.get(CAP_PROP_POS_FRAMES);
        putText(frame, ss.str(), Point(15, 15),
                FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));
        imshow("Frame", frame);
        imshow("FG Mask", fgMask);
        int key = waitKey(30);
        if (key == 'q' || key == 27) break;
    }
    capture.release();
    destroyAllWindows();
    return 0;
}

完整示例:Java

相较原文,把 args[1] == "MOG2" 改为内容比较,并在打开失败时抛出失败状态;释放视频与窗口。

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgproc.Imgproc;
import org.opencv.video.BackgroundSubtractor;
import org.opencv.video.Video;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;
class BackgroundSubtraction {
    public void run(String[] args) {
        String input = args.length > 0 ? args[0] : "../data/vtest.avi";
        boolean useMOG2 = args.length > 1
                ? "MOG2".equals(args[1]) : true;
        BackgroundSubtractor backSub = useMOG2
                ? Video.createBackgroundSubtractorMOG2()
                : Video.createBackgroundSubtractorKNN();
        VideoCapture capture = new VideoCapture(input);
        if (!capture.isOpened())
            throw new IllegalArgumentException("Unable to open: " + input);
        Mat frame = new Mat(), fgMask = new Mat();
        while (capture.read(frame) && !frame.empty()) {
            backSub.apply(frame, fgMask); // 可选显式 learningRate 重载
            Imgproc.rectangle(frame, new Point(10, 2), new Point(100, 20),
                    new Scalar(255, 255, 255), -1);
            String number = String.format("%d",
                    (int) capture.get(Videoio.CAP_PROP_POS_FRAMES));
            Imgproc.putText(frame, number, new Point(15, 15),
                    Core.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 0));
            HighGui.imshow("Frame", frame);
            HighGui.imshow("FG Mask", fgMask);
            int key = HighGui.waitKey(30);
            if (key == 'q' || key == 27) break;
        }
        capture.release();
        HighGui.destroyAllWindows();
    }
}
public class BackgroundSubtractionDemo {
    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        new BackgroundSubtraction().run(args);
    }
}

完整示例:Python

原文将 waitKey 返回的整数与字符串 'q' 比较,因此 q 退出分支无效;此版本按键码比较,并检查 ret,失败时非零退出。

import argparse
import sys
import cv2 as cv

parser = argparse.ArgumentParser(
    description="Apply background subtraction to videos or image sequences."
)
parser.add_argument("--input", type=str, default="vtest.avi",
                    help="Path to a video or image sequence.")
parser.add_argument("--algo", choices=("MOG2", "KNN"), default="MOG2",
                    help="Background subtraction method.")
args = parser.parse_args()
if args.algo == "MOG2":
    backSub = cv.createBackgroundSubtractorMOG2()
else:
    backSub = cv.createBackgroundSubtractorKNN()
capture = cv.VideoCapture(cv.samples.findFileOrKeep(args.input))
if not capture.isOpened():
    print("Unable to open: " + args.input, file=sys.stderr)
    sys.exit(1)
try:
    while True:
        ret, frame = capture.read()
        if not ret or frame is None:
            break
        fgMask = backSub.apply(frame)  # 也可设置 learningRate
        cv.rectangle(frame, (10, 2), (100, 20), (255, 255, 255), -1)
        cv.putText(frame, str(int(capture.get(cv.CAP_PROP_POS_FRAMES))),
                   (15, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
        cv.imshow("Frame", frame)
        cv.imshow("FG Mask", fgMask)
        key = cv.waitKey(30) & 0xFF
        if key == ord("q") or key == 27:
            break
finally:
    capture.release()
    cv.destroyAllWindows()

MOG2 默认 history=500、varThreshold=16、开启阴影检测;KNN 默认 history=500、dist2Threshold=400、开启阴影检测。每一帧都会参与模型更新;需要固定更新速率时,C++/Java 的 apply 重载或 Python 的 apply(frame, learningRate=...) 可设学习率,具体 API 随绑定版本核验。掩膜可能同时编码背景、阴影和前景,阴影灰度不应直接当对象前景。

以下输入帧和前景掩膜均直接取自 OpenCV 4.13.0 官方教程的仓库版本,属于官方示例材料,不是本次执行结果。MOG2 与 KNN 的默认参数、训练历史和阴影处理会影响掩膜;它们不是对象检测或跟踪结果。

OpenCV 4.13.0 官方教程使用的 vtest.avi 输入帧:固定机位室外场景中有多名行人。
官方教程的输入帧,来源:OpenCV 官方仓库。用于说明源示例场景,不是本文实验输入。
OpenCV 官方教程中的 MOG2 前景掩膜示例:黑色背景上,行人和部分阴影以灰阶区域显示。
MOG2 官方示例输出。灰阶掩膜应结合背景、阴影与前景值解释;来源与许可同上。
OpenCV 官方教程中的 KNN 前景掩膜示例:黑色背景上,行人轮廓和部分阴影以灰阶区域显示。
KNN 官方示例输出。两种算法的视觉差异受默认模型参数及训练历史影响;来源与许可同上。

作者 Domenico Daniele Bloisi;项目官方仓库根目录提供 Apache License 2.0。随稿保留许可证全文;未发现该仓库版本的根级 NOTICE 文件。中文翻译与原创示意图依单独发布授权提供。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容