OpenCVSharp:使用 MOG(Mixture of Gaussians,高斯混合模型)算法来从视频流中分离前景和背景

前言

今天来学习Samples中的第二个例子:使用 MOG(Mixture of Gaussians,高斯混合模型)算法来从视频流中分离前景和背景。

示例中的代码很短:

  public override void RunTest()   {       using var capture = new VideoCapture(MoviePath.Bach);       using var mog = BackgroundSubtractorMOG.Create();       using var windowSrc = new Window("src");       using var windowDst = new Window("dst");        using var frame = new Mat();       using var fg = new Mat();       while (true)       {           capture.Read(frame);           if (frame.Empty())               break;           mog.Apply(frame, fg, 0.01);            windowSrc.Image = frame;           windowDst.Image = fg;           Cv2.WaitKey(50);       }   } 

先从示例代码开始学习,然后再使用WPF做一个界面。

效果:

OpenCVSharp:使用 MOG(Mixture of Gaussians,高斯混合模型)算法来从视频流中分离前景和背景

就这张效果图你可能不清楚在干嘛,但是一看动态的效果图你大概就懂分离前景和背景是什么意思了。

OpenCVSharp:使用 MOG(Mixture of Gaussians,高斯混合模型)算法来从视频流中分离前景和背景

开始学习

using var capture = new VideoCapture(MoviePath.Bach); 

创建了一个VideoCapture对象,这个是什么呢?

VideoCapture 是OpenCVSharp中用于视频捕获和读取的核心类,它提供了从多种视频源(包括摄像头、视频文件、网络流等)获取图像帧的统一接口。该类是计算机视觉应用中视频处理的基础组件,支持实时视频流处理和离线视频文件分析。

using var mog = BackgroundSubtractorMOG.Create(); 

BackgroundSubtractorMOG 是 OpenCV 中的一个背景减法算法,用于:

从视频流中分离前景和背景

检测运动物体

基于高斯混合模型(Mixture of Gaussians)进行背景建模

mog.Apply(frame, fg, 0.01); // 对每一帧应用背景减法 

其中:

frame 是输入的视频帧

fg 是输出的前景掩码

0.01 是学习率参数

BackgroundSubtractorMOG又是什么呢?

BackgroundSubtractorMOG 是 OpenCvSharp 中实现基于高斯混合模型(Gaussian Mixture-based)的背景/前景分割算法的类。该类继承自 BackgroundSubtractor 抽象类,用于从视频序列中分离前景和背景对象。

高斯混合模型是一种无监督的聚类算法,它假设所有数据点都是由若干个不同的、符合高斯分布(正态分布)的模型“混合”生成的。它的目标就是找出这些高斯分布的最佳参数。刚入门学习,先当一名合格的掉包侠,知道这些算法在哪些场景下可以用到就行了,基本上都已经封装好了。

 /// <summary>  /// Creates mixture-of-gaussian background subtractor  /// </summary>  /// <param name="history">Length of the history.</param>  /// <param name="nMixtures">Number of Gaussian mixtures.</param>  /// <param name="backgroundRatio">Background ratio.</param>  /// <param name="noiseSigma">Noise strength (standard deviation of the brightness or each color channel). 0 means some automatic value.</param>  /// <returns></returns>  public static BackgroundSubtractorMOG Create(      int history = 200, int nMixtures = 5, double backgroundRatio = 0.7, double noiseSigma = 0)  {      NativeMethods.HandleException(          NativeMethods.bgsegm_createBackgroundSubtractorMOG(              history, nMixtures, backgroundRatio, noiseSigma, out var ptr));      return new BackgroundSubtractorMOG(ptr);  } 

Creat方法中提供了一组参数的默认值。

参数说明:

history: 历史帧长度,默认为 200 帧,较长的历史可以提供更稳定的背景模型,但会增加计算成本和内存使用。

nMixtures: 高斯混合数量,默认为 5,更多的混合成分可以更好地建模复杂背景,但会增加计算复杂度。

backgroundRatio: 背景比例,默认为 0.7,该值决定了哪些高斯成分被视为背景的一部分。

