异步方法未将帧添加到我的列表中

问题描述 投票:0回答:1

我一直在努力使用 Emgu CV 库从视频中读取帧,并使用计时器在图片框中播放帧以创建视频的印象。这一次,我使用了一种相当复杂的方法,其中我有一个异步方法,可以从视频中读取帧并将它们添加到 Mat 对象列表中。然后,我将 Form 订阅到 Application.Idle 事件处理程序,该处理程序检查异步方法返回的任务对象是否不为 null 并且已完成。如果满足这些条件,则启用计时器,开始循环遍历 Mat 列表并以 33 毫秒的间隔将它们显示在图片框中。然而,当我单步执行调试器时,Mats 列表为空,并且计数为 0。我什至在异步方法中执行了 Debug.WriteLine 来监视其正在执行的操作,但它没有添加 Mat(这基本上是一个图像容器 l )到我的列表。我已经使用该库查询了视频的帧计数,编译器返回帧计数 69,这意味着视频有帧。帮助我使这个异步方法将帧读取到列表中。 异步方法的实现细节

private async Task ReadFrames(){
  if(video !=null){
   await Task.Run())=>{
     while(video.Grab()){
      //add the frames to the list
      frames.Add(video.QueryFrame());
      }
    });
  }
}

在我的类中,我定义了一个任务对象、帧列表、帧计数和用于播放视频的计时器对象。

public partial class Form1 :Form {
 //object to store the result of the asynchronous task
  private Task task = null;
  private Timer timer = new Timer();
  //integer to store the frame count
  private int frameCount = 0;
  //integer to keep track of the current frame 
  private int currentFrame = 0;
  //list to hold the frames in the video
  private List<Mat> frames = new List<Mat>();
  //inside the ctor, the timer is disabled by default
 public Form1(){
  timer.Interval = 33;
  timer.Enabled = false;
  timer.Elapsed += Timer_Elapsed;
 
  //subscribing to application idle event
  Application.Idle += Application_Idle;
 }
 //here I check if the task is completed and set to an instance 
   private void Application_Idle(object sender, EventArgs e){
    if(task !=null && task.IsCompleted) 
    { 
   //enable the timer to start looping through the frames
   timer.Enabled = true;
    }
  }
 }

现在 on elapse 检查当前帧索引是否小于总数,并以 33 毫秒的间隔不断变化。

private void Timer_Elapsed(object? sender, ElapsedEventArgs e){
  if(currentFrame < frameCount){
    pictureBox1.Image = ToBitmap(frames[currentFrame];
    //Update the index
    currentFrame +=1;
    Task.Delay(33);
  }else{
    //disable the timer
     timer.Enabled = false:
  }
}

当我单步执行调试器时,它显示帧数为 0,我该如何解决这个问题?

c# timer emgucv
1个回答
1
投票

这并不是进行异步捕获的好方法。它不必要地复杂,并且可能容易出错。如果您打算捕获一些固定数量的帧,您可以执行以下操作:

public Task<List<Mat>> CaptureFrames(int frameCount){

    List<Mat> CaptureLocal(){
        var result = new List<Mat>();
        for(int i = 0; i < frameCount && video.Read(out var image); i++){
            result.Add(image);
        }
        return result;
    }
    return Task.Run(CaptureLocal);
}

要进行播放,您可以执行类似的操作

public async Task Playback(IEnumerable<Mat> frames, CancellationToken cancel){
    foreach(var frame in frames){
        cancel.ThrowIfCancellationRequested();
        pictureBox1.Image = ToBitmap(frame);
        await Task.Delay(33);
    }
}

但请注意,Windows 中的计时器(包括 Task.Delay)默认限制为 ~16ms。如果您需要更高的准确性,您可能需要在播放期间使用MultiMediaTimer或增加计时器频率

如果您想要并发捕获和播放,可以使用 ConcurrentQueue,这样您就可以安全地从后台线程添加帧,并从 UI 线程显示它们。但这可能会变得复杂,并且取决于优先级,即您应该优先考虑平滑帧速率吗?或者显示最新的图像?使用 DataFlow 可能会让事情变得更简单。

另请注意,为了流畅高效的播放,您可能需要考虑许多其他事项。但是对异步代码使用适当的技术至少应该有一点帮助。

© www.soinside.com 2019 - 2024. All rights reserved.