@lwx228 demandé comment faire des formules qui font des images. Je ne suis pas allé jusqu'au bout (comme la belle animation ici ), mais je me suis rapproché.
Chargez une image et utilisez le filtre Canny pour trouver des limites entre la lumière et l'obscurité. Plus on se rapproche d'une seule boucle fermée, mieux c'est pour la suite. La discontinuité ci-dessous montrera des artefacts plus tard, mais pas trop mal.
Original, left. Canny filtered, right.Original, left. Canny filtered, right.Original, left. Canny filtered, right.Original, left. Canny filtered, right.D'origine, à gauche. Canny a filtré, d'accord.
//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 );Ci-dessus, le code a un clear globals() commenté qui réinitialisera la variable contenant l'image et la fera recharger lorsque le try() détecte qu'il n'y a pas d'image. Vous pouvez passer à l'image de l'éléphant quelques lignes plus bas. Le bip vous rappellera de re-commenter les globales claires. La première NewWindow() affiche l'image d'origine. Le bitmap est ensuite copié à partir de l'original et filtré. Le filtre Canny est un suiveur de bord avec des paramètres qui doivent être modifiés pour chaque image. Les deux images ci-dessus fonctionnent avec ces paramètres (parce que l'éléphant est trop facile.) La dernière newwindow() montre le contour.
Malheureusement, le contour est en bitmap. Le code qui suit nécessite une liste de coordonnées x,y qui forment une boucle continue.
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;
Ci-dessus, placez les pixels dans une matrice nommée bitmap, et le nombre de lignes et de colonnes dans nr et nc. Utilisez la fonction loc() pour localiser les pixels non nuls. Le noir est zéro, le blanc est un dans l'image filtrée. Loc renvoie des index qui fonctionnent, mais ils supposent que le bitmap est une colonne de nr*nc. Pour tracer le contour il sera préférable d'avoir les index x,y. les rowlocs et les collocs sont calculés avec une formule de ici . Allez-y et redimensionnez les index dans un espace de -1 à 1... ils ne seront pas utilisés pour l'indexation car ils ne pointent que vers ceux du bitmap. Mais ils seront utilisés pour les tests voisins.
// 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
);
Vous pouvez probablement penser à d'autres façons de faire ce que fait ce code, peut-être plus rapidement. Il construit une table des voisins les plus proches des coordonnées 2D, trouve une coordonnée de départ (la plus proche de 0,0, qui aurait pu être n'importe où ailleurs), puis enregistre la chaîne des voisins les plus proches en supprimant chacun au fur et à mesure qu'il est trouvé et en utilisant le supprimé coordonner pour trouver le suivant. Il sautera les lacunes au besoin.Faites un graphique pour vérifier que tout fonctionne.
Tracing the Canny outlineTracing the Canny outlineTracing the Canny outlineTracing the Canny outlineTracer le contour de 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 )} )
)
);Les deux premières instructions ci-dessus utilisent les index enregistrés précédemment pour trier les index de ligne et de colonne dans l'ordre. AsTable crée une table de données à partir de la matrice à deux colonnes créée en concaténant les index de ligne et de colonne. GraphBuilder crée l'image. Utilisez le tableau pour sélectionner les points dans l'ordre et vérifiez que la boucle dans le graphique trace du début à la fin, et non de haut en bas.
Le reste consiste en une formule que je ne comprends pas bien et un tas de travaux de displaybox.
// run the fft
{colx, coly} = FFT( {collocs} );
{rowx, rowy} = FFT( {rowlocs} );
//dt=astable(colx||rowx);dt<<SETDIRTY></SETDIRTY>Il y a deux appels fft(), un pour l'axe X et un pour l'axe Y. La fonction reconstruction() reconstruit une paire de matrices utilisées par la fonction graphbox() line(). Le curseur nInterp contrôle le nombre de points qui seront interpolés à partir des données FFT et le curseur nKeep détermine le nombre de tranches de données fft qui seront conservées pour chaque point interpolé.
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 dataBeaucoup de points interpolés le rendent lisse, mais il n'y a pas assez de données
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 pointsBeaucoup de données, mais pas assez de points interpolés
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 interpolationSemble à peu près correct, suffisamment de données et suffisamment d'interpolation
C'était amusant! Si quelqu'un veut publier plus sur les FFT et obtenir ces cercles astucieux, ce serait génial !