Por que obtenho esse padrão de cor específico ao usar rand ()?

170

Eu tentei criar um arquivo de imagem, assim:

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

Eu esperava obter algo aleatório (ruído branco). No entanto, a saída é interessante:

Você sabe o motivo?


Editar

Agora, está claro que isso não tem nada a ver rand().

Tente também este código:

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

Pequeno pônei
fonte
26
cos rand isnt rand - boa demonstração disso. É 100% determinístico
pm100
50
@ pm100: Como a resposta de bames53 explica tão bem, você obteria o mesmo padrão mesmo se usasse um gerador de números aleatórios perfeito.
TonyK
13
Desculpe uma pergunta: Como você está usando as coordenadas xey para calcular os valores de pixel, por que você espera que esses valores sejam independentes das coordenadas? Se a imagem parecer muito aleatória, é disso que você precisaria, certo?
Thomas Padron-McCarthy
15
Lições aprendidas: Fazer coisas aleatórias com números aleatórios não produz resultados aleatórios :)
Hagen von Eitzen

Respostas:

355

Inicialmente, eu teria a mesma resposta que todo mundo tinha e atribuiria isso aos problemas rand(). No entanto, achei melhor fazê-lo e, em vez disso, analisei a distribuição que sua matemática está realmente produzindo.

TL; DR: O padrão que você vê não tem nada a ver com o gerador de números aleatórios subjacente e, em vez disso, é simplesmente devido à maneira como seu programa está manipulando os números.

Vou manter a sua função azul, uma vez que são todos semelhantes.

uint8_t blue(uint32_t x, uint32_t y) {
    return (rand() % 2)                  ? (x + y) % rand() :
           ((x * y % 1024) % rand()) % 2 ? (x - y) % rand() :
                                           rand();
}

Cada valor de pixel é seleccionado a partir de um de três funções: (x + y) % rand(), (x - y) % rand()e rand();

Vejamos as imagens produzidas por cada uma delas sozinha.

  • rand()

Isto é o que você esperaria, apenas barulho. Chame isso de "Imagem C"

insira a descrição da imagem aqui


  • (x + y) % rand()

Aqui você adiciona as coordenadas de pixel e evita que o restante seja dividido por um número aleatório. Se a imagem for 1024x1024, a soma estará no intervalo [0-2046]. O número aleatório pelo qual você está mergulhando está no intervalo [0, RAND_MAX], onde RAND_MAX é de pelo menos 32k e em alguns sistemas é de 2 bilhões. Em outras palavras, há uma chance de 1 em 16 de que o restante não seja apenas (x + y). Portanto, na maior parte das vezes, essa função produz apenas um gradiente de aumento de azul na direção + x + y.

No entanto, você está usando apenas os 8 bits mais baixos, porque retorna a uint8_t, para ter faixas de gradientes com 256 pixels de largura.

Chame isso de "Imagem A"

insira a descrição da imagem aqui


  • (x - y) % rand()

Aqui você faz algo semelhante, mas com subtração. Contanto que x seja maior que y, você terá algo semelhante à imagem anterior. Mas onde y é maior, o resultado é um número muito grande porque xe ynão está assinado (resultados negativos são agrupados no topo do intervalo do tipo não assinado) e, em seguida, o sinal % rand()entra em ação e você realmente recebe ruído.

Chame isso de "Imagem B"

insira a descrição da imagem aqui

Cada pixel em sua imagem final é obtido de uma dessas três imagens usando as funções rand() % 2e ((x * y % 1024) % rand()) % 2. A primeira delas pode ser lida como uma escolha com 50% de probabilidade (ignorando problemas com rand()e seus bits de baixa ordem).

Aqui está um close de onde rand() % 2é verdade (pixels brancos) para que a Imagem A seja selecionada.

insira a descrição da imagem aqui

A segunda função ((x * y % 1024) % rand()) % 2novamente apresenta o problema em que rand()geralmente é maior do que o que você está dividindo (x * y % 1024), que é no máximo 1023. Então (x*y%1024)%2não produz 0 e 1 com a mesma frequência. Qualquer número ímpar multiplicado por qualquer número par é par. Qualquer número par multiplicado por qualquer número par também é par. Somente um número ímpar multiplicado por um número ímpar é ímpar, e assim por %2diante, valores que são até três quartos das vezes produzirão 0 três quartos das vezes.

Aqui está um close de onde ((x * y % 1024) % rand()) % 2é verdade, para que a Imagem B possa ser selecionada. É selecionar exatamente onde as duas coordenadas são ímpares.

insira a descrição da imagem aqui

E aqui está um close de onde a Imagem C pode ser selecionada:

insira a descrição da imagem aqui

Finalmente, combinando as condições, é aqui que a Imagem B é selecionada:

insira a descrição da imagem aqui

E onde a Imagem C está selecionada:

insira a descrição da imagem aqui

A combinação resultante pode ser lida como:

Com 50% de probabilidade, use o pixel da Imagem A. O restante do tempo escolhe entre a Imagem B e a Imagem C, B, onde ambas as coordenadas são ímpares, C, onde ambas são pares.

Finalmente, como você faz o mesmo com três cores diferentes, mas com orientações diferentes, os padrões são orientados de maneira diferente em cada cor e produzem as tiras de cruzamento ou o padrão de grade que você está vendo.

bames53
fonte
45

Muitos dos cálculos que você está fazendo no seu código não levam a valores verdadeiramente aleatórios. Essas linhas nítidas que você vê correspondem a lugares onde os valores relativos de suas coordenadas xey são trocados entre si e, quando isso acontece, você usa fórmulas fundamentalmente diferentes. Por exemplo, a computação (x + y) % rand()geralmente devolve o valor x + y, pois rand()(geralmente) retorna um número muito, muito maior do que o x + ydado, RAND_MAXgeralmente um número bastante grande. Nesse sentido, você não deve esperar receber ruído branco, já que o algoritmo que você está usando para gerar coisas é tendencioso para gerar ruído branco. Se você deseja ruído branco, basta definir cada pixel comorand(). Se você deseja um bom padrão como o que você tem acima, mas com um pouco de aleatoriedade aqui e ali, continue usando o código que você escreveu.

Além disso, como @ pm100 observou nos comentários, a randfunção não retorna números verdadeiramente aleatórios e usa uma função pseudo-aleatória para produzir seus valores. A implementação padrão de randmuitos sistemas usa um tipo de gerador de números pseudo-aleatórios chamado gerador congruencial linear que produz números que, em breves rajadas, podem parecer aleatórios, mas que na prática são decididamente não-aleatórios. Por exemplo, aqui está uma animação da Wikipedia mostrando como pontos aleatórios no espaço escolhido com um gerador congruencial linear acabam caindo em um número fixo de hiperplanos:

A imagem

Se você substituir as coordenadas x, ye z pelas coordenadas R, G e B, isso será notavelmente semelhante à saída produzida pelo seu programa. Suspeito que esse provavelmente não seja o problema principal aqui, pois o outro aspecto mencionado acima provavelmente será muito mais pronunciado.

Se você estiver procurando por números aleatórios de alta qualidade, precisará usar uma fonte aleatória de alta qualidade. Em C, você pode considerar ler bytes de /dev/urandom/(em um sistema semelhante ao Linux), o que fornece valores aleatórios de maneira bastante uniforme. O C ++ agora possui várias boas primitivas de geração de números aleatórios em suas bibliotecas padrão, se estiverem disponíveis para você.

templatetypedef
fonte