【问题标题】:Using a background worker to efficiently write to a GUI thread使用后台工作程序有效地写入 GUI 线程
【发布时间】:2015-07-28 21:15:46
【问题描述】:

我正在使用 BackgroundWorker 从相机中提取视频并将其写入 WinForms 表单上的 PictureBox。在 BW 线程中,我只是从相机中拉出一帧,将其放入 PictureBox,休眠,然后继续:

while (CaptureThreadRunning)
{
    Thread.Sleep(5);
    Image tmp = Camera.GetFrame(500);
    pbCameraFeed.Image = tmp;
    //this.BeginInvoke(new MethodInvoker(delegate { pbCameraFeed.Image = Camera.GetFrame(500); }));
}

问题是最终在我的屏幕上调整或移动表单会引发异常System.InvalidOperationException,并在pbCameraFeed.Image = tmp;行上显示消息Additional information: Object is currently in use elsewhere.

我假设库试图在我的 while 循环同时绘制与 PictureBox 相关的内容,所以我切换到上面注释掉的 this.BeginInvoke 实现。不幸的是,这大大降低了我的帧率。我在一台速度非常慢的 Mini PC 上运行此代码,这可能会导致该问题。

我真正想要的是一种可靠地更新我的图形用户界面的方法,它不会使我的帧率下降近一半。还有其他标准方法可以做到这一点吗? BW 线程似乎非常适合此应用程序,但我是否遗漏了什么?

谢谢

【问题讨论】:

  • 如果你用try {} catch {} 捕获这个异常怎么办?
  • 您是否尝试保留BeginInvoke 但增加您的Sleep 时间? 5 毫秒低于Thread.Sleep 的精度(大约为 15 毫秒,具体取决于您的操作系统上的活动),因此您绝对可以增加它。这应该有助于减少负载/延迟。
  • 增加睡眠时间会减少延迟?我假设在 PictureBox 更新之间增加更多时间会进一步降低帧速率(即使它是很小的数量)。我会尝试调整睡眠时间,看看有什么效果。
  • @Fabjan 不是一个好的建议。例外是有原因的。它说你做错了什么,应该修复而不是隐藏在 try/catch 后面。
  • @user912447 相反,你现在每 5 毫秒告诉 UI 线程“嘿,停止你正在做的事情并为我更新那个图片框”。如果您在更新上放轻松一点,UI 将有更多时间来做更多的事情,比如重绘自身和响应用户的交互。这也是为什么您将长时间运行的操作放在 BW 上的原因;所以 UI 可以做其他事情并保持响应。因此,如果您将睡眠时间设置为 33 毫秒左右,您应该会获得相当于 30 FPS 的效果。

标签: c# multithreading winforms webcam


【解决方案1】:

如果我是你,我肯定会查看 AForge.NET Framework。无需重新发明轮子;)

http://www.aforgenet.com/framework/samples/video.html

AForge.NET 是一个开源 C# 框架,专为开发人员和 计算机视觉和人工智能领域的研究人员 智能——图像处理、神经网络、遗传算法、 模糊逻辑、机器学习、机器人等。

该框架由一组库和示例组成 展示其功能的应用程序:

AForge.Imaging - 带有图像处理例程和过滤器的库;

AForge.Vision - 计算机视觉库;

AForge.Video - 一组用于视频处理的库

...

【讨论】:

  • AForge 看起来确实很不错。我认为需要一些时间来使用这个框架,而不仅仅是使用我的小 while 循环。然而,看起来 AForge 可以相对轻松地解决我遇到的 FPS 问题。
  • 作为一个小更新,我确实出于不相关的原因开始使用 AForge,但不幸的是,AForge 似乎没有办法与不适用于 DirectShow 的相机接口。我最初使用的是非 DirectShow 相机,所以如果我想坚持使用 AForge,我也将不得不切换相机......
【解决方案2】:

我建议不要使用 PictureBox,而是直接绘制到 UserControl 表面。这可以通过在 UserControl 的 Paint 和 Invalidate 事件中添加代码来轻松完成。

下面的示例创建了一个用户控件,该控件具有一个 BitMap 属性,每次控件失效时都会将其绘制到其表面。 因此,例如,要随机渲染文件夹 D:\MyPictures 中的 JPG 图像,您可以执行以下操作:

using System.Windows.Forms;
using System.Drawing;

void Main()
{
    var pictures = Directory.GetFiles(@"D:\MyPictures", "*.jpg");
    var rnd = new Random();
    var form = new Form();
    var control = new MyImageControl() { Dock = DockStyle.Fill };
    form.Controls.Add(control);

    var timer = new System.Windows.Forms.Timer();
    timer.Interval = 50;
    timer.Tick += (sender, args) => 
    {
        if (control.BitMap != null)
            control.BitMap.Dispose();
        control.BitMap = new Bitmap(pictures[rnd.Next(0, pictures.Length)]);
        control.Invalidate();
    };
    timer.Enabled = true;
    form.ShowDialog();
}

public class MyImageControl : UserControl // or PictureBox
{
    public Bitmap BitMap { get; set; }
    public MyImageControl()
    {
        this.Paint += Graph_Paint;
        this.Resize += Graph_Resize;
    }
    private void Graph_Paint(object sender, PaintEventArgs e)
    {
        if (this.BitMap != null)
        {
            lock (this.BitMap)
            {
                Graphics g = e.Graphics;
                g.DrawImage(this.BitMap, this.ClientRectangle);
            }
        }
    }
    private void Graph_Resize(object sender, System.EventArgs e)
    {
        this.Invalidate();
    } 
}

我认为这可以很容易地更改为渲染相机图像而不是图片文件。

代码在LinqPad上测试

【讨论】:

  • 听起来很有趣。但是,在 PictureBox 上使用 UserControl 表面有什么好处?
  • 我认为使用 UserControl 会更有效,但我不确定。尝试将MyImageControl : UserControl 更改为MyImageControl : PictureBox。它应该可以工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多