我有两个高速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语句)?
答案 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)
}
至于显示图像,您可能会使用类似的东西,并添加一些代码来确定是否需要推送新图像。