Rumore casuale basato sul seme


16

Attualmente sto lavorando a un programma che dovrebbe generare rumore casuale su uno schermo basato sulle "coordinate" di un pixel. Le coordinate dovrebbero avere lo stesso colore ogni volta che riavvii il programma. Tuttavia, usando util.Random di Java, i risultati che ottengo non sono così casuali come vorrei:

printscreen

Pensavo che se avessi usato le coordinate combinate (come in un numero intero formato da entrambe le coordinate accanto a ciascuna) ogni coordinata avrebbe un numero diverso. Usando quel numero come seme mi aspettavo di ottenere un numero casuale diverso per ciascuna coordinata da utilizzare per il valore rgb di quella coordinata.

Questo è il codice che ho usato:

public class Generate {

static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(Integer.valueOf(Integer.toString(x)+Integer.toString(y)));
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

Lo schema creato dal programma a causa del modo in cui la funzione Casuale di Java funziona o sto facendo qualcosa di sbagliato e dovrei provare un approccio diverso?

Aggiornamento: ora ho provato a eliminare i problemi relativi alla concatenazione utilizzando il seguente codice:

public static int TileColor(int x, int y){  
            Randomy = new Random(y);
            Randomx = new Random(x);
            Random = new Random(Integer.valueOf(Integer.toString(Randomx.nextInt(1234))+Integer.toString(Randomy.nextInt(1234))));
            int b = 1 + Random.nextInt(100);
            int g = 1 + Random.nextInt(100);
            int r = 1 + Random.nextInt(100);
            int color = -Color.rgb888(r, g, b);
            return color;
}

In qualche modo, questo ha anche fornito un'immagine (a mio avviso) sufficientemente casuale:

miagolosa immagine

Questo codice viene comunque ridimensionato tre volte per pixel. Anche se questo non è un problema per me in questo momento, prendo in considerazione la possibilità di modificare questo codice nel caso in cui avessi bisogno di preformance in seguito.


3
Non sono sicuro di Java's Random, ma sono abbastanza sicuro che non sia proprio casuale ... Leggi en.wikipedia.org/wiki/Pseudorandom_number_generator Capirai perché vedi questi schemi.
Salketer,

23
Qualcosa di cruciale che manca alle altre risposte: non ridimensionare l'RNG per ogni pixel. Semina una volta e genera valori consecutivi per tutti i pixel dell'immagine in base a quello.
Konrad Rudolph,

4
Nota: un genere pseudocasuale può essere distribuito uniformemente in una dimensione , ma fallisce quando si utilizza più di una dimensione ... si stanno effettivamente generando punti in 3D (r, ge b e 3 coordinate diverse) quindi è necessario un generatore casuale che garantisce non solo che i valori che genera siano distribuiti uniformemente, ma anche le terzine che genera siano distribuite uniformemente nello spazio 3D.
Bakuriu,

6
@Bakuriu Se X, Y e Z sono variabili casuali uniformi indipendenti, allora sono abbastanza sicuro (X, Y, Z) è uniforme nello spazio 3d.
Jack M,

2
Potresti sperimentare l'utilizzo di diversi RNG, come il Mersenne Twister .
Kevin,

Risposte:


21

La java.util.Randomclasse Java di solito ti dà sequenze di numeri pseudocasuali che sono abbastanza buoni per l'uso nei giochi 1 . Tuttavia, tale caratteristica si applica solo a una sequenza di più campioni basata su un seme. Quando si reinizializza l'RNG con l'incremento dei valori seed e si osserva solo il primo valore di ciascuna sequenza, le caratteristiche di casualità non saranno altrettanto buone.

Cosa potresti fare invece:

  • Usa lo stesso seme per generare interi pezzi di pixel alla volta. Ad esempio, quando è necessario il valore del colore del pixel 425: 487, immettere le coordinate 400: 400 nell'RNG, generare 10000 colori casuali e utilizzare il colore all'indice 2587 (25 * 100 + 87). I blocchi generati in questo modo dovrebbero essere memorizzati nella cache per evitare di rigenerare quei 10000 colori casuali per ogni singolo pixel di quel blocco.
  • Invece di utilizzare un generatore di numeri casuali, utilizzare una funzione di digest dei messaggi per trasformare una coppia di coordinate in un valore di colore. L'output della maggior parte degli MDF è abbastanza imprevedibile da soddisfare la maggior parte dei test di casualità. L'output di solito è superiore ai 24 bit necessari per un valore RGB, ma troncarli non è in genere un problema.

