3

我需要在两个不同的线程中获得一个锁才能访问 EmguCv 中的位图(从网络摄像头填充)。我有一个“GetFrame”函数,可以查询相机并将它返回的内容放入 .NET 位图中。我有两个线程需要访问这个位图,一个需要写入位图并将位图分配给图片框,另一个需要读取位图,将其转换为图像对象并将其分配给 EMGU ImageBox。我首先锁定一个任意对象,然后执行我的操作。代码如下(_Camera.LiveFrame为Bitmap):

写/读线程:

while (_CaptureThreadRunning)
{
   lock (_Camera)
   { 
      // _Camera.GetFrame writes to the Bitmap
      if (_VideoPlaying && _Camera.GetFrame(500)) 
           pbLiveFeed.Invalidate();
    }
}
_Camera.CloseCamera(true);
_CaptureExitEvent.Set();           // Set to signal captureThread has finished

阅读/ImageBox 线程:

while (_ProcessThreadRunning)
{
   lock (_Camera)
   {
      //  _Camera.LiveFrame is the Bitmap
      procImage = new Image<Bgr, int>((Bitmap)_Camera.LiveFrame.Clone());          
      procImage.Draw(new Rectangle(10,20,20,15),new Bgr(Color.LightGreen), 5);

      ibProcessed.Image = procImage;
      ibProcessed.Invalidate();
    }
}
_ProcessExitEvent.Set();

这在大多数情况下运行良好,但是当我尝试 Clone() 位图时,时不时会出现“对象正在其他地方使用”错误。这不是正确的锁定方式吗?我不明白为什么这会导致问题。

附言。我的线程也不能再优雅地退出。我的循环之外的 .Set() 调用永远不会被调用。我猜线程是死锁的?

4

3 回答 3

3

GDI+ 有一个锁定机制,可以防止两个线程使用 Bitmap 对象——这是您收到的错误。

您正在尝试访问该位图,而 UI 线程已经在访问它。例如,1)您将位图分配给图片框,2)图片框无效然后重新绘制,3)您退出写/读线程锁,然后 4)读/图像框线程试图访问相同重绘仍在进行时的位图。

要解决这个问题,只需复制位图,并使用该副本进行操作。无论您给图片框什么,都不要假设您可以从非 UI 线程再次触摸它。

例如,在 _Camera.GetFrame 中:

// Get the bitmap from the camera
capturedBitmap = GetFromCamera();

// Clone the bitmap first before assigning to the picture box
_Camera.LiveFrame = new Bitmap(capturedBitmap);

// Assign to the picture box
pbLiveFeed.Image = capturedBitmap;

现在, _Camera.LiveFrame 应该可以从线程访问,只要您有适当的锁定。

我会在这里考虑其他几个问题:

  1. 您提到您正在锁定一个“任意对象”,但 _Camera 似乎并非如此 - 它是一个可以以不可预测的方式在其他地方使用的对象。我建议制作一个仅用于锁定的对象,例如

    object lockObject = new lockObject;
    lock (lockObject)
    {
        // put your synchronized code here
    }
    
  2. Bitmap.Clone() 创建一个与原始位图共享像素数据的位图。当您转换为图像对象以分配给 EMGU ImageBox 时,您正在使用该克隆,它维护对位图的引用。因此,在这种情况下,创建一个新位图而不是使用 Clone() 对我来说似乎更安全。

于 2013-02-03T16:53:49.857 回答
0

我认为你完全可以避免在这里使用显式锁。只需将位图创建操作移至接收线程 - 这样您将保证原始位图上的所有操作都从接收线程执行。

位图创建完成后,将对新位图的引用传递给读取线程 - 将其分配给为其提供服务的类的成员。引用赋值是一个原子操作,你可以保证读取线程要么看到新值,要么看到旧值。虽然您只在完成创建位图后才传递参考,但您可以保证只有读取线程才能使用它

于 2013-02-03T17:13:22.257 回答
0

您可以使用 ManualResetEvent 代替锁来编排读取操作和写入。一个例子是这样的。

写/读线程:

while (_CaptureThreadRunning)
{
   imageBoxTrhead.WaitOne();
   readWriteThread.Reset();

   // _Camera.GetFrame writes to the Bitmap
   if (_VideoPlaying && _Camera.GetFrame(500)) 
      pbLiveFeed.Invalidate();

   readWriteThread.Set();

}
_Camera.CloseCamera(true);
_CaptureExitEvent.Set();

阅读/ImageBox 线程:

while (_ProcessThreadRunning)
{
   readWriteThread.WaitOne();
   imageBoxTrhead.Reset();

   //  _Camera.LiveFrame is the Bitmap
   procImage = new Image<Bgr, int>((Bitmap)_Camera.LiveFrame.Clone());          
   procImage.Draw(new Rectangle(10,20,20,15),new Bgr(Color.LightGreen), 5);

   imageBoxTrhead.Set();

   ibProcessed.Image = procImage;
   ibProcessed.Invalidate();
}
_ProcessExitEvent.Set();

默认情况下,readWriteThread 和 imageBoxTrhead 是 ManualResetEvent 对象。

于 2013-02-06T15:05:05.593 回答