Parallel.For Schleife und Sperren
-
Hallo, ich versuche gerade sequentiellen Code nachträglich zu parallelisieren.
Soweit läuft das schon ganz gut, nur habe ich noch unerwünschte Streifen im Bild, was glaube ich daran liegt ,dass auf string l von jedem Thread aus zugegriffen werden kann. Eine Sperre wie bei "res" funktioniert in diesem Fall leider nicht, da die If anweisung auf das l zugreift, und diese dann nicht mehr erkennt. Hat jemand eine Idee?object Sperre = new object(); object Sperre2 = new object(); object Sperre3 = new object(); for (ushort x = 0; x < i.ImageBitmap.Width; x++) { Parallel.For(0, i.ImageBitmap.Height, y => { string l = CurrentClassificationPipeline.SelectedClassifier.classify(i.PixelValue(x, Convert.ToUInt16(y))); if (Problem.CurrentProblem.Labels.ContainsKey(l)) { System.Drawing.Color col = Problem.CurrentProblem.Labels[l]; lock (Sperre) { res.SetPixel(x, y, col); } } else lock (Sperre2) { res.SetPixel(x, y, System.Drawing.Color.Black); } }); }
-
Besteht das Problem noch? Habe den Thread früher nicht gesehen.
Naja: Innerhalb des Lambda-Ausdrucks greifst Du auf die äußere Zählvariable x zu. Der Wert einer "captured variable" ergibt sich aber erst zum Auswertungszeitpunkt.Zum Beispiel erzeugt folgendes...
for (int x = 0; x < 10; ++x) { ThreadPool.QueueUserWorkItem((o) => Console.Write("{0} ", x)); }
... so eine Ausgabe (nichtdeterministisch):
4 7 4 10 10 10 10 10 10 10
So kannst Du es reparieren:
for (int x = 0; x < 10; ++x) { int xtmp = x; ThreadPool.QueueUserWorkItem((o) => Console.Write("{0} ", xtmp)); }
-->
0 1 8 9 5 2 4 3 6 7
Außerdem: Deine vielen locks dürften jeden Geschwindigkeitszuwachs der Parallelisierung zu Nichte machen. Bei vielen Bildverarbeitungsalgorithmen sind locks auch nicht notwendig. Du kannst Threads auf Bildzeilen verteilen, ohne dass sie sich jemals in die Quere kommen.
Und SetPixel ist darüber hinaus auch sehr langsam. Es ist besser auf den Bilddaten direkt zu arbeiten.
-
Hallo µ,
Vielen Dank erst einmal für deine Mithilfe.
Sagen wir mal so, das Problem mit den Streifen ist weg, allerdings ist meine Lösung recht bescheiden was unter anderem an den vielen Sperren liegt. Diese "Lösung" verbraucht mehr Zeit als der Sequentielle Code.Die Streifen sind dadurch entstanden das ein Thread string l verändert:
l = CurrentClassificationPipeline.SelectedClassifier.classify(i.PixelValue(x, Convert.ToUInt16(y)));
während zur gleichen Zeit ein andererer Thread diese Zeile abgearbeitet hat, die von String l abhängig ist:
System.Drawing.Color col = Problem.CurrentProblem.Labels[l];
So bekommt Pixel 1 der eigentlich z.b. Grün sein sollte, nun z.b. die Farbe blau. Durch die zusätzliche Sperre
lock (Sperre3) { l = CurrentClassificationPipeline.SelectedClassifier.classify(i.PixelValue(x, Convert.ToUInt16(y))); }
habe ich sozusagen eine Zeitverschiebung erschaffen, die die Zeit zur Abarbeitung des Codes in der Sperre 3 benötigt. Somit hingt einem Thread den anderen immer in dieser Zeitspanne hinterher, und schaft es nicht string l zu ändern, während der andere Thread bereits die Farbe gesetzt hat.
Hier erstmal meine Lösung:
for (ushort x = 0; x < i.ImageBitmap.Width; x++) { Parallel.For(0, i.ImageBitmap.Height, y => { string l; lock (Sperre3) { l = CurrentClassificationPipeline.SelectedClassifier.classify(i.PixelValue(x, Convert.ToUInt16(y))); } if (Problem.CurrentProblem.Labels.ContainsKey(l)) { System.Drawing.Color col = Problem.CurrentProblem.Labels[l]; lock (Sperre) { res.SetPixel(x, y, col); } } else lock (Sperre2) { res.SetPixel(x, y, System.Drawing.Color.Black); } // } }); }
Ich habe deinen Vorschlag einmal ausprobiert und x temporär angelegt.Ergebnis Streifen sind vorhanden, dafür wird aber nur die halbe Zeit benötigt gegenüber sequentieller Lösung.
object Sperre = new object(); object Sperre2 = new object(); for (ushort x = 0; x < i.ImageBitmap.Width; x++) { Parallel.For(0, i.ImageBitmap.Height, y => { ushort xtmp = x; ushort ytmp = Convert.ToUInt16(y); string l = CurrentClassificationPipeline.SelectedClassifier.classify(i.PixelValue(xtmp, ytmp)); if (Problem.CurrentProblem.Labels.ContainsKey(l)) { System.Drawing.Color col = Problem.CurrentProblem.Labels[l]; lock (Sperre) { res.SetPixel(xtmp, ytmp, col); } } else lock (Sperre2) { res.SetPixel(xtmp, ytmp, System.Drawing.Color.Black); } }); }
Außerdem: Deine vielen locks dürften jeden Geschwindigkeitszuwachs der Parallelisierung zu Nichte machen.
Richtig, gutes Beispiel meine obere Lösung
Bei vielen Bildverarbeitungsalgorithmen sind locks auch nicht notwendig. Du kannst Threads auf Bildzeilen verteilen, ohne dass sie sich jemals in die Quere kommen.
Bei meiner Lösung ist mir wichtig, dass die Anzahl der Threads dynamisch erstellt wird, je nachdem wie viel Kerne der Rechner zur verfügung hat und auch gleichzeitig selbst die Lastverteilung übernimmt.In meinem Beispiel übernimmt das die Task Paralell Library. Ich finde die Grundidee super, das Bild zeilenweise zu bearbeiten. Hast du ein Codebeispiel wie das ganze dann aussieht? Ich denke mal da wiederum mit Threads gearbeitet wird, wird dann manuel entschieden wie viele Threads erstellt werden, oder?
um Set Pixel kümmere ich mich dann später, habe da aber eventuell auch schon etwas entdeckt
http://www.mycsharp.de/wbb2/thread.php?threadid=29667
-
Die Sache mit den captured Variables musst Du trotzdem beachten.
Egal.Ich kann Dir ein Beispiel aus meiner Bildverarbeitungsbibliothek zur Verfügung stellen. Nicht die Ganze, aber ein erweiterbarer Teil mit ein oder zwei Filtern. Die Threads werden auf die Bildzeilen verteilt. Entweder je nach vorgabe oder je nachdem wieviele Cores zur Verfügung stehen.
Das ganze passt nicht direkt auf Dein Problem, könnte aber als Inspiration dienen.
Würde das helfen?
-
Ja ich denke das würde helfen. Inspiration ist immer gut
-
Okay. Musst bedenken, dass das aus einer bestehenden Bibliothek herausgerissen wurde und deshalb etwas umfangreicher ist als für die konkreten Beispielfilter notwendig. Dafür aber erweiterbar.
Ich poste mal stückchenweise.
Kleine Testform:
Es wird ein Bild geladen und dann erst ein Invertierungsfilter und danach ein Glättungsfilter angewandt. Im Beispiel werden so viele Threads verwendet, wie Kerne zur Verfügung stehenusing System; using System.Drawing; using System.Windows.Forms; using ImageLib.Filter.Basic; using ImageLib.Filter.Smoothing; using ImageLib.Base; namespace fastimaging { public partial class Form1 : Form { PictureBox pb; public Form1() { InitializeComponent(); Load += new EventHandler(Form1_Load); pb = new PictureBox(); pb.SizeMode = PictureBoxSizeMode.Zoom; pb.Dock = DockStyle.Fill; Controls.Add(pb); } void Form1_Load(object sender, EventArgs e) { Bitmap inBitmap = (Bitmap)Image.FromFile(@"C:\xxxxxxxxxx.jpg"); Rectangle rec = new Rectangle(0, 0, inBitmap.Width, inBitmap.Height); //Lastverteilung auf so viele Threads, wie Kerne zur Verfügung stehen int coreCount = Environment.ProcessorCount; //Invertierungsfilter PixelFilterSingleSource inv = new Invert(ImageLib.Enums.FilterWorkPlace.InBitmap); //Gaussscher Glättungsfilter PixelFilterSingleSource gauss = new Gaussian(10); inv.Apply(inBitmap, rec, coreCount); pb.Image = gauss.Apply(inBitmap, rec, coreCount); } } }
-
Hilfsklassen für das Threading.
ThreadPoolDispatcher verteilt auf #coreCnt viele Kerne Aufgaben, die von außem mit dem Action<int> Delegat angegeben werden.
Asynchrone Delegate bedienen sich im Threadpool -> SchnellThreadBarrier ist notwendig um die Berechnungen bei manchen Bildfiltern zu synchronisieren.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ImageLib.InternalUtils.Parallel { internal static class SimpleThreadDispatcherFactory { public static IThreadDispatcher Create() { return new ThreadPoolDispatcher(); } } internal interface IThreadDispatcher { void Do(int coreCnt, Action<int> indexedAction); } internal class ThreadPoolDispatcher : IThreadDispatcher { public void Do(int coreCnt, Action<int> indexedAction) { Action[] action = new Action[coreCnt]; IAsyncResult[] asynResults = new IAsyncResult[coreCnt]; //dispatch... //int i = 0; //Die Berechnung für Testzwecke auf einen Thread beschränken (Auskommentieren der beiden Zeilen "for...") for (int i = 0; i < coreCnt; ++i) { int index = i; action[i] = () => indexedAction(index); asynResults[i] = action[i].BeginInvoke(null, null); } //...and synchronize for (int i = 0; i < coreCnt; ++i) action[i].EndInvoke(asynResults[i]); } } //Synchronisierungskonstrukt public class ThreadBarrier { int Counter; object threadLocker = new object(); readonly int BarrierMaxCount; public ThreadBarrier(int BarrierMaxCount) { this.BarrierMaxCount = BarrierMaxCount; } public void Synchronize() { if (BarrierMaxCount <= 1) return; lock (threadLocker) { if (++Counter < BarrierMaxCount) Monitor.Wait(threadLocker); else { Counter = 0; Monitor.PulseAll(threadLocker); } } } } }
-
Ein paar Hilfsklassen und Methoden, die ich auf die schnelle von verschiedenen Dateien im Projekt zusammenkopiert habe.
Langweiliges Zeug.using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System; namespace ImageLib.Enums { public enum ColorComponent { Blue_Value_Hellwert, Green_Saturation_Sättigung, Red_Hue_Farbton } public enum FilterWorkPlace { InBitmap, CreateNewBitmap } } namespace ImageLib.InternalUtils { internal class ImageScheme { public static int PixelformatToStep(PixelFormat pixelFormat) { if (pixelFormat == PixelFormat.Format24bppRgb) return 3; if (pixelFormat == PixelFormat.Format32bppRgb || pixelFormat == PixelFormat.Format32bppArgb) return 4; if (pixelFormat == PixelFormat.Format8bppIndexed) return 1; throw new ArgumentException(string.Format("Unknown Pixelformat in {0} for PixelformatToStep", typeof(ImageScheme).FullName)); } public static int ColorComponentToIndex(Enums.ColorComponent color) { if (color == Enums.ColorComponent.Red_Hue_Farbton) return 2; else if (color == Enums.ColorComponent.Green_Saturation_Sättigung) return 1; else if (color == Enums.ColorComponent.Blue_Value_Hellwert) return 0; throw new ArgumentException(string.Format("Unknown ColorComponent in {0} for ColorComponentToIndex", typeof(ImageScheme).FullName)); } //Muss für ein neues Pixelformat erweitert werden. public static Bitmap CreateBitmap(int width, int height, PixelFormat outPixelFormat) { Bitmap bmp = new Bitmap(width, height, outPixelFormat); if (outPixelFormat == PixelFormat.Format24bppRgb || outPixelFormat == PixelFormat.Format32bppRgb || outPixelFormat == PixelFormat.Format32bppArgb) { return bmp; } else if (outPixelFormat == PixelFormat.Format8bppIndexed) { ColorPalette palette = bmp.Palette; for (int c = 0; c <= 255; ++c) palette.Entries[c] = Color.FromArgb(c, c, c); bmp.Palette = palette; return bmp; } throw new Exception("Unsupported PixelFormat in ImageLib.InternalUtils.ImageScheme.CreateBitmap()"); } } }
-
Hier wirds interessant.
Die Basisklasse für weitere Filter.
In abgeleiteten Klassen findet in der überschriebenen Worker-Methode die Algorithmik statt.
Die Methode DoApply kümmert sich um die Lastenverteilung auf verschiedene Threads.using System; using System.Linq; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; namespace ImageLib.Base { //Vorlage nach dem Template-Method-Muster für Filter mit einem Eingabebild public abstract class PixelFilterSingleSource { protected abstract Enums.FilterWorkPlace FilterPlace { get; } protected abstract IDictionary<PixelFormat, PixelFormat> PixelFormats { get; } protected virtual bool AllowMultiThreading { get { return true; } } protected InternalUtils.Parallel.ThreadBarrier ThreadBarrier; public Bitmap Apply(Bitmap bitmap) { return DoApply(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Environment.ProcessorCount); } public Bitmap Apply(Bitmap bitmap, Rectangle r, int coreCnt) { return DoApply(bitmap, r, coreCnt); } protected virtual void PreWorkSync(Bitmap bitmap, Rectangle r, int coreCnt) { } protected virtual void PostWorkSync() { } protected abstract void Worker( IntPtr inPtr, IntPtr outPtr, PixelFormat inPixelFormat, PixelFormat outPixelFormat, int width, int height, int inStride, int inStep, int outStride, int outStep, int coreCnt, int index); private Bitmap DoApply(Bitmap bitmap, Rectangle r, int coreCnt) { if (!PixelFormats.ContainsKey(bitmap.PixelFormat)) throw new ArgumentException( string.Format("Unexpected Pixelformat ({0}) for {1}.\nFilter allows {2} as input", bitmap.PixelFormat, this.GetType().FullName, PixelFormats.Select(p => p.Key.ToString()).Aggregate((accu, pxformat) => accu + ", " + pxformat))); PreWorkSync(bitmap, r, coreCnt); ThreadBarrier = new ImageLib.InternalUtils.Parallel.ThreadBarrier(coreCnt); int width = r.Width; int height = r.Height; BitmapData inBitmapData = bitmap.LockBits(r, ImageLockMode.ReadWrite, bitmap.PixelFormat); Bitmap bitmapResult = bitmap; BitmapData outBitmapData = inBitmapData; if (FilterPlace == Enums.FilterWorkPlace.CreateNewBitmap) { bitmapResult = InternalUtils.ImageScheme.CreateBitmap(bitmap.Width, bitmap.Height, PixelFormats[bitmap.PixelFormat]); outBitmapData = bitmapResult.LockBits(r, ImageLockMode.WriteOnly, bitmapResult.PixelFormat); } //Auch der lesende Zugriff auf das Pixelformat scheint nicht threadsicher zu sein => Exception wegen gesperrtem Bitmapbereich. PixelFormat inPixelFormat = bitmap.PixelFormat; PixelFormat outPixelFormat = bitmapResult.PixelFormat; if (AllowMultiThreading) { var threadDispatcher = InternalUtils.Parallel.SimpleThreadDispatcherFactory.Create(); threadDispatcher.Do(coreCnt, (index) => Worker( inBitmapData.Scan0, outBitmapData.Scan0, inPixelFormat, outPixelFormat, width, height, inBitmapData.Stride, InternalUtils.ImageScheme.PixelformatToStep(inPixelFormat), outBitmapData.Stride, InternalUtils.ImageScheme.PixelformatToStep(outPixelFormat), coreCnt, index)); } else { Worker( inBitmapData.Scan0, outBitmapData.Scan0, inPixelFormat, outPixelFormat, width, height, inBitmapData.Stride, InternalUtils.ImageScheme.PixelformatToStep(inPixelFormat), outBitmapData.Stride, InternalUtils.ImageScheme.PixelformatToStep(outPixelFormat), 1, 0); } if (FilterPlace == Enums.FilterWorkPlace.CreateNewBitmap) bitmapResult.UnlockBits(outBitmapData); bitmap.UnlockBits(inBitmapData); ThreadBarrier = null; PostWorkSync(); return bitmapResult; } } }
-
Ein Invertierungsfilter.
Abgeleitet von PixelFilterSingleSource.using System.Collections.Generic; using System.Drawing.Imaging; using System.Drawing; using System; namespace ImageLib.Filter.Basic { public class Invert : Base.PixelFilterSingleSource { private Enums.FilterWorkPlace filterWorkPlace; public Invert(Enums.FilterWorkPlace filterWorkPlace) { this.filterWorkPlace = filterWorkPlace; } protected override Enums.FilterWorkPlace FilterPlace { get { return filterWorkPlace; } } protected override IDictionary<PixelFormat, PixelFormat> PixelFormats { get { var pixelFormats = new Dictionary<PixelFormat, PixelFormat>(); pixelFormats.Add(PixelFormat.Format24bppRgb, PixelFormat.Format24bppRgb); pixelFormats.Add(PixelFormat.Format32bppRgb, PixelFormat.Format24bppRgb); pixelFormats.Add(PixelFormat.Format32bppArgb, PixelFormat.Format24bppRgb); pixelFormats.Add(PixelFormat.Format8bppIndexed, PixelFormat.Format8bppIndexed); return pixelFormats; } } protected unsafe override void Worker( IntPtr inPtr, IntPtr outPtr, PixelFormat inPixelFormat, PixelFormat outPixelFormat, int width, int height, int inStride, int inStep, int outStride, int outStep, int coreCnt, int offset) { byte* inImagePointer = (byte*)inPtr; byte* outImagePointer = (byte*)outPtr; int inCurrentPos = offset * inStride; int inLineStep = coreCnt * inStride - width * inStep; int outCurrentPos = offset * outStride; int outLineStep = coreCnt * outStride - width * outStep; if (inPixelFormat == PixelFormat.Format8bppIndexed) { for (int y = offset; y < height; y += coreCnt, inCurrentPos += inLineStep, outCurrentPos += outLineStep) { for (int x = 0; x < width; ++x, inCurrentPos += inStep, outCurrentPos += outStep) { outImagePointer[outCurrentPos] = (byte)(255 - inImagePointer[inCurrentPos]); } } } else { for (int y = offset; y < height; y += coreCnt, inCurrentPos += inLineStep, outCurrentPos += outLineStep) { for (int x = 0; x < width; ++x, inCurrentPos += inStep, outCurrentPos += outStep) { outImagePointer[outCurrentPos] = (byte)(255 - inImagePointer[inCurrentPos]); outImagePointer[outCurrentPos + 1] = (byte)(255 - inImagePointer[inCurrentPos + 1]); outImagePointer[outCurrentPos + 2] = (byte)(255 - inImagePointer[inCurrentPos + 2]); } } } } } }
PixelFormats gibt an, wie Pixelformate ineinander übertragen werden. Also ein Eingabebild mit PixelFormat X wird in das PixelFormatY übertragen u.s.w.. Die Basisklasse stellt dann ein entsprechendes Bitmap für die Ausgabe bereit.
In der überschriebenen Worker-Methode ist der eigentliche Invertierungsfilter. Später etwas mehr zur Threadaufteilung .
-
Und zum Schluß noch der Gaußfilter zur Glättung.
using System.Collections.Generic; using System.Drawing.Imaging; using System.Drawing; using System; namespace ImageLib.Filter.Smoothing { //Gaußscher Glättungsfilter (separiert und parallelisiert) public unsafe class Gaussian : Base.PixelFilterSingleSource { int filterSize; // ==sigma float[] gaussianCoeff; //Thread-shared temporary arrays for seperated gaussian-filter. private float[,] tmpGray; private float[,] tmpRed; private float[,] tmpGreen; private float[,] tmpBlue; public Gaussian(int FilterSize) { filterSize = FilterSize; calcGaussianCoeff(); } private void calcGaussianCoeff() { gaussianCoeff = new float[filterSize + 1]; for (int i = 0; i <= filterSize; i++) gaussianCoeff[i] = (float)Math.Exp((-1.0 * (i * i)) / (filterSize * filterSize)); } protected override Enums.FilterWorkPlace FilterPlace { get { return Enums.FilterWorkPlace.CreateNewBitmap; } } protected override IDictionary<PixelFormat, PixelFormat> PixelFormats { get { var pixelFormats = new Dictionary<PixelFormat, PixelFormat>(); pixelFormats.Add(PixelFormat.Format24bppRgb, PixelFormat.Format24bppRgb); pixelFormats.Add(PixelFormat.Format32bppRgb, PixelFormat.Format24bppRgb); pixelFormats.Add(PixelFormat.Format32bppArgb, PixelFormat.Format24bppRgb); pixelFormats.Add(PixelFormat.Format8bppIndexed, PixelFormat.Format8bppIndexed); return pixelFormats; } } protected override void PreWorkSync(Bitmap bitmap, Rectangle r, int coreCnt) { if (bitmap.PixelFormat == PixelFormat.Format8bppIndexed) tmpGray = new float[bitmap.Width, bitmap.Height]; else { tmpRed = new float[bitmap.Width, bitmap.Height]; tmpGreen = new float[bitmap.Width, bitmap.Height]; tmpBlue = new float[bitmap.Width, bitmap.Height]; } } protected override void PostWorkSync() { tmpGray = tmpRed = tmpGreen = tmpBlue = null; } protected unsafe override void Worker( IntPtr inPtr, IntPtr outPtr, PixelFormat inPixelFormat, PixelFormat outPixelFormat, int width, int height, int inStride, int inStep, int outStride, int outStep, int coreCnt, int offset) { byte* inImagePointer = (byte*)inPtr; byte* outImagePointer = (byte*)outPtr; int outCurrentPos = offset * outStride; int outLineStep = coreCnt * outStride - width * outStep; if (inPixelFormat == PixelFormat.Format8bppIndexed) { for (int y = offset; y < height; y += coreCnt) { for (int x = 0; x < width; ++x) { float sum = 0; float coeffSum = 0; for (int i = -filterSize; i <= filterSize; ++i) { if (x + i >= 0 && x + i < width) { float coeff = gaussianCoeff[Math.Abs(i)]; coeffSum += coeff; int position = y * inStride + (x + i) * inStep; sum += coeff * inImagePointer[position]; } } tmpGray[x, y] = sum / coeffSum; } } ThreadBarrier.Synchronize(); for (int y = offset; y < height; y += coreCnt, outCurrentPos += outLineStep) { for (int x = 0; x < width; ++x, outCurrentPos += outStep) { float sum = 0; float coeffSum = 0; for (int j = -filterSize; j <= filterSize; ++j) { if (y + j >= 0 && y + j < height) { float coeff = gaussianCoeff[Math.Abs(j)]; coeffSum += coeff; sum += coeff * tmpGray[x, y + j]; } } outImagePointer[outCurrentPos] = (byte)(sum / coeffSum); } } } else { for (int y = offset; y < height; y += coreCnt) { for (int x = 0; x < width; ++x) { float sumRed = 0, sumGreen = 0, sumBlue = 0; float coeffSum = 0; for (int i = -filterSize; i <= filterSize; ++i) { if (x + i >= 0 && x + i < width) { int position = y * inStride + (x + i) * inStep; float coeff = gaussianCoeff[Math.Abs(i)]; coeffSum += coeff; sumRed += coeff * inImagePointer[position + 2]; sumGreen += coeff * inImagePointer[position + 1]; sumBlue += coeff * inImagePointer[position]; } } tmpRed[x, y] = sumRed / coeffSum; tmpGreen[x, y] = sumGreen / coeffSum; tmpBlue[x, y] = sumBlue / coeffSum; } } ThreadBarrier.Synchronize(); for (int y = offset; y < height; y += coreCnt, outCurrentPos += outLineStep) { for (int x = 0; x < width; ++x, outCurrentPos += outStep) { float sumRed = 0, sumGreen = 0, sumBlue = 0; float coeffSum = 0; for (int j = -filterSize; j <= filterSize; ++j) { if (y + j >= 0 && y + j < height) { float coeff = gaussianCoeff[Math.Abs(j)]; coeffSum += coeff; sumRed += coeff * tmpRed[x, y + j]; sumGreen += coeff * tmpGreen[x, y + j]; sumBlue += coeff * tmpBlue[x, y + j]; } } outImagePointer[outCurrentPos] = (byte)(sumBlue / coeffSum); outImagePointer[outCurrentPos + 1] = (byte)(sumGreen / coeffSum); outImagePointer[outCurrentPos + 2] = (byte)(sumRed / coeffSum); } } } } } }
-
Das wars.
Auf welche Art und Weise die Last auf Threads verteilt wird, entscheidet jeweils die abgeleitet Klasse.
Hier, sowohl bei Invert als auch bei Gaussian, findet eine Zeilenweise Bildverarbeitung statt.
Zur Verfügung stehen dazu die Parameter: offset und coreCnt in den überschriebenen Worker-Methoden, welche von der Basisklasse aufgerufen werden.Es ist: 0<=offset<coreCnt
offset ist also ein "Index" für jeden Thread, coreCnt die Anzahl der Threads.z.B.: offset = 2 und coreCnt = 6.
-> 6 Threads und die Methode arbeitet auf Thread mit offset/index = 2. Bei zeilenweiser Bearbeitung wird also bei der dritten Bildzeile begonnen (weil offset nullbasiert ist) und dann immer um 6 Zeilen weitergesprungen. So kommen sich Threads niemals in die Quere.
-
Vielen Dank. Ich werde mich mal damit beschäftigen und Rückmeldung geben wenn ich weiter bin.
-
Ok.
Kannst ja die Dateien einfach mal in ein Projekt kopieren und die Problemlösung der zeilenweisen Bildverarbeitung isolieren.
Bei Fragen, nur zu.
-
So, hier nochmal die Lösung zum Schluß. Ich hab das nun doch noch anders gelöst. Das Problem war das Set/Get Pixel kein Mehrfachzugriff erlaubt weil diese jeweils auf das Bildobjekt zugreifen. Mein Bild wird nun in ein ByteArray gespeichert und ich ändere die Daten direkt im Array. Wie man das ganze macht, wird hier beschrieben: http://ilab.ahemm.org/tutBitmap.html
Das problem der Streifen lag am Mehrfachzugriff auf den Klassifikator und nicht wie vorhher beschrieben. Diesen werde ich noch bearbeiten und bis dahin bleibt die Sperre erstmal drinne.Für diejenigen die den Beitrag fleißig mitverfolgt haben hier meine Umsetzung:
//Image Locking BitmapData lockData = i.ImageBitmap.LockBits(new System.Drawing.Rectangle(0, 0, i.ImageBitmap.Width, i.ImageBitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb); //Array erstellen um Bild darin zu speichern Int32[] imageData = new Int32[i.ImageBitmap.Width * i.ImageBitmap.Height]; //benutzen der Marshal Klasse um image data zu kopieren System.Runtime.InteropServices.Marshal.Copy(lockData.Scan0, imageData, 0, imageData.Length); int w = i.ImageBitmap.Width; int h = i.ImageBitmap.Height; //Dummy erzeugen und anzeigen: object Sperre = new object(); Parallel.For(0, w, x => { for (ushort y = 0; y < h; y++) { {//Laufindex für ByteArray erzeugen, durch X und Y Werte int j = 0; if (y == 0) j = x; if (y >= 1 && x == 0) j = w * y; if (y >= 1 && x >= 0) j = (w * y) + x; string l; lock (Sperre) { l = CurrentClassificationPipeline.SelectedClassifier.classify(i.PixelValue(Convert.ToUInt16(x), y)); } if (Problem.CurrentProblem.Labels.ContainsKey(l)) { System.Drawing.Color col = Problem.CurrentProblem.Labels[l]; imageData[j] = col.ToArgb(); } else imageData[j] = Color.Black.ToArgb(); } } }); //Copy Image Data back Marshal.Copy(imageData, 0, lockData.Scan0, imageData.Length); //Unlock Image i.ImageBitmap.UnlockBits(lockData); System.Drawing.Bitmap res = i.ImageBitmap.Clone(new System.Drawing.Rectangle(0, 0, i.ImageBitmap.Width, i.ImageBitmap.Height), System.Drawing.Imaging.PixelFormat.Format24bppRgb); Watch.Stop(); MessageBox.Show(Watch.ElapsedMilliseconds.ToString() + " ms"); return res;