2012-04-18 14 views
12

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?

Respuesta

10

Crea un valid bitmap header y precedelo a la matriz de bytes. Solo necesitará crear manualmente de 56 a 128 bytes de datos.

Segundo, create a stream from a byte array.

A continuación, create an image or bitmap class by using Image.FromStream()/Bitmap.FromStream()

no me imagine que hay algún tipo de características de imagen primas en .Net. Hay tanta información necesaria para una imagen, sería una lista de parámetros extremadamente larga para un método que acepte bytes sin formato y cree una imagen (es un mapa de bits, jpeg, gif, png, etc. y todos los datos adicionales de cada uno aquellos requieren para crear una imagen válida) no tendría sentido.

+0

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

+3

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. –

+1

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

4

Parece que todas sus cámaras son del mismo tipo, por lo que los datos de las imágenes. No sé si esto es posible en su caso, pero podría crear un "mapa de bits completo" a partir de cualquier imagen y luego simplemente usar el encabezado como plantilla (ya que será el mismo para todas las imágenes).

+1

+1 ¡Es una idea fantástica! –

6

probar este

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

Creo que no tiene que declararlo como "var". Usted puede declararlo como "Mapa de bits" en su lugar, ¿no? ¡Pero la respuesta es excelente, por supuesto! – tmighty

+0

Por supuesto que puedes. RTM https://msdn.microsoft.com/ru-ru/library/bb383973.aspx – Cheburek

Cuestiones relacionadas