![]() |
Image Segementation with K means für VCL und FMX Framework, Teil #2
ich hatte unter
![]() Rechendauer für ein Demo Bild mit 400*400 pixel ca. 10 sec. jetzt habe ich eine Implementierung welche die Canvas.Pixels Zugriffe minimiert, nur leider hat sich in dieser Generic Implementierung die Rechnzeit auf 10 min erhöht. Hat jemand eine Idee wie ich hier auf eine Ausführungszeit < 10 sec komme ?? ( PS: vielleich probiert jemand den Code auch aus :-) )
Delphi-Quellcode:
// Define a centroid function that returns the TClusterData object with the average coordinates of all elements in the cluster
function Centroidfct(const A: TClusterDataREC): Cardinal; begin Result := Round((A.DrawingColor and $FF) + ((A.DrawingColor shr 8) and $FF) + ((A.DrawingColor shr 16) and $FF)) div 3; end; function DistanceMetric(const A, B: TClusterDataREC): Double; begin // Result := Sqrt(Sqr(A.x - B.x) + Sqr(A.y - B.y)); Result := ABS(Centroidfct(A) - Centroidfct(B)); end; procedure TForm1.Button_FullTestClick(Sender: TObject); var MyKMeans: TImageClusterKMeans; Stopwatch: TStopwatch; begin // Create a new TStopwatch instance // Stopwatch := TStopwatch.Create; DebugMemo.lines.Clear; Stopwatch.Reset; UpdateStatus('start kmean image segmentation ...'); try // Start the stopwatch Stopwatch.Start; MyKMeans := TImageClusterKMeans.Create(5, DistanceMetric, Centroidfct, 10, UpdateStatus); try MyKMeans.LoadData(FBitmap); MyKMeans.Execute; MyKMeans.SaveData(FOutBitmap); FOutBitmap.SaveToFile('c:\temp\outkmeans.bmp'); OutImage.Picture.Bitmap.Assign(FOutBitmap); finally MyKMeans.Free; UpdateStatus('kmean image segmentation done!'); end; Stopwatch.Stop; DebugMemo.lines.Add(Format('Elapsed time: %d ms', [Stopwatch.ElapsedMilliseconds])); finally // Free the TStopwatch instance // Stopwatch.Free; end; end;
Delphi-Quellcode:
unit Unit_TKmeans;
interface uses types, classes, SysUtils, Generics.Collections, {$IFDEF FrameWork_VCL} vcl.Graphics; {$ENDIF} {$IFDEF FrameWork_FMX} System.UITypes, System.UIConsts, FMX.types, FMX.Utils, FMX.Graphics; {$ENDIF} const Infinity = 10000000; type TStatusCallback = reference to procedure(const Status: string); type TClusterDataREC = record {$IFDEF FrameWork_VCL} DrawingColor: TColor; {$ENDIF} {$IFDEF FrameWork_FMX} DrawingColor: TAlphaColor; {$ENDIF} x, y: Integer; chrlabel: char; // ... // .. // . end; TClusterData = class DrawingColor: TColor; x, y: Integer; chrlabel: char; // ... // .. // . end; /// <summary> /// a bit different pixeldefinition /// </summary> TClusterDataREC2 = record BWColor: Byte; x, y: Integer; // tbd. // ... // .. // . end; /// <summary> /// here it can be just a simple pixel description, /// in general we store the complete orginal data inside this list /// </summary> TRawData<T> = class(TList<T>) end; /// <summary> /// store the data now inside a cluster with a Centroid /// </summary> TCluster<T> = record /// <summary> /// <para> /// as of today T, but in future some other data type , depending /// </para> /// <para> /// on future research :-) /// </para> /// </summary> Center: T; /// <summary> /// the selected elements from out complete raw data /// </summary> ClusterElements: TArray<T>; end; /// <summary> /// the cluster list /// </summary> TClusterList<T> = class(TList < TCluster < T >> ) private function GetItem(Aindex: Integer): TCluster<T>; procedure SetItem(Aindex: Integer; const Value: TCluster<T>); public property Items[Aindex: Integer]: TCluster<T> Read GetItem Write SetItem; end; type /// <summary> /// measure the distance according to this function /// </summary TDistanceMetricfunction < T >= reference to function(const A, B: T): Double; type /// <summary> /// result of this function could be the TColor value , but also /// coordinates my have some impact in future .... /// </summary TCentroidfunction < T >= reference to function(const A: T): Cardinal; type TKMeans<T> = class private private FClusteredData: TClusterList<T>; FRawData: TArray<T>; FNumClusters: Integer; FDistanceMetric: TDistanceMetricfunction<T>; FCentroidfct: TCentroidfunction<T>; FMaxIterations: Integer; FStatusCallback: TStatusCallback; public constructor Create(NumClusters: Integer; DistanceMetric: TDistanceMetricfunction<T>; Centroidfct: TCentroidfunction<T>; MaxIterations: Integer = 10; StatusCallback: TStatusCallback = nil); function FindNewClusterCentroids: Boolean; function InitClusters: Boolean; function Execute: Integer; function ClusterCentroidsToString: String; virtual; abstract; procedure GroupData2NearestCluster; property RawData: TArray<T> read FRawData write FRawData; end; type TImageClusterKMeans = class(TKMeans<TClusterDataREC>) private FBMPwidth: Integer; FBMPheight: Integer; public function ClusterCentroidsToString: String; procedure LoadData(SoureBitMap: TBitmap); procedure SaveData(OutBitMap: TBitmap); end; implementation constructor TKMeans<T>.Create(NumClusters: Integer; DistanceMetric: TDistanceMetricfunction<T>; Centroidfct: TCentroidfunction<T>; MaxIterations: Integer = 10; StatusCallback: TStatusCallback = nil); begin FNumClusters := NumClusters; FDistanceMetric := DistanceMetric; FMaxIterations := MaxIterations; FClusteredData := TClusterList<T>.Create; // FRawData := TRawData<T>.Create; FDistanceMetric := DistanceMetric; FCentroidfct := Centroidfct; FStatusCallback := StatusCallback; end; function TKMeans<T>.Execute: Integer; var i: Integer; Changed: Boolean; Status: String; begin i := 0; if (self.InitClusters) then begin repeat GroupData2NearestCluster; Changed := FindNewClusterCentroids; inc(i); if Assigned(FStatusCallback) then begin Status := Format('Clustering iteration %d of %d', [i, 10]); // FStatusCallback(Status + ClusterCentroidsToString); end; until ((i > FMaxIterations) or (NOT Changed)); end; result := i; end; function TKMeans<T>.FindNewClusterCentroids: Boolean; var i, j: Integer; SelectedCluster: TCluster<T>; OldCentroid: Cardinal; ElementCount: Cardinal; Centroid: Cardinal; begin for i := 0 to FClusteredData.Count - 1 do begin SelectedCluster := FClusteredData.Items[i]; ElementCount := length(SelectedCluster.ClusterElements); OldCentroid := FCentroidfct(SelectedCluster.Center); for j := low(SelectedCluster.ClusterElements) to High(SelectedCluster.ClusterElements) do begin Centroid := Centroid + FCentroidfct(SelectedCluster.ClusterElements[j]); end; if (ElementCount <> 0) then begin Centroid := Round(Centroid / ElementCount); end else begin // this should not happen ! end; end; result := true; end; procedure TKMeans<T>.GroupData2NearestCluster; var i, j: Integer; closestCluster: Integer; minDist: Double; Dist: Double; ReferenceClusterCenter: T; RawDataItem: T; UpdateCluster: TCluster<T>; begin /// loop all raw data elements for j := low(FRawData) to high(FRawData) do begin RawDataItem := FRawData[j]; closestCluster := -1; minDist := Infinity; // Find the nearest cluster for i := 0 to FClusteredData.Count - 1 do begin Dist := FDistanceMetric(RawDataItem, FClusteredData[i].Center); if Dist < minDist then begin closestCluster := i; minDist := Dist; end; end; // these lines are wrong and do not compile, fix the code here !!!! UpdateCluster := FClusteredData[closestCluster]; SetLength(UpdateCluster.ClusterElements, length(UpdateCluster.ClusterElements) + 1); UpdateCluster.ClusterElements[High(UpdateCluster.ClusterElements)] := FRawData[j]; FClusteredData[closestCluster] := UpdateCluster; end; end; function TKMeans<T>.InitClusters: Boolean; var OneCluster: TCluster<T>; i: Integer; DataSize: Integer; begin DataSize := length(FRawData); FClusteredData.Clear; // Initialize the clusters with randomly chosen centers for i := 1 to FNumClusters do begin OneCluster.Center := FRawData[Random(DataSize)]; SetLength(OneCluster.ClusterElements, 0); FClusteredData.Add(OneCluster); end; result := ((FClusteredData.Count = FNumClusters) and (DataSize > FNumClusters)); end; {$IFDEF FrameWork_VCL} procedure TImageClusterKMeans.SaveData(OutBitMap: TBitmap); var i, j: Integer; ClusterIndex: Integer; closestCluster: Integer; minDist: Double; Dist: Double; OneCluster: TCluster<TClusterDataREC>; ClusteredData: TClusterDataREC; begin // Loop through all the pixels in the output bitmap // Clear the old data OutBitMap.Height := FBMPheight; OutBitMap.Width := FBMPwidth; OutBitMap.PixelFormat := pf24bit; for i := 0 to FClusteredData.Count - 1 do begin OneCluster := FClusteredData[i]; for j := low(OneCluster.ClusterElements) to high(OneCluster.ClusterElements) do begin ClusteredData := OneCluster.ClusterElements[j]; OutBitMap.Canvas.Pixels[ClusteredData.x, ClusteredData.y] := OneCluster.Center.DrawingColor; end; end; // Save the output bitmap to a file or show it in a GUI component // For example, to save the bitmap to a file: OutBitMap.SaveToFile('c:\temp\output.bmp'); end; function TImageClusterKMeans.ClusterCentroidsToString: String; var i: Integer; OneCluster: TCluster<TClusterDataREC>; begin result := ''; for i := 0 to FClusteredData.Count - 1 do begin OneCluster := FClusteredData[i]; {$IFDEF FrameWork_VCL} result := result + ColorToString(OneCluster.Center.DrawingColor) + '|' + IntTostr(length(OneCluster.ClusterElements)) + '; '; {$ENDIF} {$IFDEF FrameWork_FMX} result := result + AlphaColorToString(OneCluster.Center.DrawingColor) + '|' + IntTostr(length(OneCluster.ClusterElements)) + '; '; {$ENDIF} end; end; procedure TImageClusterKMeans.LoadData(SoureBitMap: TBitmap); var x, y: Integer; ClusterData: TClusterDataREC; begin // Clear the old data SetLength(FRawData, SoureBitMap.Height * SoureBitMap.Width); FBMPwidth := SoureBitMap.Width; FBMPheight := SoureBitMap.Height; // Loop through all the pixels in the bitmap for y := 0 to SoureBitMap.Height - 1 do begin for x := 0 to SoureBitMap.Width - 1 do begin // Create a TClusterData object for each pixel ClusterData.DrawingColor := SoureBitMap.Canvas.Pixels[x, y]; ClusterData.x := x; ClusterData.y := y; // Add the TClusterData object to the FRawData list FRawData[y * SoureBitMap.Width + x] := ClusterData; end; end; end; {$ENDIF} {$IFDEF FrameWork_FMX} procedure SetPixel(Color: TAlphaColor; i, j: Integer; bitdata: TBitmapData; PixelFormat: TPixelFormat); begin AlphaColorToPixel(Color, @PAlphaColorArray(bitdata.Data) [j * (bitdata.Pitch div PixelFormatBytes[PixelFormat]) + 1 * i], PixelFormat); end; function GetPixel(i, j: Integer; bitdata: TBitmapData; PixelFormat: TPixelFormat): TAlphaColor; begin result := PixelToAlphaColor(@PAlphaColorArray(bitdata.Data) [j * (bitdata.Pitch div PixelFormatBytes[PixelFormat]) + 1 * i], PixelFormat); end; procedure TImageClusterKMeans.SaveData(OutBitMap: TBitmap); var bitdata1: TBitmapData; i: Integer; j: Integer; Color: TAlphaColor; Cquer: Byte; OneCluster: TCluster<TClusterDataREC>; ClusteredData: TClusterDataREC; begin // Clear the old data OutBitMap.Height := FBMPheight; OutBitMap.Width := FBMPwidth; // Loop through all the pixels in the bitmap if (OutBitMap.Map(TMapAccess.ReadWrite, bitdata1)) then try for i := 0 to FClusteredData.Count - 1 do begin OneCluster := FClusteredData[i]; for j := low(OneCluster.ClusterElements) to high(OneCluster.ClusterElements) do begin ClusteredData := OneCluster.ClusterElements[j]; SetPixel(OneCluster.Center.DrawingColor, ClusteredData.x, ClusteredData.y, bitdata1, OutBitMap.PixelFormat) end; end; finally OutBitMap.Unmap(bitdata1); end; end; procedure TImageClusterKMeans.LoadData(SoureBitMap: TBitmap); var bitdata1: TBitmapData; i: Integer; j: Integer; Color: TAlphaColor; Cquer: Byte; ClusterData: TClusterDataREC; begin // Clear the old data SetLength(FRawData, SoureBitMap.Height * SoureBitMap.Width); FBMPwidth := SoureBitMap.Width; FBMPheight := SoureBitMap.Height; // Loop through all the pixels in the bitmap if (SoureBitMap.Map(TMapAccess.ReadWrite, bitdata1)) then try for i := 0 to SoureBitMap.Width - 1 do for j := 0 to SoureBitMap.Height - 1 do begin Color := GetPixel(i, j, bitdata1, SoureBitMap.PixelFormat); Cquer := Round(TAlphaColorRec(Color).B * 0.3 + TAlphaColorRec(Color).G * 0.59 + TAlphaColorRec(Color).R * 0.11); ClusterData.DrawingColor := Color; ClusterData.x := i; ClusterData.y := j; // Add the TClusterData object to the FRawData list FRawData[j * SoureBitMap.Width + i] := ClusterData; end; finally SoureBitMap.Unmap(bitdata1); end; end; {$ENDIF} { TClusterList<T> } function TClusterList<T>.GetItem(Aindex: Integer): TCluster<T>; begin result := inherited Items[Aindex]; end; procedure TClusterList<T>.SetItem(Aindex: Integer; const Value: TCluster<T>); begin inherited Items[Aindex] := Value; end; end. |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Hilft es was die in der Schleife im Execute aufgerufenen Unterroutinen
als Inline zu deklarieren? Dann fällt zumindest der Overhead der Funktionsaufrufe weg... |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Delphi-Quellcode:
Canvas.Pixels ist für sowas generell ungeeignet. Das ist als wolltest Du ein Fahrrad den weg tragen anstatt es zu fahren.... ClusterData.DrawingColor := SoureBitMap.Canvas.Pixels[x, y]; ... TBitmap hat ScanLine. Je nach gesetztem PixelFormat liefert es pro Aufruf einen Zeiger auf eine Zeile Pixel. Du hast da dann RGB (PixelFormat pf24Bit) oder ARGB (PixelFormat pf32Bit) direkt drinne stehen. |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Zitat:
So muss man ganz schön viel machen, bis das der Quelltext am Laufen ist. |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Was ist das denn für ein Anfängerfehler in procedure TKMeans<T>.GroupData2NearestCluster?
Delphi-Quellcode:
Das wird doch bei jeden neuen Vergrößern alle Daten umkopiert! Und das wird sehr schnell sehr groß!
SetLength(UpdateCluster.ClusterElements,
length(UpdateCluster.ClusterElements) + 1); |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
*.zip bzw. Download link kommt heute noch
was wäre die Lösung um das unkopieren zu sparen bzw. schneller zu machen? |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
TCluster wird eine Klasse und ClusterElements eine Liste.
Ich bin gleich soweit und stelle das Ergebnis gleich rein. |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Liste der Anhänge anzeigen (Anzahl: 1)
Hier meine Verbesserungen.
LoadData kann analog wie SaveData verbessert werden, dass überlasse ich dir aber als Übung. Lesetipp: ![]() Die Bitmap am besten auch in C:\Temp werfen oder über den anderen Button eine neue Öffnen. Anhang 56553 |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
die Projekte gibt es hier zum Download,
![]() |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Zitat:
|
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Die VCL Variante des GitHub Demo Projektes habe ich zum laufen bekommen.
- Canvas.Pixel entfernt. - SetLength(..., Length+1) verbessert -> 10 Sekunden für 800*600 Punkte! Immernoch viel viel viel zu lange. Ich habe vor bald 20 Jahren Bilder mit 10K*10K Pixeln in zwei Sekunden untersucht, wobei 5-Tausend bis 10-Tausend Zonen gefunden wurden. |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
die generic Code Verions VCL und FMX sollten eigentlich komplett sein unter
Delphi-Quellcode:
für die Variante
https://github.com/ImageProcessingFMX/kmeans/tree/main/VCL_AND_FMX
Delphi-Quellcode:
muss ich noch eine DEMO app erstellen
https://github.com/ImageProcessingFMX/kmeans/tree/main/VCL_ONLY
|
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
@ Sinspin : kannst du Deine Variante hier Posten?
|
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Was? Mein Programm von vor 20 Jahren? Das funktioniert anders. Da ist anfangs komplett unbekannt wieviele Zonen es geben könnte. Ist ein watershed.
Bei deinem Algo verstehe ich noch nicht was es mit "Cluster.Center" auf sich hat. Init mit Zufall gibt oft grottige Resultate. Init mit Festwert gab immer grottige Resultate (wobei ich eben kapiert habe woran es liegt, denke ich). Aber, muss das wirklich initialisiert werden? Dann ist der Algo von Anfang an zum Scheitern verurteilt. Was wirklich toll wäre, wenn Du dein Testbild mit uns teilen würdest. Sonst ist es nicht möglich unsere Resultate ordentlich zu vergleichen. |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Zitat:
@bernhard_LA Hast du hier mal in die ZIP geguckt oder ging das jetzt unter? |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
( bin gerade am testen :-) )
Zwischenstand : 4032*3024 pixel pf32bit
Delphi-Quellcode:
start kmean
image segmentation ... MyKMeans.LoadData elapsed time: 55577,86 ms MyKMeans.Execute elapsed time: 52690,50 ms MyKMeans.SaveData elapsed time: 2724,70 ms kmean image segmentation done in 110993,06 ms |
AW: Image Segementation with K means für VCL und FMX Framework, Teil #2
Liste der Anhänge anzeigen (Anzahl: 1)
Mal meine Variante.
Lesen/schreiben ist schnell, bei KMeans hatte ich ein bisschen was probiert, nutzte aber nix um besseres Resultat zu bekommen. Wie gesagt, dein Testbild wäre schön. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 23:41 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz