如何避免显式调用GC.Collect()在这种情况下,使用BitmapSource不断更新相机图像
本文关键字:BitmapSource 使用 图像 相机 更新 这种情况下 何避免 调用 GC Collect | 更新日期: 2023-09-27 18:03:42
我需要在我的UI中包含一个相机图像。View
和ViewModel
的解如下所示。在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;
}
用创建一次并经常更新的单个WriteableBitmap
代替创建每个UpdateCameraImage
的新BitmapSource
。这样可以避免在内存中创建映像副本。
这段代码假设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
。