我尝试使用
AForge.Video.DirectShow
包制作 WPF QR 代码扫描仪页面来实现视频捕获,该视频捕获使用 XAML 端的图像来显示它。
应用程序的内存使用量迅速增加,并在 15 秒内达到 3.6GB+,然后在这些代码行崩溃并抛出内存不足异常:
private ImageSource BitmapToImageSource(Bitmap bitmap)
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bitmap.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
WPF扫描仪页面的完整代码为:
using AForge.Video;
using AForge.Video.DirectShow;
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ZXing;
using System.IO;
namespace QRCodeApp
{
/// <summary>
/// Interaction logic for Scanner.xaml
/// </summary>
public partial class Scanner : Page
{
private FilterInfoCollection videoDevices;
private VideoCaptureDevice videoSource;
public Scanner()
{
InitializeComponent();
InitializeCamera();
}
private void InitializeCamera()
{
videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (videoDevices.Count > 0)
{
videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
videoSource.NewFrame += VideoSource_NewFrame;
videoSource.Start();
}
else
{
MessageBox.Show("No video capture devices found.","No Camera");
}
}
private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
try
{
using (Bitmap bitmap = (Bitmap)eventArgs.Frame.Clone())
{
Application.Current.Dispatcher.Invoke(() =>
{
cameraImage.Source = BitmapToImageSource(bitmap);
});
BarcodeReader barcodeReader = new BarcodeReader();
Result result = barcodeReader.Decode(bitmap);
if (result != null)
{
string downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string initialDirectory = Path.Combine(downloadsPath, "QR Codes");
if (!Directory.Exists(initialDirectory))
{
Directory.CreateDirectory(initialDirectory);
}
DateTime currentDateTime = DateTime.Now;
string fdt = currentDateTime.ToString("yyyy-MM-dd HH_mm_ss");
string fileName = "QRCode " + fdt + ".png";
fileName = CleanFileName(fileName);
string path = Path.Combine(initialDirectory, fileName);
bitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);
videoSource.NewFrame -= VideoSource_NewFrame;
videoSource.SignalToStop();
System.Threading.Tasks.Task.Run(() =>
{
Application.Current.Dispatcher.Invoke(() =>
{
cameraImage.Source = null;
videoSource = null;
videoDevices = null;
myframe.frame.Content = new Scanned(bitmap, path, false, false);
});
});
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}", "An Exception occured");
}
}
private ImageSource BitmapToImageSource(Bitmap bitmap)
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bitmap.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (videoSource != null && videoSource.IsRunning)
{
videoSource.SignalToStop();
videoSource.WaitForStop();
}
}
private string CleanFileName(string fileName)
{
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
return fileName;
}
private void Back(object sender, RoutedEventArgs e)
{
myframe.frame.Content = new ScanSelection();
}
}
}
扫描完成后我尝试了
bitmap.Dispose()
,但没有效果。同时停止摄像头并将UI图像源、视频源和视频设备设置为空。请记住,当 QR 码在应用程序崩溃之前完美扫描时,内存使用量将停止在扫描完成之前的水平,因此,如果在扫描 QR 码之前内存使用量为 1.5GB,则应用程序期间内存使用量将停止增加并保持在 1.5GB移动到我设置的页面。
内存使用量增加得如此之多且如此之快,只是因为旧的位图没有被处理,并且每次都从 BitmapToImageSource() 中删除 hBitmap 完全解决了问题。
上一个 BitmapToImageSource() 函数:
private ImageSource BitmapToImageSource(Bitmap bitmap)
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bitmap.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
更新的 BitmapToImageSource() 函数以及用于删除位图的外部函数解决了问题:
private ImageSource BitmapToImageSource(Bitmap bitmap)
{
IntPtr hBitmap = bitmap.GetHbitmap();
try
{
return Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(hBitmap);
}
}
[DllImport("gdi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject(IntPtr hObject);