![見出し画像](https://assets.st-note.com/production/uploads/images/6346516/rectangle_large_2c81a43e16f5cd38c364dff54542fda1.jpg?width=1200)
【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 さん及び検証・提言くださった方々に感謝致します。