OpenCVSharp:使用 MOG实现背景替换
概述
背景替换是计算机视觉中的一个常见应用,广泛应用于视频会议、直播特效和电影制作等领域。本文将介绍如何使用 OpenCV 中的 MOG(Mixture of Gaussians)背景减法算法实现实时背景替换功能。虽然说效果不太行,但是可以学习一下OpenCV中的一些方法。
效果:

动态效果:

实现
前面已经介绍过了MOG的基本用法,今天在这里主要介绍一下与之前相比实现这个简单的Demo需要注意的地方。
1、调整背景图像大小以匹配视频帧
由于我们选择的背景大小与视频帧的大小不一定匹配,因此需要调整一下,可以使用 Cv2.Resize方法:
// 调整背景图像大小以匹配视频帧
var resizedBackground = new Mat();
Cv2.Resize(backgroundMat, resizedBackground, frame.Size());
然后使用MOG获取前景掩码:
// 应用背景减法获取前景掩码
mog.Apply(frame, foregroundMask, 0.01);
为了提高背景替换的质量和准确性,需要处理前景掩码。
2、前景掩码处理
阈值处理
// 应用阈值
Cv2.Threshold(mask, mask, Threshold, 255, ThresholdTypes.Binary);
将灰度掩码转换为二值掩码,明确区分前景和背景,用户可以通过 Threshold 参数调整敏感度。
高斯模糊
// 应用高斯模糊以减少噪声
if (BlurSize > 0)
{
Cv2.GaussianBlur(mask, mask, new OpenCvSharp.Size(BlurSize, BlurSize), 0);
}
减少掩码中的噪声和小的伪影,使前景区域更加平滑和连续。
高斯模糊(英语:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪声以及降低细节层次。这种模糊技术生成的图像,其视觉效果就像是经过一个半透明屏幕在观察图像,这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。高斯平滑也用于计算机视觉算法中的预先处理阶段,以增强图像在不同比例大小下的图像效果(参见尺度空间表示以及尺度空间实现)。 从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积。由于正态分布又叫作“高斯分布”,所以这项技术就叫作高斯模糊。图像与圆形方框模糊做卷积将会生成更加精确的焦外成像效果。由于高斯函数的傅立叶变换是另外一个高斯函数,所以高斯模糊对于图像来说就是一个低通滤波器。

来源:https://zh.wikipedia.org/wiki/高斯模糊
形态学操作
// 应用形态学操作
if (UseMorphology && MorphologySize > 0)
{
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new OpenCvSharp.Size(MorphologySize, MorphologySize));
Cv2.MorphologyEx(mask, mask, MorphTypes.Open, kernel);
Cv2.MorphologyEx(mask, mask, MorphTypes.Close, kernel);
}
开运算:去除小的噪声点
闭运算:填充前景对象内部的小空洞
这样可以使前景区域更加完整和连续。
边缘检测
if (UseEdgeDetection)
{
using var edges = new Mat();
Cv2.Canny(mask, edges, 50, 150);
Cv2.BitwiseOr(mask, edges, mask);
}
参数:
输入:mask(经过阈值处理的前景掩码,二值图像)
输出:edges(检测到的边缘图像)
50:低阈值(lower threshold)
150:高阈值(upper threshold)
Canny算法的工作原理:
高斯滤波:首先对图像进行高斯模糊以减少噪声
梯度计算:计算图像中每个像素点的梯度强度和方向
非极大值抑制:细化边缘,只保留梯度方向上的局部最大值
双阈值检测:
梯度值 > 150:被认为是强边缘,直接保留
50 < 梯度值 ≤ 150:被认为是弱边缘,只有当与强边缘相连时才保留
梯度值 ≤ 50:被抑制,不作为边缘
然后将边缘与原始掩码进行合并,使用Cv2.BitwiseOr,按位或操作。
按位或操作的作用:
将原始掩码 mask 和边缘图像 edges 进行按位或运算
结果存储回原始掩码 mask 中
任何在原始掩码或边缘图像中为白色的像素,在结果中都会是白色
3、替换背景图像
现在需要将原始视频帧的前景与新的背景图像进行融合,核心思想是使用前景掩码来决定哪些像素保留原始帧,哪些像素使用背景图像。
掩码通道标准化
// 确保掩码是单通道的
if (mask.Channels() > 1)
{
Cv2.CvtColor(mask, mask, ColorConversionCodes.BGR2GRAY);
}
后续操作需要单通道掩码,如果输入是多通道则转换为灰度。
创建掩码副本并归一化
// 创建3通道前景掩码和背景掩码
using var foregroundMask = new Mat();
using var backgroundMask = new Mat();
// 归一化掩码到0-1范围
mask.ConvertTo(foregroundMask, MatType.CV_32F, 1.0/255.0);
// 创建反向掩码
Cv2.BitwiseNot(mask, backgroundMask);
backgroundMask.ConvertTo(backgroundMask, MatType.CV_32F, 1.0/255.0);
归一化之后语义更加清晰:1.0表示完全前景,0.0表示完全背景。
// 将单通道掩码扩展为3通道
using var foregroundMask3 = new Mat();
using var backgroundMask3 = new Mat();
Cv2.CvtColor(foregroundMask, foregroundMask3, ColorConversionCodes.GRAY2BGR);
Cv2.CvtColor(backgroundMask, backgroundMask3, ColorConversionCodes.GRAY2BGR);
// 将帧和背景转换为浮点型
using var frameFloat = new Mat();
using var backgroundFloat = new Mat();
frame.ConvertTo(frameFloat, MatType.CV_32F);
background.ConvertTo(backgroundFloat, MatType.CV_32F);
// 应用掩码混合 - 保持原始颜色
using var foreground = new Mat();
using var bg = new Mat();
Cv2.Multiply(frameFloat, foregroundMask3, foreground);
Cv2.Multiply(backgroundFloat, backgroundMask3, bg);
Cv2.Add(foreground, bg, result);
// 转换回8位
result.ConvertTo(result, MatType.CV_8UC3);
步骤1:将单通道掩码扩展为3通道。
步骤2:将帧和背景转换为浮点型。
步骤3:应用掩码混合。
主要看这三行:
Cv2.Multiply(frameFloat, foregroundMask3, foreground);
Cv2.Multiply(backgroundFloat, backgroundMask3, bg);
Cv2.Add(foreground, bg, result);
Cv2.Multiply(frameFloat, foregroundMask3, foreground);
操作:原始帧 × 前景掩码 = 前景图像
效果:
前景区域(掩码值=1.0):原始像素 × 1.0 = 原始像素 → 保留人物
背景区域(掩码值=0.0):原始像素 × 0.0 = 0 → 变成黑色
边缘区域(掩码值=0.5):原始像素 × 0.5 = 半亮度 → 准备混合
Cv2.Multiply(backgroundFloat, backgroundMask3, bg);
操作:背景图像 × 背景掩码 = 背景图像
效果:
背景区域(掩码值=1.0):背景像素 × 1.0 = 背景像素 → 保留新背景
前景区域(掩码值=0.0):背景像素 × 0.0 = 0 → 变成黑色
边缘区域(掩码值=0.5):背景像素 × 0.5 = 半亮度 → 准备混合
Cv2.Add(foreground, bg, result);
操作:前景图像 + 背景图像 = 最终结果
效果:
前景区域:人物像素 + 0 = 人物像素 → 显示原始人物
背景区域:0 + 背景像素 = 背景像素 → 显示新背景
边缘区域:半亮度人物 + 半亮度背景 = 混合效果 → 自然过渡

浙公网安备 33010602011771号