2009-07-24 9 views
38

Ich habe eine WPF Bitmap, die ich aus einer JPG-Datei geladen wird, wie folgt:Suche nach spezifischen Pixelfarben eines Bitmap

this.m_image1.Source = new BitmapImage(new Uri(path)); 

ich abfragen will, was die Farbe an bestimmten Punkten. Zum Beispiel, was ist der RGB-Wert bei Pixel (65,32)?

Wie gehe ich vor? Ich nahm diesen Ansatz:

ImageSource ims = m_image1.Source; 
BitmapImage bitmapImage = (BitmapImage)ims; 
int height = bitmapImage.PixelHeight; 
int width = bitmapImage.PixelWidth; 
int nStride = (bitmapImage.PixelWidth * bitmapImage.Format.BitsPerPixel + 7)/8; 
byte[] pixelByteArray = new byte[bitmapImage.PixelHeight * nStride]; 
bitmapImage.CopyPixels(pixelByteArray, nStride, 0); 

Obwohl ich ein bisschen gestehen wird es vom Affen sehen, Affe mit diesem Code kann los. Wie auch immer, gibt es eine direkte Möglichkeit, dieses Array von Bytes zu konvertieren, um RGB-Werte zu konvertieren?

+0

Für welchen Zweck NStride? Und warum fügst du 7 hinzu und dividierst durch 8 in nStride Berechnung? – Jviaches

+3

@Jviaches Add 7 und dividiere durch 8, um richtig genug Bytes zu runden (f.i. 10 Bits werden 2 Bytes benötigen.) – erikH

Antwort

13

Die Interpretation des resultierenden Bytearrays hängt vom Pixelformat der Quellbitmap ab, aber im einfachsten Fall eines 32-Bit-ARGB-Bildes besteht jedes Pixel aus vier Bytes im Bytearray. Das erste Pixel würde thusly interpretiert werden:

alpha = pixelByteArray[0]; 
red = pixelByteArray[1]; 
green = pixelByteArray[2]; 
blue = pixelByteArray[3]; 

Um jedes Pixel im Bild zu verarbeiten, würden Sie wahrscheinlich wollen verschachtelte Schleifen erstellen, um die Zeilen und Spalten zu gehen, eine Indexvariable durch die Anzahl des Bytes in jedem Inkrementieren Pixel.

Einige Bitmap-Typen kombinieren mehrere Pixel zu einem einzigen Byte. Zum Beispiel packt ein monochromes Bild acht Pixel in jedes Byte. Wenn Sie mit anderen Bildern als 24/32 Bit pro Pixel (den einfachen) arbeiten müssen, würde ich vorschlagen, ein gutes Buch zu finden, das die zugrunde liegende Binärstruktur von Bitmaps abdeckt.

+4

Danke, aber ... ich fühle mich wie die.NET-Framework sollte eine Schnittstelle haben, die die gesamte Komplexität der Codierung abstrahiert. Offensichtlich weiß die System.Windows.Media-Bibliothek, wie eine Bitmap decodiert wird, da es es auf einem Bildschirm rendert. Warum sollte ich alle Implementierungsdetails kennen müssen? Kann ich die bereits implementierte Rendering-Logik nicht nutzen? –

+2

Ich fühle deinen Schmerz. In Windows-Formularen verfügt die Bitmap-Klasse über die GetPixel- und SetPixel-Methoden zum Abrufen und Festlegen des Werts einzelner Pixel. Diese Methoden sind jedoch notorisch langsam und bis auf sehr einfache Fälle praktisch nutzlos. Sie können RenderTargetBitmap und andere Objekte auf höherer Ebene verwenden, um Bitmaps zu erstellen und zusammenzusetzen, aber meiner Meinung nach ist die Bildverarbeitung am besten in der Domäne von C++ zu belassen, wo der direkte Zugriff auf die Bits besser passt. Sicher, Sie können Bildverarbeitung in C# /. NET tun, aber meiner Erfahrung nach fühlt es sich immer an, als würde man versuchen, einen quadratischen Stift durch ein rundes Loch zu stecken. –

