Estoy trabajando con cámaras (en este caso, monocromo) en VB.NET. La API de las cámaras está en C++ y el fabricante proporciona envolturas. Como es típico en estos casos, la imagen se devuelve como una matriz Byte
. En mi caso particular, es de 8 bits por píxel, así que no hay ningún bitpack de fantasía para deshacer.¿Cómo se convierte una matriz de bytes en una instancia de mapa de bits (.NET)?
Fui flabbergasted que .NET tiene muy poco soporte para este tipo de cosas. Buscando en línea encontré varios temas, pero no ayuda que en muchos casos las personas tengan una matriz de bytes correspondiente a los datos de imagen (es decir, lean un archivo de imagen en una matriz de bytes y luego interpreten .NET la matriz como un archivo, con encabezado y todo).
En este caso, necesito convertir una matriz de valores de píxeles en bruto, en particular, algo que puedo mostrar en la pantalla.
No parece haber ninguna forma integrada de hacerlo, porque el formato 8bpp integrado en .NET está indexado (necesita una paleta) pero no parece que sea compatible con la configuración de la paleta. Nuevamente, esto realmente me sorprendió. This es el tema más prometedor que encontré.
Así que la única forma que encontré para hacer esto es crear un Bitmap
y luego copiar los valores de píxel píxel por píxel. En mi caso, una imagen de 8 MPixel tomó varios segundos en el i7 más rápido al usar SetPixel
.
La alternativa que encontré al usar SetPixel
es LockBits
, que luego le da acceso a la matriz (no administrada) de bytes que constituye la imagen. Parece un desperdicio, porque tengo que manipular el arreglo en .NET y luego copiarlo nuevamente en el espacio no manufacturado. Ya estoy copiando los datos de la imagen original una vez (por lo que el original puede ser reutilizado o descartado por el resto de lo que está pasando) así que aunque es significativamente más rápido que usar SetPixel
, esto todavía parece un desperdicio.
Este es el código VB que tengo:
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
Para una imagen de 8 Megapíxeles la versión secuencial consume alrededor de 220 milisegundos desde la creación de TheBitmap
hasta (e incluyendo) el llamado a TheBitmap.UnlockBits()
. La versión paralela es mucho más inconsistente pero oscila entre 60 y 80 milisegundos. Teniendo en cuenta que estoy comprando desde cuatro cámaras simultáneamente y transmitiendo al disco con tiempo de sobra, esto es bastante decepcionante. La API viene con código de muestra en C++ que puede mostrarse desde las cuatro cámaras simultáneamente a una velocidad de cuadro completa (17 fps). Nunca trata con Bitmap
, por supuesto, ya que GDI accede a matrices de valores de píxel sin procesar.
En resumen, tengo que viajar a través de la barrera gestionada/no gestionada simplemente porque no hay una forma directa de tomar una matriz de valores de píxeles y "rellenarla" en una instancia Bitmap
. En este caso, también estoy copiando los valores de píxeles en 24bpp Bitmap
porque no puedo "configurar" la paleta en una imagen indexada, por lo que 8bpp no es un formato viable para mostrar.
¿No hay mejor manera?
FromStream requiere que la corriente contiene un mapa de bits "lleno" (con información de cabecera), no sólo los valores de los píxeles --- he tratado de explicar esto en mi pregunta. – pelesl
Actualizado con referencia a la información de encabezado de bmp. La preinstalación de unos 128 bytes y luego el uso de la corriente continua debería ser considerablemente más rápido. –
Merece la pena intentarlo, especialmente si me permite anteponer un encabezado de mapa de bits con una paleta de escala de grises para no tener que copiar los valores de píxel 3 veces para obtener 24 bpp. – pelesl