信号量是适合此视频序列捕获/保存作业的工具吗?

发布于 2024-10-05 06:55:59 字数 3093 浏览 9 评论 0原文

我正在使用 C# (.NET 4.0) 开发一个 WPF 项目,从高速摄像机捕获 300 个视频帧的序列,并需要将其保存到磁盘(BMP 格式)。视频帧需要以近乎精确的时间间隔捕获,因此我无法在捕获帧时将帧保存到磁盘 - 磁盘 I/O 是不可预测的,并且会影响帧之间的时间间隔。采集卡有大约 60 个可用帧缓冲区。

我不确定实施此问题的解决方案的最佳方法是什么。我最初的想法是创建一个“BufferToDisk”线程,在帧缓冲区可用时保存图像。在这种情况下,主线程捕获帧缓冲区,然后向线程发出信号以指示可以保存帧。问题是捕获帧的速度比线程保存文件的速度快,因此需要某种同步来处理这个问题。我认为信号量将是完成这项工作的好工具。不过,我从未以这种方式使用过信号量,所以我不确定如何继续。

这是解决这个问题的合理方法吗?如果是这样,有人可以发布一些代码来帮助我开始吗?

非常感谢任何帮助。

编辑: 查看链接的“C# 中的线程 - 第 2 部分”书籍摘录后,我决定通过调整“ProducerConsumerQueue”类示例来实现该解决方案。这是我改编的代码:

class ProducerConsumerQueue : IDisposable
{
    EventWaitHandle _wh = new AutoResetEvent(false);
    Thread _worker;
    readonly object _locker = new object();
    Queue<string> _tasks = new Queue<string>();

    public ProducerConsumerQueue()
    {
        _worker = new Thread(Work);
        _worker.Start();
    }

    public void EnqueueTask(string task)
    {
        lock (_locker) _tasks.Enqueue(task);
        _wh.Set();
    }

    public void Dispose()
    {
        EnqueueTask(null);     // Signal the consumer to exit.
        _worker.Join();         // Wait for the consumer's thread to finish.
        _wh.Close();            // Release any OS resources.
    }

    void Work()
    {
        while (true)
        {
            string task = null;
            lock (_locker)
                if (_tasks.Count > 0)
                {
                    task = _tasks.Dequeue();
                    if (task == null)
                    {
                        return;
                    }
                }
            if (task != null)
            {
                // parse the parameters from the input queue item
                string[] indexVals = task.Split(',');
                int frameNum = Convert.ToInt32(indexVals[0]);
                int fileNum = Convert.ToInt32(indexVals[1]);
                string path = indexVals[2];
                // build the file name
                string newFileName = String.Format("img{0:d3}.bmp", fileNum);
                string fqfn = System.IO.Path.Combine(path, newFileName);
                // save the captured image to disk
                int ret = pxd_saveBmp(1, fqfn, frameNum, 0, 0, -1, -1, 0, 0);
            }
            else
            {
                _wh.WaitOne();         // No more tasks - wait for a signal
            }
        }
    }
}

在主例程中使用该类:

// capture bitmap images and save them to disk
using (ProducerConsumerQueue q = new ProducerConsumerQueue())
{
     for (int i = 0; i < 300; i++)
     {
         if (curFrmBuf > numFrmBufs)
         {
              curFrmBuf = 1;  // wrap around to the first frame buffer
         }

         // snap an image to the image buffer
         int ret = pxd_doSnap(1, curFrmBuf, 0);

         // build the parameters for saving the frame to image file (for the queue)
         string fileSaveParams = curFrmBuf + "," + (i + 1) + "," + newPath;
         q.EnqueueTask(fileSaveParams);

         curFrmBuf++;
    }
}

非常漂亮的类——用于此功能的少量代码。

非常感谢你们的建议,伙计们。

I am working on a WPF project with C# (.NET 4.0) to capture a sequence of 300 video frames from a high-speed camera that need to be saved to disk (BMP format). The video frames need to be captured in near-exact time intervals, so I can't save the frames to disk as they're being captured -- the disk I/O is unpredictable and it throws off the time intervals between frames. The capture card has about 60 frame buffers available.

I'm not sure what the best approach is for implementing a solution to this problem. My initial thoughts are to create a "BufferToDisk" thread that saves the images from the frame buffers as they become available. In this scenario, the main thread captures a frame buffer and then signals the thread to indicate that it is OK to save the frame. The problem is that the frames are being captured quicker than the thread can save the files, so there needs to be some kind of synchronization to deal with this. I was thinking a Semaphore would be a good tool for this job. I have never used a Semaphore in this way, though, so I'm not sure how to proceed.

Is this a reasonable approach to this problem? If so, can someone post some code to get me started?

Any help is much appreciated.

Edit:
After looking over the linked "Threading in C# - Part 2" book excerpt, I decided to implement the solution by adapting the "ProducerConsumerQueue" class example. Here is my adapted code:

