見出し画像

【P5 Tips】 noiseの平均は0.5ではなかった話

Processing において、連続的な乱数を得たいときによく使うのがパーリンノイズを実装した noise 関数であり、0 ~ 1 の範囲をうろちょろする値を返してくれます。 この noise 関数について私は「当然 0.5 付近を中心にうろちょろするだろう」と思っていました。

しかし @kajitaj63b3 さん投稿の記事に、「noiseがそもそもそういうものなのか負の値に偏りがある」という記述がありました。

記事内では noise 関数を使って -n ~ n の範囲をうろちょろする値を作り出していますので、これが負に偏るということは noise 関数の出力が平均して 0.5 よりも下に偏っていることを意味します。

検証してみた

ま、まさかそんな訳ないだろ~と思い、いそいそと Processing を起動して noise 関数を100万回叩いて度数分布を取りました。 結果がこちら。

うん、偏ってますね。 平均値は 0.46 ~ 0.47 あたりにありそうです。 検証に使用したソースも記載しておきます。

float offset;
float step = 0.01;
int binSize = 100;
int[] frequency = new int[binSize];
int sample = 1000000;
float ave;

void setup() {
  size(500, 250);
  init();
}

void init() {
  ave = 0;
  offset = random(10000);
  for (int i = 0; i < binSize; i++) {
    frequency[i] = 0;
  }
  for (int i = 0; i < sample; i++) {
    float value = noise(offset + i * step);
    int index = int(value * binSize);
    frequency[index]++;
    ave += value;
  }
  ave /= float(sample);
}

void draw() {
  background(255);

  stroke(255, 0, 0);
  for (int i = 0; i < 3; i++) {
    float x = (i + 1) * width / 4.0;
    line(x, 0, x, height);
  }

  pushMatrix();
  translate(0, height);
  scale(1, -1);
  float w = float(width) / binSize;
  noStroke();
  fill(0, 200);
  for (int i = 0; i < binSize; i++) {
    rect(i * w, 0, w, frequency[i] * 0.3 * height * binSize / sample);
  }
  popMatrix();
  
  fill(0);
  textSize(18);
  text("ave: " + ave, 10, 20);
}

void keyPressed() {
  if (key == 's') {
    save("dist/sketch.png");
  } else {
    init();
  }
}

openFrameworks ではどうか

openFrameworks でも同じように検証してみました。 結果がこちら。

偏ってないですね。 素晴らしいです。 平均値は 0.4999 ~ 0.5000 あたりにありそうです。 何回か試しましたが平均値が 0.4999 を下回ることはありませんでした。 Processing の noise が正規分布っぽい形状をしていたのに対して openFrameworks の ofNoise は若干とんがっていますね。 こちらも検証に使用したソースも記載しておきます。

// main.cpp
#include "ofMain.h"
#include "ofApp.h"

int main( ){
  ofSetupOpenGL(500,250,OF_WINDOW);
  ofRunApp(new ofApp());
}
// ofApp.h
#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{
  public:
    void setup();
    void draw();
    void keyPressed(int key);
    void init();

    float offset;
    float step = 0.01;
    int binSize = 100;
    int *frequency = new int[binSize];
    int sample = 1000000;
    float ave;
};
// ofApp.cpp
#include "ofApp.h"

void ofApp::setup() {
  ofBackground(255);
  init();
}

void ofApp::init() {
  ave = 0;
  offset = ofRandom(10000);
  for (int i = 0; i < binSize; i++) {
    frequency[i] = 0;
  }
  for (int i = 0; i < sample; i++) {
    float value = ofNoise(offset + i * step);
    int index = int(value * binSize);
    frequency[index]++;
    ave += value;
  }
  ave /= float(sample);
}

void ofApp::draw() {
  ofSetColor(255, 0, 0);
  for (int i = 0; i < 3; i++) {
    float x = (i + 1) * ofGetWidth() / 4.0;
    ofDrawLine(x, 0, x, ofGetHeight());
  }
  
  ofPushMatrix();
  ofTranslate(0, ofGetHeight());
  ofScale(1, -1);
  float w = float(ofGetWidth()) / binSize;
  ofSetColor(0, 0, 0, 200);
  for (int i = 0; i < binSize; i++) {
    ofDrawRectangle(i * w, 0, w, frequency[i] * 0.2 * ofGetHeight() * binSize / sample);
  }
  ofPopMatrix();
  
  ofDrawBitmapString("ave: " + ofToString(ave), 10, 20);
}

void ofApp::keyPressed(int key) {
  if (key == 's') {
    ofSaveScreen("sketch.png");
  } else {
    init();
  }
}

noiseDetail との関連性

ほう。 Processing には noise で生成するパーリンノイズの形状特性を制御する noiseDetail 関数があり、その数値をあげると平均が 0.5 に近づく性質があるようです。

確かにこの noiseDetail を使えば手っ取り早く平均を 0.5 付近にできそうです。 ただ、noiseDetail の本来の目的はノイズ形状特性を制御することで、平均が 0.5 に近づくのは副次的な作用でしょうから、平均を 0.5 付近にするために noiseDetail をあげるというのも考えものです。

結局のところ、大事なのは Processing の noise の平均は 0.5 より少し下にあるよ~というのを知っておくことです。 この誤差を許容するか、あるいは誤差が蓄積しないような実装にするとよいと思います。 もし高精度なパーリンノイズが必要ならば(そんなケースがあるのか不明ですが)外部ライブラリを使うか、諦めて openFrameworks を使った方がいいかも知れません。 

ともあれ、知るきっかけとなった @kajitaj63b3 さん及び検証・提言くださった方々に感謝致します。

この記事が気に入ったらサポートをしてみませんか?