如何将字节数组转换为位图实例 (.NET)?

Posted

技术标签:

【中文标题】如何将字节数组转换为位图实例 (.NET)?【英文标题】:How do you convert a byte array to a Bitmap instance (.NET)? 【发布时间】:2012-04-30 03:04:23 【问题描述】:

我正在使用 VB.NET 中的相机(在本例中为单色)。相机的 API 采用 C++ 语言,制造商提供包装器。在这些情况下,图像通常以Byte 数组的形式返回。在我的特殊情况下,它是每像素 8 位,因此没有花哨的位打包可以撤消。

大吃一惊 .NET 对这类东西的支持如此之少。网上搜索找到了各种主题,但是很多情况下人们都有对应图像数据的字节数组(也就是将图像文件读入字节数组,然后让.NET将数组解释为文件),这于事无补,带有标题和所有内容)。

在这种情况下,我需要将原始像素值数组转换为可以在屏幕上显示的内容。

似乎没有任何内置方法可以做到这一点,因为 .NET 中内置的 8bpp 格式已编入索引(需要调色板),但似乎不支持设置调色板。再一次,这真的让我感到惊讶。 This 是我发现的最有前途的话题。

所以我发现这样做的唯一方法是创建一个Bitmap,然后逐个像素地复制像素值。在我的情况下,使用SetPixel 时,在最快的 i7 上,一张 8 MPixel 图像需要 几秒钟

我发现使用SetPixel 的替代方法是LockBits,然后您可以访问构成图像的(非托管)字节数组。这似乎很浪费,因为我必须在 .NET 中操作数组,然后将其复制回非托管空间。我已经复制了一次原始图像数据(因此原始图像数据可以被其余的重复使用或丢弃)所以虽然比使用 SetPixel 快得多,但这似乎仍然很浪费。

这是我的VB代码:

Public Function GetBitmap(ByRef TheBitmap As System.Drawing.Bitmap) As Integer
    Dim ImageData() As Byte
    Dim TheWidth, TheHeight As Integer
    'I've omitted the code here (too specific for this question) which allocates
    'ImageData and then uses Array.Copy to store a copy of the pixel values
    'in it. It also assigns the proper values to TheWidth and TheHeight.

    TheBitmap = New System.Drawing.Bitmap(TheWidth, TheHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb)
    Dim TheData As System.Drawing.Imaging.BitmapData = TheBitmap.LockBits(
        New System.Drawing.Rectangle(0, 0, TheWidth, TheHeight), Drawing.Imaging.ImageLockMode.ReadWrite, TheBitmap.PixelFormat)
    Dim Image24(Math.Abs(TheData.Stride) * TheHeight) As Byte
    'Then we have two choices: to do it in parallel or not. I tried both; the sequential version is commented out below.
    System.Threading.Tasks.Parallel.For(0, ImageData.Length, Sub(i)
                                                                 Image24(i * 3 + 0) = ImageData(i)
                                                                 Image24(i * 3 + 1) = ImageData(i)
                                                                 Image24(i * 3 + 2) = ImageData(i)
                                                             End Sub)
    'Dim i As Integer
    'For i = 0 To ImageData.Length - 1
    '    Image24(i * 3 + 0) = ImageData(i)
    '    Image24(i * 3 + 1) = ImageData(i)
    '    Image24(i * 3 + 2) = ImageData(i)
    'Next
    System.Runtime.InteropServices.Marshal.Copy(Image24, 0, TheData.Scan0, Image24.Length)
    TheBitmap.UnlockBits(TheData)

    'The above based on
    'http://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.aspx

    Return 1

End Function

对于 8 MPixel 图像,顺序版本从创建 TheBitmap 到(包括)调用 TheBitmap.UnlockBits() 大约需要 220 毫秒。并行版本更加不一致,但范围在 60 到 80 毫秒之间。考虑到我同时从四个摄像头获取数据并流式传输到磁盘并且有足够的空闲时间,这非常令人失望。该 API 附带 C++ 示例代码,可以同时以全帧速率 (17 fps) 从所有四个摄像头显示。当然,它从不处理 Bitmap,因为 GDI 访问的是原始像素值数组。

总而言之,我必须跨越托管/非托管障碍,因为没有直接的方法来获取像素值数组并将其“填充”到Bitmap 实例中。在这种情况下,我还将像素值复制到 24bpp Bitmap,因为我无法在索引图像中“设置”调色板,因此 8bpp 不是一种可行的显示格式。

没有更好的办法吗?

【问题讨论】:

【参考方案1】:

创建一个valid bitmap header,并将其预先附加到字节数组中。您只需手动创建 56-128 字节的数据。

第二,create a stream from a byte array。

接下来,create an image or bitmap class by using Image.FromStream()/Bitmap.FromStream()

我不认为 .Net 中有任何类型的原始成像功能。图像有太多需要的信息,对于接受原始字节并创建图像(是位图、jpeg、gif、png 等,以及每个附加数据的方法)来说,这将是一个极其冗长的参数列表这些需要创建一个有效的图像)这是没有意义的。

【讨论】:

更新参考 bmp 标头信息。预先附加大约 128 个字节然后使用 fromstream 应该会更快。 值得一试,特别是如果它能让我在位图标题前添加一个灰度调色板,这样我就不必复制像素值 3 次来获得 24 bpp。 这就是答案。即使每次都重新创建标头并多次复制数组,也可以缩短到 10 毫秒。 另一个更新:我必须在创建位图的函数中调用 System.GC.Collect()。否则,在垃圾收集器运行之前会消耗大量内存(超过 15 GB),导致最终运行时长时间暂停(和帧丢失)。当 GC 运行时,时间会跳回到大约 100 毫秒的时间,但它会阻止任何长时间的停顿和帧丢失。 我想知道你是否可以重复使用你的数组/位图而不是每次都重新创建它,因为这会覆盖内存中的当前数据并且根本不需要任何垃圾收集。我不太了解如何做到这一点,只是重用内存比在为旧对象释放内存时为新对象分配更多内存要好。只是一个想法:)【参考方案2】:

试试这个

var img = new Bitmap(new MemoryStream(yourByteArray));

【讨论】:

我认为您不必将其声明为“var”。您可以将其声明为“位图”,不是吗?但答案当然很棒!【参考方案3】:

似乎您所有的相机都是同一类型,所以图像数据。我不知道这在您的情况下是否可行,但是您能否从任何图像创建一个“完整位图”,然后只使用标题作为模板(因为它对于所有图像都是相同的)。

【讨论】:

以上是关于如何将字节数组转换为位图实例 (.NET)?的主要内容,如果未能解决你的问题,请参考以下文章

如何将从 C++ 发送的 cv::MAT 字节数组转换为 Java 中的位图?

将位图转换为字节数组

将Java位图转换为字节数组

MFC如何将一个unsigned int数组转换为bitmap?

将NV21字节数组转换为位图可读格式[重复]

Xamarin 安卓。将字节数组转换为位图。 Skia 解码器返回 false