C# 对于使用BitmapSource连续更新相机图像的情况,如何避免显式调用GC.Collect()

C# 对于使用BitmapSource连续更新相机图像的情况,如何避免显式调用GC.Collect(),c#,wpf,mvvm,garbage-collection,bitmapsource,C#,Wpf,Mvvm,Garbage Collection,Bitmapsource,我需要在我的UI中包含一个摄像头图像。下面给出了我为视图和视图模型提出的解决方案。在ViewModel中,我使用的是一个BitmapSource,它保存相机图像,并在相机发出新帧信号时不断更新。为此BitmapSource我绑定了视图 这段代码在很大程度上是有效的。然而,我有一个问题,应用程序周期性地消耗大量内存。当视频尚未启动时,消耗量约为245MB。但是当视频开始时,它会在4秒内迅速攀升到1GB,这时垃圾收集器启动并将该值降低到245MB。此时,视频将短暂停顿(我怀疑是由于CPU税)。这种情

我需要在我的UI中包含一个摄像头图像。下面给出了我为
视图
视图模型
提出的解决方案。在
ViewModel
中,我使用的是一个
BitmapSource
,它保存相机图像,并在相机发出新帧信号时不断更新。为此
BitmapSource
我绑定了
视图

这段代码在很大程度上是有效的。然而,我有一个问题,应用程序周期性地消耗大量内存。当视频尚未启动时,消耗量约为245MB。但是当视频开始时,它会在4秒内迅速攀升到1GB,这时
垃圾收集器启动并将该值降低到245MB。此时,视频将短暂停顿(我怀疑是由于CPU税)。这种情况会定期发生,大约每4秒钟发生一次。有时,当
GC
在4秒后没有启动时,内存使用量甚至可能达到2GB,并导致内存不足异常

我找到的解决方法是,每次更新帧时显式调用
GC
。请参见两行注释。执行此操作时,视频启动后,内存将继续悬停在~245MB

但是,这会导致CPU使用率从~20%显著增加到~35%

我不太了解
GC
是如何工作的,但我怀疑,GC启动得这么晚的原因是更新
BitmapSource
的线程忙于更新视频(以25FPS的速度运行),因此没有时间运行
GC
,除非它明确要求这样做

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

我曾尝试用
using
-语句将其包装在
中,但
BitmapSource
没有实现
IDisponsable
,据我所知
using
不是为这种情况创建的,而是为您访问外部/非托管资源时创建的

代码如下:

CameraImageView

<Image Source="{Binding CameraImageSource}"/>
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();
    }
}
    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;
    }
图像处理。UMatToBitmapSource

<Image Source="{Binding CameraImageSource}"/>
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();
    }
}
    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;
    }

将创建新的
BitmapSource
UpdateCamerage
替换为使用单个
WriteableBitmap
,您只需创建一次并经常更新。这样可以避免在内存中创建映像的副本

此代码假定
ImageWidth
ImageHeight
不随时间变化,并且是预先知道的。如果情况并非如此,则必须在尺寸更改时动态重新创建图像

  • 将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);
    }
    

  • 也可能不需要在
    UMat
    上调用
    currentImage.Bitmap
    。我不知道您正在使用的
    EmguCV
    库,但UMat还有其他属性,如
    Ptr
    ,可以直接传递到
    CopyPixels

    ,如果它工作正常,如果内存超过x mb,而不是每帧,您可以每x ms运行一次GC。谢谢您的回答。你介意写一个简短的例子吗?我已经看到了
    writeablebitmap
    here(),但我不太明白如何使用我的代码,因为我使用的是
    BitmapSource
    (?)。