在具有高刷新率的WPF中显示字节数组图像



上下文

我有一台巴斯勒相机,当拍摄到新图像时,它会引发一个事件
在事件arg中,我可以获取作为字节数组获取的图像。

我必须对这个图像进行计算,然后在WPF应用程序中显示它。相机刷新率高达40FPS。

问题和找到的解决方案

将字节数组转换为WPF映像的解决方案可以在这里找到:在WPF中将字节数组转换成映像
这个解决方案非常适合只转换一次字节数组,但我觉得在40FPS时会损失很多内存。每次都会创建一个新的BitmapImage(),无法进行处理。

是否有更好的解决方案可以在WPF中显示变化高达40 FPS的字节数组(处理问题的方式可以完全重新思考)

代码

这个在WPF中显示相机流的解决方案是有效的,但BitmapImage image = new BitmapImage();行对我来说不太好。

private void OnImageGrabbed(object sender, ImageGrabbedEventArgs e)
{
// Get the result
IGrabResult grabResult = e.GrabResult;
if (!grabResult.GrabSucceeded)
{
throw new Exception($"Grab error: {grabResult.ErrorCode} {grabResult.ErrorDescription}");
}
// Make process on the image
imageProcessor.Process(grabResult);
// Convert grabResult in BGR 8bit format
using Bitmap bitmap = new Bitmap(grabResult.Width, grabResult.Height, PixelFormat.Format32bppRgb);
BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr ptrBmp = bmpData.Scan0;
converter.Convert(ptrBmp, bmpData.Stride * bitmap.Height, grabResult);
bitmap.UnlockBits(bmpData);
// Creat the BitmapImage
BitmapImage image = new BitmapImage(); // <-- never Disposed !
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, ImageFormat.Bmp);
memory.Position = 0;
image.BeginInit();
image.StreamSource = memory;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
}
LastFrame = image; // View is binded to LastFrame
}

我建议您使用WriteableBitmap来显示结果。这就避免了重新分配UI映像的需要。如果源中的像素格式与位图中的格式匹配,则可以简单地使用WritePixels来更新图像。

请注意,您只能从主线程修改WriteableBitmap,并且ImageGrabbed事件将在后台线程上引发。一旦事件处理程序返回,grabResult就会被处理掉。因此,您需要让UI线程进行实际更新,并且需要一个中间缓冲区。但是,如果需要,可以将这个缓冲区合并。

另一种选择可能是编写自己的循环,重复调用RetrieveResult,这样可以在UI更新后手动处理抓取结果。也可以保留一个WriteableBitmaps池,我想如果UI没有实际使用它,那么它应该是安全的。

在每一帧上,您都是

  • 创建位图
  • 将其编码为MemoryStream
  • 创建位图图像
  • 将MemoryStream解码为BitmapImage

最好创建一次WritableBitmap,并重复调用其WritePixels方法。

您可能仍然需要转换原始缓冲区,因为WPF似乎没有PixelFormat.Format32bppRgb的等价物,或者它可能是PixelFormats.Bgr32

var wb = LastFrame as WriteableBitmap;
if (wb == null)
{
wb = new WriteableBitmap(
grabResult.Width, grabResult.Height,
96, 96, PixelFormats.Bgr32, null);
LastFrame = wb;
}
wb.WritePixels(...);

我也处于类似的情况:从相机上提取实时图像,并将其转储到UI中,以进行"活的";看法我花了很多时间试图找到最有效的解决方案。对我来说,结果是BitmapSource.Create。我获取原始字节数组(加上一个描述图像特征(如宽度、高度等)的结构),并使用一个函数调用将其转换为BitmapSource。

现在在我的例子中,图像是灰度级的,8位图像,所以如果你试图显示颜色,你的论点会有所不同。但这是我所做的一个片段。

public class XimeaCameraImage : ICameraImage
{
public unsafe XimeaCameraImage(byte[] imageData, ImgParams imageParams)
{
Data             = imageData;
var fmt          = PixelFormats.Gray8;
var width        = imageParams.GetWidth();
var bitsPerPixel = 8;   // TODO: Get ready for other image formats
var height       = imageParams.GetHeight();
var stride       = (((bitsPerPixel * width) + 31) / 32) * 4;
var dpi          = 96.0;
// Copy the raw, unmanaged, image data from the Sdk.Image object.
Source = BitmapSource.Create(
width,
height,
dpi,
dpi,
fmt,
BitmapPalettes.Gray256,
imageData,
stride);
Source.Freeze();
}
public           byte[]        Data { get; }
public           BitmapSource  Source            { get; }
}

最新更新