【问题标题】:Reusing Bitmap to Decode JPEG Data重用位图解码 JPEG 数据
【发布时间】:2014-11-07 18:29:08
【问题描述】:

我的应用程序的这一部分在图片框中显示来自 IP 摄像机的实时图像流。该流是典型的运动 JPEG,格式为多部分 HTTP 响应。

所有代码都已编写完毕,我的应用程序在 CPU 使用率非常低 (1-2%) 和跷跷板式内存占用(逐渐增加直到触发 GC,然后全部回收恢复正常)的情况下运行良好。所以这更像是一个优化问题、最佳实践等,不是必需的。

除此之外,现在我以正常的Bitmap.FromStream() 方式执行此操作,它为每帧生成一个全新的位图,频率为 60Hz。即使从运行时的角度来看这很好,这对作为程序员的我来说也是不和谐的。

如果我要使用更多的动手方法,我会预先分配已知大小的位图(320x240 或 640x480,取决于选项),然后将流解码为我的位图,但我没有看到 .NET执行此操作的功能。我必须使用我自己的 JPEG 解码器,这既是工作量太大,又是二进制大小(这东西已经是 55mb 的编译代码)。

所以我的问题是,我在这里遗漏了什么吗?有没有最好的方法来做这样的事情?我提到的功能会很完美,但如果不可用,我还能如何改进呢?

【问题讨论】:

  • 您是否在每个位图对象上调用 dispose()?它将有助于内存管理,因为位图同时使用托管和非托管内存。
  • 与我的问题无关,但不是。自己处理它会将它带到我的 UI 线程上,而不是让 GC 在它自己的终结器线程上执行它。它没有真正的收获,GC 处理 30-40mb 的位图。

标签: .net


【解决方案1】:

请记住,从 60 fps jpeg 获得 2% 的 cpu 负载是一个非常好的结果。你可以做的很少,以使这大大改善。一定要确保你的基准是现实的,当它变得如此低时,你不能对位图数据做太多事情。一个潜在的陷阱是编解码器懒惰地转换压缩的像素数据。如果您实际上从未调用 Graphics.DrawImage() 或 Bitmap.LockBits(),那么编解码器除了解析标头之外不会做任何其他事情。您获得的 .NET 对象也非常适中,只需要 20 字节的 GC 堆。

几乎没有理由担心底层内存管理。它在 GDI+ 中是极其不透明的,它完全自行处理它,除了指针之外没有暴露任何东西。 Image.FromFile() 远远优于 GDI+ 使用内存映射文件来访问原始像素数据。当然,在相机应用程序中没有用,由于 GC 和本机内存之间的阻抗不匹配, Image.FromStream() 涉及更多,引擎盖下有一个 Marshal.Copy() 来铲字节。编解码器可能会将自身分配给缓冲区/缓存解码的像素,您对此无法控制。没有理由担心这一点,因为您重复使用完全相同的分配,Windows 内存管理器非常喜欢这一点。

你所要求的可能的。两种基本方法。第一个是Bitmap constructor,它需要一个 IntPtr,它需要指向解码的像素数据。这使您可以重新使用原始像素缓冲区。第二种方式是Bitmap.LockBits(),你可以直接在GDI+分配的像素缓冲区中涂鸦。两者没有本质区别。

当然,您需要带上自己的 JPEG 解码器。 libjpeg library 是最常用的一种。用 C 编写,最好的方法是使用 C++/CLI 包装器。 libjpeg-turbo 库值得注意,它有望将解码速度提高 2-4 倍。不知道这些库与 Microsoft 的编解码器相比如何。注意你解码成的像素格式,24bppRgb 是一种自然的 JPEG 匹配,所以你可能会得到一个编解码器,但如果你显示图像,32bppPArgb 是迄今为止最好的格式 (x10)。

【讨论】:

  • 这基本上就是我所说的,我只是在 GDI+ 本身中寻找一些东西,以避免用另一个库来重新发明轮子来解码。编解码器本身是可互换的(因此保存 jpeg 需要 10 行代码),你会认为我可以告诉它只在我已经创建的位图对象中解码。
  • 嗯,不,你不能使用像“位图对象”这样的词,如果你想把它带个好结局。我链接到的那个位图构造函数需要一个指针,而不是一个对象。没有什么神奇的咒语可以让 GDI+ 封装的编解码器做一些你已经知道的事情。重用 MemoryStream 当然是微不足道的。
  • 你是迂腐的,但是好的,它为我解码的每个流分配一个位图形状的内存块(大小宽度 * 高度 * bpp + 一些步幅 + 一个标题)。光与否,当我每秒获得 30 个 jpeg 并且它们都是相同的形状时,这是一个无用的步骤。
  • 问和答,传递给 Bitmap 构造函数的指针需要指向那块内存。
  • 是的,这几乎也是我发现的唯一方法,只是因为我不希望它分配内存而无法使用 C# decompressor 感觉很奇怪为了我。所以 tldr 是使用 libjpeg 绘制位图 :(
【解决方案2】:

首先,您可以尝试使用WPF,它的图像组件和渲染能力。 AFAIK,有几次提到 WPF Image 类的性能比 WinForm 基于 GDI+ 的类具有更好的性能。

第二,如果您仍想使用 WinForm 作为 UI,如果您允许包含对 Systems.Windows.Media.Imaging (PresentationCore.dll) 的引用,那么您可以尝试使用JpegBitmapDecoder 类来解码图像,然后将解码后的像素设置到您预先分配的Bitmap 实例中。

在 JpegBitmapDecoder 构造函数中有几个选项可以调整以获得(可能)更好的性能,即 create-options 和 cache-options --> http://msdn.microsoft.com/en-us/library/ms602494.aspx

然后获取BitmapFrame实例为

BitmapFrame bitmapFrame = jpegBitmapDecoder.Frames[0];

BitmapFrame 有几个CopyPixels method 的重载。该方法可以复制到 Array 或 IntPtr。我相信除我之外的另一个答案已经提到过 IntPtr 作为 Bitmap 中的原始像素缓冲区,因此您可以将 BitmapFrame 中的像素直接复制到预分配的 Bitmap 中。

【讨论】:

  • 我也可以用 GDI+ 做到这一点。基本上JpegBitmapDecoder 为每个图像流分配一个完整的图像,与 GDI+ Bitmap 构造函数相同。这是我要避免的步骤。当然,我知道我可以在另一个位图上绘制一个位图。
  • 嗯,澄清一下,你只需要创建/分配一个Bitmap实例一次,然后你就可以用最新的图片“更新”它的内容,这样这个实例就可以重用了。但无论选择何种 API/类,我认为您应该尝试使用分析器来更好地说明应用程序中的瓶颈(资源最密集)部分。
  • 注意到但无用的建议。我原来的帖子涵盖了它!
猜你喜欢
  • 2020-10-02
  • 1970-01-01
  • 2021-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-06
  • 1970-01-01
相关资源
最近更新 更多