+11

Ich stimme nicht zu. Meiner Meinung nach ist C# für 90% der Bildverarbeitungsbedürfnisse eine bessere Wahl als C++. Die C# -Sprache hat viele Vorteile gegenüber C++, und ihre "unsicheren" und "festen" Schlüsselwörter geben Ihnen direkten Zugriff auf die Bits a la C++, wenn Sie sie brauchen. In den meisten Fällen wird eine C# -Implementierung eines Bildverarbeitungsalgorithmus in der Geschwindigkeit der von C++ sehr ähnlich sein, aber gelegentlich wird sie wesentlich langsamer sein. Ich würde C++ nur in den wenigen Fällen verwenden, in denen C# aufgrund von Optimierungen, die der JIT-Compiler nicht erfüllt, schlecht abschneidet. –

53

Hier ist, wie ich Pixel in C# multidimensionalen Arrays manipulieren würde:

[StructLayout(LayoutKind.Sequential)] 
public struct PixelColor 
{ 
    public byte Blue; 
    public byte Green; 
    public byte Red; 
    public byte Alpha; 
} 

public PixelColor[,] GetPixels(BitmapSource source) 
{ 
    if(source.Format!=PixelFormats.Bgra32) 
    source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0); 

    int width = source.PixelWidth; 
    int height = source.PixelHeight; 
    PixelColor[,] result = new PixelColor[width, height]; 

    source.CopyPixels(result, width * 4, 0); 
    return result; 
} 

Nutzung:

var pixels = GetPixels(image); 
if(pixels[7, 3].Red > 4) 
    ... 

Wenn Sie Pixel zu aktualisieren, sehr ähnlich Code funktioniert, außer Sie eine WritableBitmap schaffen und verwenden Sie dieses:

public void PutPixels(WritableBitmap bitmap, PixelColor[,] pixels, int x, int y) 
{ 
    int width = pixels.GetLength(0); 
    int height = pixels.GetLength(1); 
    bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, width*4, x, y); 
} 

so:

var pixels = new PixelColor[4, 3]; 
pixel[2,2] = new PixelColor { Red=128, Blue=0, Green=255, Alpha=255 }; 

PutPixels(bitmap, pixels, 7, 7); 

Beachten Sie, dass dieser Code Bitmaps in Bgra32 konvertiert, wenn sie in einem anderen Format eintreffen. Dies ist im Allgemeinen schnell, kann jedoch in einigen Fällen zu einem Leistungsengpass führen. In diesem Fall würde diese Technik so geändert, dass sie dem zugrunde liegenden Eingabeformat besser entspricht.

aktualisieren

BitmapSource.CopyPixels Da kein zweidimensionales Array akzeptieren es notwendig ist, um das Array zwischen eindimensionalen und zweidimensionalen zu konvertieren. Die folgende Erweiterungsmethode sollte es tun:

public static class BitmapSourceHelper 
{ 
#if UNSAFE 
    public unsafe static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset) 
    { 
    fixed(PixelColor* buffer = &pixels[0, 0]) 
     source.CopyPixels(
     new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight), 
     (IntPtr)(buffer + offset), 
     pixels.GetLength(0) * pixels.GetLength(1) * sizeof(PixelColor), 
     stride); 
    } 
#else 
    public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset) 
    { 
    var height = source.PixelHeight; 
    var width = source.PixelWidth; 
    var pixelBytes = new byte[height * width * 4]; 
    source.CopyPixels(pixelBytes, stride, 0); 
    int y0 = offset/width; 
    int x0 = offset - width * y0; 
    for(int y=0; y<height; y++) 
     for(int x=0; x<width; x++) 
     pixels[x+x0, y+y0] = new PixelColor 
     { 
      Blue = pixelBytes[(y*width + x) * 4 + 0], 
      Green = pixelBytes[(y*width + x) * 4 + 1], 
      Red = pixelBytes[(y*width + x) * 4 + 2], 
      Alpha = pixelBytes[(y*width + x) * 4 + 3], 
     }; 
    } 
#endif 
} 

