IFS で絵を描く(2020.03.26)

作品解説

 つぶやき Processing で写実的な猫の画像が描けたら面白いだろうな、と思ってます。 つぶやき Processing のような、文字数に制限のある、つまり、 記述できる情報量に制限のある状況下で実写画像を表現するためには、 必然的に非常に高い圧縮効率が要求されます。

となると、通常の画像圧縮技術ではダメそうです。 であれば、これはもうフラクタル圧縮しかないだろう、 ということで IFS - IFS - iterated function system による 画像生成を試みました。

 結果としては、上で示したとおり、 バーンスレイ(Barnsley)のシダですら 1 ツィートに収めることができず、 猫や画像処理でよく使われる lena などは、 とてもではないが つぶやき Processing で表現できませんでした。

IFS による図形描画

 IFS とは反復関数系とも呼ばれ、繰り返して関数を適用する系のことです。 Iterated Function System の略なのですが、 フラクタルに関する用語という印象があります。

 なんのことだかさっぱり分からん、という人は 「IFS フラクタル」で検索すると解説しているページがいくつか出てきます。

数学的に説明すると、ある集合 S は S 自体を何らかの変換で処理したものの集合、 となるのですが、詳しいことは他の Web ページに譲ります。

 上で紹介した作品は、 こちらのページ https://rosettacode.org/wiki/Barnsley_fern で紹介している方法を実装したものです。

点 p0=(0,0) より出発して、

$$ p_{n+1}=f_i(p_n) $$

を順次計算するというものです。

 バーンスレイのシダの場合、fi は { f1, f2, f3, f4 } という 4 つの関数集合の要素となります。 どの関数が選ばれるのかは、確率で決まっており、 先に示したページによると、

  • 確率 1% で f1 が
  • 確率 85% で f2 が
  • 確率 7% で f3 または f4 が選ばれる

とあります。

なので、例えば、3 回の反復計算で得られる p3 は、

$$ p_3=f_2(f_3(f_1(p_0))) $$

により計算されたりします。

ソースコードとその解説

 最初に示したツィートの映像を生成するプログラムのソースコードを以下に示します:

def setup():
    size(500,500)
    fill(0,255,0)
    clear()
    noStroke()

# {f_i} を定めるパラメータ
C=[ [[ 0,   0,  0],[ 0,  .16,0  ]],
    [[ .85, .04,0],[-.04,.85,1.6]],
    [[ .2, -.26,0],[ .23,.22,1.6]],
    [[-.15, .28,0],[ .26,.24, .44]]
]

def draw():
    x=y=0
    for k in range(50):
        # f_i の決定処理
        d=random(100)
        if d<1:i=0    #  1% の確率で
        elif d<86:i=1 # 85% の確率で
        elif d<93:i=2 #  7% の確率で
        else: i=3     #  7% の確率で

        # f_i を適用
        s=C[i][0]
        t=C[i][1]
        u=s[0]*x+s[1]*y+s[2]
        v=t[0]*x+t[1]*y+t[2]
        x=u;y=v

        # 描画
        rect(x*40+200,450-y*40,2,2)

 確率により、適用する f_i を定めている部分について少し説明をします。 乱数により、d に 0〜100(未満) の値が入ります。 d<1 になる確率は 1% 以下です。 この処理については、特に解説することもないかと思います。

次の d<86 にて確率 86% になるのは、なぜかというと、 この処理は if d<1 の elif 処理にて処理されています。 つまり、i=1 となるのは、d が 1 以上、86 未満(これを、[1,86) と書きます)のときです。 この範囲は [0,100) を流さ 100 の数直線として表す場合、 その長さは 85 になります。 つまり、確率 85% で i=1 となることを示しています (i=2 や i=3 についても全く同様に説明されます)。

確率計算部分を簡略化したもの

 85% でとある関数を適用ーなどという処理では、どうしても文字数が多くなってしまいます。 未だ、この作品を つぶやき Processing できていないのですが、 例えば、

  • 確率 10% で f1 が
  • 確率 70% で f2 が
  • 確率 10% で f3 または f4 が選ばれる

と簡略化するならば、次のように関数選択処理も簡略化できます:

D=[0,1,1,1,1,1,1,1,2,3]
i=D[int(random(10))]

このように変更しても、シダの形はあまり変わりません。 とはいえ、つぶやき Processing として収めるためには、 まだ 50 文字ほど削減しなければなりません(なかなかムツカシイですね)。

def setup():size(500,500)
C=[[[0,0,0],[0,.16,0]],
   [[.85,.04,0],[-.04,.85,1.6]],
   [[.2,-.26,0],[.23,.22,1.6]],
   [[-.15,.28,0],[.26,.24,.44]]
]
D=[0,1,1,1,1,1,1,1,2,3]
F=lambda m,x,y:m[0]*x+m[1]*y+m[2]
def draw():
 x=y=0
 for k in range(50):
  i=D[int(random(10))]
  x,y=F(C[i][0],x,y),F(C[i][1],x,y)
  point(x*40+200,450-y*40)