Calculer des points aléatoires (pixel) dans un cercle (image)

19

J'ai une image qui contient des cercles à un endroit spécifique et d'un diamètre spécifique. Ce que je dois faire, c'est être capable de calculer des points aléatoires dans le cercle, puis de manipuler les pixels auxquels ces points sont corrélés. J'ai déjà le code suivant:

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

Et cela fonctionne bien pour trouver tous les points à la circonférence du cercle, mais j'ai besoin de tous les points de n'importe où dans le cercle. Si cela n'a pas de sens, faites le moi savoir et je ferai de mon mieux pour clarifier.

DMills
la source
Vérifiez la mise à jour.
David Gouveia
3
Bonne question dans le sens où la création par inadvertance d'une distribution pondérée est une erreur courante.
Tim Holt

Réponses:

35

Si vous voulez une solution simple, randomisez également le rayon:

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

Cela se traduit cependant par une concentration accrue de vos points vers le centre du cercle:

entrez la description de l'image ici

Afin d'obtenir une distribution uniforme, apportez la modification suivante à l'algorithme:

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

Ce qui donnera le résultat suivant:

entrez la description de l'image ici

Pour plus d'informations, consultez le lien suivant: MathWorld - Disk Point Picking .

Et enfin, voici une simple démonstration de JsFiddle comparant les deux versions de l'algorithme.

David Gouveia
la source
1
Excellente réponse. Juste une chose à ajouter: n'oubliez pas d'
amorcer le
Oop vous avez battu rencontré - je n'ai pas vu ce post quand j'ai posté le mien. Le site wolfram est une ressource formidable pour ce genre de chose.
Tim Holt
1
@TimHolt arrive tout le temps :)
David Gouveia
Est-ce à supposer que le centre du cercle est à 0,0?
jjxtra
@PsychoDad Le centre du cercle serait (_originX, _originY)
David Gouveia
5

N'utilisez PAS seulement r et thêta aléatoires! Cela crée une distribution pondérée avec plus de points au centre. Cette page l'illustre bien ...

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

Voici la méthode qui crée une distribution non pondérée ...

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

var x = sqrt(r) * cos(theta)
var y = sqrt(r) * sin(theta)
Tim Holt
la source
Oups, double de la réponse sélectionnée: P
Tim Holt
Je suis confus parce que vous dites de ne pas utiliser aléatoire r et thêta car cela crée une distribution pondérée, puis le code que vous montrez que vous prétendez créer une distribution non pondérée génère r dans la plage [0,1]. Avez-vous l'intention de créer une racine carrée du nombre aléatoire?
PeteUK
Oui, faire la racine carrée du rayon (tant que c'est 0-1) réduit la concentration inattendue de points au milieu. Voir le lien Wolfram que j'ai posté, qui l'illustre et l'explique mieux que moi avec les mathématiques.
Tim Holt
Ma faute. Je vois que vous faites sqrt (r) lors du calcul de x et y.
PeteUK
4

Vous êtes à mi-chemin. En plus de générer un angle aléatoire, il suffit de générer une distance aléatoire, inférieure ou égale au rayon, pondérée pour obtenir une distribution 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);
}

Maintenant tu penses avec polar .

Vous pouvez également pondérer la distance comme ceci pour éviter une racine carrée:

var distance = _random.NextDouble() + _random.NextDouble();
distance = (distance <= 1 ? distance : 2 - distance) * _radius;
Jon Purdy
la source
D'accord, nous avons donc donné exactement la même réponse en quelques secondes de différence. Maintenant quoi? :)
David Gouveia
@DavidGouveia Nous obtenons tous les deux des votes positifs pour avoir raison. Tout le monde gagne! : D
Jon Purdy
Les deux réponses sont très appréciées (et le lien aussi!). Mec, je suis un idiot de ne pas l'avoir vu moi-même, -1 pour moi :( Merci encore à vous deux!
DMills
Cela générera des points aléatoires, mais ils ne seront pas distribués uniformément sur le disque, non? Ils seront plutôt pondérés vers le centre. Je vérifie juste que je ne manque rien.
PeteUK
1
@PeteUK: Vous avez raison, la distance doit être pondérée. Permettez-moi de mettre à jour.
Jon Purdy
3

Si les performances sont un problème, une solution alternative consiste à générer une position aléatoire dans une boîte avec la largeur / hauteur de votre cercle, puis à jeter tous les points qui ne sont pas dans la zone du cercle.

L'avantage de cette méthode est que vous n'effectuez aucune fonction cos / sin / sqrt, ce qui, selon votre plate-forme, peut représenter une économie de vitesse importante.

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éduse M
la source
Je vais essayer cela et voir si cela accélère les choses. Je ne suis pas sûr d'avoir un problème de performances, mais je vais essayer quand même, merci!
DMills
0

J'ai adopté l'approche de l'un des commentaires énumérés et étendu la fonctionnalité pour créer un système de génération de points en forme de beignet.

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

Il s'agit d'une approche similaire à celle mentionnée précédemment mais qui a donné des résultats différents. La partie intérieure du cercle sera laissée vierge sans point.

Branden
la source