仓库源文站点原文


layout: post title: 四个参数的大象与傅立叶变换 categories:


2015-06-07 14:40:47

冯诺依曼有句话经费米引用之后变得非常出名:

With four parameters I can fit an elephant, and with five I can make him wiggle his trunk

刑志忠在一篇博文费米与大象中对这个故事有详细的说明.

最近的那篇文章中使用了四个复数来描绘大象的轮廓, 这其实有点耍赖的味道. 4个复数, 再加上一个复数, 5个复数, 共10个参数, 才可以让大象的鼻子动起来.

这种利用傅立叶展开描绘图形的方法, 其原理类似于天文学地心说中的本轮均轮理论. 只要项数足够多, 可以描绘出任何图形. 感兴趣的可以看看下面一些资料:

Mathematica中有一个函数可以用来生成各种人物的头像, 示例看这里. 用这种方法可以做出很好的辛普森头像, Ptolemy and Homer (Simpson).

好多人都拿这个大象的绘图来练习编程, 所以各种语言版本都有, Python, scratch, Flash, Mathematica. 我也不能免俗, 就拿它来练习kinetic了.

<script src="/jscss/kinetic.min.js"></script> <div id="kin"></div> <script> var p1Rea= 50, p1Img=-30, p2Rea= 18, p2Img= 8, p3Rea= 12, p3Img=-10, p4Rea=-14, p4Img=-60, p5Rea= 40, p5Img= 20, pFac=2; var aX=[0, 0, 0, p3Rea, 0, p4Rea], aY=[0, p4Img, 0, 0, 0, 0], bX=[0, p1Rea, p2Rea, 0, 0, 0], bY=[0, p1Img, p2Img, p3Img, 0, 0]; var stage, bodyLayer, trunkLayer, frmTime, wid, hig, ctx // window.onload = function() { stage = new Kinetic.Stage({ container: 'kin', width: 400, height: 400 }); bodyLayer = new Kinetic.Layer(); trunkLayer = new Kinetic.Layer(); wid=stage.getWidth()/2; hig=stage.getHeight()/2 stage.add(bodyLayer); stage.add(trunkLayer); var eye = new Kinetic.Circle({ x: p5Img*pFac, y:-p5Img*pFac, radius: 5, fill: 'red' }); bodyLayer.getContext().translate(wid, hig) bodyLayer.add(eye); bodyLayer.draw() drawBody(); ctx=trunkLayer.getContext() var trunk=new Kinetic.Shape({ }); trunkLayer.add(trunk) var anim = new Kinetic.Animation(function(frame) { time=frame.time/100 trunk.setDrawFunc(drawTrunk); }, trunkLayer); anim.start(); //} function drawBody() { var ctx=bodyLayer.getContext() ctx.beginPath(); var tbgn = 0.4 + 1.3 * Math.PI, tend = 2 * Math.PI + 0.9 * Math.PI, dt = (tend - tbgn)/100, x = fourier(tbgn, aX, bX), y = fourier(tbgn, aY, bY); ctx.moveTo(y*pFac, x*pFac) for (var t=tbgn; t<tend; t += dt) { x = fourier(t, aX, bX); y = fourier(t, aY, bY); ctx.lineTo(y*pFac, x*pFac); } ctx.setAttr('strokeStyle', 'red'); ctx.setAttr('lineWidth', 4); ctx.stroke(); } var drawTrunk=function() { ctx.clear(); ctx.beginPath(); var tbgn = 2 * Math.PI + 0.9 * Math.PI, tend = 0.4 + 3.3 * Math.PI, dt = (tend - tbgn)/100, x,y, x0 = fourier(tbgn, aX, bX), y0 = fourier(tbgn, aY, bY); ctx.moveTo(y0*pFac+wid, x0*pFac+hig) for (var t=tbgn-3*dt; t<tend+3*dt; t += dt) { x = fourier(t, aX, bX); y = fourier(t, aY, bY); x += Math.sin((y - y0) * Math.PI/1000) * Math.sin(time) * p5Rea; ctx.lineTo(y*pFac+wid, x*pFac+hig); } ctx.setAttr('strokeStyle', 'red'); ctx.setAttr('lineWidth', 4); ctx.stroke(); } function fourier(t, a, b) { var r = 0; for (var k=0; k<6; k++) { r += a[k] * Math.cos(k*t) + b[k] * Math.sin(k*t); } return r; } </script>