@lwx228 fragte wie man Formeln erstellt, die Bilder erzeugen. Ich bin nicht bis ganz nach unten gekommen (wie bei der schönen Animation). Hier ), aber ich bin nah dran.
Laden Sie ein Bild und verwenden Sie den Canny-Filter, um einige Grenzen zwischen Hell und Dunkel zu finden. Je näher man einem einzelnen geschlossenen Kreislauf kommt, desto besser für das Folgende. Die Diskontinuität unten wird später Artefakte zeigen, aber nicht so schlimm.
Original, left. Canny filtered, right.Original, left. Canny filtered, right.Original, left. Canny filtered, right.Original, left. Canny filtered, right.Original, links. Canny hat gefiltert, richtig.
//clear globals();// uncomment this if you change the picture 3 or 4 lines down to make it reload...
If( Try( Type( original ) != "Picture", 1 ), // set to 1 long enough to download once
original = Open(
//"https://i.stack.imgur.com/wtJoA.png", // this was an elephant...the loop closes nicely
// this fish does not close the loop...
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Malayan_fishes_%28PLATE_LXX%29_BHL46234933.jpg/800px-Malayan_fishes_%28PLATE_LXX%29_BHL46234933.jpg", // https://commons.wikimedia.org/wiki/File:Malayan_fishes_(PLATE_LXX)_BHL46234933.jpg
"png"
);
Beep();// comment out the clear globals to prevent re-loading the image over the internet each time
);
New Window( "Original Image", original );
bitmap = New Image( original );
bitmap << filter(
"canny", // TWEAK THESE for different images; you want a single loop for best results
largeThreshold( .5 )/*tracing begins when a large-value edge is found*/,
smallThreshold( .01 )/*tracing stops when the edge value gets too small*/,
radius( 2 )/*a blur-filter, use 0 for no blurring*/,
Repeat( 3 )/* number of repeats of a box-blur; 3 approximates a gaussian-blur filter */
);
New Window( "Original Edge Detection", bitmap );Oben enthält der Code eine kommentierte Clear-Globals()-Funktion, die die Variable zurücksetzt, die das Bild enthält, und dafür sorgt, dass sie neu geladen wird, wenn try() erkennt, dass kein Bild vorhanden ist. Sie können ein paar Zeilen weiter zum Elefantenbild wechseln. Der Piepton erinnert Sie daran, die gelöschten Globals erneut zu kommentieren. Das erste NewWindow() zeigt das Originalbild. Anschließend wird die Bitmap vom Original kopiert und gefiltert. Der Canny-Filter ist ein Kantenfinder-Follower mit Parametern, die für jedes Bild angepasst werden müssen. Beide Bilder oben funktionieren mit diesen Einstellungen (da der Elefant zu einfach ist). Das abschließende newwindow() zeigt den Umriss.
Leider handelt es sich bei der Gliederung um eine Bitmap. Der folgende Code benötigt eine Liste von XY-Koordinaten, die eine Endlosschleife bilden.
bitmap = bitmap << getpixels;
nr = N Rows( bitmap );
nc = N Cols( bitmap );
points = Loc( bitmap );
// see https://community.jmp.com/t5/JSL-Cookbook/Using-Loc-with-a-2D-Matrix/ta-p/195207
rowlocs = Floor( (points) / nc ) + 1; // nc is the number of columns in the 2D pixmat
collocs = Mod( (points), nc ) + 1; // rowlocs and collocs are 2D indexes (1-based)
// center and scale
rowlocs = rowlocs - Min( rowlocs );
rowlocs = rowlocs / Max( rowlocs );
rowlocs = rowlocs * 2 - 1;
collocs = collocs - Min( collocs );
collocs = collocs / Max( collocs );
collocs = collocs * 2 - 1;
Oben werden die Pixel in eine Matrix mit dem Namen „Bitmap“ und die Anzahl der Zeilen und Spalten in „nr“ und „nc“ eingefügt. Verwenden Sie die Funktion loc(), um die Pixel ungleich Null zu lokalisieren. Schwarz ist Null, Weiß ist Eins im gefilterten Bild. Loc gibt funktionierende Indizes zurück, geht jedoch davon aus, dass die Bitmap eine Spalte von nr*nc ist. Zum Nachzeichnen des Umrisses ist es besser, die XY-Indizes zu haben. Rowlocs und Collocs werden mit einer Formel von berechnet Hier . Fahren Sie fort und skalieren Sie die Indizes auf einen Bereich von -1 zu 1. Sie werden nicht für die Indizierung verwendet, da sie nur auf Indizes in der Bitmap verweisen. Aber sie werden für Nachbartests verwendet.
// extract the line-loop. crude, probably slow... first thing that comes to mind...
kdt = KDTable( rowlocs || collocs );
idxSort = J( N Rows( points ), 1, . );
idx = (kdt << K nearest rows( 1, [0, 0] ))[1][1];
idxSort[1] = idx;
For( i = 2, i <= N Rows( points ), i += 1,
kdt << removerows( idx ); // remove this point from the table so
idx = (kdt << K nearest rows( 1, idx ))[1][1]; // can find the next one
idxSort[i] = idx; // save a sorted list of the indexes
);
Sie können sich wahrscheinlich andere Möglichkeiten vorstellen, das zu tun, was dieser Code tut, vielleicht schneller. Es erstellt eine Tabelle der nächsten Nachbarn mit den 2D-Koordinaten, findet eine Startkoordinate (am nächsten bei 0,0, die irgendwo anders hätte sein können) und zeichnet dann die Kette der nächsten Nachbarn auf, indem sie jeden einzelnen Nachbarn, sobald er gefunden wird, entfernt und die entfernten verwendet koordinieren, um den nächsten zu finden. Lücken werden bei Bedarf übersprungen.Erstellen Sie ein Diagramm, um zu überprüfen, ob alles funktioniert.
Tracing the Canny outlineTracing the Canny outlineTracing the Canny outlineTracing the Canny outlineDen Canny-Umriss nachzeichnen
// put the row/col in connected order
rowlocs = rowlocs[idxSort];
collocs = collocs[idxsort];
dt = As Table( rowlocs || collocs );
dt << setdirty( 0 );
dt << Graph Builder(
Size( 514, 444 ),
Show Control Panel( 0 ),
Variables( X( :Col2 ), Y( :Col1 ) ),
Elements( Points( X, Y, Legend( 4 ) ) ),
SendToReport(
Dispatch( {}, "Col1", ScaleBox, {Min( 1.1 ), Max( -1.1 )} ),
Dispatch( {}, "graph title", TextEditBox, {Set Text( "Original Trace" )} ),
Dispatch( {}, "Graph Builder", FrameBox, {Marker Size( 0 )} )
)
);Die ersten beiden Anweisungen oben verwenden die zuvor gespeicherten Indizes, um die Zeilen- und Spaltenindizes in die richtige Reihenfolge zu bringen. AsTable erstellt eine Datentabelle aus der zweispaltigen Matrix, die durch Verketten von Zeilen- und Spaltenindizes erstellt wird. GraphBuilder macht das Bild. Verwenden Sie die Tabelle, um die Punkte der Reihe nach auszuwählen und sicherzustellen, dass die Schleife im Diagramm vom Anfang bis zum Ende und nicht von oben nach unten verläuft.
Der Rest besteht aus einer Formel, die ich nicht gut verstehe, und einer Menge Displaybox-Arbeit.
// run the fft
{colx, coly} = FFT( {collocs} );
{rowx, rowy} = FFT( {rowlocs} );
//dt=astable(colx||rowx);dt<<SETDIRTY></SETDIRTY>Es gibt zwei fft()-Aufrufe, einen für die X-Achse und einen für die Y-Achse. Die Funktion rebuild() erstellt ein Matrizenpaar neu, das von der Funktion graphbox() line() verwendet wird. Der nInterp-Schieberegler steuert, wie viele Punkte aus den FFT-Daten zurückinterpoliert werden, und der nKeep-Schieberegler bestimmt, wie viele Bins von FFT-Daten für jeden interpolierten Punkt beibehalten werden.
Lots of interpolated points make it smooth, but there is not enough dataLots of interpolated points make it smooth, but there is not enough dataLots of interpolated points make it smooth, but there is not enough dataLots of interpolated points make it smooth, but there is not enough dataViele interpolierte Punkte machen es glatt, aber es sind nicht genügend Daten vorhanden
Plenty of data, but not enough interpolated pointsPlenty of data, but not enough interpolated pointsPlenty of data, but not enough interpolated pointsPlenty of data, but not enough interpolated pointsViele Daten, aber nicht genügend interpolierte Punkte
Seems about right, enough data and enough interpolationSeems about right, enough data and enough interpolationSeems about right, enough data and enough interpolationSeems about right, enough data and enough interpolationScheint ungefähr richtig zu sein, genügend Daten und genügend Interpolation
Das hat Spaß gemacht! Wenn jemand mehr über FFTs und diese raffinierten Kreise posten möchte, wäre das großartig!