class ProducerConsumerQueue : IDisposable
{
    EventWaitHandle _wh = new AutoResetEvent(false);
    Thread _worker;
    readonly object _locker = new object();
    Queue<string> _tasks = new Queue<string>();

    public ProducerConsumerQueue()
    {
        _worker = new Thread(Work);
        _worker.Start();
    }

    public void EnqueueTask(string task)
    {
        lock (_locker) _tasks.Enqueue(task);
        _wh.Set();
    }

    public void Dispose()
    {
        EnqueueTask(null);     // Signal the consumer to exit.
        _worker.Join();         // Wait for the consumer's thread to finish.
        _wh.Close();            // Release any OS resources.
    }

    void Work()
    {
        while (true)
        {
            string task = null;
            lock (_locker)
                if (_tasks.Count > 0)
                {
                    task = _tasks.Dequeue();
                    if (task == null)
                    {
                        return;
                    }
                }
            if (task != null)
            {
                // parse the parameters from the input queue item
                string[] indexVals = task.Split(',');
                int frameNum = Convert.ToInt32(indexVals[0]);
                int fileNum = Convert.ToInt32(indexVals[1]);
                string path = indexVals[2];
                // build the file name
                string newFileName = String.Format("img{0:d3}.bmp", fileNum);
                string fqfn = System.IO.Path.Combine(path, newFileName);
                // save the captured image to disk
                int ret = pxd_saveBmp(1, fqfn, frameNum, 0, 0, -1, -1, 0, 0);
            }
            else
            {
                _wh.WaitOne();         // No more tasks - wait for a signal
            }
        }
    }
}

Using the class in the main routine:

// capture bitmap images and save them to disk
using (ProducerConsumerQueue q = new ProducerConsumerQueue())
{
     for (int i = 0; i < 300; i++)
     {
         if (curFrmBuf > numFrmBufs)
         {
              curFrmBuf = 1;  // wrap around to the first frame buffer
         }

         // snap an image to the image buffer
         int ret = pxd_doSnap(1, curFrmBuf, 0);

         // build the parameters for saving the frame to image file (for the queue)
         string fileSaveParams = curFrmBuf + "," + (i + 1) + "," + newPath;
         q.EnqueueTask(fileSaveParams);

         curFrmBuf++;
    }
}

Pretty slick class -- a small amount of code for this functionality.

Thanks so much for the suggestions, guys.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

蓝梦月影 2024-10-12 06:55:59

当然,听起来很合理。您可以使用信号量或其他线程同步原语。这听起来像是一个标准的生产者/消费者问题。看看这里一些伪代码

Sure, sounds reasonable. You can use semaphores or other thread synchronization primitives. This sounds like a standard producer/consumer problem. Take a look here for some pseudo-code
.

倾城花音 2024-10-12 06:55:59

如果磁盘速度太慢(例如某些其他进程将其固定)以致 60 个帧缓冲区不够,会发生什么情况?也许您需要一个 BufferToMemory 和 BufferToDisk 线程或某种组合。您将希望主线程(捕获到缓冲区)具有最高优先级,BufferToMemory 中等,BufferToDisk 最低。

无论如何,回到信号量,我建议您阅读以下内容: http://www.albahari.com /threading/part2.aspx#_Semaphore。信号量应该可以满足您的需要,但我会推荐 SemaphoreSlim (.NET 4)。

What happens if the disk is so slow (e.g. some other process pegs it) that 60 frame buffers are not enough? Maybe you'll need a BufferToMemory and BufferToDisk thread or some sort of combination. You'll want the main thread (capture to buffer) to have the highest priority, BufferToMemory medium, and BufferToDisk the lowest.

Anyway, back to Semaphores, I recommend you read this: http://www.albahari.com/threading/part2.aspx#_Semaphore. Semaphores should do the trick for you, though I would recommend SemaphoreSlim (.NET 4).

暗地喜欢 2024-10-12 06:55:59

由于您将此视为生产者/消费者问题(根据您对 @siz 答案的回复来判断),您可能需要查看 BlockingCollection,它正是为此类场景而设计的。

它允许任意数量的生产者线程将数据推送到集合中,并允许任意数量的消费者线程将数据再次拉出。在这种情况下,您可能只需要一个生产者线程和一个消费者线程。

BlockingCollection 完成所有工作,确保消费者线程仅在生产线程表示还有更多工作要做时才唤醒并处理工作。它还负责允许建立工作队列。

Since you're treating this as a producer/consumer problem (judging by your reply to @siz's answer), you might want to look at BlockingCollection<T>, which is designed for precisely this sort of scenario.

It allows any number of producer threads to push data into the collection, and any number of consumer threads to pull it out again. In this case, you probably want just one producer and one consumer thread.

The BlockingCollection<T> does all the work of making sure the consumer thread only wakes up and processes work once the producing thread has said that there's more work to do. And it also takes care of allowing a queue of work to build up.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文