如何避免显式调用GC.Collect(),这种情况下使用BitmapSource来连续更新摄像机图像
我需要在我的UI中包含摄像机图像。下面给出了我为View
和ViewModel
提出的解决方案。在ViewModel
中,我使用的是一个BitmapSource
,用于保存相机图像,并且每当相机发出一个新帧时就会持续更新。对此BitmapSource
我绑定View
。如何避免显式调用GC.Collect(),这种情况下使用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
使用创造新BitmapSource
每UpdateCameraImage
。这样你将避免在内存中创建图像的副本。
此代码假定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
。
感谢您的回答。你介意写一个简短的例子吗?我已经在这里看到'WritableBitmap'的用法(http://stackoverflow.com/questions/30727343/fast-converting-bitmap-to-bitmapsource-wpf/30729291),但我真的不明白我会怎么做我的代码,因为我正在使用'BitmapSource'(?)。 – packoman
如果工作正常,如果内存超过xmb,而不是每一帧,则可以每x毫秒运行一次GC。 – null