WPF中在摄像头视频上叠加控件的解决方案

yizhihongxing

下面是“WPF中在摄像头视频上叠加控件的解决方案”的完整攻略,包含以下内容:

1. 必备条件

要在摄像头视频上叠加控件,需要满足以下两个条件:

  1. 需要使用WPF作为UI框架。
  2. 使用的摄像头必须支持DirectShow协议。

2. 解决方案

WPF中有一个叫做“D3DImage”的控件,可以用于在摄像头视频上叠加其他控件。具体步骤如下:

2.1 创建DirectShow视频播放器

需要在WPF窗口中创建DirectShow视频播放器,这里我们可以使用DirectShowLib库中的SampleGrabber来获取视频帧。

using System.Runtime.InteropServices;
using DirectShowLib;

[ComImport]
[Guid("6B652FFF-11FE-4fce-92AD-0266B5D7C78F")]
public class SampleGrabber { }

直接将上面的SampleGrabber类添加到你的代码中即可。

接下来,我们需要实现一个继承自Control类的VideoPlayer控件。在VideoPlayer的构造函数中,我们需要初始化DirectShow视频播放器、创建SampleGrabber对象并设置回调函数等。代码如下:

public class VideoPlayer : Control, ISampleGrabberCB
{
    private IFilterGraph2 _graphBuilder;
    private ICaptureGraphBuilder2 _captureGraphBuilder;
    private IBaseFilter _sourceFilter;
    private IBaseFilter _sampleGrabberFilter;
    private ISampleGrabber _sampleGrabber;
    private IBaseFilter _rendererFilter;
    private DsROTEntry _rotEntry;

    public VideoPlayer()
    {
        // 初始化DirectShow视频播放器
        _graphBuilder = (IFilterGraph2)new FilterGraph();
        _captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();

        // 创建SampleGrabber对象并设置回调函数
        _sampleGrabber = (ISampleGrabber)new SampleGrabber();
        var sampleGrabberConfig = (ISampleGrabber)_sampleGrabber;
        sampleGrabberConfig.SetCallback(this, 1);

        _sampleGrabberFilter = (IBaseFilter)_sampleGrabber;

        // 创建渲染器
        _rendererFilter = (IBaseFilter)new VideoRenderer();

        // 添加Filter到图形中
        _graphBuilder.AddFilter(_sampleGrabberFilter, "SampleGrabber");
        _graphBuilder.AddFilter(_rendererFilter, "Renderer");

        // 创建过程链
        _captureGraphBuilder.SetFiltergraph(_graphBuilder);
        _captureGraphBuilder.RenderStream(PinCategory.Capture, MediaType.Video, _sourceFilter, _sampleGrabberFilter, _rendererFilter);

        // 显示预览窗口
        InitializeComponent();

        // 将Filter Graph加入ROT,这样可以使用Graph编辑器查看
        _rotEntry = new DsROTEntry(_graphBuilder);
    }

    // ...
}

需要注意的是,上面的代码中省略了一些初始化语句和其他实现细节,需要按实际情况进行修改。

2.2 创建D3DImage控件

在WPF窗口中创建一个D3DImage控件,并将其作为视频播放器的渲染目标。代码如下:

public class VideoPlayer : Control, ISampleGrabberCB
{
    private D3DImage _d3DImage;

    public VideoPlayer()
    {
        // ...

        // 创建D3DImage控件
        _d3DImage = new D3DImage();
        _d3DImage.IsFrontBufferAvailableChanged += _d3DImage_IsFrontBufferAvailableChanged;
        _d3DImage.Lock();

        // 将D3DImage绑定到视频播放器
        var videoRenderer = (IVMRWindowlessControl)_rendererFilter;
        videoRenderer.SetVideoClippingWindow(Handle);
        videoRenderer.SetAspectRatioMode(VMRAspectRatioMode.LetterBox);
        videoRenderer.SetVideoPosition(null, new DsRect(-1, -1, -1, -1));
        videoRenderer.SetWindowForeground(-1);
        videoRenderer.SetBorderColor(1);
        videoRenderer.SetVideoBackground(0);

        _d3DImage.Unlock();
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        base.OnRenderSizeChanged(sizeInfo);
        _d3DImage.Lock();
        _d3DImage.SetBackBufferSize((int)RenderSize.Width, (int)RenderSize.Height);
        _d3DImage.Unlock();
    }

