四谷ラボ公式ブログ

四谷ラボはいつでも誰でも自由に参加・研究・交流・発信のできる街のオープンイノベーションラボ

技術とアートの融合 - シミュレーティッドアニーリング - 美しさの追求

小学4年生のとき・・・

・・・褒めることのない美術の先生が、僕の描いた「お花」の絵を褒めてくれ、「自分にはアートの道があるんだ」と将来を夢見てから40年・・・。

結局、芸術家になることはなく、技術家になって久しい。

こんにちは、やましんです。

今日のテーマは「技術とアートの融合」です。

最近では「アートサイエンス」という言葉があるらしく、芸大の学科にもなっているみたい。 確かに、数学や自然科学には、美しさがあり、洗練された工学デザインにも、技術の精緻さが織りなす優美さがあるよね。

よし、技術でアートをやってみよう。

技術をアートにし、小学4年生のときに夢見た芸術家への思いを昇華するので、応援してね。

今回技術で創り上げる芸術作品

ということで、今回はシミュレーティッドアニーリングという技術を使って、この三次元アートをつくるよ。

ある瞬間だけこの世で最も大切なものが見えるでしょ?

大切なモノって刹那なんだねぇ。

プログラム

コードのありか

こちらにコードがあるので、触ってみて。

ドキュメントよりコード重視派の皆さんにはもちろんだけど、計算に1時間弱かかるので、 効率重視派の皆さんも、最初に走らせておいて、文章を読み進めながら結果を待つことをお勧めします。 処理中の画像がフリーズしているように見えるかもしれなけど、我慢して待ってみて。

計算用のプログラムコードはわずか200行程度なので、興味ある方はいろいろ遊んでみて。

そうそう、今回はGPUは不要です。

github.com

動かし方

コンソールから次のコマンドを打って、60分程度待てばアートが完成します。

git clone https://github.com/428lab/simulated_annealing_3d_art.git
cd simulated_annealing_3d_art
./run.sh

動作原理

このプログラムでは、ある組合せ最適化問題をシミュレーティッドアニーリングという技術を使って解いてます。

最適化する問題

「3D空間にある立方体を動かして、特定の角度から見たときに、あらかじめ決められた画像に似た形になるように、立方体の位置、大きさ、回転の角度を変えて配置する」という最適化問題

シミュレーティッドアニーリングって?

とても面白い考え方なので、ぜひ、この機会に理解を深めてちょうだい。

真っ赤に熱した日本刀を水の中に入れて急激に冷やすシーンってみたことあるでしょ?

あれって「焼入れ」っていうんだけど、これは金属を固くする目的があるんだ。

逆に、高温の金属を、ゆっくり徐々に温度を下げることで、柔らかく、加工しやすくなるんだ。 このことを「焼きなまし」と言って、かっこよく英語でいうと「アニーリング」って言うんだね。

シミュレーティッドアニーリングを日本語に訳すと「疑似焼きなまし」になるんだけど、高温の金属を、徐々に温度を下げることで、最小エネルギー状態を保持した秩序ある構造の状態を作り出す過程をコンピュータ上で疑似的に再現した技術です。略してSA法と言ったりします。

実際にはコンピューターの温度は全く関係なく、温度と見立てた変数を徐々に下げていき、問題の解を探していると考えるんだ。

この「温度」とは、どれだけ探索の範囲を広くするか、どれだけ新しい解を受け入れるかを示す数値のことなんだよね。

最初にこの温度は高く設定されていて、コンピュータは多くの異なる解を試すことができるから、最適な解を見つける確率が高いんだけど、その分無駄な試行も多くなるんだ。

でも、時間が経つにつれて、温度は徐々に下がっていくんだ。これによって、コンピュータはだんだんと探索範囲を狭め、より良い解に焦点を当てるようになる。

シミュレーテッドアニーリングでは、「温度」が下がるにつれて、新しい解を受け入れる確率も下がっていく。だから、最初は全然違う解も試すけど、段々と目的の解に近い解を探すようになる。これが、金属の「焼きなまし」の過程に似ているから、この名前が付けられたんだ。

この方法のいいところは、局所的な最適解に囚われずに、より良い全体的な最適解を見つける可能性が高いということ。逆に言えば、時間がかかることや、最適解を保証するものではないというデメリットもあるんだけどね。

最近は量子アニーリングマシンが使えるようになったので、一瞬で解を得られるかもしれないね。興味ある人はぜひチャレンジして結果を報告して欲しい!

コンピューターで問題を解くときに、シミュレーテッドアニーリングを使うと、このように徐々に解を絞り込んでいくことで、最終的には良い解が得られる可能性が高くなるんだよ。

