【问题标题】:How to save save high frame rate camera images with live preview如何使用实时预览保存保存高帧率相机图像
【发布时间】:2015-04-14 16:33:42
【问题描述】:

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

目前我使用了一个通过

调用的录制功能
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{}
}

如何在录制时显示实时预览以及如何使整个代码更稳定(目前由于“对象已在使用中”-错误或“GDI+ 中的一般错误”而导致一些丢帧Image.Save() 调用,我使用 try/catch 语句跳过)?

【问题讨论】:

  • 你知道保存到硬盘不是最好的主意。 2 mpx 大约是 2.5 mb 文件。您希望每秒 170 张图像,因此希望每秒向硬盘写入 425 mb,并且仍然能够将其加载到图片框中。如果您有 SSD,则写入速度可能会不足。有些人每秒几乎不能达到 300 mb。你想要一个缓冲区。 Ram 的读写速度要快得多。
  • @Franck 在我的情况下,我保存到 SSD 或 Ram Disk - 所以写入速度应该足够快。此外,图像是单色的,所以尺寸不是很大 - 2Mpx tifs 是 700KB 与这台相机。

标签: c# image winforms camera frame-rate


【解决方案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
}

【讨论】:

    【解决方案2】:

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

    至于显示图像,请确保您没有尝试显示每张图像。您的显示器很可能以 60 Hz 的频率运行,因此任何比这更快的都将是浪费。我还怀疑(凭借 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)
    }
    

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

    【讨论】:

    • 不幸的是,我不知道如何做到这一点 - 一些伪代码将不胜感激。 GDI 错误通常来自已保存的时间戳 - 不知道队列如何实现这一点,但显然有时已经存在具有相同时间戳的图像。
    • Chris 关于刷新率是对的,如果你的屏幕不是至少 170hz,170 fps 是没用的,我认为在 120hz 之后它会跳到 300hz。而且花这么多钱在这样的显示器上显示一个很小的视频是一个奇怪的选择。
    • @Franck 我显然不想在屏幕上显示 170 fps - 我只需要 30 fps 来查看正在发生的事情以及对象是否仍在焦点上。谢谢克里斯 - 会试试看。
    • @Chris 我试过你的代码,起初我认为它可以工作。在通过记录秒表对其进行测试后,我发现图像显然没有正确保存到集合中。我这样做是为了让 Worker 仅在录制完成后才启动(RAM 在我的机器上不是问题),并且保存的图像来自录制完成后。不知何故,新图像似乎覆盖了集合中的现有图像。执行 m_Queue.Add(new Bitmap(bitmap)) 有助于但会大大降低帧速率。你有什么想法吗?
    • @DanielM 这看起来确实很奇怪。如果您使用BlockingCollection 的默认无参数构造函数,它将在内部使用ConcurrentQueue(FIFO)并且没有最大容量。尝试用一些额外的数据为位图编写一个包装类,比如一个 image# 整数,看看是否所有的图像都通过了。
    猜你喜欢
    • 2012-07-04
    • 2018-05-12
    • 1970-01-01
    • 1970-01-01
    • 2017-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多