Emgu CV异步方法无法向List添加视频帧的问题求助
问题修复方案
以下是针对你的Emgu CV视频帧读取问题的具体修复步骤:
1. 修正帧读取逻辑(核心问题)
你的ReadFrames方法中同时调用Grab()和QueryFrame()是错误的——QueryFrame()内部已经包含了Grab()操作,这会导致帧跳过甚至无法读取到任何帧。同时,QueryFrame()返回的Mat是复用的内部缓冲区,直接添加到列表会导致所有元素指向同一帧,最终被覆盖。
修改后的ReadFrames方法:
private async Task ReadFrames() { if (video != null && video.IsOpened) { await Task.Run(() => { // 重置视频到起始位置 video.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.PosFrames, 0); frames.Clear(); // 清空旧帧 Mat frame; // 直接用QueryFrame循环读取,直到返回null(视频结束) while ((frame = video.QueryFrame()) != null) { // 克隆帧,避免复用内部缓冲区 frames.Add(frame.Clone()); } }); } }
2. 正确更新帧计数与启动定时器
在Application_Idle事件中,任务完成后需要从UI线程更新frameCount,确保计数正确:
private void Application_Idle(object sender, EventArgs e) { if (task != null && task.IsCompleted) { frameCount = frames.Count; // 从UI线程更新帧计数 timer.Enabled = true; task = null; // 重置任务,避免重复触发 } }
3. 修复定时器事件的UI线程安全问题
System.Timers.Timer的Elapsed事件运行在后台线程,直接操作PictureBox会引发跨线程异常。同时需要正确处理图像释放,避免内存泄漏:
private void Timer_Elapsed(object? sender, ElapsedEventArgs e) { if (currentFrame < frameCount) { // 切换到UI线程更新控件 pictureBox1.Invoke((MethodInvoker)delegate { using (var tempBitmap = frames[currentFrame].ToBitmap()) { // 释放旧图像 pictureBox1.Image?.Dispose(); pictureBox1.Image = new Bitmap(tempBitmap); } currentFrame++; }); } else { timer.Enabled = false; currentFrame = 0; // 重置索引,方便重播 } }
4. 确保任务正确启动
需要在合适的时机(比如按钮点击)启动ReadFrames任务,例如:
private void btnLoadVideo_Click(object sender, EventArgs e) { // 假设此处初始化video对象 video = new VideoCapture("你的视频路径"); if (!video.IsOpened) { MessageBox.Show("视频打开失败"); return; } currentFrame = 0; frames.Clear(); task = ReadFrames(); // 启动异步读取任务 }
5. 添加资源清理
在窗体关闭时释放所有资源,避免内存泄漏:
protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); // 释放所有Mat对象 foreach (var mat in frames) { mat.Dispose(); } frames.Clear(); video?.Dispose(); timer.Dispose(); }
内容的提问来源于stack exchange,提问作者user16612111
相关产品推荐
相关产品推荐