    void _d3DImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (_d3DImage.IsFrontBufferAvailable)
        {
            Render();
        }
    }

    private void Render()
    {
        _d3DImage.Lock();

        var sourceRect = new DsRect(0, 0, 640, 480);
        var destRect = new System.Windows.Int32Rect(0, 0, (int)RenderSize.Width, (int)RenderSize.Height);

        _d3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _rendererFilter, sourceRect);
        _d3DImage.AddDirtyRect(destRect);
        _d3DImage.Unlock();
    }

    // ...
}

在上面的代码中,我们将D3DImage控件与DirectShow的渲染器进行了绑定。在D3DImage的IsFrontBufferAvailableChanged事件中,我们进行了渲染操作。Render()方法则是用于实现渲染过程。

2.3 叠加其他控件

将其他控件叠加到摄像头视频上时,可以在VideoPlayer控件的OnRender方法中进行绘制。可以参考如下代码:

public class VideoPlayer : Control, ISampleGrabberCB
{
    public override void OnRender(DrawingContext dc)
    {
        base.OnRender(dc);

        dc.DrawImage(_d3DImage, new Rect(RenderSize));
        dc.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, 100, 100));
    }

    // ...
}

上面的代码中,我们使用了dc.DrawImage方法将D3DImage控件绘制到窗口中,并在视频上方叠加了一个100*100的矩形。

3. 示例

接下来,我们将使用上面的解决方案实现两个示例:

示例1:在视频中叠加一个文本框

在此示例中,我们将在视频上方叠加一个可以编辑的文本框。

  1. 首先,新建一个WPF应用程序,将MainWindow.xaml文件中的内容替换为以下代码:
<Window x:Class="WpfCameraOverlayExample.MainWindow"
        ...

        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:VideoPlayer x:Name="videoPlayer" />
        <TextBox HorizontalAlignment="Left" Height="23" Margin="83,27,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
    </Grid>
</Window>

其中,local:VideoPlayer是我们自定义的控件。在代码中,我们将VideoPlayer和一个可编辑的文本框放在同一个Grid控件中。

  1. 接下来,我们需要在VideoPlayer控件的OnRender方法中画出文本框。
// 在VideoPlayer控件中添加如下代码
public override void OnRender(DrawingContext dc)
{
    base.OnRender(dc);

    if (IsLoaded)
    {
        dc.DrawImage(_d3DImage, new Rect(RenderSize));
        dc.DrawRectangle(Brushes.Black, null, new Rect(20, 20, 150, 30));
        dc.DrawRectangle(Brushes.White, new Pen(Brushes.Gray, 2), new Rect(20, 20, 150, 30));
        dc.DrawText(new FormattedText(textBox.Text, CultureInfo.GetCultureInfo("en-us"),
            FlowDirection.LeftToRight, new Typeface("Verdana"), 14, Brushes.Black), new Point(25, 25));
    }
}

在上面的代码中,我们使用了DrawRectangle、DrawText等方法,将一个带有灰色边框的文本框绘制在了视频上方。此处的textBox是在XAML中定义的一个TextBox控件。

  1. 修改VideoPlayer控件,在其构造函数中给视频分配一些初始化大小。
public VideoPlayer()
{
    Width = 640;
    Height = 480;

    // ...

}

这是必须的,这样才能使视频控件显示正常。

  1. 最后,需要在TextBox的TextChanged事件中重新绘制视频。
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        videoPlayer.Start();

        textBox.TextChanged += TextBox_TextChanged;
    }

    private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        videoPlayer.InvalidateVisual();
    }
}

