プレコの模様を自動生成するプログラムを書いてみた

2007/11/7

「プレコの模様を数式で表せないのかなぁ?」と漠然と疑問に思ったので調べて実験プログラムを書いてみました。 今回、自動模様生成に挑戦したプレコはキングロイヤルペコルティア(学名: Hypancistrus sp.)とクイーンダップルドペコルティア(学名: Hypancistrus sp.)です。 (というか、たまたま生成できた種類とも言います。。。)

昔どこかでシマウマや豹の模様を数式で表す事が可能だという話を読んだ事があったので、とりあえず、Google様に尋ねてみることにしました。 最初は「zebra pattern theory」などで探してみましたが、よさそうなのが見つかりませんでした。 そのうち、「フラクタルとかオートマトンとかかなぁ」などと考え始めて「automaton fractal zebra」で検索したら、それらしきページを発見できました!

How the Leopard Gets Its Spots; How the Zebra Gets Its Stripes」というページです。 アメリカの大学の授業のページですかね?

そこでは、Alan Turing氏によって発案されたシマウマや豹柄を生成するオートマトンが解説されていました。 ですが、良く見るとサンプルで掲載されている画像の模様がキンペコそのものだったので「これだ!」と思いました。

ある特定の細胞(セル)に注目したときに、そのセルを中心とする円を小さい円と大きい円の2つ描きます。 そして、それぞれの中に含まれる色付きセルを数えます。 最後に、大きい円の方の和に対して特定の変数をかけたものと小さい円の和を比較します。 小さい円の和が大きくなれば色付き、逆ならば色なし、同じならば中心セルの色は変更せず、というアルゴリズムでした。

結果

結果です。 結構似てると思いませんか?w

キンペコ
クイーンダップルド

W=0.21, T=10

W=0.26, T=10

コード

試しに書いたコードです。 C言語です。 コードは、かなりいい加減です。。。 許してください。。。 本来は注目するセルを中心とする円内に含まれる色つきセルの数を計測するべきなのですが、面倒なので四角の格子内に含まれる色付きセルの数というコードになっています。

出力はPPM形式です。 P4(バイナリ2値)もしくはP5でやろうとしましたが、Windowsでの表示がうまくいかなかったので、P6で出力しています。 PPM形式を扱った事がない方は、netpbmなどで他の形式に変換してみてください。

サンプルコードの使い方は、「a.out > a.ppm」です。 そうするとa.ppmというファイル名でPPM形式のファイルが出力されます。 「#define W」の数値を変更すると模様が変わります。

最初に用意する元画像にポイントがありました。 ある程度くっついて存在している色付きセルが散らばっていないと、真っ白や真っ黒になってしまいました。

画像の端の部分の計算は折り返すようにしました。 例えば、0行目のセルを中心とする計算には、0〜5行目とHEIGHT-5〜HEIGHTまでの行を使っています。



#include <stdio.h>

#define WIDTH 200  /* 画像の幅 */
#define HEIGHT 200 /* 画像の高さ */
#define T 10       /* 繰り返し回数(いじってください) */
#define W 0.21     /* 定数(いじってください) */

int
main()
{
  int x,y;
  int n_x, n_y;
  int p,q;
  int t;
  char img[HEIGHT][WIDTH];
  char img_new[HEIGHT][WIDTH];
  double a, i;
  double w = W;

  srandom(time(0));

  /* PPM ヘッダ */
  printf("P6\n%d %d\n255\n", WIDTH, HEIGHT);

  for (y=0; y<HEIGHT-2; y++) {
    for (x=0; x<WIDTH-2; x++) {
      int r = random();
      if (r % 100 < 2) {
        for (p=0; p<3; p++) {
          for (q=0; q<3; q++) {
            img_new[y+p][x+q] = 255;
          }
        }
      }
    }
  }

  for (t=0; t<T; t++) {
    memcpy(img, img_new, sizeof(img));

    for (y=0; y<HEIGHT; y++) {
      for (x=0; x<WIDTH; x++) {
        a = 0; /* 小さい円の中の合計 */
        i = 0; /* 大きい円の中の合計 */

        /* 大きい円 */
        for (p=-5; p<6; p++) {
          for (q=-5; q<6; q++) {

            /* 端っこの処理 */
            n_y = y + p;
            n_x = x + q;
            if (n_y < 0) {
              n_y = HEIGHT - n_y;
            }
            if (n_y >= HEIGHT) {
              n_y = n_y - HEIGHT;
            }
            if (n_x < 0) {
              n_x = WIDTH - n_x;
            }
            if (n_x >= WIDTH) {
              n_x = n_x - WIDTH;
            }

            /* 黒なら足す */
            if (img[n_y][n_x] == 0) {
              i++;
            }
          }
        }

        /* 小さい円 */
        for (p=-2; p<3; p++) {
          for (q=-2; q<3; q++) {

            /* 端っこの処理 */
            n_y = y + p;
            n_x = x + q;
            if (n_y < 0) {
              n_y = HEIGHT - n_y;
            }
            if (n_y >= HEIGHT) {
              n_y = n_y - HEIGHT;
            }
            if (n_x < 0) {
              n_x = WIDTH - n_x;
            }
            if (n_x >= WIDTH) {
              n_x = n_x - WIDTH;
            }

            /* 黒なら足す */
            if (img[y+p][x+q] == 0) {
              a++;
            }
          }
        }

        /* 白黒を判断している部分 */
        if (a - w*i > 0) {
          img_new[y][x] = 0;
        } else if (a - w*i == 0) {
          /* leave cell unchanged */
        } else {
          img_new[y][x] = 255;
        }

      } /* end of for(WIDTH) */
    } /* end of for(HEIGHT) */
  } /* end of for(t) */

  /* PPMの出力 */
  for (y=0; y<HEIGHT; y++) {
    for (x=0; x<WIDTH; x++) {
      if (img[y][x] == 0) {
        /* 黒 */
        printf("%c%c%c", 0,0,0);
      } else {
        /* 白 */
        printf("%c%c%c", 255,255,255);
      }
    }
  }

  return 0;
}


最近のエントリ

過去記事

過去記事一覧

IPv6基礎検定

YouTubeチャンネルやってます!