@lwx228 fragte wie man Formeln macht, die Bilder machen. Ich bin nicht ganz auf den Grund gekommen (wie die schöne 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 an einer einzigen geschlossenen Schleife, 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, links. Canny 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 hat der Code ein kommentiertes clear globals(), das die Variable zurücksetzt, die das Bild enthält, und bewirkt, dass es 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 Clear Globals erneut zu kommentieren. Das erste NewWindow() zeigt das Originalbild. Die Bitmap wird dann vom Original kopiert und gefiltert. Der Canny-Filter ist ein Kantensucher-Follower mit Parametern, die für jedes Bild angepasst werden müssen. Beide obigen Bilder funktionieren mit diesen Einstellungen (weil der Elefant zu einfach ist). Das letzte newwindow() zeigt die Kontur.
Leider ist die Gliederung in einer 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;
Holen Sie sich oben die Pixel in eine Matrix namens Bitmap und die Anzahl der Zeilen und Spalten in nr und nc. Verwenden Sie die loc()-Funktion, um die Nicht-Null-Pixel zu lokalisieren. Schwarz ist Null, Weiß ist Eins im gefilterten Bild. Loc gibt Indizes zurück, die funktionieren, aber sie gehen davon aus, dass die Bitmap eine Spalte von nr*nc ist. Um den Umriss zu verfolgen, ist es besser, die x-, y-Indizes zu haben. rowlocs und collocs werden mit einer Formel aus berechnet hier . Fahren Sie fort und skalieren Sie die Indizes in ein Leerzeichen von -1 zu 1 ... sie werden nicht für die Indizierung verwendet, da sie nur auf Einsen in der Bitmap zeigen. 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, um das zu tun, was dieser Code tut, vielleicht schneller. Es erstellt eine Nächste-Nachbar-Tabelle der 2D-Koordinaten, findet eine Startkoordinate (am nächsten bei 0,0, die irgendwo anders hätte sein können), zeichnet dann die Kette der nächsten Nachbarn auf, indem sie jeden gefundenen 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 outlineVerfolgen der Canny-Umrisse
// 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 obigen Anweisungen verwenden die zuvor gespeicherten Indizes, um die Zeilen- und Spaltenindizes in der Reihenfolge zu sortieren. 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 vergewissern Sie sich, dass die Schleife im Diagramm von Anfang bis Ende und nicht von oben nach unten gezeichnet wird.
Der Rest besteht aus einer Formel, die ich nicht gut verstehe, und einem Haufen 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() baut ein Matrizenpaar neu auf, das von der Funktion graphbox() line() verwendet wird. Der nInterp-Schieberegler steuert, wie viele Punkte aus den FFT-Daten heraus interpoliert 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 dataViele interpolierte Punkte machen es glatt, aber es gibt nicht genug Daten
Plenty of data, but not enough interpolated pointsViele Daten, aber nicht genug interpolierte Punkte
Seems about right, enough data and enough interpolationScheint ungefähr richtig zu sein, genug Daten und genug Interpolation
Das hat Spaß gemacht! Wenn jemand mehr über FFTs und diese raffinierten Kreise posten möchte, wäre das großartig!