処理の流れ

今回のプログラムは、OpenGLPygameを使用して3Dグラフィックスを作成し、SA法を利用して目標画像に近似する立方体の配置を探索するもので、処理手順は下記の通り。

  1. 「温度」変数の初期値を決定

  2. 解の候補として立方体をランダム配置し、画像を生成(画像が指定するビットマップに近づく場合は受理、そうでない場合も一定の確率で受理)

  3. 一定回数2.を試行したら温度を下げる

  4. 温度が下がりきるまで2-3を繰り返す

プログラム詳細

シミュレーティッドアニーリングを含むメインプログラムsimulated_annealing_3d_art.pyの主なコードを解説します。

立方体の定義: 立方体の頂点(vertices)と面(surfaces)を定義しています。今回は立方体だけど、任意のポリゴンでこのプログラムは機能する(はず。

# Cube vertices and surfaces
vertices = (
    (1, -1, -1),
    (1, 1, -1),
    (-1, 1, -1),
    (-1, -1, -1),
    (1, -1, 1),
    (1, 1, 1),
    (-1, -1, 1),
    (-1, 1, 1)
)

surfaces = (
    (0,1,2,3),
    (3,2,7,6),
    (6,7,5,4),
    (4,5,1,0),
    (1,5,7,2),
    (4,0,3,6)
)

setup_viewport: OpenGLのビューポートを設定する関数です。ウィンドウのサイズに基づき、視野を設定します。ビューポートを変更すると立方体の配置が同じでも見え方が変化するので、結果に大きく影響を与えるよ。

def setup_viewport(width, height):
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45, (width / height), 0.1, 50.0)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0)  # カメラの位置と向きを設定

create_initial_cubes: 初期の立方体の位置、角度、サイズをランダムに生成する関数です。各パラメータは一定の範囲内でランダムに変化します。

def create_initial_cubes(num_cubes, image_size, min_cube_size, max_cube_size, init_cubes=None):
    pygame.init()
    display = (image_size[0], image_size[1])
    pygame.display.set_mode(display, DOUBLEBUF | OPENGL)

    glClearColor(1.0, 1.0, 1.0, 1.0)  # 白色に設定
    glEnable(GL_DEPTH_TEST)

    # ここでビューポートと投影を設定
    setup_viewport(display[0], display[1])

    cubes = []

    if init_cubes == None:

        """ 初期の立方体の配置を生成 """
        for _ in range(num_cubes):
            x, y, z = [random.uniform(-5, 5) for _ in range(3)]
            angle = random.uniform(0, 45)
            size = random.uniform(min_cube_size, max_cube_size)
            cubes.append((x, y, z, angle, size))
    else:
        for x, y, z, angle, size in init_cubes:
            cubes.append((x, y, z, angle, size))

    return cubes

generate_image: 現在の立方体の配置から画像を生成する関数です。もっとスマートで高速な方法があれば教えてね。

def generate_image(current_cubes, img_size):

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    for x, y, z, angle, size in current_cubes:
        glPushMatrix()
        glTranslatef(x, y, z)
        glRotatef(angle, 0, 1, 0)
        draw_cube(size)  # 立方体を描画
        glPopMatrix()

    #glFinish()  # レンダリングが完了するまで待機
    pygame.display.flip()

    # OpenGLのフレームバッファからビットマップを取得
    bitmap = grab_opengl_bitmap(img_size)

    # NumPy配列をPillow画像に変換
    pillow_image = numpy_to_pillow_image(bitmap)

    return pillow_image.convert('L')

calculate_error: 目標画像と生成された画像の誤差(RMSE)を計算する関数です。今回はモノクロのビットマップだけど、カラーでも同様に最適化できます。

def calculate_error(target_img, generated_img):
    """ RMSEを計算 """
    target_arr = np.array(target_img, dtype=np.float64)
    generated_arr = np.array(generated_img, dtype=np.float64)
    mse = np.mean((target_arr - generated_arr) ** 2)
    rmse = np.sqrt(mse)
    return rmse

simulated_annealing: シミュレーテッドアニーリングを行い、最適な立方体の配置を見つける関数です。これがメインの関数です。3Dグラフィックスを使って特定の画像を再現するために、立方体の配置を最適化する目的の関数です。ランダムな探索と段階的な冷却を組み合わせた関数です。

