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?
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
Aggiornato con riferimento alle informazioni dell'intestazione bmp. Aggiungere in anticipo circa 128 byte e utilizzare fromstream dovrebbe essere notevolmente più veloce. –
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