@lwx228 chiesto come creare formule che creano immagini. Non sono arrivato fino in fondo (come la bellissima animazione qui ), ma mi sono avvicinato.
Carica un'immagine e usa il filtro Canny per trovare dei confini tra chiaro e scuro. Più ci si avvicina a un singolo circuito chiuso, meglio è per ciò che segue. La discontinuità di seguito mostrerà artefatti in seguito, ma non troppo male.
Original, left. Canny filtered, right.Originale, a sinistra. Canny ha filtrato, giusto.
//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 );Sopra, il codice ha un clear globals() commentato che ripristinerà la variabile che contiene l'immagine e la farà ricaricare quando try() rileva che non c'è immagine. Puoi passare all'immagine dell'elefante poche righe più in basso. Il segnale acustico ti ricorderà di ricommentare i chiari globali. Il primo NewWindow() mostra l'immagine originale. La bitmap viene quindi copiata dall'originale e filtrata. Il filtro Canny è un edge-finder-follower con parametri che devono essere modificati per ogni immagine. Entrambe le immagini sopra funzionano con queste impostazioni (perché l'elefante è troppo facile.) L'ultima finestra newwindow() mostra il contorno.
Sfortunatamente, il contorno è in una bitmap. Il codice che segue necessita di un elenco di coordinate x,y che formano un ciclo continuo.
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;
Sopra, inserisci i pixel in una matrice denominata bitmap e il numero di righe e colonne in nr e nc. Utilizzare la funzione loc() per individuare i pixel diversi da zero. Il nero è zero, il bianco è uno nell'immagine filtrata. Loc restituisce indici che funzionano, ma presuppongono che la bitmap sia una colonna di nr*nc. Per tracciare il contorno sarà meglio avere gli indici x,y. rowlocs e collocs sono calcolati con una formula da qui . Vai avanti e ridimensiona gli indici in uno spazio da -1 a 1 ... non verranno utilizzati per l'indicizzazione perché puntano solo a quelli nella bitmap. Ma saranno usati per i test del vicino.
// 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
);
Probabilmente puoi pensare ad altri modi per fare ciò che fa questo codice, forse più velocemente. Costruisce una tabella dei vicini più vicini delle coordinate 2D, trova una coordinata di partenza (la più vicina a 0,0, che avrebbe potuto essere altrove), quindi registra la catena dei vicini più vicini rimuovendo ciascuno di essi quando viene trovato e utilizzando il comando rimosso coordinare per trovare il prossimo. Salterà gli spazi vuoti secondo necessità.Crea un grafico per verificare che tutto funzioni.
Tracing the Canny outlineTracciare il contorno di Canny
// 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 )} )
)
);Le prime due istruzioni precedenti utilizzano gli indici salvati in precedenza per ordinare gli indici di riga e col. AsTable crea una tabella di dati dalla matrice a due colonne creata concatenando gli indici di riga e col. GraphBuilder crea l'immagine. Utilizzare la tabella per selezionare i punti in ordine e verificare che il ciclo nel grafico disegna dall'inizio alla fine, non dall'alto verso il basso.
Il resto consiste in una formula che non capisco bene e un mucchio di lavoro da vetrina.
// run the fft
{colx, coly} = FFT( {collocs} );
{rowx, rowy} = FFT( {rowlocs} );
//dt=astable(colx||rowx);dt<<SETDIRTY></SETDIRTY>Ci sono due chiamate fft(), una per l'asse X e una per l'asse Y. La funzione build() sta ricostruendo una coppia di matrici usate dalla funzione graphbox() line(). Il dispositivo di scorrimento nInterp controlla quanti punti verranno interpolati all'esterno dei dati FFT e il dispositivo di scorrimento nKeep determina quanti bin di dati fft verranno conservati per ciascun punto interpolato.
Lots of interpolated points make it smooth, but there is not enough dataMolti punti interpolati lo rendono fluido, ma non ci sono dati sufficienti
Plenty of data, but not enough interpolated pointsMolti dati, ma non abbastanza punti interpolati
Seems about right, enough data and enough interpolationSembra giusto, abbastanza dati e abbastanza interpolazione
È stato divertente! Se qualcuno volesse postare di più su FFT e ottenere quei cerchi eleganti, sarebbe fantastico!