简体   繁体   中英

Using a background worker to efficiently write to a GUI thread

I am using a BackgroundWorker to pull video from a camera and write it to a PictureBox on my WinForms form. In the BW thread I simply pull a frame from the camera, put it into the PictureBox, sleep, and continue:

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

The issue is that eventually adjusting or moving the form around my screen will throw the exception System.InvalidOperationException with the message Additional information: Object is currently in use elsewhere. on the line pbCameraFeed.Image = tmp;

I assume that the library is trying to paint something to do with the PictureBox at the same time as my while loop is, so I switched to the this.BeginInvoke implementation that is commented out above. Unfortunately that cuts my framerate significantly. I am running this code on a very slow Mini PC which may be contributing to the issue.

What I really want is a way to update my GUI with the image reliably that doesn't drop my framerate by nearly half. Are there other standard ways to do this? A BW thread seemed perfect for this application, but am I missing something?

Thanks

If I were you I would definitely check out the AForge.NET Framework. No need to reinvent the wheel ;)

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

AForge.NET is an open source C# framework designed for developers and researchers in the fields of Computer Vision and Artificial Intelligence - image processing, neural networks, genetic algorithms, fuzzy logic, machine learning, robotics, etc.

The framework is comprised by the set of libraries and sample applications, which demonstrate their features:

AForge.Imaging - library with image processing routines and filters;

AForge.Vision - computer vision library;

AForge.Video - set of libraries for video processing ;

...

I would recommend not to use a PictureBox, and instead directly draw to a UserControl surface. This can be easily done by addig code to the Paint and Invalidate events of a UserControl.

This example below, creates a user control which has a BitMap property that it's drawed to its surface every time the control is invalidated. So, for example, to randomly render JPG images from folder D:\\MyPictures, you can do the following:

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();
    } 
}

I think this can be easily changed to render the camera images instead of the picture files.

The code was tested on LinqPad

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM