如何避免显式调用GC.Collect()在这种情况下,使用BitmapSource不断更新相机图像

本文关键字:BitmapSource 使用 图像 相机 更新 这种情况下 何避免 调用 GC Collect | 更新日期: 2023-09-27 18:03:42

我需要在我的UI中包含一个相机图像。ViewViewModel的解如下所示。在ViewModel中,我使用的是BitmapSource,它保存相机图像,并且在相机发出新帧信号时不断更新。我将View绑定到这个BitmapSource

这段代码在大多数情况下都可以找到。然而,我有一个问题,即应用程序定期消耗非常大量的内存。当视频尚未启动时,消耗为~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不是为这种情况创建的,而是为您访问外部/非托管资源时创建的。

代码如下:

CameraImageView :

<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;
    }

如何避免显式调用GC.Collect()在这种情况下,使用BitmapSource不断更新相机图像

用创建一次并经常更新的单个WriteableBitmap代替创建每个UpdateCameraImage的新BitmapSource。这样可以避免在内存中创建映像副本。

这段代码假设ImageWidthImageHeight不随时间变化,并且是已知的。如果不是这种情况,则必须在尺寸更改时动态地重新创建图像。

  1. 将cameraImageSource替换为:

    private cameraImageSource = new WriteableBitmap(
            ImageWidth,
            ImageHeight,
            96,
            96,
            PixelFormats.Gray8,
            null);
    
  2. 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