我一直在努力使用 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,我该如何解决这个问题?
这并不是进行异步捕获的好方法。它不必要地复杂,并且可能容易出错。如果您打算捕获一些固定数量的帧,您可以执行以下操作:
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 可能会让事情变得更简单。
另请注意,为了流畅高效的播放,您可能需要考虑许多其他事项。但是对异步代码使用适当的技术至少应该有一点帮助。