oder [C#]: Fast Gaußian Blur

  • VB.NET

Es gibt 35 Antworten in diesem Thema. Der letzte Beitrag () ist von Artentus.

    Artentus schrieb:

    @diylab
    Ihr habts mit .Net geschafft, solche Ergebnisse bei 100 Pixel Radius zu erreichen?


    Moin,

    ja - aber nur mit dem STACKBLUR (der Code wurde weiter oben schon einmal gepostet).
    Hier meine Klasse:

    Spoiler anzeigen

    C-Quellcode

    1. using System;
    2. using System.Diagnostics;
    3. using System.Drawing;
    4. using System.Drawing.Imaging;
    5. using System.IO;
    6. using System.Runtime.InteropServices;
    7. namespace BlurTest
    8. {
    9. public class ImageTools
    10. {
    11. /// <summary>
    12. /// Load image without leaving the file locked
    13. /// </summary>
    14. /// <param name="fileName">Source path</param>
    15. /// <returns>Image from stream</returns>
    16. public static Image LoadBitmap(string fileName)
    17. {
    18. using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    19. return Image.FromStream(stream);
    20. }
    21. /// <summary>
    22. /// Save blurred bitmap
    23. /// </summary>
    24. /// <param name="SourceImage">Source bitmap</param>
    25. /// <param name="filePath">Target path</param>
    26. public static void SaveBlur(Bitmap SourceImage, string filePath)
    27. {
    28. SourceImage.Save(filePath);
    29. }
    30. /// <summary>
    31. /// Stackblur
    32. /// </summary>
    33. /// <param name="SourceImage">Source bitmap</param>
    34. /// <param name="BlurRadius">Blur radius</param>
    35. public static Bitmap FastBlur(Bitmap SourceImage, int BlurRadius)
    36. {
    37. if (BlurRadius > 0)
    38. {
    39. // Start stopwatch
    40. Stopwatch watch = Stopwatch.StartNew();
    41. var rct = new Rectangle(0, 0, SourceImage.Width, SourceImage.Height);
    42. var dest = new int[rct.Width * rct.Height];
    43. var source = new int[rct.Width * rct.Height];
    44. var bits = SourceImage.LockBits(rct, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
    45. Marshal.Copy(bits.Scan0, source, 0, source.Length);
    46. SourceImage.UnlockBits(bits);
    47. int w = rct.Width;
    48. int h = rct.Height;
    49. int wm = w - 1;
    50. int hm = h - 1;
    51. int wh = w * h;
    52. int div = BlurRadius + BlurRadius + 1;
    53. var r = new int[wh];
    54. var g = new int[wh];
    55. var b = new int[wh];
    56. int rsum, gsum, bsum, x, y, i, p1, p2, yi;
    57. var vmin = new int[max(w, h)];
    58. var vmax = new int[max(w, h)];
    59. var dv = new int[256 * div];
    60. for (i = 0; i < 256 * div; i++)
    61. {
    62. dv[i] = (i / div);
    63. }
    64. int yw = yi = 0;
    65. for (y = 0; y < h; y++)
    66. { // blur horizontal
    67. rsum = gsum = bsum = 0;
    68. for (i = -BlurRadius; i <= BlurRadius; i++)
    69. {
    70. int p = source[yi + min(wm, max(i, 0))];
    71. rsum += (p & 0xff0000) >> 16;
    72. gsum += (p & 0x00ff00) >> 8;
    73. bsum += p & 0x0000ff;
    74. }
    75. for (x = 0; x < w; x++)
    76. {
    77. r[yi] = dv[rsum];
    78. g[yi] = dv[gsum];
    79. b[yi] = dv[bsum];
    80. if (y == 0)
    81. {
    82. vmin[x] = min(x + BlurRadius + 1, wm);
    83. vmax[x] = max(x - BlurRadius, 0);
    84. }
    85. p1 = source[yw + vmin[x]];
    86. p2 = source[yw + vmax[x]];
    87. rsum += ((p1 & 0xff0000) - (p2 & 0xff0000)) >> 16;
    88. gsum += ((p1 & 0x00ff00) - (p2 & 0x00ff00)) >> 8;
    89. bsum += (p1 & 0x0000ff) - (p2 & 0x0000ff);
    90. yi++;
    91. }
    92. yw += w;
    93. }
    94. for (x = 0; x < w; x++)
    95. { // blur vertical
    96. rsum = gsum = bsum = 0;
    97. int yp = -BlurRadius * w;
    98. for (i = -BlurRadius; i <= BlurRadius; i++)
    99. {
    100. yi = max(0, yp) + x;
    101. rsum += r[yi];
    102. gsum += g[yi];
    103. bsum += b[yi];
    104. yp += w;
    105. }
    106. yi = x;
    107. for (y = 0; y < h; y++)
    108. {
    109. dest[yi] = (int)(0xff000000u | (uint)(dv[rsum] << 16) | (uint)(dv[gsum] << 8) | (uint)dv[bsum]);
    110. if (x == 0)
    111. {
    112. vmin[y] = min(y + BlurRadius + 1, hm) * w;
    113. vmax[y] = max(y - BlurRadius, 0) * w;
    114. }
    115. p1 = x + vmin[y];
    116. p2 = x + vmax[y];
    117. rsum += r[p1] - r[p2];
    118. gsum += g[p1] - g[p2];
    119. bsum += b[p1] - b[p2];
    120. yi += w;
    121. }
    122. }
    123. // copy back to image
    124. var bits2 = SourceImage.LockBits(rct, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
    125. Marshal.Copy(dest, 0, bits2.Scan0, dest.Length);
    126. SourceImage.UnlockBits(bits);
    127. // Stop stopwatch
    128. watch.Stop();
    129. Console.WriteLine(watch.ElapsedMilliseconds.ToString() + " ms");
    130. return SourceImage;
    131. }
    132. else
    133. {
    134. return null;
    135. }
    136. }
    137. private static int min(int a, int b) { return Math.Min(a, b); }
    138. private static int max(int a, int b) { return Math.Max(a, b); }
    139. }
    140. }


    Und der Aufruf:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. using System;
    2. using System.Drawing;
    3. namespace BlurTest
    4. {
    5. class Program
    6. {
    7. static void Main(string[] args)
    8. {
    9. using (Bitmap bm = new Bitmap(ImageTools.LoadBitmap(@"F:\BlurTest\2.jpg")))
    10. {
    11. ImageTools.SaveBlur(ImageTools.FastBlur(bm, 130), @"F:\BlurTest\2a.jpg");
    12. }
    13. Console.ReadKey();
    14. }
    15. }
    16. }


    Da komme ich wie gesagt bei 130px Radius auf 3 Sekunden für das erste Bild und 1,7 Sekunden für zweite Bild.
    Ich habe zum Test die Stoppuhr quick and dirty einfach in die Methode geballert.

    Ich bin mir sicher, wenn Du da wieder hand anlegst und das Marshalcopy rausschmeist und die Hauptschleifen für vertikal und horizontal parallelisierst, kommen wir auf min. 1/4 der Gesamtzeit!
    Hat auch beim Sharpen echt Wunder bewirkt (danke nochmals).
    Allein kriege ich das aber nicht hin - da verknoten sich meine Synapsen :rolleyes: ..

    Aber das Resultat bei 130px ist für meine Begriffe prima.

    LG,
    Bruno

    Artentus schrieb:

    Ich hab die FastBlur-Funktion kopiert und einfach statt meiner BoxBlur-Funktion aufgerufen.
    Kannst du mir mal nen Link geben, der erklärt, wie dieser Blur funktioniert? Ich würde das dann parallelisieren und mit Pointern arbeiten.


    Original Urheber: LINK
    C# Implementierung: LINK

    Aber eine richtige Erklärung? Öhhm..
    Hatte sogar mal eine schöne Erklärung in deutsch gefunden - aber nun isse weg.
    Ich weiß nur, das erst vertikal und dann horizontal geblurt wird - das macht wohl den Speed aus.

    Hier noch die Ergebnisse mit 130px:
    Bild 1
    Bild 2

    Mit größerem Radius gehts noch matschiger :D .

    LG,
    Bruno

    Artentus schrieb:

    Jo, ich sitz gerade dran. Die beschreibung bei dem Quellcode ist nicht gerade pralle, aber das wird schon.


    Jo - aber wie Gonger schon sagte - an einen gaußscher Weichzeichner kommt der Stackblur nicht heran - da fehlt noch der Weichspüler im letzten Gang.
    Aber schnell ist der Algo.. hmm.
    Vielleicht gehts ja noch weicher..

    LG,
    Bruno

    NACHTRAG:
    mal ganz quer gedacht - vielleicht einfach eine WPF-Klasse verwenden?
    Da gibts doch einen Haufen Image-Filter..

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „diylab“ ()

    Bitteschön, das große Bild mit Radius 130 800 Millisekunden: ^^
    Spoiler anzeigen

    C-Quellcode

    1. public static unsafe Bitmap StackBlur(this Bitmap sourceImage, int radius)
    2. {
    3. int width = sourceImage.Width;
    4. int height = sourceImage.Height;
    5. int size = radius << 1;
    6. var destinationImage = new Bitmap(width, height);
    7. var lockRect = new Rectangle(0, 0, width, height);
    8. BitmapData sourceData = sourceImage.LockBits(lockRect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    9. BitmapData destinationData = destinationImage.LockBits(lockRect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
    10. Parallel.For(0, height, (int y) =>
    11. {
    12. byte* sourcePointer = (byte*)sourceData.Scan0 + y * sourceData.Stride;
    13. byte* destinationPointer = (byte*)destinationData.Scan0 + y * destinationData.Stride;
    14. int alpha = 0;
    15. int red = 0;
    16. int green = 0;
    17. int blue = 0;
    18. var colors = new Queue<byte[]>(size);
    19. for (int i = 0; i < Math.Min(radius, width); i++)
    20. {
    21. var color = new byte[4];
    22. color[0] = sourcePointer[3];
    23. color[1] = sourcePointer[2];
    24. color[2] = sourcePointer[1];
    25. color[3] = sourcePointer[0];
    26. colors.Enqueue(color);
    27. alpha += color[0];
    28. red += color[1];
    29. green += color[2];
    30. blue += color[3];
    31. sourcePointer += 4;
    32. }
    33. for (int x = 0; x < width; x++)
    34. {
    35. if (colors.Count == size)
    36. {
    37. byte[] subtract = colors.Dequeue();
    38. alpha -= subtract[0];
    39. red -= subtract[1];
    40. green -= subtract[2];
    41. blue -= subtract[3];
    42. }
    43. if (x + radius < width)
    44. {
    45. var color = new byte[4];
    46. color[0] = sourcePointer[3];
    47. color[1] = sourcePointer[2];
    48. color[2] = sourcePointer[1];
    49. color[3] = sourcePointer[0];
    50. colors.Enqueue(color);
    51. alpha += color[0];
    52. red += color[1];
    53. green += color[2];
    54. blue += color[3];
    55. sourcePointer += 4;
    56. }
    57. destinationPointer[0] = (byte)(blue / colors.Count);
    58. destinationPointer[1] = (byte)(green / colors.Count);
    59. destinationPointer[2] = (byte)(red / colors.Count);
    60. destinationPointer[3] = (byte)(alpha / colors.Count);
    61. destinationPointer += 4;
    62. }
    63. });
    64. Parallel.For(0, width, (int x) =>
    65. {
    66. byte* sourcePointer = (byte*)destinationData.Scan0 + x * 4;
    67. byte* destinationPointer = (byte*)destinationData.Scan0 + x * 4;
    68. int alpha = 0;
    69. int red = 0;
    70. int green = 0;
    71. int blue = 0;
    72. var colors = new Queue<byte[]>(size);
    73. for (int i = 0; i < Math.Min(radius, height); i++)
    74. {
    75. var color = new byte[4];
    76. color[0] = sourcePointer[3];
    77. color[1] = sourcePointer[2];
    78. color[2] = sourcePointer[1];
    79. color[3] = sourcePointer[0];
    80. colors.Enqueue(color);
    81. alpha += color[0];
    82. red += color[1];
    83. green += color[2];
    84. blue += color[3];
    85. sourcePointer += destinationData.Stride;
    86. }
    87. for (int y = 0; y < height; y++)
    88. {
    89. if (colors.Count == size)
    90. {
    91. byte[] subtract = colors.Dequeue();
    92. alpha -= subtract[0];
    93. red -= subtract[1];
    94. green -= subtract[2];
    95. blue -= subtract[3];
    96. }
    97. if (y + radius < height)
    98. {
    99. var color = new byte[4];
    100. color[0] = sourcePointer[3];
    101. color[1] = sourcePointer[2];
    102. color[2] = sourcePointer[1];
    103. color[3] = sourcePointer[0];
    104. colors.Enqueue(color);
    105. alpha += color[0];
    106. red += color[1];
    107. green += color[2];
    108. blue += color[3];
    109. sourcePointer += destinationData.Stride;
    110. }
    111. destinationPointer[0] = (byte)(blue / colors.Count);
    112. destinationPointer[1] = (byte)(green / colors.Count);
    113. destinationPointer[2] = (byte)(red / colors.Count);
    114. destinationPointer[3] = (byte)(alpha / colors.Count);
    115. destinationPointer += destinationData.Stride;
    116. }
    117. });
    118. sourceImage.UnlockBits(sourceData);
    119. destinationImage.UnlockBits(destinationData);
    120. return destinationImage;
    121. }

    Ich hab übrigens nix abgeschrieben, kann also sein, dass der Code nicht 100% das wie der andere macht.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()

    Artentus schrieb:

    Bitteschön, das große Bild mit Radius 130 800 Millisekunden: ^^


    Sch*** die Wand an :D !
    Habe es gerade probiert - dieses Parallelgedöns ist wirklich irre.

    Auch wenn es vielleicht nicht den Vorstellungen des TE entspricht - ich finde es gut und kann es sicher in dieser Form gebrauchen.
    Danke für Deine Mühe!

    LG,
    Bruno
    Parallelisierung ist die Zukunft. ^^ Ist halt n' Unterschied, ob nur 3,4 GHz oder 13,6 GHz arbeiten.
    Ob man da noch mehr rausholen kann, dass man auf die gewünschten 500ms kommt, weiß ich nicht, da kann sich vielleicht jemand äußern, der mehr Verständnis von Speichermanagement hat, als ich.
    @Artentus: Ich bin begeistert!! :thumbsup: :thumbsup:
    Ich habe selbst gestern abend rumgebastelt und habs vermasselt
    Du hast das richtig gut hingekriegt - 800ms sind für mich ok :)

    Du solltest den Codeabschnitt im Tutorial bereich veröffentlichen - weil man da dann noch was über die Schnelligkeit der Parallelisierung lernen kann.

    Vielen Dank an alle und viele Grüße,
    wincrash

    ~closed~
    (\_/) Das ist Hase.
    (O.o) Kopiere Hase in deine Signatur
    (> <) und hilf ihm so auf seinem Weg zur Weltherrschaft.
    Ich kanns in den Sourcecode-Austausch stellen, nen Tutorial ists ja nicht und ich wüsste auch nicht, wie man eins draus machen könnte.
    Allerdings funktioniert der Code wirklich nur in C#, nicht in VB, von daher muss ich entweder warten, bis die Umstrukturierung erfolgt ist oder ich muss es in den Weiteren Programmiersprachen posten.