@ lwx228 preguntó cómo crear fórmulas que generen imágenes. No llegué hasta el final (como la hermosa animación aquí ), pero me acerqué.
Cargue una imagen y use el filtro Canny para encontrar algunos límites entre la luz y la oscuridad. Cuanto más cerca esté de un único circuito cerrado, mejor para lo que sigue. La discontinuidad a continuación mostrará artefactos más adelante, pero no tan mal.
Original, left. Canny filtered, right.Originales, izquierda. Canny filtró, cierto.
//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 );
Arriba, el código tiene un clear globals() comentado que restablecerá la variable que contiene la imagen y hará que se vuelva a cargar cuando try() detecte que no hay imagen. Puedes cambiar a la imagen del elefante unas líneas más abajo. El pitido le recordará que vuelva a comentar los globales claros. La primera NewWindow() muestra la imagen original. Luego, el mapa de bits se copia del original y se filtra. El filtro Canny es un seguidor de bordes con parámetros que deben modificarse para cada imagen. Ambas imágenes de arriba funcionan con estas configuraciones (porque el elefante es demasiado fácil). La nueva ventana final () muestra el contorno.
Desafortunadamente, el esquema está en un mapa de bits. El código que sigue necesita una lista de coordenadas x,y que forman un bucle 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;
Arriba, coloque los píxeles en una matriz llamada mapa de bits y el número de filas y columnas en nr y nc. Utilice la función loc() para localizar los píxeles distintos de cero. El negro es cero, el blanco es uno en la imagen filtrada. Loc devuelve índices que funcionan, pero asumen que el mapa de bits es una columna de nr*nc. Para trazar el contorno será mejor tener los índices x,y. Los rowlocs y collocs se calculan con una fórmula desde aquí . Continúe y escale los índices en un espacio de -1 a 1... no se usarán para la indexación porque solo apuntan a aquellos en el mapa de bits. Pero se utilizarán para pruebas de vecinos.
// 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
);
Probablemente se te ocurran otras formas de hacer lo que hace este código, tal vez más rápido. Construye una tabla de vecinos más cercanos de las coordenadas 2D, encuentra una coordenada inicial (más cercana a 0,0, que podría haber estado en cualquier otro lugar), luego registra la cadena de vecinos más cercanos eliminando cada uno a medida que se encuentra y usando el valor eliminado. coordinar para encontrar el siguiente. Saltará los espacios según sea necesario. Haz un gráfico para comprobar que todo funciona.
Tracing the Canny outlineTrazando el contorno 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 )} )
)
);Las dos primeras declaraciones anteriores utilizan los índices guardados previamente para ordenar los índices de fila y columna. AsTable crea una tabla de datos a partir de la matriz de dos columnas creada mediante la concatenación de índices de fila y columna. GraphBuilder hace la imagen. Utilice la tabla para seleccionar los puntos en orden y verifique que el bucle en el gráfico se dibuje de principio a fin, no de arriba a abajo.
El resto consiste en una fórmula que no entiendo bien y un montón de trabajos de displaybox.
// run the fft
{colx, coly} = FFT( {collocs} );
{rowx, rowy} = FFT( {rowlocs} );
//dt=astable(colx||rowx);dt<<SETDIRTY></SETDIRTY>There are two fft() calls, one for the X axis and one for the Y axis. The rebuild() function is rebuilding a pair of matrices used by the graphbox() line() function. The nInterp slider controls how many points will be interpolated back out of the FFT data and the nKeep slider determines how many bins of fft data will be kept for each interpolated point.
<SPAN class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Lots of interpolated points make it smooth, but there is not enough data" style="width: 677px;"><span class="lia-inline-image-display-wrapper" image-alt="Lots of interpolated points make it smooth, but there is not enough data" style="width: 677px;"><img src="https://us.v-cdn.net/6038925/uploads/images/21483i77F5CFEB70A911B5/LowResSmooth.PNG" role="button" title="LowResSmooth.PNG" alt="Lots of interpolated points make it smooth, but there is not enough data" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Lots of interpolated points make it smooth, but there is not enough data</span></span><SPAN class="lia-inline-image-caption" onclick="event.preventDefault();">Muchos puntos interpolados lo suavizan, pero no hay suficientes datos</SPAN></SPAN>
<SPAN class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Plenty of data, but not enough interpolated points" style="width: 678px;"><span class="lia-inline-image-display-wrapper" image-alt="Plenty of data, but not enough interpolated points" style="width: 678px;"><img src="https://us.v-cdn.net/6038925/uploads/images/21484iCE75E2B7F0B5035B/HiResCoarse.PNG" role="button" title="HiResCoarse.PNG" alt="Plenty of data, but not enough interpolated points" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Plenty of data, but not enough interpolated points</span></span><SPAN class="lia-inline-image-caption" onclick="event.preventDefault();">Muchos datos, pero pocos puntos interpolados</SPAN></SPAN>
<SPAN class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Seems about right, enough data and enough interpolation" style="width: 679px;"><span class="lia-inline-image-display-wrapper" image-alt="Seems about right, enough data and enough interpolation" style="width: 679px;"><img src="https://us.v-cdn.net/6038925/uploads/images/21485iD8813FA597D7F858/MedResGreat.PNG" role="button" title="MedResGreat.PNG" alt="Seems about right, enough data and enough interpolation" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Seems about right, enough data and enough interpolation</span></span><SPAN class="lia-inline-image-caption" onclick="event.preventDefault();">Parece correcto, suficientes datos y suficiente interpolación.</SPAN></SPAN>
That was fun! If someone want to post more on FFTs and getting those nifty circles, that would be great!