在上面的代码中,我们在MainWindow的构造函数中给TextBox的TextChanged事件添加了一个InvalidateVisual()方法,这样在每次文本框的值发生改变时都会刷新一下图像。

示例2:在视频中显示当前时间

在此示例中,我们将在视频上方叠加一个可以实时显示当前时间的控件。

  1. 新建一个WPF应用程序,将MainWindow.xaml文件中的内容替换为以下代码:
<Window x:Class="WpfCameraOverlayExample.MainWindow"
        ...
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:VideoPlayer x:Name="videoPlayer" />
        <TextBlock HorizontalAlignment="Left" Height="20" Margin="10,10,0,0" TextWrapping="Wrap" Text="00:00:00" VerticalAlignment="Top" Width="120" TextAlignment="Center" FontSize="14" Foreground="White"/>
    </Grid>
</Window>

其中,local:VideoPlayer是我们自定义的控件。在代码中,我们将VideoPlayer和一个用于显示时间的TextBlock控件放在同一个Grid控件中。

  1. 为TextBlock控件实现数据绑定

我们需要在TextBlock控件中实现一个数据绑定,使其能够实时显示当前时间。代码如下:

public class TimeData : INotifyPropertyChanged
{
    private string _time;
    public string Time
    {
        get { return _time; }
        set
        {
             _time = value;
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Time)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public partial class MainWindow : Window
{
    private Timer _timer;
    private readonly TimeData _timeData = new TimeData();

    public MainWindow()
    {
        InitializeComponent();
        videoPlayer.Start();

        DataContext = _timeData;

        _timer = new Timer(1000);
        _timer.Elapsed += Timer_OnElapsed;
        _timer.Start();
    }

    private void Timer_OnElapsed(object sender, ElapsedEventArgs e)
    {
        _timeData.Time = DateTime.Now.ToString("hh:mm:ss tt", CultureInfo.InvariantCulture);
    }
}

上面的代码中,我们创建了一个TimeData类,其中包含一个Time属性,用于存储当前时间。在MainWindow构造函数中,我们将该类实例绑定到了DataContext属性中,并创建了一个可以每秒执行一次的Timer。在Timer的OnElapsed事件中,我们将当前时间赋值给TimeData,以便在TextBlock控件上实时显示。

  1. VideoPlayer控件的OnRender方法中显示时间

最后,我们需要在VideoPlayer控件的OnRender方法中将TextBlock控件绘制在视频上方。代码如下:

public override void OnRender(DrawingContext dc)
{
    base.OnRender(dc);

    if (IsLoaded)
    {
        dc.DrawImage(_d3DImage, new Rect(RenderSize));
        dc.DrawRectangle(Brushes.Black, null, new Rect(20, 20, 150, 30));
        dc.DrawRectangle(Brushes.White, new Pen(Brushes.Gray, 2), new Rect(20, 20, 150, 30));
        dc.DrawText(new FormattedText(textBox.Text, CultureInfo.GetCultureInfo("en-us"),
            FlowDirection.LeftToRight, new Typeface("Verdana"), 14, Brushes.Black), new Point(25, 25));
        dc.DrawText(new FormattedText(_timeData.Time, CultureInfo.GetCultureInfo("en-us"),
            FlowDirection.LeftToRight, new Typeface("Verdana"), 14, Brushes.White), new Point(550, 10));
    }
}

在上面的代码中,我们在前面的文本框旁边,将可以实时显示当前时间的控件显示在了视频上方。

到这里,我们就完成了在WPF中在摄像头视频上叠加控件的解决方案的攻略。希望对你有所帮助!

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:WPF中在摄像头视频上叠加控件的解决方案 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • jwt加密解密

    JWT加密解密攻略 JSON Web Token(JWT)是一种用于身份验证的开放标准,可以在网络应用间传递声明。JWT通常由三部分组成:头部、载荷和签名。本文将介如何使用Python进行JWT的加密和解密,并提供两个示例说明。 安装PyJWT模块 在开始之前,需要先安PyJWT模块。可以使用pip命令进行安装: pip install PyJWT JWT加…

    other 2023年5月7日
    00
  • 详解JavaScript中的变量命名规范

    详解JavaScript中的变量命名规范 在JavaScript中,良好的变量命名规范是编写清晰、可读性强的代码的关键。本攻略将详细介绍JavaScript中的变量命名规范,并提供两个示例说明。 1. 变量命名规则 在JavaScript中,变量的命名需要遵循以下规则: 变量名只能包含字母(a-z,A-Z)、数字(0-9)、下划线(_)或美元符号($)。 变…

    other 2023年8月8日
    00
  • OPPO Pad评测 2299元,这块智慧生态屏值吗?

    OPPO Pad评测攻略 介绍 OPPO Pad是一款智慧生态屏,售价为2299元。在评估其是否值得购买之前,我们将对其进行全面评测,包括性能、功能、设计等方面的考量。 性能评测 我们将对OPPO Pad的性能进行评测,包括处理器性能、内存容量、存储空间等方面的考量。以下是两个示例说明: 处理器性能:我们将使用基准测试工具(如Geekbench)对OPPO …

    other 2023年10月18日
    00
  • entityframework4.0(六)ef4的增加、删除、更改

    以下是关于“Entity Framework 4.0(六)EF4的增加、删除、更改”的完整攻略,包括EF4的基本知识、增加、删除更改的方法和两个示例。 EF4的基本知识 Entity Framework 4.0(EF4)是微软公司推出的一种ORM(对象关系映射)架,它可以将数据库中的表映射到.NET中的实体类,使得开发人员可以使用面向对象的方式来操作数据库。…

    other 2023年5月7日
    00
  • webpack vue项目开发环境局域网访问方法

    Webpack 配置的 Vue 项目开发环境默认只能在本机进行访问。如果要在局域网内访问,则需要进行相应的配置。下面详细讲解 webpack vue 项目开发环境局域网访问方法的完整攻略。 1. 修改webpack配置 首先,我们需要修改 webpack 的配置文件,将 Host 配置为 0.0.0.0,表示接受所有的网络访问请求。 在 webpack.de…

    other 2023年6月27日
    00
  • Android中Fragment的分屏显示处理横竖屏显示的实现方法

    Android中Fragment的分屏显示处理横竖屏显示的实现方法 在Android中,Fragment是一种用于构建灵活用户界面的组件。当应用程序需要在分屏模式下处理横竖屏显示时,我们可以采取以下方法来实现。 1. 使用Fragment的动态添加和移除 在分屏模式下,我们可以使用Fragment的动态添加和移除来处理横竖屏显示。具体步骤如下: 在布局文件中…

    other 2023年8月21日
    00
  • C++ 静态成员的类内初始化详解及实例代码

    如题所述,本文将详细讲解关于C++中静态成员的类内初始化的整个流程。在本文的实现过程中,我们将会提供两个示例来帮助读者更好的理解和掌握相关内容。 一、静态成员变量概述 在开始讲解静态成员的类内初始化之前,我们先来了解一下静态成员变量的概念。静态成员变量是属于所有类的实例共享的,不同的对象可以访问相同的静态成员变量,同时,静态成员变量声明时不需要在类外部再进行…

    other 2023年6月20日
    00
  • 电脑主机启动不了怎么办 按了开机按钮无反应解决方法

    电脑主机启动不了怎么办-按了开机按钮无反应 当我们按下电脑开机按钮后,主机没有反应,没有任何动静,这时候该怎么办呢? 检查电源线 首先我们需要检查的是电源线是否正常连接到了主机,有时候电源线可能会松动,导致电源无法正常供电。此时需要将电源线重新插拔一遍,确保连接良好。 检查电源开关 如果电源线连接正常,还是无法启动,可能是电源开关出现问题。我们可以先尝试使用…

    other 2023年6月27日
    00
合作推广
合作推广
分享本页
返回顶部