为什么我在使用 rand ()时会得到这种特殊的颜色模式?

我尝试创建一个图像文件,像这样:

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; */
}

7672 次浏览

您在代码中进行的许多计算不会导致真正的随机值。你们看到的这些尖锐的直线,对应着 x 和 y 坐标的相对值相互交换的位置,当这种情况发生时,你们使用的是完全不同的公式。例如,计算 (x + y) % rand()通常会返回值 x + y,因为 rand()(通常)会返回一个比 x + y大得多的数字,因为 RAND_MAX通常是一个相当大的数字。从这个意义上说,您不应该期望得到白噪声,因为您用于生成事物的算法偏离了生成白噪声。如果你想要白噪声,只需将每个像素设置为 rand()。如果您想要一个像上面那样的漂亮模式,但是在这里和那里有一点随机性,继续使用您编写的代码。

此外,正如@pm100在注释中指出的,rand函数不返回真正的随机数,而是使用伪随机函数来产生它的值。在许多系统上,rand的默认实现使用了一种称为 线性同余方法的伪随机数生成器,它产生的数字在短时间内可能看起来是随机的,但在实际操作中绝对是非随机的。例如,这里有一个来自维基百科的动画,展示了在空间中随机选择一个线性同余方法点,最终落入一个固定数目的超平面中:

The image

如果将 x、 y 和 z 坐标替换为 R、 G 和 B 坐标,这看起来与程序生成的输出类似。我怀疑这可能不是这里的核心问题,因为上面提到的其他方面可能会更加明显。

如果您正在寻找更高质量的随机数,则需要使用更高质量的随机数源。在 C 语言中,可以考虑从 /dev/urandom/(在类 Linux 系统上)读取字节,它提供相当一致的随机值。如果您可以使用的话,C + + 的标准库中现在有许多很好的随机数生成原语。

我最初的答案和其他人一样,并把这归咎于 rand()的问题。然而,我认为这样做更好,而不是分析你的数学实际上产生的分布。

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();

让我们来看看这些单独产生的图像。

  • rand()

这就是你所期望的,只是噪音,称之为“图像 C”

enter image description here


  • (x + y) % rand()

这里你要把像素坐标加在一起然后把余数除以一个随机数。如果图像是1024x1024,那么和就在[0-2046]的范围内。你跳过的随机数在[0,RAND _ MAX ]范围内,其中 RAND _ MAX 至少是32k,在某些系统上是20亿。换句话说,最多有1/16的机会剩下的不仅仅是 (x + y)。所以在大多数情况下,这个函数只会产生一个向 + x + y 方向递增的蓝色渐变。

然而,您只使用最低的8位,因为您返回一个 uint8_t,所以您将有条纹的梯度256像素宽。

把这个叫做“图像 A”

enter image description here


  • (x - y) % rand()

这里你做一些类似的事情,但是用减法。只要 x 大于 y,就会得到类似于前面图像的结果。但是如果 y 更大,那么结果就是一个非常大的数字,因为 xy是无符号的(负结果包围在无符号类型范围的顶部) ,然后 % rand()启动,你实际上会得到噪声。

把这个叫做“图像 B”

enter image description here

最终图像中的每个像素都是使用函数 rand() % 2((x * y % 1024) % rand()) % 2从这三个图像中的一个获取的。其中的第一个可以被理解为50% 的选择(忽略 rand()及其低阶位的问题)

下面是 rand() % 2为真的特写(白色像素) ,所以图像 A 被选中。

enter image description here

第二个函数 ((x * y % 1024) % rand()) % 2也存在这样的问题,即 rand()通常大于所除的 (x * y % 1024),后者最多为1023。那么 (x*y%1024)%2不会同样频繁地产生0和1。任何奇数乘以任何偶数都是偶数。任何偶数乘以任何偶数也是偶数。只有一个奇数乘以一个奇数才是奇数,所以 %2在偶数的四分之三时间内会产生零的四分之三时间。

下面是 ((x * y % 1024) % rand()) % 2为真的特写镜头,这样就可以选择图像 B。它精确地选择了两个坐标都是奇数的位置。

enter image description here

下面是图片 C 可以选择的特写镜头:

enter image description here

最后结合以下条件选择图像 B:

enter image description here

图片 C 被选中的位置:

enter image description here

由此产生的结合可以理解为:

以50% 的概率使用图像 A 的像素。其余时间在图像 B 和图像 C 之间选择,其中两个坐标都是奇数,其中任何一个是偶数。

最后,因为你对三种不同的颜色做同样的事情,但是不同的方向图案在每种颜色中的方向是不同的,并且产生你看到的交叉条纹或者网格图案。