我正在 VB.NET 中使用相机(在本例中为单色)。相机的 API 采用 C++ 语言,制造商提供包装器。正如这些情况下的典型情况一样,图像以
Byte
数组的形式返回。在我的特定情况下,它是每像素 8 位,因此没有需要撤消的花哨的位打包。
我感到大吃一惊,.NET 对此类事物的支持如此之少。在网上搜索我发现了各种主题,但在很多情况下人们有一个与图像数据对应的字节数组(即将图像文件读入字节数组,然后让.NET将数组解释为文件,这并没有帮助,带有标题和所有内容)。
在这种情况下,我需要将原始像素值数组转换为可以在屏幕上显示的内容。
似乎没有任何内置方法可以做到这一点,因为.NET 内置的 8bpp 格式已建立索引(需要调色板),但似乎不支持设置调色板。再次,这真的让我感到惊讶。 这是我发现的最有前途的主题。
因此,我发现执行此操作的唯一方法是创建一个
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 不是可行的显示格式。有没有更好的办法?
有效的位图标头,并将其预先附加到字节数组中。您只需要手动创建 56-128 字节的数据。
第二,从字节数组创建流。
接下来,使用 Image.FromStream()/Bitmap.FromStream() 创建图像或位图类
我无法想象 .Net 中有任何类型的原始成像功能。图像所需的信息非常多,对于接受原始字节并创建图像的方法来说,这将是一个非常冗长的参数列表(是位图、jpeg、gif、png 等,以及每个图像的所有附加数据)这些需要创建一个有效的图像)这是没有意义的。
var img = new Bitmap(new MemoryStream(yourByteArray));
Dim XyBmp As Bitmap
Dim ColorPalette As Imaging.ColorPalette
Dim XyBuffer(0 To 128 * 256 - 1) As Byte
Dim XyBufferHandle As GCHandle
Private Sub createXyBmp()
Dim i As Integer
XyBufferHandle = GCHandle.Alloc(XyBuffer, GCHandleType.Pinned)
XyBmp = New Bitmap(128, 256, 128, Imaging.PixelFormat.Format8bppIndexed, XyBufferHandle.AddrOfPinnedObject())
ColorPalette = XyBmp.Palette
For i = 0 To 255
ColorPalette.Entries(i) = Color.FromArgb(255, i, i, i)
Next i
XyBmp.Palette = ColorPalette
End Sub
Friend Sub DrawXyBmp()
Do
Static b As Byte
For i = 0 To XyBuffer.Length - 1
XyBuffer(i) = b
Next i
Me.Refresh()
Me.BackgroundImage = XyBmp
b = CByte((b + 1) And &HFF)
Loop
End Sub
Private Sub close()
XyBmp.Dispose()
XyBufferHandle.Free()
End Sub