再帰プログラムを描画に応用すると複雑な絵が簡単に描ける。

まず、プログラムで図を描くための準備だけど、pythonで図を描くために、Pillowというライブラリーが便利だ。

僕は、wondows10 にanaconda でpython を入れて jupyter notebook でプログラムを書いている。Pillow をinstallするのために、anacond prompt から以下のコマンドを実行した。
[crayon]
$ pip install Pillow
[/crayon]

慣れない言語でもライブラリーでも、線を描くところまでできれば、結構色々なことが出来るものだ。

以下は灰色の画像を準備して、太さ3ピクセルの線を描くプログラム。始点(x0, y0)と終点(x1, y1)、
[crayon]
from PIL import Image, ImageDraw

W = 200 # 画面の横の長さ
H = 100 # 画面の縦の長さ
col = (128, 128, 128) # 画面の色
img = Image.new(“RGB”, (W, H), col) # 画像imgの準備

draw = ImageDraw.Draw(img) # drawオブジェクトの生成

x0 = 10 # 始点のx座標
y0 = 20 # 始点のy座標
x1 = 100 # 終点のx座標
y1 = 50 # 終点のy座標
line_col = (255, 255, 0) # 線の色 (R, G, B)
line_w = 3 # 線の太さ
draw.line((x0, y0, x1, y1), fill=line_col, width=line_w) # lineを描く

img.save(’00.jpg’, quality=95) # 画像をファイルに保存
img # 画像をjupyter notebookに表示
[/crayon]

最後の行の img は、jupyter notebook 内で画像を表示するため。別な画像表示ソフトで表示するには、img.show() とする。

さて、画像の座標は左上が(0, 0) で、左下が(横幅w, 立幅H) のようになるけれど(uv座標系)、画面の中心が(0, 0)で横軸x (x0からx1までの範囲) 縦軸y(y0からy1までの範囲)とした座標系(xy座標系)で絵を描きたい。

そこで、座標を変換する関数、u2x()とv2y()を作ることにする。
[crayon]
# 画像の大きさ
W, H = 200, 200
# XYの範囲
X0, X1 = -10, 10
Y0, Y1 = -10, 10

# Xからuへの変換
def x2u(x):
a = W/(X1 – X0)
b = -X0*a
return int(a * x + b)

# Yからvへの変換
def y2v(y):
a = H/(Y0 – Y1)
b = -Y1*a
return int(a * y + b)
[/crayon]

そして、直線を始点終点でなく、始点、線の角度、長さ、を指定して線を描く関数myline()を作る。myline()は終点を返すとする。
[crayon]
import numpy as np
def myline(draw, sx, sy, theta, length, col=(255, 255, 0), w=3):
ex = sx + np.cos(theta/180*np.pi) * length
ey = sy + np.sin(theta/180*np.pi) * length
draw.line((x2u(sx), y2v(sy), x2u(ex), y2v(ey)),
fill=col, width=w)
return ex, ey

# test
img = Image.new(“RGB”, (W, H), (128, 128, 128))
draw = ImageDraw.Draw(img)
sx, sy = 0, 0 # 始点
theta = 60 # 角度(右方向が0で時計回り)
length = 5 # 線の長さ
ex, ey = myline(draw, sx, sy, theta, length, col=(0, 255, 0), w=5)

img.save(’01.jpg’, quality=95) # 画像保存
img # 画像表示
[/crayon]



このmyline()を回帰で応用すると、尻尾のような絵が描ける。
[crayon]
DTHETA = 25
L_RATE = 0.8

def shippo(draw, sx, sy, theta, length, n):
if n>0:
ex, ey = myline(draw, sx, sy, theta, length, w=n)
shippo(draw, ex, ey, theta + DTHETA,
length * L_RATE, n-1)
else:
return

# test
img = Image.new(“RGB”, (W, H), (128, 128, 128))
draw = ImageDraw.Draw(img)
sx, sy = 0, -9
theta = 90
length = 4
n = 8
shippo(draw, sx, sy, theta, length, n)

img.save(’02.jpg’, quality=95)
img
[/crayon]

shippo()の中で呼び出されるmyline()は、角度が25°だけ足され、長さは0.8倍になる。

ここまでは、関数の中で自分自身を1回だけ読んでいた。これを、2回呼ぶとどうなるか。次の関数shippo2 は、今の角度に25°足した線を描くだけでなく、今の角度から25°引いた線の二つを描く。いったいどんな図が描けるだろう。
[crayon]
DTHETA = 25
L_RATE = 0.8

def shippo2(draw, sx, sy, theta, length, n):
if n>0:
ex, ey = myline(draw, sx, sy, theta, length, w=n)
shippo2(draw, ex, ey, theta + DTHETA,
length * L_RATE, n-1) # 左方向の線
shippo2(draw, ex, ey, theta – DTHETA,
length * L_RATE, n-1) # 右方向の線
else:
return

# test
img = Image.new(“RGB”, (W, H), (128, 128, 128))
draw = ImageDraw.Draw(img)
sx, sy = 0, -9
theta = 90
length = 4
n = 8
shippo2(draw, sx, sy, theta, length, n)

img.save(’04.jpg’, quality=95)
img
[/crayon]


こんな図が描けてしまうのだ。たくさんの線が規則正しく並び、その枝の広がり具合はまるで木のようだ。

さて、この図を見ていると、ゲームなどの全探索に応用できるのでは?という考えが生まれてくるのではなだろうか。

03