Click here to Skip to main content
15,886,689 members
Please Sign up or sign in to vote.
2.50/5 (2 votes)
Hello,

I am developing an application using EmguCV. I wanto to take frames from 2 seperate webcams and display them in 2 seperate pictureboxes. I use a backgroundworker to make the operation, but when i try to update pictureboxes i get "Cross thread operation..." error. How can i handle this problem? Here a part of my code:

C#
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            Capture cam = new Capture(1);
            Capture cap1 = new Capture(0);
          
            Bitmap b;
            Bitmap b1;

            while (cam.Grab())
            {
                using (b = cam.QuerySmallFrame().ToBitmap())
                using (b1 = cam1.QuerySmallFrame().ToBitmap())
                {
                    pictureBox1.Image = b;
                    pictureBox2.Image = b1;
                    this.Refresh();
                }

            }
        }

 private void button3_Click(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }
Posted
Comments
Herman<T>.Instance 20-Nov-14 9:54am    
read more about the use of BackGroundWorker. It works in another thread then the thread that shows the pictureboxes. You need ProcessChanged event to update the controls: See here
Çağrı Daşkın 20-Nov-14 10:12am    
Thank you for your quick response.
BillWoodruff 20-Nov-14 10:00am    
I wonder why you are not asking this on an EMGU forum: this question involves detailed knowledge of the software interface to multiple hardware devices.
Çağrı Daşkın 20-Nov-14 10:14am    
No, this is not a specific Emgu problem. This is about general cross threading subject.

Essentially, your problem is right here:

C#
pictureBox1.Image = b;
pictureBox2.Image = b1;
this.Refresh();


These are UI interactions and the WinFroms model dictates that all UI operations must execute on the UI thread.

Fortunately, fixing the code is relatively easy. You need to queue a delegate to update the UI on the main thread using the BeginInvoke(..) method.

C#
this.BeginInvoke((Action)delegate {
   pictureBox1.Image = b;
   pictureBox2.Image = b1;
});


When you want to execute code on the UI thread, you have two options:

- synchronous: queue a delegate and wait for it to be fully executed (using the Invoke(..) method,
- asynchronous: queue the delegate and resume processing immediately, without waiting for the delegate to finish executing (or event to start) (using the BeginInvoke(..))

Which method you choose depends on your needs but in your case the asynchronous option is most likely the right one since you don't care about the result of updating the picture boxes, and you want to resume capturing as soon as possible.

Also, the this.Refresh() call is not necessary because once you update the picture boxes they will automatically refresh the UI.
 
Share this answer
 
v2
Comments
Sergey Alexandrovich Kryukov 20-Nov-14 13:40pm    
Why BeginInvoke and not Invoke? You don't use return value and don't take care of the order of some operations.
—SA
mikanu 28-Nov-14 14:57pm    
@Sergey I mentioned in the answer why BeginInvoke and not Invoke. There is no reason to wait around for the picture box update to complete.
Çağrı Daşkın 21-Nov-14 2:59am    
Thank you very much for your reply. I did it with your way, but while the action is going on, the UI is not redponding. I can't click any buttons or move the form.
mikanu 28-Nov-14 15:01pm    
@Çağrı Daşkın if you're running the capture on a background thread and updating UI using BeginInvoke and it's still not responsive it might be because you're making too many calls to swap the image in the picture box. A better option might be to buffer the most recent image from the PictureBox and then have a timer on the UI thread update the picture box every so many frames. I'll post another answer as this is a different technique.
In addition to Solution 1:

Most likely, you need to use Invoke, not BeginInvoke. Some background:

You cannot call anything related to UI from non-UI thread. Instead, you need to use the method Invoke or BeginInvoke of System.Windows.Threading.Dispatcher (for both Forms or WPF) or System.Windows.Forms.Control (Forms only).

You will find detailed explanation of how it works and code samples in my past answers:
Control.Invoke() vs. Control.BeginInvoke()[^],
Problem with Treeview Scanner And MD5[^].

See also more references on threading:
.NET event on main thread[^],
How to get a keydown event to operate on a different thread in vb.net[^],
Control events not firing after enable disable + multithreading[^].

—SA
 
Share this answer
 
Comments
Çağrı Daşkın 21-Nov-14 3:00am    
Thank you very much, Sergey.
Sergey Alexandrovich Kryukov 21-Nov-14 11:23am    
You are welcome. Will you accept the answer formally?
—SA
Aside from the Invoke and BeginInvoke topics covered in the previous answer, I'd like to add another possible method of implementing the updating of the captured images on the UI.

The idea is to let the capture thread capture as fast as possible and let the UI thread updated the UI only when it needs to, based on a timer that runs at a controlled refresh rate of approx. 25 frames per second. The UI update frame rate can then be adjusted independent of the camera capture rate.

C#
// object that will be used to synchronize UI & capture thread
private Object syncRoot = new Object();
// two Bitmap resources that will hold the most recent captured image
private Bitmap lastBitmap1;
private Bitmap lastBitmap2;
// a timer that will run on the UI thread, to update the UI periodically
private System.Windows.Forms.Timer uiUpdateTimer;

// this method needs to be called on form load
private void InitUITimer()
{
    uiUpdateTimer = new System.Windows.Forms.Timer();
    // run every 40 ms (25 frames / second)
    uiUpdateTimer.Interval = 40;  
    uiUpdateTimer.Tick += uiUpdateTimer_TickHandler;
    uiUpdateTimer.Start();
}

private void uiUpdateTimer_TickHandler(object sender, EventArgs e)
{
   lock(syncRoot)
   {
       if(lastBitmap1 != null) pictureBox1.Image = lastBitamp1;
       if(lastBitmap2 != null) pictureBox2.Image = lastBitamp2;
   }
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    Capture cam = new Capture(1);
    Capture cap1 = new Capture(0);
          
    while (cam.Grab())
    {         
       Bitmap b1 = cam.QuerySmallFrame().ToBitmap();
       Bitmap b2 = cam1.QuerySmallFrame().ToBitmap();
       lock(syncRoot)
       {
          if(lastBitmap1 != null) lastBitamp1.Dispose();
          lastBitmap1 = b1;
          if(lastBitmap2 != null) lastBitamp2.Dispose();
          lastBitmap2 = b2;
       }
    }
}
 
Share this answer
 
v2
Comments
Çağrı Daşkın 29-Nov-14 5:35am    
Wow! That fully solved the problem. Thank you very much.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900