c# - 对于这种使用 BitmapSource 不断更新相机图像的情况,如何避免显式调用 GC.Collect()

标签 c# wpf mvvm garbage-collection bitmapsource

我需要在我的 UI 中包含相机图像。我为 View 提出的解决方案和 ViewModel下面给出。在 ViewModel我正在使用 BitmapSource每当相机发出新帧信号时,它就会保存相机图像并且不断更新。对此BitmapSource我绑定(bind)View .

这段代码大部分都能找到。但是,我遇到的问题是应用程序会定期消耗大量内存。视频尚未启动时,消耗量约为 245MB。但是当视频开始时,它会在约 4 秒内迅速攀升至约 1GB,即 Garbage-Collector开始并将该值降低到〜245MB。此时视频会短暂卡顿(我怀疑是因为 CPU 税)。这会周期性地发生,每 4 秒左右。有时,当 GC 4秒后不启动,内存使用甚至可以达到2GB,并且还导致了内存不足的异常。

我发现解决这个问题的方法是显式调用 GC每次更新帧。请参阅两条注释行。执行此操作时,视频开始后内存将继续悬停在 ~245MB。

然而,这会导致 CPU 使用率从 ~20% 显着增加到 ~35%。

我不是很明白GC有效,但我怀疑 GC 启动这么晚的原因是更新 BitmapSource 的线程正忙于更新视频(以 25FPS 运行),因此没有时间运行 GC除非它明确要求这样做。

所以我的问题是:这种行为的原因是什么,是否有更好的方法来实现,我正在尝试做什么,同时避免显式调用 GC ?

我尝试将其包装在 using 中-声明,但是 BitmapSource未实现 IDisponsable据我了解using不是为这种情况创建的,而是在您访问外部/非托管资源时创建的。

这是代码:

摄像头图像查看 :

<Image Source="{Binding CameraImageSource}"/>

CameraImageViewModel :
public class CameraImageViewModel : ViewModelBase
{
    private ICamera camera;
    private UMat currentImage;

    public BitmapSource CameraImageSource
    {
        get { return cameraImageSource; }
        set
        {
            cameraImageSource = value;
            RaisePropertyChanged("CameraImageSource");
        }
    }
    private BitmapSource cameraImageSource;

    public CameraImageViewModel(ICamera camera)
    {
        this.camera = camera;
        camera.EventFrame += new EventHandler(UpdateCameraImage);
    }

    private void UpdateCameraImage(object s, EventArgs e)
    {
        camera.GetMatImage(out currentImage);
        // commenting from here on downward, will also remove the described memory usage, but then we do not have an image anymore
        BitmapSource tmpBitmap = ImageProcessing.UMatToBitmapSource(currentImage);
        tmpBitmap.Freeze();
        DispatcherHelper.CheckBeginInvokeOnUI(() => CameraImageSource = tmpBitmap);
        //GC.Collect(); // without these lines I have the memory issue
        //GC.WaitForPendingFinalizers();
    }
}

ImageProcessing.UMatToBitmapSource :
    public static BitmapSource UMatToBitmapSource(UMat image)
    {
        using (System.Drawing.Bitmap source = image.Bitmap)
        {
            return Convert(source);
        }
    }

    /*
     * REF for implementation of 'Convert': http://stackoverflow.com/questions/30727343/fast-converting-bitmap-to-bitmapsource-wpf/30729291#30729291
     */
    public static BitmapSource Convert(System.Drawing.Bitmap bitmap)
    {
        var bitmapData = bitmap.LockBits(
            new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);

        var bitmapSource = BitmapSource.Create(
            bitmapData.Width, bitmapData.Height, 96, 96, PixelFormats.Gray8, null,
            bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);

        bitmap.UnlockBits(bitmapData);
        return bitmapSource;
    }

最佳答案

替换创建新 BitmapSourceUpdateCameraImage使用单个 WriteableBitmap您创建一次并经常更新。这样,您将避免在内存中创建图像副本。

此代码假定 ImageWidthImageHeight不随时间变化,提前知晓。如果不是这种情况,则必须在尺寸更改时动态重新创建图像。

  • 将您的 cameraImageSource 替换为:
    private cameraImageSource = new WriteableBitmap(
            ImageWidth,
            ImageHeight,
            96,
            96,
            PixelFormats.Gray8,
            null);
    
  • 更改您的 UpdateCameraImage到:
    private void UpdateCameraImage(object s, EventArgs e)
    {
        camera.GetMatImage(out currentImage);
        System.Drawing.Bitmap bitmap = currentImage.Bitmap;
    
        var bitmapData = bitmap.LockBits(
            new System.Drawing.Rectangle(0, 0, ImageWidth, ImageHeight),
            System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
    
    
        cameraImageSource.CopyPixels(new Int32Rect(0, 0, ImageWidth, 
            ImageHeight), bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
    
        bitmap.UnlockBits(bitmapData);
    }
    

  • 也可以调用 currentImage.BitmapUMat没有必要。我不知道EmguCV您似乎正在使用的库,但 UMat 还有其他属性,例如 Ptr可以直接传递给 CopyPixels .

    关于c# - 对于这种使用 BitmapSource 不断更新相机图像的情况,如何避免显式调用 GC.Collect(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39122503/

    相关文章:

    c# - 触摸事件不触发 C# WPF

    c# - Bing map 缩小拉伸(stretch)

    c# - WPF 设计器错误 : frameworkName cannot be an empty string

    c# - Windows XP 上的 WPF 视频撕裂

    asp.net-mvc - ASP.NET MVC : Creating SelectList in the view or action?

    .net - 有人可以帮我记忆一下这个框架/技术叫什么吗?

    c# - 如何立即在循环中将文本追加到 TextBlock 中?

    c# - 从 Dapper 结果创建二维数组

    c# - 具有值数据绑定(bind)的 WPF ProgressBar

    c# - MVVM - 具有多态性的集合