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

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 的默认参数、训练历史和阴影处理会影响掩膜;它们不是对象检测或跟踪结果。



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











暂无评论内容