noiseSigma: 噪声强度(亮度或每个颜色通道的标准差),0 表示自动值,用于处理图像中的噪声,值越大对噪声的容忍度越高。

 /// <summary>  /// the update operator that takes the next video frame and returns the current foreground mask as 8-bit binary image.  /// </summary>  /// <param name="image"></param>  /// <param name="fgmask"></param>  /// <param name="learningRate"></param>  public virtual void Apply(InputArray image, OutputArray fgmask, double learningRate = -1)  {      if (image is null)          throw new ArgumentNullException(nameof(image));      if (fgmask is null)          throw new ArgumentNullException(nameof(fgmask));      image.ThrowIfDisposed();      fgmask.ThrowIfNotReady();       NativeMethods.HandleException(          NativeMethods.video_BackgroundSubtractor_apply(ptr, image.CvPtr, fgmask.CvPtr, learningRate));                fgmask.Fix();      GC.KeepAlive(this);      GC.KeepAlive(image);      GC.KeepAlive(fgmask);  } 

Apply方法更新背景模型并返回前景掩码。

参数:

image: 输入的视频帧

fgmask: 输出的前景掩码(8位二进制图像)

learningRate: 学习率,-1 表示使用自动学习率

这个例子中只用到了返回前景图像,我们应该也能猜得到肯定也能返回背景图像。

 /// <summary>  /// computes a background image  /// </summary>  /// <param name="backgroundImage"></param>  public virtual void GetBackgroundImage(OutputArray backgroundImage)  {      if (backgroundImage is null)          throw new ArgumentNullException(nameof(backgroundImage));      backgroundImage.ThrowIfNotReady();       NativeMethods.HandleException(          NativeMethods.video_BackgroundSubtractor_getBackgroundImage(ptr, backgroundImage.CvPtr));      GC.KeepAlive(this);      GC.KeepAlive(backgroundImage);      backgroundImage.Fix();  } 

功能: 计算并返回当前背景图像

参数:

backgroundImage: 输出的背景图像

做一个WPF应用

现在我们已经学习了基本用法,现在正好学习一下WPF,用WPF做一个简单应用。

根据这个示例做一个WPF应用可能需要注意的地方。

