使用后台工作线程有效地写入GUI线程

3

我正在使用一个BackgroundWorker来从摄像头中获取视频并写入到WinForms窗体上的PictureBox中。在BW线程中,我只需从摄像头中获取一帧图像,将其放入PictureBox中,执行sleep操作,然后继续执行:

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,并显示消息Additional information: Object is currently in use elsewhere.,该异常发生在pbCameraFeed.Image = tmp;这一行。
我猜测库正在尝试同时绘制与我的while循环有关的PictureBox内容,因此我切换到了上面注释掉的this.BeginInvoke实现。不幸的是,这样会显著降低我的帧率。我在一个非常慢的Mini PC上运行这段代码,这可能是问题的原因之一。
我真正想要的是一种可靠地更新GUI图像的方法,而且不会使我的帧率减少近一半。还有其他标准的方法吗?对于这个应用程序来说,BW线程似乎是完美的,但我错过了什么吗?
谢谢。

你尝试过保留 BeginInvoke 但增加 Sleep 时间吗?5 毫秒低于 Thread.Sleep 的精度(大约在 15 毫秒左右,取决于操作系统的活动情况),因此你肯定可以增加它。这应该有助于减少负载/延迟。 - Pierre-Luc Pineault
3
@Fabjan的建议并不是很好。异常会有其存在的原因。它表明你正在做错误的事情,需要被修复而不是被try/catch隐藏起来。 - Sriram Sakthivel
1
相反,您目前正在告诉UI线程“嘿,停止您正在做的事情并为我更新那个图片框”,每5毫秒一次。如果您减少更新频率,UI将有更多时间执行其他操作,如重新绘制自身和响应用户的交互。这也是为什么将长时间运行的操作放在BW上的原因; 这样UI就可以做其他事情并保持响应性。因此,如果您将睡眠设置为约33ms,则应获得30FPS的等效值。 - Pierre-Luc Pineault
我不知道 Camera.GetFrame(500); 是否是一项昂贵的操作,但简单的计时器是否可以同样发挥作用呢? - LarsTech
@Enigmativity 当然:http://i.imgur.com/N1rLrFc.png - user912447
显示剩余9条评论
2个回答

3
如果我是你,我一定会去看看AForge.NET框架。没有必要重新发明轮子 ;)

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

AForge.NET是一个开源的C#框架,旨在为计算机视觉和人工智能领域的开发人员和研究人员提供支持 - 包括图像处理、神经网络、遗传算法、模糊逻辑、机器学习、机器人等。该框架由一组库和示例应用程序组成,演示其功能:AForge.Imaging - 图像处理例程和过滤器库;AForge.Vision - 计算机视觉库;AForge.Video - 视频处理库集合;...

AForge看起来非常不错。我认为需要一些时间才能将其移植到使用这个框架而不仅仅是使用我的小while循环。但它看起来AForge可以相对容易地解决我遇到的FPS问题。 - user912447
只是一个小更新,我开始使用AForge,但不幸的是,AForge似乎没有一种与不使用DirectShow的相机进行接口的方法。最初我使用的是非DirectShow相机,因此如果我想坚持使用AForge,我还需要更换相机... - user912447

0
我建议不要使用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 上进行了测试。


听起来很有趣。然而,使用UserControl表面相比PictureBox有什么好处呢? - user912447
我认为使用UserControl会更有效率,但我并不确定。尝试将 MyImageControl : UserControl 改为 MyImageControl : PictureBox。它应该能行。 - thepirat000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接