Calcular pontos aleatórios (pixel) dentro de um círculo (imagem)

19

Eu tenho uma imagem que contém círculos em um local específico e com um diâmetro específico. O que preciso fazer é ser capaz de calcular pontos aleatórios dentro do círculo e manipular os pixels aos quais os pontos se correlacionam. Eu já tenho o seguinte 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 );
}

E isso funciona bem para encontrar todos os pontos na circunferência do círculo, mas eu preciso de todos os pontos de qualquer lugar do círculo. Se isso não fizer sentido, avise-me e farei o possível para esclarecer.

DMills
fonte
Verifique a atualização.
David Gouveia
3
Boa pergunta no sentido de que criar inadvertidamente uma distribuição ponderada é um erro comum.
Tim Holt

Respostas:

35

Se você deseja uma solução simples, apenas randomize o raio também:

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

No entanto, isso resulta em seus pontos mais concentrados em direção ao centro do círculo:

insira a descrição da imagem aqui

Para obter uma distribuição uniforme, faça a seguinte alteração no algoritmo:

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

O que dará o seguinte resultado:

insira a descrição da imagem aqui

Para mais informações, consulte o seguinte link: MathWorld - Seleção de ponto de disco .

E finalmente, aqui está uma demonstração simples do JsFiddle comparando as duas versões do algoritmo.

David Gouveia
fonte
1
Excelente resposta. Apenas uma coisa a acrescentar: não se esqueça de semear o gerador de números aleatórios :)
kevintodisco
Oop você bateu conheceu a ele - não vi este post quando eu postei o meu. O site wolfram é um recurso incrível para esse tipo de coisa.
Tim Holt
1
@TimHolt Acontece o tempo todo :)
David Gouveia
Isso está assumindo que o centro do círculo está em 0,0?
Jjxtra # 29/13
@PsychoDad O centro do círculo seria (_originX, _originY)
David Gouveia
5

NÃO use apenas r aleatório e teta! Isso cria uma distribuição ponderada com mais pontos no centro. Esta página ilustra bem ...

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

Aqui está o método que cria uma distribuição não 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
fonte
Ops, duplicado da resposta selecionada: P
Tim Holt
Estou confuso porque você diz para não usar r aleatório e teta, pois ele cria uma distribuição ponderada; em seguida, o código que você mostra que afirma criar uma distribuição não ponderada gera r dentro do intervalo [0,1]. Você pretendia fazer a raiz quadrada do número aleatório?
PeteUK
Sim, fazer a raiz quadrada do raio (desde que seja 0-1) reduz a concentração inesperada de pontos no meio. Veja o link da Wolfram que eu postei, que ilustra e explica com a matemática melhor do que eu posso.
Tim Holt
Foi mal. Vejo que você faz sqrt (r) ao calcular x e y.
PeteUK
4

Você está no meio do caminho. Além de gerar um ângulo aleatório, apenas gere uma distância aleatória, menor ou igual ao raio, ponderada para obter uma distribuição 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);
}

Agora você está pensando com polar .

Você também pode ponderar a distância dessa maneira para evitar uma raiz quadrada:

var distance = _random.NextDouble() + _random.NextDouble();
distance = (distance <= 1 ? distance : 2 - distance) * _radius;
Jon Purdy
fonte
Ok, então demos exatamente a mesma resposta em segundos de diferença. O que agora? :)
David Gouveia
@DavidGouveia Nós dois recebemos votos por ambos estarem certos. Todo mundo ganha! : D
Jon Purdy
Ambas as respostas foram muito apreciadas (e o link também!). Homem que eu sou um idiota por não ver que me embora, -1 para mim :( Obrigado novamente para vocês dois!
DMills
Isso irá gerar pontos aleatórios, mas eles não serão distribuídos uniformemente pelo disco, certo? Em vez disso, eles serão pesados ​​em direção ao centro. Só checando que não estou perdendo alguma coisa.
PeteUK
1
@PeteUK: Você está certo, a distância deve ser ponderada. Deixe-me atualizar.
Jon Purdy
3

Se o desempenho é um problema, uma solução alternativa é gerar uma posição aleatória em uma caixa com a largura / altura do seu círculo e jogar fora quaisquer pontos que não estejam na área do círculo.

A vantagem desse método é que você não está executando funções cos / sin / sqrt, o que, dependendo da sua plataforma, pode ser uma grande economia de velocidade.

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 Jellyfish
fonte
Vou tentar isso e ver se isso acelera as coisas. Não tenho certeza se tenho um problema de desempenho, mas tentarei mesmo assim, obrigado!
DMills
0

Adotei a abordagem de um dos comentários listados e estendi a funcionalidade para criar um sistema de geração de pontos em forma de anel.

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

É uma abordagem semelhante à mencionada anteriormente, mas forneceu resultados diferentes. A parte interna do círculo será deixada em branco sem pontos.

Branden
fonte