Calcular puntos aleatorios (píxeles) dentro de un círculo (imagen)

19

Tengo una imagen que contiene círculos en una ubicación específica y de un diámetro específico. Lo que tengo que hacer es poder calcular puntos aleatorios dentro del círculo y luego manipular los píxeles con los que se correlacionan dichos puntos. Ya tengo el siguiente código:

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

Y eso funciona bien para encontrar todos los puntos en la circunferencia del círculo, pero necesito todos los puntos de cualquier parte del círculo. Si esto no tiene sentido, hágamelo saber y haré todo lo posible para aclararlo.

DMills
fuente
Revisa la actualización.
David Gouveia
3
Buena pregunta en el sentido de que crear accidentalmente una distribución ponderada es un error común.
Tim Holt

Respuestas:

35

Si desea una solución simple, simplemente aleatorice el radio también:

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

Sin embargo, eso hace que tus puntos se concentren más hacia el centro del círculo:

ingrese la descripción de la imagen aquí

Para obtener una distribución uniforme, realice el siguiente cambio en el algoritmo:

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

Lo que dará el siguiente resultado:

ingrese la descripción de la imagen aquí

Para obtener más información, consulte el siguiente enlace: MathWorld - Selección de punto de disco .

Y finalmente, aquí hay una demostración simple de JsFiddle que compara ambas versiones del algoritmo.

David Gouveia
fuente
1
Excelente respuesta Solo una cosa para agregar: no olvide sembrar el generador de números aleatorios :)
kevintodisco
Vaya, lo superaste, no vi esta publicación cuando publiqué la mía. El sitio de Wolfram es un recurso increíble para este tipo de cosas.
Tim Holt
1
@TimHolt sucede todo el tiempo :)
David Gouveia
¿Asume esto que el centro del círculo está en 0,0?
jjxtra
@PsychoDad El centro del círculo sería (_originX, _originY)
David Gouveia
5

¡NO use simplemente r y theta al azar! Esto crea una distribución ponderada con más puntos en el centro. Esta página lo ilustra bien ...

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

Aquí está el método que crea una distribución no ponderada ...

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

var x = sqrt(r) * cos(theta)
var y = sqrt(r) * sin(theta)
Tim Holt
fuente
Vaya duplicado de la respuesta seleccionada: P
Tim Holt
Estoy confundido porque dices que no uses r y theta al azar, ya que crea una distribución ponderada, entonces el código que muestras que afirmas que crea una distribución no ponderada genera r dentro del rango [0,1]. ¿Tenía la intención de enraizar al cuadrado el número aleatorio?
PeteUK
Sí, hacer la raíz cuadrada del radio (siempre que sea 0-1) reduce la concentración inesperada de puntos en el medio. Vea el enlace de Wolfram que publiqué, que lo ilustra y explica con las matemáticas mejor que yo.
Tim Holt
Culpa mía. Veo que haces sqrt (r) al calcular x e y.
PeteUK
4

Estás a medio camino. Además de generar un ángulo aleatorio, solo genera una distancia aleatoria, menor o igual que el radio, ponderada para obtener una distribución 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);
}

Ahora estás pensando con polar .

También puede pesar la distancia de esta manera para evitar una raíz cuadrada:

var distance = _random.NextDouble() + _random.NextDouble();
distance = (distance <= 1 ? distance : 2 - distance) * _radius;
Jon Purdy
fuente
Bien, entonces dimos exactamente la misma respuesta en segundos de diferencia. ¿Ahora que? :)
David Gouveia
@DavidGouveia Ambos recibimos votos positivos por tener razón. ¡Todos ganan! : D
Jon Purdy
Ambas respuestas son muy apreciadas (¡y el enlace también!). Hombre, soy un idiota por no ver eso, -1 para mí :( ¡Gracias de nuevo a los dos!
DMills
Esto generará puntos aleatorios, pero no se distribuirán uniformemente en el disco, ¿verdad? Más bien serán pesados ​​hacia el centro. Solo comprobando que no me falta algo.
PeteUK
1
@PeteUK: Tienes razón, la distancia debe ser ponderada. Déjame actualizar
Jon Purdy
3

Si el rendimiento es un problema, entonces una solución alternativa es generar una posición aleatoria en un cuadro con el ancho / alto de su círculo y luego tirar cualquier punto que no esté en el área del círculo.

La ventaja de este método es que no está haciendo funciones cos / sin / sqrt, lo que dependiendo de su plataforma puede ser un gran ahorro de velocidad.

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 medusa
fuente
Voy a probar eso y ver si acelera las cosas. No estoy seguro de tener un problema de rendimiento, pero lo intentaré de todos modos, ¡gracias!
DMills
0

Tomé el enfoque de uno de los comentarios enumerados y amplié la funcionalidad para crear un sistema de generación de puntos en forma de rosquilla.

            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 un enfoque similar al mencionado anteriormente, pero proporcionó resultados diferentes. La parte interna del círculo se dejará en blanco sin puntos.

Branden
fuente