    Per migliorare le prestazioni è possibile combinare la generazione del digest del messaggio con blocchi. Genera piccoli blocchi di pixel che sono abbastanza grandi da utilizzare l'intera lunghezza di un output della tua funzione digest.

1 quando è assolutamente essenziale che nessuno possa prevedere il numero successivo, utilizzare il più lento ma meno prevedibilejava.security.SecureRandom


13

Le coordinate dovrebbero avere lo stesso colore a tutti i riavvii del programma

In tal caso, ti consigliamo di utilizzare una funzione di rumore deterministico come il rumore Perlin o il rumore simplex .

( Vedi questa domanda per ulteriori informazioni sul rumore di Perlin con alcune belle immagini. )

Per la maggior parte, l'uso di una funzione integrata random()o simile ti darà valori diversi ogni volta che esegui il programma, poiché potrebbero usare l'orologio come input o qualche altro valore pseudocasuale.

Un'altra opzione è quella di generare una "mappa del rumore" una volta, offline, e successivamente utilizzarla come fonte di numeri casuali.

Nella tua implementazione, stai concatenando le rappresentazioni di stringa di xe y. Ciò è negativo in quanto non è univoco in tutto il dominio. Per esempio,

x    y   concatenated
40   20  4020
402   0  4020
10   10  1010
101   0  1010
12   34  1234
123   4  1234
1   234  1234

In bocca al lupo!


1
Un buon punto sui numeri concatenati. Tuttavia, il programma dà sempre lo stesso esatto risultato se eseguo il programma più volte. Ho considerato anche il rumore perlin / simplex, potrei dare un'occhiata a questo e vedere se funziona meglio. Tuttavia non sono ancora sicuro del motivo per cui Java crei modelli, poiché il problema di concatenazione non sembra risolverlo del tutto
libellula

1
Non è sufficiente seminare semplicemente Random con un valore di seme costante prima di generare i pixel?
Jack M,

1
@JackM Dipende interamente dall'algoritmo PRNG in gioco.
3Daveva il

4
"Una volta ho visto un'implementazione di rand () che utilizzava ogni valore come seme per il valore successivo." Non funziona così la maggior parte dei generatori di numeri pseudocasuali non crittografici? Usano il precedente numero casuale (o stato) come input per generare il successivo numero / stato casuale.
JAB,

2
@DavidLively Praticamente tutti i PRNG fanno questo o qualcosa di equivalente, a meno che il loro stato interno non sia maggiore dell'intervallo di numeri che generano (ad es. Twister di Mersenne), e anche allora la sequenza di numeri casuali è ovviamente interamente determinata dal seme.
Konrad Rudolph,

9

Vediamo cosa stai facendo esattamente:

  • Esegui il ciclo di tutti i pixel uno per uno
  • Per ogni pixel, usi la concatenazione delle sue coordinate come seme
  • Quindi inizi un nuovo casuale dal seme dato ed estrai 3 numeri

Tutto questo suona bene, ma stai ricevendo uno schema perché:

Pixel a 1,11 e pixel a 11,1 hanno entrambi il seme del numero 111, quindi hanno sicuramente lo stesso colore.

Inoltre, purché si pedali sempre allo stesso modo, è possibile utilizzare solo un generatore, non è necessario utilizzarne uno per ciascun pixel. Uno per l'intera immagine farà! Ci saranno ancora dei modelli a causa della pseudo-casualità. @David_Lively ha ragione sull'uso dell'algoritmo Noise, lo farà sembrare più casuale.


Il fatto è che la vista dell'immagine dovrebbe essere in grado di spostarsi (oltre alle coordinate positive). Quindi questo approccio non funzionerà completamente
libellula,

4
In realtà "tutto questo" non suona bene: ridimensionare un RNG deterministico per ogni pixel è una strategia terribile a meno che il seme stesso non provenga da un RNG crittografico (e anche allora è una strategia traballante per ragioni non correlate alla distribuzione).
Konrad Rudolph,

potresti includere il modo corretto di concatenare i numeri in questo contesto. Vale a dire. (x + y * larghezza)
Taemyr,

1

Crea un generatore di colore, quindi produce i tuoi colori per la tua piastrella. Semina una sola volta! Non è necessario seminare più di quello, almeno per tessera.

public class RandomColorGenerator {
  private final int minValue;
  private final int range;
  private final Random random;
  public RandomColorGenerator(int minValue, int maxValue, Random random) {
    if (minValue > maxValue || (long)maxValue - (long)minValue > (long)Integer.MAX_VALUE) {
      throw new IllegalArgumentException();
    }
    this.minValue = minValue;
    this.range = maxValue - minValue + 1;
    this.random = Objects.requireNonNull(random);
  }

  public int nextColor() {
    int r = minValue + random.nextInt(range);
    int g = minValue + random.nextInt(range);
    int b = minValue + random.nextInt(range);
    return -Color.rgb888(r, g, b);
  }
}

public class Tile {
  private final int[][] colors;
  public Tile(int width, int height, RandomColorGenerator colorGenerator) {
    this.colors = new int[width][height];
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        this.colors[x][y] = colorGenerator.nextColor();
      }
    }
  }

  public int getColor(int x, int y) {
    return colors[x][y];
  }
}

E l'utilizzo sarà come segue:

RandomColorGenerator generator = new RandomColorGenerator(1, 100, new Random(0xcafebabe));
Tile tile = new Tile(300, 200, generator);
...
// getting the color for x, y:
tile.getColor(x, y);