Es gibt zwei Implementierungen hier: Die ersten schnell ist, aber verwendet unsicheren Code ein IntPtr zu einem Array zu bekommen (muss mit/unsicheren Option kompilieren). Der zweite ist langsamer, erfordert aber keinen unsicheren Code. Ich verwende die unsichere Version in meinem Code.

WritePixels akzeptiert zweidimensionale Arrays, daher ist keine Erweiterungsmethode erforderlich.

+2

Wenn ich das versuche, bekomme ich "Input Array ist kein gültiger Rang". Irgendwelche Ideen? – whitehawk

+0

@whitehawk: siehe diesen Beitrag: http://stackoverflow.com/questions/3418494/input-array-is-not-a-valid-rank-error-message-when-using-copypixels-method/3418567#3418567 –

+0

@whitehawk: Danke, dass du mich über den Fehler informiert hast. Ich habe meine Antwort aktualisiert, um zu zeigen, wie ich sie beheben kann. In meinem eigenen Code benutzte ich tatsächlich eine andere CopyPixels-Überladung, die ein IntPtr benötigt. –

14

Ich möchte Ray's Antwort hinzufügen, die Sie auch PixelColor Struktur als eine Vereinigung erklären kann:

[StructLayout(LayoutKind.Explicit)] 
public struct PixelColor 
{ 
    // 32 bit BGRA 
    [FieldOffset(0)] public UInt32 ColorBGRA; 
    // 8 bit components 
    [FieldOffset(0)] public byte Blue; 
    [FieldOffset(1)] public byte Green; 
    [FieldOffset(2)] public byte Red; 
    [FieldOffset(3)] public byte Alpha; 
} 

Und diese Weise können Sie auch Zugriff auf die UInit32 BGRA (für schnelle Pixel haben werden Zugriff oder Kopie), neben den einzelnen Byte-Komponenten.

4

Ich möchte Ray's Antwort verbessern - nicht genug Rep zu kommentieren. > :(Diese Version hat das Beste von beiden, sicher und verwaltet, und die Effizienz der unsicheren Version. Außerdem habe ich es geschafft, den Schritt zu bestehen, da die .Net-Dokumentation für CopyPixels sagt, dass es der Schritt der Bitmap ist, nicht Es ist irreführend und kann trotzdem innerhalb der Funktion berechnet werden. Da das PixelColor-Array den gleichen Schritt wie die Bitmap haben muss (um es als Einzelkopie-Aufruf auszuführen), ist es sinnvoll, einfach ein neues zu erstellen Array in der Funktion als auch Leicht

public static PixelColor[,] CopyPixels(this BitmapSource source) 
    { 
     if (source.Format != PixelFormats.Bgra32) 
      source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0); 
     PixelColor[,] pixels = new PixelColor[source.PixelWidth, source.PixelHeight]; 
     int stride = source.PixelWidth * ((source.Format.BitsPerPixel + 7)/8); 
     GCHandle pinnedPixels = GCHandle.Alloc(pixels, GCHandleType.Pinned); 
     source.CopyPixels(
      new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight), 
      pinnedPixels.AddrOfPinnedObject(), 
      pixels.GetLength(0) * pixels.GetLength(1) * 4, 
       stride); 
     pinnedPixels.Free(); 
     return pixels; 
    } 
+0

Hinweis: Best of Managed bedeutet nicht, dass der Code sicher ist. Dieser Code wird nicht sicher laufen (zumindest konnte ich ihn auch nicht bekommen). – Peter

+1

Für diese Methode Array arbeiten muß [Höhe, Breite] –

0

Eine kleine Bemerkung:..

Wenn Sie diesen Code verwenden möchten (Edit: zur Verfügung gestellt von Ray Verbrennungen), aber den Fehler über die Arrays erhalten Rang, versuchen Sie, die Erweiterungsmethoden wie folgt zu bearbeiten :

public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset, bool dummy) 

und dann die CopyPixels Methode wie folgt nennen:

source.CopyPixels(result, width * 4, 0, false); 

