3

再会,

一个网络摄像头类每秒大约有 30 帧,所有这些帧都将保存在一个向量中(就像一个队列)。然后 3 个异步线程将读取队列,并尝试完成它们的工作(以保存这些图像)。为什么队列满了?所以问题是这些线程比网络摄像头慢。

Procedure TSaveThread.Execute;
begin
   while not terminated  do
   begin
      elElement:=NIL;

      EnterCriticalSection(CritSect);
         if iElementsLength>=0 then
         begin
            elElement:=vElements[iElementsLength];
            Dec(iElementsLength);
         end;
      LeaveCriticalSection(CritSect);

      if elElement<>NIL then
      begin
         JpegImg.Assign(elElement.bmWebcam) ;
         JpegImg.SaveToFile('Save\'+elElement.sTime+'.jpg') ;
         elElement.Free;
      end;

      Sleep(20);
   end;
end;

图像添加到队列中。

//------------------------------------------------------------------------------
Procedure TWebcam.OnSave(Sender:TObject; bmWebcam:TBitmap);
begin
   EnterCriticalSection(CritSect);
      inc(iElementsLength);
      vElements[iElementsLength]:=TElement.Create(bmWebcam);
   LeaveCriticalSection(CritSect);
end;

创建线程。

for i:=0 to 2 do
    TSaveThread.Create(false);

问题是,这些线程无法保存所有这些图像。为什么?如何改进我的线程?

德尔福版本:德尔福 XE2

网络摄像头帧大小:1280x760 或 960x600 此处完整源代码:http ://pastebin.com/8SekN4TE

4

2 回答 2

15

我写了以下程序:

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Vcl.Graphics, Vcl.Imaging.jpeg, 
  System.IOUtils, System.Diagnostics;

var
  i: Integer;
  bmp: TBitmap;
  jpeg: TJPEGImage;
  Stopwatch: TStopwatch;

begin
  bmp := TBitmap.Create;
  bmp.SetSize(1280, 760);
  jpeg := TJPEGImage.Create;
  Stopwatch := TStopwatch.StartNew;
  for i := 1 to 100 do begin
    jpeg.Assign(bmp);
    jpeg.SaveToFile('C:\desktop\temp\'+TPath.GetRandomFileName);
  end;
  Writeln(Stopwatch.ElapsedMilliseconds);
  Readln;
end.

它将 1280x760 像素位图转换为 JPEG 图像,然后保存到磁盘。它这样做了 100 次。在我的机器上,这需要 9 秒。这是每秒 11 张图像的吞吐量。如果我跳过转换为 JPEG 的步骤并直接保存位图,我可以获得每秒 150 张图像的吞吐量。显然,转换为 JPEG 是一个瓶颈。

您正在寻找每秒 30 张图像。虽然多线程有帮助,但我怀疑你有一台四核机器。一个处理器用于网络摄像头,三个用于节省。因此,如果您只有三个可用线程,那么您可能很难达到每秒 30 帧所需的吞吐量。我的机器上的理论峰值是 33。如果你没有达到每秒 30 帧,那么你的队列显然会溢出。

显而易见的结论是,您需要找到一个更快的 JPEG 转换库。我很确定存在这样的库。例如,我认为这libjpeg应该快得多。

至于您现有的代码,有一些明显的缺陷:

  1. Sleep通常应该避免。在您的情况下,如果最后一次尝试提取图像成功,那么睡觉就是自杀。不要那样做。您应该使用真正的线程队列。一种允许对同步对象进行适当阻塞等待的方法。使用事件对象和您最喜欢的非线程队列自己制作一个实际上非常简单。
  2. 你在打电话时拿着锁TElement.Create(bmWebcam)。这将阻碍扩展。分配TElement.Create(bmWebcam)给锁外的局部变量。然后分配给共享数据里面的锁。

因此,您可以通过首先删除对Sleep. 然后变成TWebcam.OnSave这样:

Procedure TWebcam.OnSave(Sender:TObject; bmWebcam:TBitmap);
var
  NewElement: TElement;
begin
  NewElement := TElement.Create(bmWebcam);
  EnterCriticalSection(CritSect);
    inc(iElementsLength);
    vElements[iElementsLength] := NewElement;
  LeaveCriticalSection(CritSect);
end;

这些建议会有所帮助,但我认为您需要解决基本问题,即 JPEG 转换。

于 2012-12-12T13:59:34.280 回答
4

多线程不会加速您的媒体(硬盘)。

事实上,它可以通过并行写访问减慢速度。

首先,您必须测量您的媒体(硬盘)是否能够在不到 33 毫秒的时间内存储图像 - 因为每 33.333 毫秒您将从网络摄像头获得一张新图像。

如果没有,你不能指望让它运行。

你应该(和/或)

  • 使用更多硬盘(例如每个线程一个)
  • 使用更多缓存(例如缓存控制器)
  • 使用更快的硬盘(例如 SSD)
  • 使用较小的图像(降低分辨率)
  • 删除一些图像

如果你需要快,不要浪费时间

if elElement<>NIL then
  begin
     JpegImg.Assign(elElement.bmWebcam) ;
     JpegImg.SaveToFile('Save\' + elElement.sTime + '.jpg' );
     elElement.Free;
  end
else
  Sleep(20);

OTL 不会帮助您更快地获取媒体,但会更干净:o)

所以你应该看看

于 2012-12-12T13:52:29.200 回答