私はこのように画像ファイルを作成しようとしました:
uint8_t raw_r[pixel_width][pixel_height];
uint8_t raw_g[pixel_width][pixel_height];
uint8_t raw_b[pixel_width][pixel_height];
uint8_t blue(uint32_t x, uint32_t y)
{
return (Rand()%2)? (x+y)%Rand() : ((x*y%1024)%Rand())%2 ? (x-y)%Rand() : Rand();
}
uint8_t green(uint32_t x, uint32_t y)
{
return (Rand()%2)? (x-y)%Rand() : ((x*y%1024)%Rand())%2 ? (x+y)%Rand() : Rand();
}
uint8_t red(uint32_t x, uint32_t y)
{
return (Rand()%2)? (y-x)%Rand() : ((x*y%1024)%Rand())%2 ? (x+y)%Rand() : Rand();
}
for (y=0; y<pixel_height; ++y)
{
for (x=0; x<pixel_width; ++x)
{
raw_b[x][y]=blue(x, y);
raw_g[x][y]=green(x, y);
raw_r[x][y]=red(x, y);
}
}
私は何かランダムになると予想していました(ホワイトノイズ)。しかし、その出力は興味深いものです。
その理由を知っていますか?
今、それがRand()
と関係がないことは明らかです。
また、このコードを試してください:
for (x=0; x<pixel_width; ++x)
for (y=0; y<pixel_height; ++y)
{
r[x][y] = (x+y);
g[x][y] = (y-x);
/* b[x][y] = Rand()%2? x : y; */
}
私は当初、他の人が持っていたのと同じ答えを持ち、Rand()
の問題にこれを書き留めるつもりでした。しかし、そうすることを考えて、代わりに数学が実際に生成している分布を分析しました。
TL; DR:表示されるパターンは、基になる乱数ジェネレーターとは関係ありません。代わりに、プログラムが数値を操作する方法が原因です。
それらはすべて似ているので、青い関数に固執します。
uint8_t blue(uint32_t x, uint32_t y) {
return (Rand() % 2) ? (x + y) % Rand() :
((x * y % 1024) % Rand()) % 2 ? (x - y) % Rand() :
Rand();
}
各ピクセル値は、(x + y) % Rand()
、(x - y) % Rand()
、およびRand()
の3つの関数のいずれかから選択されます。
これらのそれぞれによって生成された画像を見てみましょう。
Rand()
これはあなたが期待するもので、ただのノイズです。これを「画像C」と呼びます
(x + y) % Rand()
ここでは、ピクセル座標を加算し、残りを乱数で除算することで取得しています。画像が1024x1024の場合、合計は[0-2046]の範囲にあります。ダイビングの対象となる乱数の範囲は[0、Rand_MAX]です。Rand_MAXは少なくとも32kであり、一部のシステムでは20億です。言い換えれば、残りが(x + y)
だけではない可能性は16分の1程度です。そのため、この関数はほとんどの場合、+ x + y方向に向かって青が増加するグラデーションを生成します。
ただし、uint8_t
を返すため、最下位の8ビットのみを使用しているため、256ピクセル幅のグラデーションのストライプができます。
これを「画像A」と呼びます
(x - y) % Rand()
ここでは、同様のことを行いますが、減算を使用します。 xがyより大きい限り、前の画像と同様のものが得られます。しかし、yの方が大きい場合、x
とy
は符号なし(負の結果は符号なしの型の範囲の先頭に折り返される)であるため、結果は非常に大きい数値になり、% Rand()
実際にノイズが発生します。
これを「画像B」と呼びます
最終画像の各ピクセルは、関数Rand() % 2
および((x * y % 1024) % Rand()) % 2
を使用して、これら3つの画像のいずれかから取得されます。これらの最初のものは、50%の確率で選択するものとして読むことができます(Rand()
とその下位ビットの問題を無視します)。
Rand() % 2
がtrue(白いピクセル)であるため、画像Aが選択されているところのクローズアップです。
2番目の関数((x * y % 1024) % Rand()) % 2
には、Rand()
が通常、除算対象の(x * y % 1024)
(最大1023)よりも大きいという問題があります。その後、(x*y%1024)%2
は0と1を生成しません同様に頻繁に。奇数に偶数を掛けると偶数になります。偶数に任意の偶数を掛けたものも偶数です。奇数に奇数を掛けたもののみが奇数であるため、4分の3時間の偶数の%2
値は、4分の3時間の0を生成します。
以下は、画像Bを選択できるように((x * y % 1024) % Rand()) % 2
が真である場所のクローズアップです。両方の座標が奇数である場所を正確に選択しています。
次に、画像Cを選択できる場所の拡大図を示します。
最後に、ここで条件を組み合わせて、画像Bを選択します。
そして、画像Cが選択されている場所:
結果の組み合わせは、次のように読み取ることができます。
50%の確率で、画像Aのピクセルを使用します。残りの時間は、画像Bと画像Cの間で選択します。Bは両方の座標が奇数で、Cはどちらかが偶数です。
最後に、3つの異なる色で同じことを行いますが、方向が異なると、パターンの向きが各色で異なり、交差するストリップまたはグリッドパターンが生成されます。
コード内で行っている計算の多くは、真にランダムな値にはなりません。あなたが見ているこれらの鋭い線はあなたのxとy座標の相対的な値が互いに交換する場所に対応しています、そしてそれが起こるときあなたは根本的に異なる式を使用しています。例えば、(x + y) % Rand()
が通常かなり大きい数であることを考えると、Rand()
は(通常)x + y
よりはるかに大きい数を返すので、x + y
を計算すると一般にRand_MAX
の値が返されます。その意味では、物事を生成するために使用しているアルゴリズムはホワイトノイズの生成から偏っているので、ホワイトノイズを取り戻すことを期待してはいけません。ホワイトノイズが必要な場合は、各ピクセルをRand()
に設定するだけです。あなたが上で持っているもののようなニースパターンが欲しいけれども、あちこちで少しの乱雑さを投げかけたいなら、あなたが書いたコードを使い続けなさい。
さらに、@ pm100がコメントで述べたように、Rand
関数は真に乱数を返さず、代わりに擬似乱数関数を使って値を生成します。多くのシステムでのRand
のデフォルト実装は 合同合同法 と呼ばれる一種の疑似乱数発生器を使います。たとえば、これはウィキペディアのアニメーションで、線形合同法を使って選択した空間内のランダムな点が、決まった数の超平面に入ることを示しています。
X、y、およびz座標をR、G、およびB座標に置き換えると、これは 驚くほど /プログラムによって生成される出力に似ています。私は、これがおそらくここでの中心的な問題ではないと思う、上記の他の側面はおそらくもっともっと明白になるだろうから。
より高品質の乱数を探しているなら、より高品質の乱数ソースを使う必要があります。 Cでは、(Linux系のシステムでは)/dev/urandom/
からバイトを読み取ることを検討できます。これにより、かなり一様にランダムな値が得られます。 C++の標準ライブラリには、乱数生成のための優れたプリミティブが多数用意されています。