首先我们要注意的是图像的显示问题,在示例应用中是直接用Mat显示的,在WPF中显示图像一般用BitmapImage,那么这里就涉及到一个转换的问题,可以安装一下OpenCvSharp4.Extensions这个库,作者已经提供了一些转换方法。

 private BitmapImage MatToBitmapImage(Mat mat)  {      // 将Mat转换为Bitmap      var bitmap = mat.ToBitmap();            // 将Bitmap转换为BitmapImage      var bitmapImage = new BitmapImage();      using (var stream = new System.IO.MemoryStream())      {          bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);          stream.Position = 0;          bitmapImage.BeginInit();          bitmapImage.StreamSource = stream;          bitmapImage.CacheOption = BitmapCacheOption.OnLoad;          bitmapImage.EndInit();          bitmapImage.Freeze();      }            return bitmapImage;  } 

我们还注意到示例应用是一个死循环,没有办法停止,我们可以增加一个CancellationTokenSource来进行控制。

private CancellationTokenSource _cancellationTokenSource;  private async Task RunAsync() {     IsProcessing = true;     HasProcessedImage = true;     _cancellationTokenSource = new CancellationTokenSource();      await Task.Run(() =>     {         using var capture = new VideoCapture(VideoPath);         using var mog = BackgroundSubtractorMOG.Create();          using var frame = new Mat();         using var fg = new Mat();                  while (!_cancellationTokenSource.Token.IsCancellationRequested)         {             capture.Read(frame);             if (frame.Empty())                 break;              mog.Apply(frame, fg, 0.01);              // 将Mat转换为BitmapImage并在UI线程更新             Application.Current.Dispatcher.Invoke(() =>             {                 OriginalImage = MatToBitmapImage(frame);                 ProcessedImage = MatToBitmapImage(fg);             });              Thread.Sleep(50); // 控制帧率         }     }, _cancellationTokenSource.Token);      IsProcessing = false; } 

全部代码:

<UserControl x:Class="OpenCVLearning.Views.BgSubtractorMOGView"              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"              xmlns:local="clr-namespace:OpenCVLearning.Views"              xmlns:prism="http://prismlibrary.com/"              mc:Ignorable="d"              prism:ViewModelLocator.AutoWireViewModel="True"              d:DesignHeight="450" d:DesignWidth="800">     <UserControl.Resources>         <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>     </UserControl.Resources>     <Grid Margin="10">         <Grid.RowDefinitions>             <RowDefinition Height="Auto"/>             <RowDefinition Height="Auto"/>             <RowDefinition Height="*"/>         </Grid.RowDefinitions>          <!-- 第一行:选择视频文件按钮和路径显示 -->         <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,10">             <Button Content="选择视频文件" Command="{Binding SelectVideoCommand}" Width="120" Height="30" Margin="0,0,10,0"/>             <TextBlock Text="{Binding VideoPath}" VerticalAlignment="Center" Foreground="Gray"/>         </StackPanel>          <!-- 第二行:运行和停止按钮 -->         <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,10">             <Button Content="运行" Command="{Binding RunCommand}" Width="100" Height="30" Margin="0,0,10,0"/>             <Button Content="停止" Command="{Binding StopCommand}" Width="100" Height="30"/>         </StackPanel>          <!-- 第三行:视频处理结果显示区域 - 分为两列 -->         <Border Grid.Row="2" BorderBrush="Gray" BorderThickness="1">             <Grid>                 <Grid.ColumnDefinitions>                     <ColumnDefinition Width="*"/>                     <ColumnDefinition Width="*"/>                 </Grid.ColumnDefinitions>                                  <!-- 左列:原始图像 -->                 <Border Grid.Column="0" BorderBrush="LightGray" BorderThickness="0,0,1,0" Padding="5">                     <Grid>                         <Image Source="{Binding OriginalImage}" Stretch="Uniform"                                Visibility="{Binding HasProcessedImage, Converter={StaticResource BooleanToVisibilityConverter}}"/>                         <TextBlock Text="原始图像" HorizontalAlignment="Center" VerticalAlignment="Center"                                    Visibility="{Binding HasNoProcessedImage, Converter={StaticResource BooleanToVisibilityConverter}}"/>                     </Grid>                 </Border>                                  <!-- 右列:处理后图像 -->                 <Border Grid.Column="1" BorderBrush="LightGray" Padding="5">                     <Grid>                         <Image Source="{Binding ProcessedImage}" Stretch="Uniform"                                Visibility="{Binding HasProcessedImage, Converter={StaticResource BooleanToVisibilityConverter}}"/>                         <TextBlock Text="处理后图像" HorizontalAlignment="Center" VerticalAlignment="Center"                                    Visibility="{Binding HasNoProcessedImage, Converter={StaticResource BooleanToVisibilityConverter}}"/>                     </Grid>                 </Border>             </Grid>         </Border>     </Grid> </UserControl> 
using Microsoft.Win32; using OpenCvSharp; using OpenCvSharp.Extensions; using Prism.Commands; using Prism.Mvvm; using System; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media.Imaging;  namespace OpenCVLearning.ViewModels {     public class BgSubtractorMOGViewModel : BindableBase     {         private string _videoPath;         private BitmapImage _originalImage;         private BitmapImage _processedImage;         private bool _hasProcessedImage;         private bool _isProcessing;         private CancellationTokenSource _cancellationTokenSource;          public string VideoPath         {             get { return _videoPath; }             set { SetProperty(ref _videoPath, value); }         }          public BitmapImage OriginalImage         {             get { return _originalImage; }             set { SetProperty(ref _originalImage, value); }         }          public BitmapImage ProcessedImage         {             get { return _processedImage; }             set { SetProperty(ref _processedImage, value); }         }          public bool HasProcessedImage         {             get { return _hasProcessedImage; }             set             {                 SetProperty(ref _hasProcessedImage, value);                 RaisePropertyChanged(nameof(HasNoProcessedImage));             }         }          public bool HasNoProcessedImage         {             get { return !_hasProcessedImage; }         }          public bool IsProcessing         {             get { return _isProcessing; }             set             {                 SetProperty(ref _isProcessing, value);                 RaisePropertyChanged(nameof(CanRun));                 RaisePropertyChanged(nameof(CanStop));             }         }          public ICommand SelectVideoCommand { get; private set; }         public ICommand RunCommand { get; private set; }         public ICommand StopCommand { get; private set; }          public BgSubtractorMOGViewModel()         {             SelectVideoCommand = new DelegateCommand(SelectVideo);             RunCommand = new DelegateCommand(async () => await RunAsync(), CanRun).ObservesProperty(() => VideoPath).ObservesProperty(() => IsProcessing);             StopCommand = new DelegateCommand(Stop, CanStop).ObservesProperty(() => IsProcessing);         }          private void SelectVideo()         {             OpenFileDialog openFileDialog = new OpenFileDialog             {                 Filter = "视频文件|*.mp4;*.avi;*.mov;*.mkv;*.wmv;*.flv|所有文件|*.*",                 Title = "选择视频文件"             };              if (openFileDialog.ShowDialog() == true)             {                 VideoPath = openFileDialog.FileName;             }         }          private bool CanRun()         {             return !string.IsNullOrEmpty(VideoPath) && File.Exists(VideoPath) && !IsProcessing;         }          private bool CanStop()         {             return IsProcessing;         }          private void Stop()         {             _cancellationTokenSource?.Cancel();         }          private async Task RunAsync()         {             IsProcessing = true;             HasProcessedImage = true;             _cancellationTokenSource = new CancellationTokenSource();              await Task.Run(() =>             {                 using var capture = new VideoCapture(VideoPath);                 using var mog = BackgroundSubtractorMOG.Create();                  using var frame = new Mat();                 using var fg = new Mat();                                  while (!_cancellationTokenSource.Token.IsCancellationRequested)                 {                     capture.Read(frame);                     if (frame.Empty())                         break;                      mog.Apply(frame, fg, 0.01);                      // 将Mat转换为BitmapImage并在UI线程更新                     Application.Current.Dispatcher.Invoke(() =>                     {                         OriginalImage = MatToBitmapImage(frame);                         ProcessedImage = MatToBitmapImage(fg);                     });                      Thread.Sleep(50); // 控制帧率                 }             }, _cancellationTokenSource.Token);              IsProcessing = false;         }          private BitmapImage MatToBitmapImage(Mat mat)         {             // 将Mat转换为Bitmap             var bitmap = mat.ToBitmap();                          // 将Bitmap转换为BitmapImage             var bitmapImage = new BitmapImage();             using (var stream = new System.IO.MemoryStream())             {                 bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);                 stream.Position = 0;                 bitmapImage.BeginInit();                 bitmapImage.StreamSource = stream;                 bitmapImage.CacheOption = BitmapCacheOption.OnLoad;                 bitmapImage.EndInit();                 bitmapImage.Freeze();             }                          return bitmapImage;         }            } } 

应用

我们大概知道怎么使用了之后,关键是要知道在哪些场景下可能会用到这个东西,现在我们可以配合AI去测试一下几个可能能用上的场景。

我测试了两个场景,一个是运动物体检测,另一个是背景图像转换。

运动物体检测效果:

OpenCVSharp:使用 MOG(Mixture of Gaussians,高斯混合模型)算法来从视频流中分离前景和背景

可以发现其实结果也不是很准确,也是比较一般。

背景图像转换效果:

OpenCVSharp:使用 MOG(Mixture of Gaussians,高斯混合模型)算法来从视频流中分离前景和背景

可以发现其实效果不是很好,现在直播背景图像替换可能更推荐MediaPipe Selfie Segmentation与OpenCV结合起来。

虽然说这两个Demo效果不是很好,但是可以学习一些OpenCVSharp的用法。

发表评论

评论已关闭。

相关文章