C# 对于使用BitmapSource连续更新相机图像的情况,如何避免显式调用GC.Collect()
我需要在我的UI中包含一个摄像头图像。下面给出了我为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税)。这种情
视图
和视图模型
提出的解决方案。在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
(?)。