@lwx228问如何创建生成图像的公式。 我没有一路到达底部(就像美丽的动画一样这里),但我已经很接近了。
加载图像并使用 Canny 滤镜来查找明暗之间的一些界限。 越接近单个闭环,接下来的事情就越好。 下面的不连续性稍后会显示伪像,但还不错。
Original, left. Canny filtered, right.Original, left. Canny filtered, right.Original, left. Canny filtered, right.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;
上面,将像素放入名为 bitmap 的矩阵中,并将行数和列数放入 nr 和 nc 中。 使用 loc() 函数定位非零像素。 过滤后的图片中黑色为 0,白色为 1。 Loc 返回有效的索引,但它们假设位图是 nr*nc 的一列。 为了追踪轮廓,最好有 x,y 索引。 rowlocs 和 collocs 的计算公式如下:这里。 继续将索引缩放到 -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 outlineTracing the Canny outlineTracing the Canny outlineTracing the Canny outline追踪精明的轮廓
// 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 )} )
)
);上面的前两条语句使用之前保存的索引对 row 和 col 索引进行排序。 AsTable 从通过连接行索引和列索引构成的两列矩阵创建一个数据表。 GraphBuilder 制作图片。 使用表格按顺序选择点,并验证图中的循环是从头到尾绘制的,而不是从上到下绘制的。
其余的由一个我不太理解的公式和一堆显示框组成。
// run the fft
{colx, coly} = FFT( {collocs} );
{rowx, rowy} = FFT( {rowlocs} );
//dt=astable(colx||rowx);dt<<SETDIRTY></SETDIRTY>有两个 fft() 调用,一个用于 X 轴,一个用于 Y 轴。 rebuild() 函数正在重建 graphbox() line() 函数使用的一对矩阵。 nInterp 滑块控制从 FFT 数据中插回多少个点,nKeep 滑块确定为每个插值点保留多少个 fft 数据箱。
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 data大量插值点使其平滑,但数据不足
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 points数据很多,但插值点不够
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 interpolation似乎是正确的,足够的数据和足够的插值
蛮好玩的! 如果有人想在 FFT 上发表更多内容并获得那些漂亮的圆圈,那就太好了!