例題8-3:動くテクスチャ¶
A: 唐突なんだが、今日はすごくやる気が無いんだ。
B: なんですか、藪から棒に。
A: いやさ、例題8-2で「次は動くテクスチャをやるよ」って予告したじゃん。だけどさ、この頃忙しすぎて面白い例題を考える暇が無いんだ。
B: そういえば最近よく研究室の隅で居眠りしてますね。で?
A: こっそり例題8-3をなかったことにしようかとも思ったんだけど、まあ昔テキトーに書いたデモでも載せてお茶を濁そうかな、と。
B: ははあ。いかにもやる気無さそうですな。そりゃ。
A: そんなわけでサンプルだ。実行すると、ダイナミックランダムノイズ上でコントラストが低い縦帯が左右に移動する。 こういう刺激をVisionEgg.MoreStimuliで用意されている刺激で実現するのは無理がある。テクスチャの動的描き替えならではだな。
行番号なしのソースファイルをダウンロード→ 08-3.py
1from VisionEgg.Core import *
2from VisionEgg.Textures import *
3
4import Image, ImageDraw
5import OpenGL.GL as gl
6from random import randint
7
8
9
10
11outputFigures = 3
12outputFiguresDone = 0
13
14scale = 6
15radius = 25
16
17image_size_back= (100,100)
18scaled_size_back = (image_size_back[0]*scale, image_size_back[1]*scale)
19
20image_size_move = (20,100)
21scaled_size_move = (image_size_move[0]*scale, image_size_move[1]*scale)
22
23screen = get_default_screen()
24
25temp_image_back = Image.new('L',image_size_back,0)
26temp_texture_back = Texture(temp_image_back)
27temp_image_move = Image.new('L',image_size_move,0)
28temp_texture_move = Texture(temp_image_move)
29
30x = screen.size[0]/2.0
31y = screen.size[1]/2.0
32
33stim_back = TextureStimulus(texture=temp_texture_back,
34 position = (x,y),
35 anchor = 'center',
36 mipmaps_enabled=0,
37 size = scaled_size_back,
38 texture_min_filter = gl.GL_NEAREST,
39 texture_mag_filter = gl.GL_NEAREST
40 )
41
42stim_move = TextureStimulus(texture=temp_texture_move,
43 position = (x,y),
44 anchor = 'center',
45 mipmaps_enabled=0,
46 size = scaled_size_move,
47 texture_min_filter = gl.GL_NEAREST,
48 texture_mag_filter = gl.GL_NEAREST
49 )
50
51stim_back_texture_object = stim_back.parameters.texture.get_texture_object()
52stim_move_texture_object = stim_move.parameters.texture.get_texture_object()
53
54
55viewport = Viewport(screen=screen,stimuli=[stim_back,stim_move])
56
57starttime = VisionEgg.time_func()
58currenttime = VisionEgg.time_func()
59t = 0
60while t < 10.0:
61 randomdata = numpy.random.randint(0,256,size=(image_size_back[1],image_size_back[0]))
62 stim_back_texture_object.put_sub_image(randomdata)
63
64 randomdata = numpy.random.randint(64,192,size=(image_size_move[1],image_size_move[0]))
65 stim_move_texture_object.put_sub_image(randomdata)
66
67 movex = scale*int(radius * numpy.cos(0.2*2*numpy.pi*t)) + x
68 movey = y
69 stim_move.parameters.position = (movex,movey)
70
71 screen.clear()
72 viewport.draw()
73
74 if outputFigures-outputFiguresDone > 0:
75 im = screen.get_framebuffer_as_image(buffer='back',format=gl.GL_RGB)
76 im.save('ss'+str(outputFiguresDone).zfill(4)+'.jpg')
77 outputFiguresDone += 1
78
79 swap_buffers()
80
81 currenttime = VisionEgg.time_func()
82 t = currenttime-starttime
B: あれ、これ実行する時に最初なんだかカクカクするんですが…。
A: ああ、それはスクリーンショットをファイルに出力しているからだ。それは後で解説しよう。 まずこのプログラムの最重要ポイントを説明しておこう。 60行目からwhileループで画面を描いているわけだが、61行目でnumpy.random.randint()を使ってフレーム毎にランダムドットを作成している。 第1引数以上第2引数未満の整数の乱数を、sizeで指定した大きさの行列で返す。 ここでsizeに指定しているimage_size_backは17行目で定義されているとおり100×100。 これをそのまま表示すると小さすぎるので拡大して表示する。
B: 拡大?
A: うむ。33行目からのVisionEgg.Textures.TextureStimulus()を見てもらえばわかるけど、 textureに指定されているのはtemp_texture_back、これを定義している26行目によるとこのテクスチャの大きさはtemp_texture_back、すなわち100×100だね。一方、TextureStimulusの大きさにはscaled_size_backと指定されている。 同じようにプログラムをたどってもらえばわかるけど、これはtemp_texture_backを14行目で指定されているscale倍した大きさになる。 具体的には600×600。もともと100×100のテクスチャを600×600のスペースに貼り付けるわけだから、拡大されるわけだ。
B: ふむふむ。拡大しないようには出来るんですか?
A: うーん、VisionEgg.Textures.TextureStimulus()の引数にtexture_wrap_sとかtexture_wrap_tとか あるんで出来そうな気がするんだけど、単にこの辺にgl.GL_CLAMPを指定しただけではうまくいかない。ソースを読んでみないとちょっとわからないな。
B: texture…wrap…、なんですって?
A: この辺はpythonとかVisionEggじゃなくてOpenGLの話なんで、OpenGLの解説を見てくれたまえ。 ま、拡大したくないなら最初っからVisionEgg.Textures.TextureStimulus()のsizeとtextureのサイズを一致させておけばいいだけの事だ。
B: ははあ。でも同じ模様を何十回も繰り返すようなテクスチャだとその方法ではオブジェクトが多くなりすぎますよね。
A: お、するどい指摘…と一瞬思いかけたけど、それなら同じ模様を何十回も繰り返した大きなテクスチャを1枚用意すればいいだろ。
B: あ、そうか。
A: 後、テクスチャ関係で解説すべきなのは51行目と62行目かな。 VisionEgg.MoreStimuli.Target2D()など今まで見てきたVisionEggの刺激はすべて後からstimulus.parameters.on=Falseとかいう具合に後からデータ属性を変更できた。 そのノリでテクスチャを差し替えたいときにはtexture=newtextureとかやりたくなるんだけど、テクスチャの場合はそれではうまくいかない。 51行目のような方法でテクスチャオブジェクトを取得しなければいけないんだ。
B: えっと、この51行目はどういう…
A: stim_back.parameters.textureはstim_backを作るときに指定したテクスチャだよな。 そいつのメソッドget_texture_object()を呼び出してテクスチャオブジェクトを取得しているわけだ。
B: むむむ。難しいな。
A: まあこう書くんだと覚えておけばいいよ。それで、62行目でput_sub_image()メソッドを使ってテクスチャを上描きしている。今回は61行目で発生させたランダム値を描画しているわけだ。これでお終い。
B: あれ、上描きした後は何か代入したりとかしなくていいんですか?
A: 何かって、何を代入するんだよ。 この方法はVisionEgg.Textures.TextureStimulusのインスタンスの中に直接手を突っ込んで作業しているようなものなので、put_sub_image()した時点でもう中身は描きかえられている。
B: ふうん。ややこしいですねえ。
A: さて、その他の部分はここまでの例題を見てきた人なら難しいところは何もないだろう。そんなわけで、そろそろ例題8も終了かな?
B: ちょっとちょっとAさん、最初の「カクカクする」件をまだ解説してもらっていませんが。
A: ああ、このサンプルプログラムは作った時の事情で最初の3フレームのスクリーンショットを保存するように作ってあるんだよな。 72行目でVisionEgg.Core.Viewport.draw()して画面をバッファに描きこんだ後、75行目のVisionEgg.Core.Screen.get_framebuffer_as_image()メソッドでスクリーンを画像として取得している。 draw()メソッドではバックバッファに描きこみを行うから、直前に描きこんだ画面を取得するにはbuffer='back'を指定すればいい。 で、得られた画像はPILのImage.Imageのインスタンスなので、76行目のようにsave()メソッドで簡単にファイルに保存できる。
B: これは便利そうですね。ふむふむ。
A: もちろん保存するときに余計な時間がかかるので、実験やデモの本番では保存しないように。 論文やら何やらを書くために刺激の画像が欲しいって時にちょいちょいと書き足してやればいい。
B: PILって便利ですねえ。もうちょっと詳しく解説してもらえませんかねえ。
A: うーん、私もPILは「こんな事できないのかな?」とか疑問を持った時にささっと調べるだけで、きちんとした解説を書けるほど知らんのだよな。 まあそれを言ったらVisionEggもそうなんだが。まあ、VisionEggがらみの解説がある程度片付いて、そこで気力がまだ残っていたらね。
B: それは「やる気がありません」という意思表明に限りなく近いような…。
A: まあまあ、勘弁してくれよ。この頃本当に仕事がしんどいんだ。それに、ここに書いている以上の事は自分で調べる能力も身につけてほしい。 そうじゃないと次々と新しい技術が生まれてくるのに対応できなくなるぞ。
B: なんだかぼくが全然自分で調べてないみたいな言い草ですね。こう見えてもAさんに教えてもらった後自分でいろいろ調べてるんですよ。
A: そりゃ頼もしいね。そんなわけで、例題8はそろそろお開き。