OpenCVSharp相机初始化后随机FPS骤降问题排查求助
WinForms + OpenCVSharp 视频录制卡顿问题排查求助
我开发的WinForms桌面应用基于OpenCVSharp实现视频录制与存储,目前用以下代码初始化相机:
VideoCapture videoCapture = VideoCapture.FromCamera(index: cameraIndex, apiPreference: VideoCaptureAPIs.DSHOW);
多数场景下运行正常,但近期客户端反馈:应用加载、相机初始化完成后,FPS会随机骤降至1-5帧,画面严重卡顿,只有重启应用才能恢复。我通过反复初始化VideoCapture能复现这个随机问题,已经花了两天排查但没进展。
换成VideoCaptureAPIs.MSMF作为apiPreference后,FPS问题基本解决,性能还明显提升,但出现了类似GitHub已知问题的新bug,现在陷入瓶颈。不确定是API的问题还是代码有疏漏,附上相关代码片段,求帮忙排查:
OpenCVHelper中创建VideoCapture的代码
public VideoCapture CreateVideoCaptureFromCamera(int cameraIndex, int width, int height, double inputFPS, int IsAutoFocus) { try { VideoCapture videoCapture = VideoCapture.FromCamera(index: cameraIndex, apiPreference: VideoCaptureAPIs.DSHOW); videoCapture.FrameHeight = height; videoCapture.FrameWidth = width; videoCapture.Fps = inputFPS; if (IsAutoFocus == (int)AutoFocusStatus.On) { videoCapture.AutoFocus = true; } else { videoCapture.AutoFocus = false; videoCapture.Focus = 100; } videoCapture.Zoom = 0; videoCapture.FourCC = "MJPG"; return videoCapture; } catch { throw; } }
Presenter层创建VideoCapture并启用多线程的代码
videoCapturePresenter = recorderMainAppManager.CreateVideoCapture( index: camera.Index, resolution: resolution, //resolutions[resolutions.Count - 1], fps: ProfilePresenter.CameraFps, focus: ProfilePresenter.IsAutoFocus ?? (int)AutoFocusStatus.Off ); captureTokenSource = new(); _captureThread = new Thread(() => CaptureFrame(videoCapturePresenter, captureTokenSource)) { Name = "Capture Thread", Priority = ThreadPriority.AboveNormal, ///This is not the cause because i added after i can reproduced the issues. }; _captureThread.Start();
CaptureFrame方法代码(帧捕获与UI更新)
private void CaptureFrame(VideoCapture capture, CancellationTokenSource captureToken) { string newOverlayText = string.Empty; try { ShipmentOverlay shipmentOverlay = recorderMainAppManager.GetShipmentOverlay ( resolution: $"{capture.FrameWidth}x{capture.FrameHeight}" ); string username = $"Employee Name : {UserPresenter.Username}"; while (!captureToken.IsCancellationRequested) { string datetime = DateTimeOffset.Now.ToString("dd/MM/yyyy HH:mm:ss"); string overlayText = createNewOverlayString(); using (Mat rawFrame = new()) { newOverlayText = $"Date : {datetime} | {username} | {overlayText}"; if (!capture.Read(rawFrame)) { Console.WriteLine("Error: Failed to read frame."); break; } if (rawFrame.Empty()) { Console.WriteLine("Error: Empty frame."); break; } using (Mat frameWithOverlay = recorderMainAppManager.DrawOverlay(frame: rawFrame, shipmentOverlay: shipmentOverlay, overlayText: newOverlayText)) { Mat clonedFrame = frameWithOverlay.Clone(); { lock (clonedFrame) { var currentPictureBox = recorderMainAppView.GetPicRecordResolution(); var targetSize = new OpenCvSharp.Size() { Width = currentPictureBox.Width, Height = currentPictureBox.Height }; if (targetSize.Width > 0 && targetSize.Height > 0) { Mat captureMat = new(); Cv2.Resize(clonedFrame, captureMat, targetSize, interpolation: InterpolationFlags.Area); using (Bitmap _bitmap = BitmapConverter.ToBitmap(captureMat)) { Bitmap clonedResizeFrame = (Bitmap)_bitmap.Clone(); recorderMainAppView.UpdateCameraView(clonedResizeFrame); } if (_isRecording == RecordStatus.RECORD) { _frameQueue.Enqueue(clonedFrame); } else { captureMat.Dispose(); } } } } } } _threadStopEvent.Wait(1); if (captureToken.IsCancellationRequested) break; } } catch { throw; } }
UI层UpdateCameraView方法代码
public void UpdateCameraView(Bitmap img) { try { if (picRecord.InvokeRequired && WindowState != FormWindowState.Minimized) { using (picRecord.Image) { Invoke(() => picRecord.Image = img); } } } catch (ObjectDisposedException) { // This function throw everytime in debug mode, apparently because some video frame aren't being disposed yet // But frmMain is already disposed. Happened only when i exit the app. // For now, do nothing. But if you know how to solve this. Feel free. return; } }
补充:帧写入线程代码(问题出现于录制前)
创建写入线程代码
videoWriterPresenter = recorderMainAppManager.CreateVideoWriter( capture: videoCapturePresenter, savedDir: outputDirectory, BaseLabel: data.FirstOrDefault(t => !string.IsNullOrEmpty(t))); _threadStopEvent.Reset(); writerTokenSource = new(); _writerThread = new Thread(() => WriteFrame(writerTokenSource)) { Name = "Writer Thread", IsBackground = true }; _writerThread.Start();
WriteFrame方法代码
private void WriteFrame(CancellationTokenSource writerToken) { Stopwatch stopwatch = Stopwatch.StartNew(); double _delay = 1_000 / videoCapturePresenter!.Fps; try { while (!_threadStopEvent.Wait(0) && !writerToken.IsCancellationRequested) { if (stopwatch.Elapsed.TotalMilliseconds <= _delay) { _threadStopEvent.Wait(1); continue; } while (_frameQueue.Count != 0) { if (_frameQueue.TryDequeue(out Mat? frameDequeue)) { using (frameDequeue) { videoWriterPresenter!.Write(frameDequeue); } _threadStopEvent.Wait(1); stopwatch.Restart(); } } } } catch { throw; } finally { stopwatch.Stop(); } }
排查方向建议
DSHW模式资源泄漏/竞争
- 每次销毁
VideoCapture时,必须调用Release()和Dispose(),避免相机设备资源被残留实例占用,导致新实例无法获取足够带宽引发卡顿。 - 部分相机不支持多实例访问,若初始化失败后未正确释放,后续实例会陷入资源争抢,导致FPS骤降。
- 每次销毁
CaptureFrame线程资源处理
- 非录制状态下
clonedFrame未释放:在else分支添加clonedFrame.Dispose();,避免内存泄漏引发GC频繁触发拖慢帧率。 - 锁对象错误:
lock (clonedFrame)无法起到同步作用,应改用全局静态对象(如private static readonly object _frameLock = new object();)作为锁。 - 移除不必要的
_threadStopEvent.Wait(1),让capture.Read()自然阻塞获取帧,减少额外延迟。
- 非录制状态下
UI更新性能优化
- 修改
UpdateCameraView的Image释放逻辑,在Invoke内部处理旧Image:Invoke(() => { var oldImage = picRecord.Image; picRecord.Image = img; oldImage?.Dispose(); }); - 减少Bitmap克隆和转换的开销,考虑直接在UI线程进行图像转换,或使用更高效的Mat到PictureBox数据传输方式。
- 修改
MSMF模式问题规避
- 强制指定相机硬件支持的分辨率和帧率,设置
videoCapture.Set(VideoCaptureProperties.FourCC, FourCC.FromString("MJPG")),降低驱动兼容性问题。 - 确认客户端设备已授予摄像头完全访问权限,部分系统对MSMF模式有额外权限要求。
- 强制指定相机硬件支持的分辨率和帧率,设置
内容的提问来源于stack exchange,提问作者Jirayuth Porka
相关产品推荐
相关产品推荐

