sonoshouのまじめなブログ

情報系大学生からのウェブ見習い人生の記録

自己組織化写像をprocessingで実装する。 part.2

3次元の色データを2次元にマッピングする自己組織化写像

さて、前回のエントリーでハニカム構造を作りました。
この後はどのようなデータを扱っていくかによって実装が変わってくるのですが、
今回は3次元の色データを2次元にマッピングする自己組織化写像のプログラムを書いてみようと思います。

早速プログラムに入っていっても良いのですが、
その前に少し、自己組織化写像について説明を加えようと思います。

前回、自己組織化写像は、
高次元のデータを(人間が見やすいように)2次元にビジュアライズする手法
と説明しましたが、少しわかりづらいですよね。
このことについて、今回の問題を例にとって説明します。

色データの次元

(パソコン画面などの液晶画面上の)色の次元は3次元で表されます。
光の三原色である赤、緑、青の色の強さの割合で様々な色を表現しているのです。
よく、この3種類の色の英語の頭文字をとってRGBと省略されます。

色の強さの段階は用途に応じて様々なのですが、ウェブ上では各々0~255の値で様々な色を表現するような仕組みになっています。
例えば、赤はR=255,G=0,B=0となり、黄はR=255,G=255,B=0となります。 黄は赤と緑の混色なので、R=255,G=255と2つの色の強さが最大になるのですね。

2次元データを2次元にマッピング

パソコンや携帯電話のディスプレイや新聞紙などの紙面は、
縦と横の2次元を持っています。
それでは、色のデータをマッピングしてみましょう。
ここでは、赤と緑の2次元をマッピングしてみました。

f:id:sonoshou:20130619191011p:plain

void setup()
{
  size(256, 256);

  for (int i = 0; i < 256; i++)
  {
    for (int j = 0; j < 256; j++)
    {
      rectColor(i, j, 1,color(i,j,0));     
    }      
  } 
}

void draw()
{
}

void rectColor(int x, int y, int size, color strokeColor)
{
  stroke(strokeColor);
  rect(x, y, size, size);
}

上の図は、横(X軸)に赤、縦(Y軸)に緑をとった図です。
右へ行けば行くほど赤が強く、下へ行けば行くほど緑が強く出ます。
従って、右下は赤と緑の両方共強くなったので黄が出力されています。
これが、2次元データのマッピングです。
とっても自然でとっても簡単だと思います。
このように、2次元のデータを2次元にマッピングすることは簡単なのです。

3次元データを2次元にマッピング

ところが、色は3次元です。
今回は赤と緑の2次元に話を限定しましたが、
ここに、青の要素を加えようとすると、とたんに話が難しくなります。

このような状況、高次元(今回の場合は3次元)データを2次元にビジュアライズしたいような状況のときに、
自己組織化写像が用いられます。

というわけで、本ブログでは、自己組織化写像を用いて、
3次元の色データを2次元に落としこむことを目標にしていきます。

自己組織化写像のアルゴリズム

早速、自己組織化写像の実装に入っていきますが、 その前に、自己組織化写像の全体像を見てみましょう。

自己組織化写像のアルゴリズムは、Wikipediaが詳しいです。
Wikipedia:自己組織化写像
上のリンク先の算法のステップから引用します。

1.全重みベクトルをランダマイズする
2.入力ベクトルを一つ用意する
3.各ノードを検査して、最も一致値が小さいノードを見つける。(BMU)
4.BMUの近傍のノードの重みベクトルを変更し、入力ベクトルに近付ける。
5.最大繰り返し回数に達していなければ2.に戻る。
Wikipedia:自己組織化写像 を一部改変~

ではでは、まずは1.全重みベクトルをランダマイズするですね!

1.全重みベクトルをランダマイズする

重みベクトルとは、色データのことです。
すなわち、前回のエントリーでハニカム構造を作成しましたが、
このハニカム構造のひとつひとつの六角形の色をランダムにすることが今回の1.に対応します。

文章ではわかりづらいので、まず結果を御覧ください。
その後、プログラムの説明を行いたいと思います。

f:id:sonoshou:20130619191014p:plain

void setup()
{
  size(797, 810);

  int randR, randG, randB;

  for (int i = 0; i < 28; i++)
  {
    for (int j = 0; j < 25; j++)
    {
      randR = int(random(256));
      randG = int(random(256));
      randB = int(random(256));

      if (i % 2 == 0)
      {
        hexagon(j * 34.64102, i * 30, 20, color(randR,randG,randB), color(0, 0, 0));
      } else {
        hexagon(j * 34.64102 - 17.32051 , i * 30, 20, color(randR,randG,randB), color(0, 0, 0));
      }      
    }      
  }
}

void draw()
{

}

// hexagon
void hexagon(float centerX, float centerY, float size, color fillColor, color strokeColor)
{
  final float COS[] = {0, -0.8660254, -0.8660254, 0, 0.86602524, 0.86602524};
  final float SIN[] = {1, 0.5,  -0.5, -1, -0.5, 0.5};

  final float RADIUS = size;

  // color
  fill(fillColor);
  stroke(strokeColor);

  beginShape();
  for(int i = 0; i < 6; i++)
  {
    float tx = COS[i] * RADIUS + centerX;
    float ty = SIN[i] * RADIUS + centerY;
    vertex(tx, ty);
  }
  endShape(CLOSE);
}

プログラムの説明

プログラムの以下の部分

randR = int(random(256));
randG = int(random(256));
randB = int(random(256));

この部分で各六角形の赤と緑と青の強さを決定しています。
色の強さは各々0~255で表されるので、
0~255の整数値がランダムで得られれば良いのです。
従って、2つの関数をうまく使って、目標の値を得ます。

(random(256));

は0以上256未満の値をランダムに返す変数です。
しかし、この関数の返り値はfloatであり、
返り値に小数点を含みます。
そこで、

int();

上のint関数を用いて、引数の値の小数点を切り捨てて整数とします。

このように、random関数とint関数をうまく使って、
0~255のランダムの整数値を得ています。

あとはここで得られたランダムの整数値を、
前回作成したhexagon関数の引数に加えるだけです。

まだ簡単ですかね? これにてプログラムの説明を終わります。

続きます。
続き書きましたよー><b
自己組織化写像をprocessingで実装する。 part.3