Calcola punti casuali (pixel) all'interno di un cerchio (immagine)


19

Ho un'immagine che contiene un cerchio in una posizione specifica e di un diametro specifico. Quello che devo fare è essere in grado di calcolare punti casuali all'interno del cerchio e quindi manipolare i pixel a cui tali punti sono correlati. Ho già il seguente codice:

private Point CalculatePoint()
{
    var angle = _random.NextDouble() * ( Math.PI * 2 );
    var x = _originX + ( _radius * Math.Cos( angle ) );
    var y = _originY + ( _radius * Math.Sin( angle ) );
    return new Point( ( int )x, ( int )y );
}

E questo funziona benissimo per trovare tutti i punti sulla circonferenza del cerchio, ma ho bisogno di tutti i punti da qualsiasi punto del cerchio. Se questo non ha senso, fammi sapere e farò del mio meglio per chiarire.


Controlla l'aggiornamento.
David Gouveia,

3
Buona domanda, nel senso che la creazione involontaria di una distribuzione ponderata è un errore comune.
Tim Holt,

Risposte:


35

Se vuoi una soluzione semplice, randomizza anche il raggio:

private Point CalculatePoint()
{
    var angle = _random.NextDouble() * Math.PI * 2;
    var radius = _random.NextDouble() * _radius;
    var x = _originX + radius * Math.Cos(angle);
    var y = _originY + radius * Math.Sin(angle);
    return new Point((int)x,(int)y);
}

Ciò comporta tuttavia che i tuoi punti siano più concentrati verso il centro del cerchio:

inserisci qui la descrizione dell'immagine

Per ottenere una distribuzione uniforme, apportare la seguente modifica all'algoritmo:

var radius = Math.Sqrt(_random.NextDouble()) * _radius;

Che darà il seguente risultato:

inserisci qui la descrizione dell'immagine

Per ulteriori informazioni, consultare il seguente collegamento: MathWorld - Selezione del punto del disco .

E finalmente ecco una semplice dimostrazione di JsFiddle che confronta entrambe le versioni dell'algoritmo.


1
Risposta eccellente. Solo una cosa da aggiungere: non dimenticare di seminare il generatore di numeri casuali :)
kevintodisco,

Oop hai battuto incontrato ad esso - non ho visto questo post quando ho pubblicato il mio. Il sito wolfram è una risorsa fantastica per questo genere di cose.
Tim Holt,

1
@TimHolt succede sempre :)
David Gouveia,

Supponiamo che il centro del cerchio sia a 0,0?
jjxtra,

@PsychoDad Il centro del cerchio sarebbe (_originX, _originY)
David Gouveia,

5

NON usare solo r e theta casuali! Questo crea una distribuzione ponderata con più punti al centro. Questa pagina lo illustra bene ...

http://mathworld.wolfram.com/DiskPointPicking.html

Ecco il metodo che crea una distribuzione non ponderata ...

var r = rand(0,1)
var theta = rand(0,360)

var x = sqrt(r) * cos(theta)
var y = sqrt(r) * sin(theta)

Oops duplicato della risposta selezionata: P
Tim Holt

Sono confuso perché dici di non usare r e theta casuali in quanto crea una distribuzione ponderata, quindi il codice che mostri che affermi crea una distribuzione non ponderata genera r nell'intervallo [0,1]. Hai intenzione di quadrare il numero casuale?
PeteUK,

Sì, facendo la radice quadrata del raggio (purché sia ​​0-1) riduce la concentrazione inaspettata di punti nel mezzo. Vedi il link Wolfram che ho pubblicato, che lo illustra e lo spiega con la matematica meglio di me.
Tim Holt,

Colpa mia. Vedo che fai sqrt (r) quando calcoli xey.
PeteUK il

4

Sei a metà strada. Oltre a generare un angolo casuale, genera semplicemente una distanza casuale, inferiore o uguale al raggio, ponderata in modo da ottenere una distribuzione uniforme:

private Point CalculatePoint()
{
    var angle = _random.NextDouble() * Math.PI * 2;
    var distance = Math.Sqrt(_random.NextDouble()) * _radius;
    var x = _originX + (distance * Math.Cos(angle));
    var y = _originY + (distance * Math.Sin(angle));
    return new Point((int)x, (int)y);
}

Ora stai pensando con polar .

Puoi anche ponderare la distanza in questo modo per evitare una radice quadrata:

var distance = _random.NextDouble() + _random.NextDouble();
distance = (distance <= 1 ? distance : 2 - distance) * _radius;

Ok, quindi abbiamo dato esattamente la stessa risposta in pochi secondi dalla differenza. E adesso? :)
David Gouveia,

@DavidGouveia Entrambi riceviamo voti positivi per entrambi. Tutti vincono! : D
Jon Purdy,

Entrambe le risposte sono molto apprezzate (e anche il link!). Amico, sono un idiota per non aver visto me stesso, -1 per me :( Grazie ancora a tutti e due!
DM,

Questo genererà punti casuali, ma non saranno distribuiti uniformemente sul disco, giusto? Piuttosto saranno pesati verso il centro. Sto solo controllando che non mi manchi qualcosa.
PeteUK,

1
@PeteUK: hai ragione, la distanza dovrebbe essere ponderata. Lasciami aggiornare.
Jon Purdy,

3

Se le prestazioni sono un problema, una soluzione alternativa è quella di generare una posizione casuale in una casella con la larghezza / altezza del cerchio e quindi eliminare tutti i punti che non si trovano nell'area del cerchio.

Il vantaggio di questo metodo è che non stai facendo alcuna funzione cos / sin / sqrt, che a seconda della tua piattaforma potrebbe essere un grande risparmio di velocità.

var x = _random.NextDouble();
var y = _random.NextDouble();
if (x*x + y*y < 1.0f)
{
    // we have a usable point inside a circle
    x = x * diameter - _radius + _OriginX;
    y = y * diameter - _radius + _OriginY;
    // use the point(x,y)
}

Ho intenzione di provarlo e vedere se accelera le cose. Non sono sicuro di avere un problema di prestazioni, ma lo proverò comunque, grazie!
DMills

0

Ho adottato l'approccio di uno dei commenti elencati e ho esteso la funzionalità per creare un sistema di generazione di punti a forma di ciambella.

            private Vector2 CalculatePosition()
            {
                double angle = _rnd.NextDouble() * Math.PI * 2;
                double radius = InnerCircleRadius + (Math.Sqrt(_rnd.NextDouble()) * (OuterCircleRadius - InnerCircleRadius));
                double x = (_context.ScreenLayout.Width * 0.5f) + (radius * Math.Cos(angle));
                double y = (_context.ScreenLayout.Height * 0.5f) + (radius * Math.Sin(angle));
                return new Vector2((int)x, (int)y);
            }

È un approccio simile a quello precedentemente menzionato, ma ha fornito risultati diversi. La parte interna del cerchio verrà lasciata vuota senza punti.

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.