@lwx228尋ねた画像を作る数式の作り方。 一番下までたどり着けなかった(美しいアニメーションのように)ここ)、でも近づいてきました。
画像をロードし、Canny フィルターを使用して、明暗の境界を見つけます。 単一の閉じたループに近づくほど、その後の動作がより良くなります。 以下の不連続部分には後でアーティファクトが表示されますが、それほどひどいものではありません。
Original, left. Canny filtered, right.オリジナル、左。 キャニーはフィルターをかけていますね。
//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 );上のコードには、コメント化されたclear globals()があり、画像を保持する変数をリセットし、try()が画像がないことを検出したときに画像を再ロードします。 数行下の象の画像に切り替えることができます。 ビープ音が鳴り、クリアグローバルを再コメントするよう通知されます。 最初の NewWindow() は元の画像を表示します。 次に、ビットマップがオリジナルからコピーされ、フィルタリングされます。 Canny フィルターは、画像ごとに調整する必要があるパラメーターを備えたエッジファインダーフォロワーです。 上の両方の画像はこれらの設定で動作します (象が簡単すぎるため)。最後の newwindow() はアウトラインを示しています。
残念ながら、アウトラインはビットマップです。 以下のコードには、連続ループを形成する X、Y 座標のリストが必要です。
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;
上では、ビットマップという名前の行列にピクセルを取得し、nr と nc で行と列の数を取得します。 loc() 関数を使用して、ゼロ以外のピクセルを見つけます。 フィルタリングされた画像では、黒はゼロ、白は 1 です。 Loc は機能するインデックスを返しますが、ビットマップが nr*nc の 1 列であると想定されます。 アウトラインをトレースするには、X、Y インデックスを使用する方が良いでしょう。 rowloc と coloc は次の式で計算されます。ここ。 先に進み、インデックスを -1 から 1 の空間にスケールします。インデックスはビットマップ内のインデックスのみを指すため、インデックス作成には使用されません。 ただし、それらは近隣テストに使用されます。
// 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
);
おそらく、このコードの動作をより速く実行する他の方法を考えることができるでしょう。 2D 座標の最近傍テーブルを構築し、開始座標 (0,0 に最も近い、他の場所にある可能性がある) を見つけて、見つかったときにそれぞれを削除し、削除された座標を使用して最近傍のチェーンを記録します。次を見つけるために調整します。 必要に応じてギャップをスキップします。グラフを作成して、すべてが機能していることを確認します。
Tracing the Canny outlineCanny の輪郭をトレースする
// 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 )} )
)
);上記の最初の 2 つのステートメントは、以前に保存したインデックスを使用して、行インデックスと列インデックスを順番に並べ替えます。 AsTable は、行インデックスと列インデックスを連結して作成された 2 列の行列からデータ テーブルを作成します。 GraphBuilder が画像を作成します。 表を使用して点を順番に選択し、グラフのループが上から下ではなく、最初から最後まで描かれていることを確認します。
残りはよくわからない数式と大量のディスプレイボックスの作業で構成されています。
// run the fft
{colx, coly} = FFT( {collocs} );
{rowx, rowy} = FFT( {rowlocs} );
//dt=astable(colx||rowx);dt<<SETDIRTY></SETDIRTY>fft() 呼び出しは 2 つあり、1 つは X 軸用、もう 1 つは Y 軸用です。 再構築() 関数は、graphbox() line() 関数によって使用される行列のペアを再構築します。 nInterp スライダは、FFT データから補間して戻すポイントの数を制御し、nKeep スライダは、各補間ポイントに対して FFT データのビンの数を保持するかを決定します。
Lots of interpolated points make it smooth, but there is not enough data多くの補間点により滑らかになりますが、データが不足しています
Plenty of data, but not enough interpolated pointsデータは豊富ですが、補間ポイントが不十分です
Seems about right, enough data and enough interpolation十分なデータと十分な補間があり、ほぼ正しいようです
それは楽しかった! 誰かが FFT についてもっと投稿して、気の利いたサークルを獲得したいと思っているなら、それは素晴らしいことです。