2012-04-18 11 views
12

Sto lavorando con telecamere (in questo caso monocromatiche) in VB.NET. L'API delle telecamere è in C++ e il produttore fornisce i wrapper. Come è tipico in questi casi, l'immagine viene restituita come array Byte. Nel mio caso particolare è di 8 bit per pixel, quindi non c'è nessun pacchetto di bit di fantasia da annullare.Come si converte un array di byte in un'istanza Bitmap (.NET)?

Sono stato sbalordito che .NET ha così poco supporto per questo tipo di cose. Cercando online ho trovato vari argomenti, ma non aiuta che in molti casi le persone abbiano un array di byte corrispondente ai dati di immagine (cioè legge un file di immagine in un array di byte e poi .NET interpreta l'array come un file, con intestazione e tutto).

In questo caso ho bisogno di convertire una matrice di valori di pixel grezzi in, in particolare, qualcosa che posso visualizzare sullo schermo.

Non sembra esserci alcun modo integrato per farlo, perché il formato 8bpp incorporato in .NET è indicizzato (necessita di una tavolozza) ma non sembra che l'impostazione della tavolozza sia supportata. Di nuovo, questo mi ha davvero sorpreso. This è l'argomento più promettente che ho trovato.

Quindi l'unico modo in cui ho trovato di farlo è quello di creare un Bitmap e quindi copiare i valori dei pixel pixel per pixel. Nel mio caso un'immagine a 8 MPixel ha impiegato diversi secondi sul i7 più veloce quando si utilizza SetPixel.

L'alternativa che ho trovato utilizzando SetPixel è LockBits, che consente di accedere alla matrice (non gestita) di byte che costituisce l'immagine. Sembra uno spreco, perché devo manipolare l'array in .NET e quindi copiarlo nello spazio non gestito. Sto già copiando i dati dell'immagine originale una volta (quindi l'originale può essere riutilizzato o scartato dal resto di ciò che sta succedendo) quindi, anche se molto più veloce dell'utilizzo di SetPixel, ciò sembra ancora dispendioso.

Ecco il codice VB ho:

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 

Per un'immagine 8 MPixel versione sequenziale consuma circa 220 millisecondi dalla creazione TheBitmap fino a (incluso) la chiamata a TheBitmap.UnlockBits(). La versione parallela è molto più incoerente ma varia tra 60 e 80 millisecondi. Considerando che sto acquisendo da quattro telecamere contemporaneamente e lo streaming su disco con un sacco di tempo libero, questo è piuttosto deludente. L'API viene fornita con un codice di esempio in C++ che può essere visualizzato da tutte e quattro le telecamere contemporaneamente a una frequenza fotogrammi completa (17 fps). Non ha mai a che fare con Bitmap, poiché GDI accede a matrici di valori di pixel non elaborati.

In sintesi, devo attraversare la barriera gestita/non gestita semplicemente perché non esiste un modo diretto per prendere una serie di valori di pixel e "inserirla" in un'istanza Bitmap. In questo caso copio anche i valori dei pixel in un 24bpp Bitmap perché non riesco a "impostare" la tavolozza in un'immagine indicizzata, quindi 8bpp non è un formato valido per la visualizzazione.

Non c'è un modo migliore?

risposta

10

Creare un valid bitmap header e preinstallarlo nell'array di byte. Devi solo creare manualmente 56-128 byte di dati.

Secondo, create a stream from a byte array.

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

non vorrei immaginare che non v'è alcun tipo di funzioni di imaging prime in .Net. Ci sono così tante informazioni necessarie per un'immagine, sarebbe un elenco di parametri estremamente lungo per un metodo per accettare byte grezzi e creare un'immagine (è una bitmap, jpeg, gif, png, ecc. E tutti i dati aggiuntivi ciascuno di quelli che richiedono di creare un'immagine valida) non avrebbe senso.

+0

FromStream richiede che il flusso contiene una bitmap "pieno" (con informazioni di intestazione), non solo i valori dei pixel --- ho cercato di spiegare questo nella mia interrogazione. – pelesl

+3

Aggiornato con riferimento alle informazioni dell'intestazione bmp. Aggiungere in anticipo circa 128 byte e utilizzare fromstream dovrebbe essere notevolmente più veloce. –

+1

Vale la pena provare, soprattutto se mi permette di anteporre un'intestazione bitmap con una tavolozza in scala di grigi, quindi non devo copiare i valori dei pixel 3 volte per ottenere 24 bpp. – pelesl

4

Sembra che tutte le telecamere siano dello stesso tipo, quindi i dati delle immagini. Non so se questo è possibile nel tuo caso, ma potresti creare questa "full bitmap" da qualsiasi immagine e quindi usare semplicemente l'intestazione come modello (poiché sarà lo stesso per tutte le immagini).

+1

+1 Questa è un'idea fantastica! –

6

Prova questa

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

Penso che non devi dichiararlo come "var". Puoi invece dichiararlo come "Bitmap", vero? Ma la risposta è superba, ovviamente! – tmighty

+0

Certo che puoi. RTM https://msdn.microsoft.com/ru-ru/library/bb383973.aspx – Cheburek

Problemi correlati