如何使用实时预览保存高帧率相机图像

时间:2015-04-14 16:33:42

标签: c# image winforms camera frame-rate

我有两个高速USB3相机(Ximea),想要编写一个用于图像记录的应用程序。帧率在VGA分辨率下高达500fps,但我也希望以170fps的速度使用2Mpx分辨率。 他们的.Net SDK告诉我,我应该简单地#34;得到"循环中的图像。 我的问题是我不知道如何获取图像并保存它们,同时仍然显示实时预览。每次我添加一些代码来实际更新图片框时,帧速率都会急剧下降。

目前我使用了一个用

调用的录音功能
Task.Run(() => Record());

并且在Record()内部我有一个循环获取位图

while(record == true)
{
    Camera.GetImage(out myImage, timeout); //From Ximea .Net SDK
    Info = Camera.GetLastImageParams();
    Timestamp = Info.GetTimeStamp();
    ThreadPool.QueueUserWorkItem(state => SaveImage(myImage, filepath, Timestamp));
}

,SaveImage为

private void SaveImage(Bitmap myImage, string filepath, double Timestamp)
{
    try
    {
        lock(myImage)
        {
             myImage.Save(filepath + Timestamp.ToString("0.00000") + ".tif");
        }
    }
    catch{}
}

如何在录制时显示实时预览,以及如何使整个代码更稳定(目前有一些丢帧因为"对象已在使用" -errors或&#34 ; GDI +中的一般错误"在Image.Save()调用中,我跳过了try / catch语句)?

2 个答案:

答案 0 :(得分:1)

我相信你可以告诉Ximea API在传入队列中需要多少个图像缓冲区...使用XI_PRM_BUFFER_POLICY和XI_PRM_BUFFERS_QUEUE_SIZE来使队列长度有点长。 然后,有一个线程,当激活时,将一个XI_IMG结构中的图像复制到您自己的缓冲区中。每隔n帧激活该线程(基于Ximea图像缓冲区队列大小的大小)...但不要在实际调用xiGetImage的循环中执行任何内存副本。你可能应该在你的线程中阻止以避免撕裂(因为如果你没有足够快地复制数据,Ximea代码可以再次使用相同的缓冲区...)但你可以动态调整缓冲区数量,以便您可以在您拥有的时间内完成复制。此外,如果您正在做一些需要很长时间的事情,您可以考虑将图像数据复制到另一个缓冲区...

伪代码(抱歉,它是C-ish):

// sync objects and a global image buffer pointer
CRITICAL_SECTION cs;
void *buf;
HANDLE ev;

int CopyImageThreadProc(...)
{
  while (true)
  {
    if (WaitOnSingleObject(ev) == WAIT_OBJ_0)
    {
      EnterCriticalSection(cs);
      // copy the image data at buf where ever you want
      LeaveCriticalSection(cs);
    }
  }
}

int main(...)
{
  // set up ximea api with appropriate buffering

  // create event and critsec, start thread

  while (!done)
  {
    XI_IMG img;
    xiGetImage(dev, 10, &img);

    // every 15 frames, tell your thread to go...
    // if you find that the critsec is causing a hiccup, you can adjust this
    // but remember to adjust the queue length, too
    // if you change this to TRY entercriticalsection, you can determine that
    if ((img.acq_nframe % 15) == 0)
    {
      EnterCriticalSection(cs);
      buf = img.bp;
      SetEvent(ev);
      LeaveCriticalSection(cs);
    }
  }

  // clean up
}

答案 1 :(得分:0)

将每个捕获的帧添加到队列中,然后让一个工作线程一次一个地获取这些图像并保存它们。尝试同时将多个图像写入磁盘很可能会变慢。此外,始终Dispose任何GDI个对象,否则您将很快遇到麻烦。我认为不这样做是为了给你例外。

至于显示图像,请确保不要尝试显示每张图像。你的显示器很可能以60赫兹的频率运行,所以任何比这更快的都是浪费。我也怀疑(随着GDI的表现),你不一定能够实现这一点。所以我建议你有第二个队列,要显示图像,如果你看到队列变得太大,你的程序将需要减慢一点,而不是将多少帧推入队列。

编辑:当然,正如@Franck所提到的,如果您的磁盘无法跟上,您的队列/缓冲区将很快填满。压缩图像可能有所帮助,假设它们具有适当的压缩内容并且处理器可以跟上。

编辑:您需要的是生产者 - 消费者模式。有很多方法可以做到这一点,但有一种可能是这样的:

// blocking collection
private BlockingCollection<Bitmap> m_Queue = ...

// camera thread
while( run )
{
    var bitmap = GrabFrame();
    m_Queue.Add( bitmap );
}

// worker thread
try
{        
    while( true )
    {
        // Take() will block if the queue is empty
        var bitmap = m_Queue.Take();
        bitmap.Save( ... );
        bitmap.Dispose();
    }
catch( InvalidOperationException )
{
     // you'll end up here if you call `m_Queue.CompleteAdding()`
     // (after the queue has been emptied, of course)
}

至于显示图像,您可能会使用类似的东西,并添加一些代码来确定是否需要推送新图像。