Berechne zufällige Punkte (Pixel) innerhalb eines Kreises (Bild)

19

Ich habe ein Bild, das Kreise an einer bestimmten Stelle und mit einem bestimmten Durchmesser enthält. Was ich tun muss, ist, in der Lage zu sein, zufällige Punkte innerhalb des Kreises zu berechnen und dann die Pixel zu manipulieren, mit denen die Punkte korrelieren. Ich habe bereits folgenden Code:

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

Und das funktioniert gut, um alle Punkte am Umfang des Kreises zu finden, aber ich brauche alle Punkte von irgendwo im Kreis. Wenn dies keinen Sinn ergibt, lassen Sie es mich wissen und ich werde mein Bestes tun, um dies zu klären.

DMills
quelle
Überprüfen Sie das Update.
David Gouveia
3
Gute Frage in dem Sinne, dass das versehentliche Erstellen einer gewichteten Verteilung ein häufiger Fehler ist.
Tim Holt

Antworten:

35

Wenn Sie eine einfache Lösung wünschen, können Sie auch den Radius zufällig bestimmen:

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

Dies hat jedoch zur Folge, dass Ihre Punkte mehr auf den Mittelpunkt des Kreises konzentriert sind:

Bildbeschreibung hier eingeben

Um eine gleichmäßige Verteilung zu erhalten, ändern Sie den Algorithmus wie folgt:

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

Welches wird das folgende Ergebnis geben:

Bildbeschreibung hier eingeben

Weitere Informationen finden Sie unter folgendem Link: MathWorld - Disk Point Picking .

Zum Schluss folgt eine einfache JsFiddle-Demonstration , in der beide Versionen des Algorithmus verglichen werden.

David Gouveia
quelle
1
Hervorragende Antwort. Nur eine Sache, die Sie hinzufügen sollten: Vergessen Sie nicht, den Zufallszahlengenerator zu setzen :)
kevintodisco
Hoppla, du hast es geschlagen. Ich habe diesen Beitrag nicht gesehen, als ich meinen gepostet habe. Die Wolfram-Site ist eine großartige Ressource für diese Art von Dingen.
Tim Holt
1
@ TimHolt passiert die ganze Zeit :)
David Gouveia
Geht man davon aus, dass der Kreismittelpunkt bei 0,0 liegt?
jjxtra
@ PsychoDad Der Mittelpunkt des Kreises wäre (_originX, _originY)
David Gouveia
5

Verwenden Sie NICHT nur zufälliges r und Theta! Dies erzeugt eine gewichtete Verteilung mit mehr Punkten in der Mitte. Diese Seite illustriert es gut ...

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

Hier ist die Methode, die eine nicht gewichtete Verteilung erstellt ...

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

var x = sqrt(r) * cos(theta)
var y = sqrt(r) * sin(theta)
Tim Holt
quelle
Ups Duplikat der ausgewählten Antwort: P
Tim Holt
Ich bin verwirrt, weil Sie sagen, dass Sie kein zufälliges r und Theta verwenden sollen, da es eine gewichtete Verteilung erzeugt. Der Code, den Sie angeben, dass Sie eine nicht gewichtete Verteilung erzeugen, generiert dann ein r innerhalb des Bereichs [0,1]. Wollten Sie die Zufallszahl wurzeln?
PeteUK
Ja, wenn Sie die Quadratwurzel des Radius verwenden (solange er 0-1 ist), wird die unerwartete Konzentration von Punkten in der Mitte verringert. Siehe den Wolfram-Link, den ich gepostet habe, der es veranschaulicht und mit der Mathematik besser erklärt, als ich kann.
Tim Holt
Mein Fehler. Ich sehe, dass Sie sqrt (r) ausführen, wenn Sie x und y berechnen.
PeteUK
4

Du bist auf halber Strecke. Zusätzlich zur Erzeugung eines zufälligen Winkels erzeugen Sie einfach einen zufälligen Abstand, der kleiner oder gleich dem Radius ist und so gewichtet wird, dass Sie eine gleichmäßige Verteilung erhalten:

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

Jetzt denkst du mit Polar .

Sie können den Abstand auch wie folgt gewichten, um eine Quadratwurzel zu vermeiden:

var distance = _random.NextDouble() + _random.NextDouble();
distance = (distance <= 1 ? distance : 2 - distance) * _radius;
Jon Purdy
quelle
Okay, also gaben wir innerhalb von Sekunden nach dem Unterschied genau dieselbe Antwort. Was jetzt? :)
David Gouveia
@DavidGouveia Wir haben beide positive Stimmen für beide, die Recht haben. Jeder gewinnt! : D
Jon Purdy
Beide Antworten sehr geschätzt (und der Link auch!). Mann, ich bin ein Idiot dafür, dass ich das nicht selbst gesehen habe, -1 zu mir :(
Nochmals vielen
Dadurch werden zufällige Punkte generiert, die jedoch nicht gleichmäßig auf der Festplatte verteilt sind. Sie werden eher in Richtung der Mitte gewichtet. Ich überprüfe nur, ob mir etwas fehlt.
PeteUK
1
@PeteUK: Du hast recht, die Entfernung sollte gewichtet werden. Lass mich aktualisieren.
Jon Purdy
3

Wenn Leistung ein Problem ist, besteht eine alternative Lösung darin, eine zufällige Position in einem Feld mit der Breite / Höhe Ihres Kreises zu generieren und dann alle Punkte wegzuwerfen, die nicht im Bereich des Kreises liegen.

Der Vorteil dieser Methode ist, dass Sie keine cos / sin / sqrt-Funktionen ausführen, was abhängig von Ihrer Plattform eine große Geschwindigkeitseinsparung sein kann.

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)
}
M Qualle
quelle
Ich werde das ausprobieren und sehen, ob es die Dinge beschleunigt. Ich bin nicht sicher, ob ich ein Leistungsproblem habe, aber ich werde es trotzdem versuchen, danke!
DMills
0

Ich habe den Ansatz eines der aufgelisteten Kommentare gewählt und die Funktionalität erweitert, um ein System zur Erzeugung von doughnutförmigen Punkten zu erstellen.

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

Es ist ein ähnlicher Ansatz wie zuvor erwähnt, lieferte jedoch unterschiedliche Ergebnisse. Der innere Teil des Kreises wird ohne Punkte leer gelassen.

Branden
quelle