def simulated_annealing(cubes, target_img, max_iter, start_temp, end_temp, img_size, min_cube_size, max_cube_size):
    """ シミュレーテッドアニーリングのメインループ """
    temp = start_temp
    current_cubes = cubes
    current_img = generate_image(current_cubes, img_size)
    current_error = calculate_error(target_img, current_img)

    for i in range(max_iter):
        new_cubes = current_cubes.copy()
        # ここでランダムに正方形を変更
        cube_index = random.randint(0, len(new_cubes) - 1)
        new_cubes[cube_index] = (
            random.uniform(-5, 5),
            random.uniform(-5, 5),
            random.uniform(-5, 5),
            random.uniform(0, 45),
            random.uniform(min_cube_size, max_cube_size)
        )

        new_img = generate_image(new_cubes, img_size)
        new_error = calculate_error(target_img, new_img)

        if i % 1000 == 0:
            print('new_error',i,new_error)
            #cv2.imshow('rendering',np.array(new_img, dtype=np.uint8))
            #cv2.waitKey(1)

        # エラーが減少するか、確率で更新を受理
        if new_error < current_error or random.random() < math.exp((current_error - new_error) / temp):
            current_cubes = new_cubes
            current_error = new_error

        # 温度を更新
        temp = start_temp * (end_temp / start_temp) ** (i / max_iter)

    return current_cubes

やってみよう!(難易度:★)

最初に走らせておいた探索がうまくいったなら、目的の画像をお好みの画像に差し替えてやってみよう。

お好みの128x128のモノクロビットマップを作成して、下記のコマンドラインを参考に、探索してみてください。一度に多くのパラメータを変更せず、ひとつずつ変更していくことが結果的に近道だよ。人生と同じだね。

探索プログラムパラメータ

python simulated_annealing_3d_art.py --target-img [your_favarit_mono_img.bmp] --num-cubes 200 --max-iter 40000 --start-temp 10.0 --end-temp 0.1 --min-cube-size 0.1 --max-cube-size 0.5
  • --target-img モノクロビットマップファイル
  • --num-cubes 立方体の数
  • --max-iter 最大イテレーション回数
  • --start-temp 開始温度
  • --end-temp 終了温度
  • --min-cube-size 立方体の最小サイズ(一辺の長さ)
  • --max-cube-size 立方体の最大サイズ(一辺の長さ)

処理が終了すると、xxxxx_cubes.pkl ファイルがカレントフォルダーに出力されます。

結果表示プログラムパラメータ

次のコマンドで上記プログラムの出力ファイル(xxxxx_cubes.pkl)を3次元表示できます。

python viewer_3d_art.py --cubes-file xxxxx_cubes.pkl --img-size 128 
  • --cubes-file 探索した立方体の座標ファイル
  • --img-size ビットマップの縦横ピクセル数(正方形)

お好みの画像がうまくいけば、ビットマップのサイズを変更してチャレンジしてみて、目的画像の白と黒の比率に応じて、立方体の数やサイズを変更すると良いよ。

やってみよう!(難易度:★★)

さらに、プログラムを改善して、モノクロだけではなく、グレースケールやカラーへの応用、任意のポリゴンへの応用などに挑戦してね。

また、量子アニーリングマシンで解を得られるようモデリングするのは難易度★★★だね。

難易度の高い課題に挑戦して、良い結果が得られた兵は、連絡ちょうだい。このマグカップをあげたい。(最大5個、無くなり次第終了。)

booth.pm

まとめ

アーティストになる道は、技術と芸術の融合によっても実現できる。私たちが普段目にするもの、感じることは、技術とアートの素晴らしいハーモニーによって生み出されている。私が今回挑戦したのは、シミュレーティッドアニーリングを用いて、技術の力でアートを創造すること。このプロジェクトは、小学4年生のときの私の夢を実現するための一歩だ。

シミュレーティッドアニーリングは、焼きなましのプロセスを模倣したアルゴリズムで、問題解決において最適な解を見つけるための効果的な方法。この技術を使って、3D空間に立方体を配置し、特定のビットマップ画像に近づけることができた。プログラムはシンプルでわずか200行程度だが、その背後にある理論は深い。

この実験は、技術とアートの融合がいかに美しい結果を生むかを示している。プログラムの各ステップ、OpenGLでの立方体の定義、ビューポートの設定、画像の生成、誤差計算、そしてシミュレーティッドアニーリングの適用という流れは、このプロジェクトの核をなす。

アートと技術は、一見異なる分野のように思えるかもしれないが、実際には互いに補完し合い、新しい可能性を生み出す。このプロジェクトは、その一例に過ぎない。技術を使ってアートを創造することに興味があるなら、ぜひ挑戦してみてほしい。そして、そのプロセスを楽しんでほしい。技術とアートの組み合わせによって、新しい扉が開かれるかもしれない。

最後に

最近、描いたタコ。この絵をみて「たこ焼きが食べたくなった」と言っていただければ、私の芸術家への挑戦は続くだろう。