Con questo se non sei soddisfatto del risultato basta cambiare Randomseme. Inoltre, devi solo archiviare / comunicare il seme e le dimensioni in modo che tutti i client abbiano la stessa immagine.


1

Invece di usare Random, considera l'utilizzo di un hash digest come MD5. Fornisce un valore "casuale" difficile da prevedere basato su un determinato input, ma sempre lo stesso valore per lo stesso input.

Esempio:

public static int TileColor(int x, int y){
        final MessageDigest md = MessageDigest.getInstance("MD5");
        final ByteBuffer b = ByteBuffer.allocate(8);
        b.putInt(x).putInt(y);
        final byte[] digest = md.digest(b.array());
        return -Color.rgb888(digest[0], digest[1], digest[2]);
}

NOTA: non so da dove provenga Color.rgb888 (..), quindi non so quale sia l'intervallo consentito. 0-255 è normale però.

Miglioramenti da considerare:

  • Rendere variabili MessageDigest e ByteBuffer al di fuori della classe, per migliorare le prestazioni. Per farlo dovrai resettare ByteBuffer e il metodo non sarà più sicuro per Thread.
  • L'array digest conterrà valori di byte 0-255, se si desidera altri intervalli, è necessario eseguire alcuni calcoli su di essi.
  • Se desideri risultati "casuali" diversi, puoi aggiungere una sorta di "seme". Ad esempio, passare a ByteBuffer.allocate (12) e aggiungere un .putInt (seed).

1

Altri hanno sottolineato che un modo per ottenere il comportamento desiderato è utilizzare una funzione hash, nota anche come "funzione digest del messaggio". Il problema è che questi sono spesso basati su algoritmi come MD5, che è crittograficamente sicuro (cioè molto, molto, molto casuale) ma molto lento. Se usi una funzione hash crittografica ogni volta che hai bisogno di un pixel casuale, incontrerai problemi di prestazioni piuttosto gravi.

Tuttavia, ci sono funzioni hash non crittografiche che possono produrre valori abbastanza casuali per il tuo scopo e allo stesso tempo essere veloci. Quello che di solito raggiungo è il mormorio . Non sono un utente Java ma sembra che sia disponibile almeno un'implementazione Java . Se scopri che è davvero necessario che ogni pixel sia generato dalle sue coordinate, piuttosto che generarli tutti in una volta e memorizzarli in una trama, questo sarebbe un buon modo per farlo.


1

Vorrei usare un numero primo oltre 2000 (risoluzione massima tipica)
Ciò minimizzerà (o eliminerebbe i semi duplicati)

public class Generate {

    static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(x + 2213 * y);
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

0

Randomè abbastanza casuale. Lo stai usando male per due motivi principali.

  • Non è stato progettato per essere ripetuto più volte. Le proprietà casuali valgono solo per una singola sequenza di numeri casuali.
  • C'è un'enorme correlazione di Integer.valueOf(Integer.toString(x)+Integer.toString(y)) tra i pixel con cui stai eseguendo il seeding.

Vorrei solo usare alcune varianti del seguente codice, in cui puoi scegliere una funzione hash (non usare Integer.getHashCode) dalle risposte su /programming/9624963/java-simplest-integer- hash

public static int TileColor(int x, int y) {
    return hash(x ^ hash(y));
}

dove potrebbe essere la funzione hash


0

Potresti provare a usare l'ora corrente del sistema come seme come questo:

Random random = new Random(System.currentTimeMillis())

Speriamo che produca un valore più casuale.


Tuttavia, ciò non crea sempre il valore dame per la coordinata dame.
libellula,

0

Ecco una funzione di shader statico a una riga che mi è venuta in mente: poltergeist (Noisy Ghost).

Prende una coordinata 2D e un seme e viene eseguito il rendering in monotono come richiesto. Funziona a fps in tempo reale, indipendentemente dalla risoluzione dello schermo. Ecco a cosa servono le GPU.

// poltergeist (noisy ghost) pseudo-random noise generator function
// dominic.cerisano@standard3d.com 03/24/2015

precision highp float;

float poltergeist(in vec2 coordinate, in float seed) 
{
    return fract(sin(dot(coordinate*seed, vec2(12.9898, 78.233)))*43758.5453); 
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) 
{   
    fragColor = vec4(poltergeist(fragCoord, iGlobalTime)); 
}

Qualsiasi risoluzione, qualsiasi trama, su qualsiasi dispositivo (anche mobile) che supporti GL (che è praticamente qualsiasi con uno schermo).

Guardalo correre qui, proprio ora!

https://www.shadertoy.com/view/ltB3zD

Puoi facilmente includere questo shader nel tuo programma java usando opengl standard o in qualsiasi browser usando webgl standard.

Solo per divertimento, lancio il guanto per consentire a chiunque di battere Poltergeist in termini di qualità e prestazioni su tutti i dispositivi. Regole del fantasma rumoroso! Imbattuto!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.