下面是“WPF中在摄像头视频上叠加控件的解决方案”的完整攻略,包含以下内容:
1. 必备条件
要在摄像头视频上叠加控件,需要满足以下两个条件:
- 需要使用WPF作为UI框架。
- 使用的摄像头必须支持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:在视频中叠加一个文本框
在此示例中,我们将在视频上方叠加一个可以编辑的文本框。
- 首先,新建一个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控件中。
- 接下来,我们需要在
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控件。
- 修改
VideoPlayer
控件,在其构造函数中给视频分配一些初始化大小。
public VideoPlayer()
{
Width = 640;
Height = 480;
// ...
}
这是必须的,这样才能使视频控件显示正常。
- 最后,需要在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:在视频中显示当前时间
在此示例中,我们将在视频上方叠加一个可以实时显示当前时间的控件。
- 新建一个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控件中。
- 为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控件上实时显示。
- 在
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技术站