Bildverarbeitung und Verwendung von Marshal

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von xd-franky-5.

    Bildverarbeitung und Verwendung von Marshal

    Hallo,
    ich arbeite immer noch an Bildverarbeitung. Das was ich im letzten Threads besprochen hatte, Hardwarebeschleunigung, habe ich jetzt mal verworfen, da ich nicht mit WPF arbeiten möchte und das mit Bitmaps nicht ganz so geht, an OpenGL etc. wollte ich mich jetzt auch noch nicht trauen, geht bisher nur um Lernzwecke. Dennoch möchte ich eine Methode haben, mit der sich gut und schnell arbeiten lässt.

    Bisher hatte ich Unsafe-Code und LockBits in einer Klasse, so wie hier in etwa: github.com/LuizZak/FastBitmap/…/FastBitmap/FastBitmap.cs
    Aber ich möchte das ein bisschen ausweiten - mehrere Farbräume zum Beispiel - deshalb habe ich bisschen bei MATLAB und OpenCV geschaut und dort wird mit mehrdimensionalen Matrizen und Vektoren gearbeitet als "Farben". Das macht bei mir glaube ich nicht ganz so viel Sinn, da ich ja gezwungenermaßen mit LockBits arbeiten muss und da habe ich ja schon einen schönen Byte-Array mit Pointer und das dann noch jedes mal umzurechnen verschwendet glaube ich zu viel Rechenleistung. Allgemein ist mein Problem aber, dass ich nicht weiß was besser/schnell ist von folgenden Varianten:

    Vorab: Es gibt 3 Strukturen; BGR, HSV und Gray mit jeweils 3 und 1 Byte und mehreren nützlichen Funktionen.

    1. Für jeden Farbraum eine unsafe FastBitmap-Klasse, wobei der Array umgerechnet wird auf den jeweiligen Farbraum z.B. GrayScale hat dann nur noch ein Byte pro Pixel. GetPixel und SetPixel geben dann eine Structure "Gray" zurück. Dabei kann ich einfach *(Gray*)(ptr + X * sizeof(Gray) + Y * Stride) machen. Jedoch ist es unpraktisch mehrere Klassen zu haben, vor allem, wenn man eigene Farbräume hat mit nur 2 Byte z.B. könnte man nicht damit arbeiten. Auch ist es langsam, dauernd Structures zu erstellen.

    2. Eine generische unsafe Klasse BitmapContainer, welche je nach eingegebener Struktur eine Struktur zurück gibt. Hierbei würde GetPixel und SetPixel auch mit Strukturen arbeiten, jedoch haben wir eine Vereinheitlichung. Das Problem ist, dass Unsafe-Code generische Typen nicht mag, sprich funktioniert nicht, deshalb muss ich mit Marshal arbeiten und ich weiß nicht inwiefern das langsamer ist oder vielleicht auch schneller, wie sind eure Erfahrungen dort? Code:

    C#-Quellcode

    1. public T GetPixel<T>(int X, int Y) where T: struct
    2. {
    3. return System.Runtime.InteropServices.Marshal.PtrToStructure<T>((IntPtr)(ptr + Y * stride + X * sizeof(T)));
    4. }

    Wiederrum ist es glaube ich nicht sonderlich gut dauernd mit Structurs zu arbeiten.

    3. Eine unsafe Klasse BitmapContainer, welche mit Byte-Arrays arbeitet als Farben. Da sehen Get und Set dann so aus: (wobei colorsize = Größe der Farbe)

    C#-Quellcode

    1. public byte[] GetPixel(int X, int Y)
    2. {
    3. byte[] color = new byte[colorsize];
    4. if (X >= 0 && X < Width && Y >= 0 && Y < Height)
    5. {
    6. for (int i = 0; i < colorsize; i++) color[i] = *(ptr + Y * stride + X * colorsize + i);
    7. return color;
    8. }
    9. return color;
    10. }
    11. public void SetPixel(int X, int Y, byte[] Color)
    12. {
    13. if (X >= 0 && X < Width && Y >= 0 && Y < Height)
    14. {
    15. for (int i = 0; i < colorsize; i++) *(ptr + Y * stride + X * colorsize + i) = Color[i];
    16. }
    17. }

    Ich weiß hier wieder nicht ob Marshal-Copy nicht doch schneller wäre. Außerdem habe ich einen Memory-Leak, wenn ich die Funktion GetPixel aufrufe.

    Also vielleicht könnt ihr mir da weiterhelfen, eine der Methoden auszusuchen und/oder zu verbessern oder eine bessere vorzuschlagen, ich sitze schon ziemlich lang daran ?(

    EDIT: Bin gerade noch auf die Idee gekommen, wenn dann mit einem float Array zu arbeiten, weil ja nicht alle Farbenräume Integer-Werte haben. Bilder in einem anderen Farbraum, also umgekrempelter Array, werden am Ende wieder zu einem RGB-Bild zusammengestückelt, da machen Matrizen eventuell doch Sinn :huh: Man merkt, ich verzweifel.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „xd-franky-5“ ()

    xd-franky-5 schrieb:

    Außerdem habe ich einen Memory-Leak, wenn ich die Funktion GetPixel aufrufe.
    Die obere, also mit <Marshal>, nehme ich an.
    Wie äußert sich das? Ich kann am Code so nix erkennen.
    Was ist <T>?
    Hast Du da mal verschiedene Tiefen getestet?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany Nein also der Memory-Leak bezieht sich auf 3. bei Marshal habe ich noch nicht auf Memory-Leaks geachtet. <T> bei Marshal ist eine Structure, deshalb PtrToStructure. So eine Structure besteht aus n Bytes. Wie meinst du verschiedene Tiefen?

    xd-franky-5 schrieb:

    Wie meinst du verschiedene Tiefen?
    In welchem Bereich variiert n?
    ====
    Wie äußert sich der Memory Leak?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany So tief wie man möchte. Das Bild liegt ja in BGR im Array vor. Je nachdem wie groß dein Farbraum ist, kann n sein. Also ich würde sagen 1 bis 4, größere Farbräume habe ich jetzt noch nicht gesehen. Also wenn ich in die Speicherauslastung schaue geht es immer um 2 MB hoch. Rufe ich GetPixel nicht auf, passiert das nicht.

    xd-franky-5 schrieb:

    C#-Quellcode

    1. public byte[] GetPixel(int X, int Y)
    Du legst hier ein großes Byte-Array an.
    Wird das iwo wieder gelöscht?
    Ansonsten poste mal das bereinigte Projekt.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hm ich dachte das wird automatisch vom GC entsorgt wenn es nicht mehr im scope ist. Wenn das zu nichts führt, kann ich später wenn ich zu Hause bin das Projekt mal posten.

    xd-franky-5 schrieb:

    Hm ich dachte das wird automatisch vom GC entsorgt
    Üblicherweise nur, wenn dsas Programm länger in der Idle Loop läuft.
    Wenn Du programmtechnisch Stress machst, must Du selbst aufräumen.
    Hatte ich auch eben in einem Projekt, wo nix weiter lief, als Bildeinzug und Bilddarstellung.
    Ohne GC.Collect() ca. 10 Bilder, mit ca. 50 Bilder bis OutOfMemory.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    bis OutOfMemory.
    Das ist aber schon echt merkwürdig, eigentlich sollte spätestens, wenn der Speicher knapp wird der GC laufen. Ich hatte schon mal den Fall in einem Programm, das jemand vergessen hat das mit Graphics.FromImage erstellte Graphics-Objekt zu Disposen und nur das darunter liegende Bitmap Diposed hat. Deswegen ist der GC nicht gelaufen und er hat mit GC.Collect() den Speicher runter gedrückt. Nach dem richtigen Disposen ging dann alles. Dazu habe ich auch schon öfter gelesen, dass man GC.Collect() so gut es geht vermeiden soll, aber manchmal ist es halt schon nötig.
    Okay also muss ich mit GC.Collect() die arrays aufräumen? Als ich zuvor mit Structures gearbeitet habe, hatte ich das Problem nicht, da wurde alles immer direkt vom GC eingesammelt(trotz Stress). Desweiteren ist ja noch die Frage offen, ob Marshal.Copy oder so wie jetzt mit der Schleife.

    xd-franky-5 schrieb:

    Okay also muss ich mit GC.Collect() die arrays aufräumen?
    Eigentlich nicht.
    Wenn Du jedoch Deinem Programm keine Zeit gibst (Idle Loop) das selbst zu tun, solltest Du nach jeder Schleife (sofern das bei Dir so organisiert ist)

    VB.NET-Quellcode

    1. GC.Collect()
    2. GC.WaitForPendingFinalizer() ' oder so ähgnlich
    aufrufen.
    =====================

    Bluespide schrieb:

    Das ist aber schon echt merkwürdig,
    Das läuft in einer VM, da ist nicht so viel Platz.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Das ist doch auch nicht sehr schön, vorallem wenn andere Anwender die Klasse benutzen und Nichts von dem Memory-Leak wissen, wie soll man da darauf kommen GC.Collect() zu verwenden. Ich werde mal versuchen Marshal zu benutzen und die Zeit zu stoppen. Vielleicht bekomme ich dann ein paar Ergebnisse, unabhängig von der „Schönheit“ des Codes.

    xd-franky-5 schrieb:

    wie soll man da darauf kommen GC.Collect() zu verwenden
    Dafür sind Entwicklerforen da, z.B. unseres hier. :thumbsup:
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Wenn es über GC.Collect aufgeräumt werden kann, dann ist es kein MemoryLeak. Wenn dann jedoch wiederum das ganze nie Aufgeräumt wird, dann heißt das, dass du irwo noch Referenzen haben musst, die nicht so einfach zu erkennen sind als "unreferenziert".
    Und dass du mit structs das Problem nicht hast, ist ja logisch, die sind ja aufm Stack und nicht aufm Heap.

    Ganz nett für solche Sachen sind gerne __makerfe,__reftype und __refvalue keywords, welche ziemlich unbekannt und sehr selten verwendet werden. Jedoch sind die für Performance oft sehr Vorteilhaft. Immer besser als Marshall und oftmals besser als fixed, denn structs muss man nicht fixieren, sind ja schon aufm Stack und können nicht verschoben werden, somit ist das fixieren unnötig.

    C#-Quellcode

    1. [MethodImpl.AggressiveInlining]
    2. public T GetPixel<T>(int X, int Y) where T: struct
    3. {
    4. int size = SizeOf<T>();
    5. T pixel = default(T);
    6. var source = (byte*)(ptr + Y * stride + X * size);
    7. var pixelRef = __makeref(pixel);
    8. var pixelPtr = *((byte**)&pixelRef);
    9. Copy(pixelPtr,source,size);
    10. }

    Jetzt fehlt natürlich noch SizeOf<T>() und Copy(byte*,byte*,int).

    C#-Quellcode

    1. public int SizeOf<T>() where T : struct
    2. {
    3. return sizeof(T);
    4. }

    Das dürfte nicht gehen, dabei wunder ich mich jedoch, du hast oben sizeOf(T) verwendet, geht das jetzt etwa inzwischen, dass man sizeof auf Generics macht? Falls es geht, natürlich direkt sizeof(T) verwenden.
    Ansonsten ist diese Möglichkeit relativ bekannt:

    C#-Quellcode

    1. public int SizeOf<T>() where T : struct
    2. {
    3. T[] measureArray = new T[2];
    4. GCHandle measureArrayHandle = GCHandle.Alloc(measureArray, GCHandleType.Pinned);//Arrays sind immer aufm Heap(naja außer Runtime entscheided in manchen Fällen anders), muss fixiert werden
    5. try
    6. {
    7. var ref0 = __makeref(measureArrayHandle[0]);
    8. var ref1 = __makeref(measureArrayHandle[1]);
    9. var ptr0 = *((byte**)&ref0);
    10. var ptr1 = *((byte**)&ref1);
    11. return (int)(ptr1 - ptr0);
    12. }
    13. finally
    14. {
    15. measureArrayHandle.Free();
    16. }
    17. }

    Und nur um die Größe eines structs herauszufinden allokiert man etwas auf dem Heap, das hört sich sehr unsinnig und nicht gerade performant an? Richtig, wenn auch immer noch besser als was auch immer Marshall macht, wenn man PtrToStructure verwendet^^
    Das ganze geht jedoch komischerweise in IL aber gibt keine Möglichkeit für C#. Das beste ist jetzt, auf der Suche danach wie es ging, bin ich wieder auf die Lösung gestoßen:
    github.com/dotnet/corefx/blob/…erServices.Unsafe.il#L158
    Jedoch gibt es das ganze jetzt im .Net Core, was natürlich noch besser ist:
    nuget.org/packages/System.Runtime.CompilerServices.Unsafe/
    Schnappst dir die und alles ist gut. Denn es geht noch besser, die haben auch Unsafe.Read<T>(void* ptr)
    Somit ist mein ganzer Aufwand von oben fast schon fürn Arsch. Naja es ist trotzdem gut über diese keywords bescheid zu Wissen.

    Am Ende sieht das ganze nur so einfach aus.

    C#-Quellcode

    1. public T GetPixel<T>(int X, int Y) where T: struct
    2. {
    3. return Unsafe.Read<T>((byte*)(ptr + Y * stride + X * Unsafe.SizeOf<T>()));
    4. }


    Worauf ich beim Copy btw. hinaus wollte war btw. dass man das ganze mit einer for-schleife machen kann. Es aber in C# keine Möglichkeit gibt von Pointer zu Pointer zu kopieren, in IL jedoch schon. Was mich auch schon aufgeregt hat, meine Lösung damals:
    github.com/OctoAwesome/engenio…us/Helper/MemoryHelper.cs
    Und ja ich hab das zur Laufzeit kompiliert, weil es mir zu blöd war dafür das ganze IL-Compile zeugs manuell einzurichten(wäre doch schön, wenn das bereits Teil von .Net wäre, am besten mit Inline-IL).
    github.com/dotnet/corefx/blob/…erServices.Unsafe.il#L180
    Und das gibt es hier natürlich auch, wie du siehst sind die Methoden an sich komplett gleich. Nur ist das aus der Lib natürlich besser:
    1. Part vom Std
    2. Hat Inlining, was bei einem Delegaten natürlich nicht geht.

    Deine Lösung hast du natürlich schon, jedoch möchte ich die anderen beiden keywords nicht unerklärt lassen, auch wenn man sie hier nicht wirklich gebraucht hat:

    C#-Quellcode

    1. int a = 3;
    2. var aRef = __makeRef(a);
    3. Console.WriteLine(__refvalue(aRef,int));//3
    4. __refvalue(aRef,int) = 42;
    5. Console.WriteLine(a);//42

    Wie man sieht bekommt man mit __makeRef eigt. nichts anderes als ne Referenz, durch welche man den Wert verändern und auch auslesen kann. aRef dürfte dabei den Type TypedReference haben.

    C#-Quellcode

    1. int a = 3;
    2. var aRef = __makeRef(a);
    3. Console.WriteLine(typeof(int).Equals(__reftype(aRef)));//True(wenn ich richtig liege^^)
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    @jvbsl Danke für die ausführliche Erklärung, habe alles verstanden und es funktioniert auch, ohne "Memory-Leak". Ich habe aber noch ein paar generelle Fragen: Müssen die Structures generell mit Bytes aufgebaut sein, um sie mit Unsafe.Read<T> zu konvertieren? Also so:

    C#-Quellcode

    1. public struct HSV
    2. {
    3. public byte H;
    4. public byte S;
    5. public byte V;
    6. ...

    Denn, wenn ich da int nehme, funktioniert das ganze nicht, da sagt es mir, mein Speicher ist beschädigt.
    Das andere: Was ist schneller beim rechnen: unsigned char oder int, ich habe nämlich nun schon oft Leute gesehen, die mit unsigned char rechnen. Und Convert.ToByte oder (byte) (gilt für alle Typen) ?

    Ach und sizeof(T) gibt's nicht, habe den Code nur aus der Hand dahin geschrieben :D
    Naja wenn du aus den bytes ints machst ist die ganze struct natürlich 4 mal so groß, was dafür sorgt, dass du an irgendwelchen Stellen auf jeden Fall außerhalb des Speichers lesen/schreiben wirst, was natürlich ein Hinweis auf beschädigten Speicher ist, aber hier einfach nur ein Hinweis, dass du auf Memory Zugreifst, der dir nicht gehört.

    Was schneller beim Rechnen ist kommt auf den Compiler an(und auf die Plattform, x86/x64 sind dabei alle gleich bzw. ähnlich), wenn er gut optimiert ist beides gleich schnell. Rechnen intern wird er aber eher mit ints als mit bytes bzw. bei 64Bit sogar mit longs. Da wenn es eben best möglich optimiert wird, dann schmeißt er das in Register und rechnet mit den Registern und die sind bei x86 32Bit groß(ein int) und bei x64 64Bit(ein long).

    Aber klar, normalerweise kann man alle value-types innerhalb dieser structs verwenden. D.h. andere structs, ints/floats/decimal/...

    Jedoch musst du dann eben mit der größe aufpassen, dass die eben noch dem Original entspricht.

    C#-Quellcode

    1. [LayoutKind.Sequential(Pack=1)]
    2. public struct ARGB
    3. {
    4. public byte A,R,G,B;
    5. }
    6. [LayoutKind.Sequential(Pack=1)]
    7. public struct ARGB
    8. {
    9. public int ARGB;
    10. }

    sind z.B. kompatibel oder eben die noch besserere Variante:

    C#-Quellcode

    1. [LayoutKind.Explicit(Pack=1)]
    2. public struct ARGB
    3. {
    4. [FieldOffset(0)]
    5. public byte A;
    6. [FieldOffset(1)]
    7. public byte R;
    8. [FieldOffset(2)]
    9. public byte G;
    10. [FieldOffset(3)]
    11. public byte B;
    12. [FieldOffset(0)]
    13. public int ARGB;
    14. }

    Jetzt braucht es kein Color.FromArgb/Color.ToArgb, man kann hier die ints sowie bytes lesen und setzen. Beim ändern der bytes wird automatisch der int geändert und umgekehrt, da sie an derselben Stelle im Memory stehen. Diese struct ist ebenfalls auch nur 4-byte groß.
    Jenach Kodierung von ARGB kann man natürlich die Reihenfolge von FieldOffset anpassen, was jedoch zur CompileTime passieren muss.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Okay, danke ja habe vergessen, dass int ja nicht ein Byte groß ist, mein IT-Lehrer würde mich umbringen. zu FieldOffset, das ist ein guter Hinweis, habe es mal ausprobiert, aber das war eigentlich nicht das was ich gemeint habe und das was ich gemeint habe ist ja nun eh hinfällig, also in dem Sinne, einfach nochmal danke :)