Das Problem ist, dass, wenn die Erweiterungsmethode aus dem ursprünglichen nicht unterscheiden, wird das Original genannt. Ich denke, das liegt daran, PixelColor[,] entspricht auch Array.

Ich hoffe, das hilft Ihnen, wenn Sie das gleiche Problem haben.

2

Wenn Sie nur ein Pixel Farbe wünschen:

using System.Windows.Media; 
using System.Windows.Media.Imaging; 
... 
    public static Color GetPixelColor(BitmapSource source, int x, int y) 
    { 
     Color c = Colors.White; 
     if (source != null) 
     { 
      try 
      { 
       CroppedBitmap cb = new CroppedBitmap(source, new Int32Rect(x, y, 1, 1)); 
       var pixels = new byte[4]; 
       cb.CopyPixels(pixels, 4, 0); 
       c = Color.FromRgb(pixels[2], pixels[1], pixels[0]); 
      } 
      catch (Exception) { } 
     } 
     return c; 
    } 
+0

Dank, sehr nützlich für die in WPF zu tun: http://www.functionx.com/vcsharp/gdi/colorselector.htm – ToastyMallows

3

ich alle Beispiele nahm und schuf eine etwas bessere - getestet es zu
(der einzige Fehler war, dass Magie 96 als DPI, die abgehört mich wirklich)

ich diese WPF Taktik auch im Vergleich gegenüber:

  • GDI durch Graphics (System.Drawing) mit
  • Interop durch direkt GetPixel von Gdi32.dll

Zu meinem supprise Aufruf
Dies funktioniert x10 schneller als GDI, und um x15-mal schneller als Interop.
Also, wenn Sie WPF verwenden - viel besser, um damit zu arbeiten, um Ihre Pixelfarbe zu bekommen.

public static class GraphicsHelpers 
{ 
    public static readonly float DpiX; 
    public static readonly float DpiY; 

    static GraphicsHelpers() 
    { 
     using (var g = Graphics.FromHwnd(IntPtr.Zero)) 
     { 
      DpiX = g.DpiX; 
      DpiY = g.DpiY; 
     } 
    } 

    public static Color WpfGetPixel(double x, double y, FrameworkElement AssociatedObject) 
    { 
     var renderTargetBitmap = new RenderTargetBitmap(
      (int)AssociatedObject.ActualWidth, 
      (int)AssociatedObject.ActualHeight, 
      DpiX, DpiY, PixelFormats.Default); 
     renderTargetBitmap.Render(AssociatedObject); 

     if (x <= renderTargetBitmap.PixelWidth && y <= renderTargetBitmap.PixelHeight) 
     { 
      var croppedBitmap = new CroppedBitmap(
       renderTargetBitmap, new Int32Rect((int)x, (int)y, 1, 1)); 
      var pixels = new byte[4]; 
      croppedBitmap.CopyPixels(pixels, 4, 0); 
      return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]); 
     } 
     return Colors.Transparent; 
    } 
} 
0

Sie können Farbkomponenten in einem Bytearray erhalten. Erste Kopie der Pixel in 32 Bit zu einem Array und umwandeln, die in 8-Bit-Array mit 4-mal größerer Größe

int[] pixelArray = new int[stride * source.PixelHeight]; 

source.CopyPixels(pixelArray, stride, 0); 

// byte[] colorArray = new byte[pixelArray.Length]; 
// EDIT: 
byte[] colorArray = new byte[pixelArray.Length * 4]; 

for (int i = 0; i < colorArray.Length; i += 4) 
{ 
    int pixel = pixelArray[i/4]; 
    colorArray[i] = (byte)(pixel >> 24); // alpha 
    colorArray[i + 1] = (byte)(pixel >> 16); // red 
    colorArray[i + 2] = (byte)(pixel >> 8); // green 
    colorArray[i + 3] = (byte)(pixel); // blue 
} 

// colorArray is an array of length 4 times more than the actual number of pixels 
// in the order of [(ALPHA, RED, GREEN, BLUE), (ALPHA, RED...] 
+0

ich richtige Linie übernehmen müssen: byte [] colorArray = neues Byte [pixelArray.Length * 4]; –

Verwandte Themen