Problema
Eu quero obter todos os pixels que estão dentro de um círculo de um determinado raio sobre um determinado ponto, onde os pontos podem ter apenas coordenadas inteiras , como pixels em uma tela.
Então, eu quero obter todos os pontos na área amarela dada (x, y)
e r
.
Abordagens
A maneira mais eficiente em que consigo pensar é percorrer um quadrado (x, y)
e verificar a distância euclidiana de cada ponto:
for (int px = x - r; px <= x + r; px++) {
for (int py = y - r; py <= y + r; py++) {
int dx = x - px, dy = y - py;
if (dx * dx + dy * dy <= r * r) {
// Point is part of the circle.
}
}
}
No entanto, isso significa que esse algoritmo verificará (r * 2)^2 * (4 - pi) / 4
pixels que não fazem parte do círculo. dx * dx + dy * dy <= r * r
, que parece bastante caro, é chamado de forma redundante quase 1 / 4
na maioria das vezes.
A integração de algo como o que foi proposto aqui pode melhorar o desempenho:
for (int px = x - r; px <= x + r; px++) {
for (int py = y - r; py <= y + r; py++) {
int dx = abs(x - px), dy = abs(y - py);
if (dx + dy <= r || (!(dx > r || dy > r) && (dx * dx + dy * dy <= r * r))) {
// Point is part of the circle.
}
}
}
No entanto, como o próprio autor apontou, isso provavelmente não será mais rápido quando a maioria dos pontos estiver dentro do círculo (especialmente por causa de abs
), que pi / 4
são neste caso.
Não consegui encontrar nenhum recurso sobre esta questão. Estou procurando especificamente uma solução em C ++ e não algo em SQL .
Respostas:
Tudo bem, aqui estão os benchmarks que prometi.
Configuração
Eu usei o google benchmark e a tarefa era inserir todos os pontos dentro do perímetro do círculo em um
std::vector<point>
. Eu comparo para um conjunto de raios e um centro constante:Os resultados de cada algoritmo são testados quanto à correção (em comparação com a saída do algoritmo de OPs).
Até agora, os seguintes algoritmos são comparados:
enclosing_square
.containing_square
.edge_walking
.binary_search
.Resultados
Código
O código de teste completo está abaixo, você pode copiá-lo e colá-lo e testá-lo você mesmo.
fill_circle.cpp
contém a implementação dos diferentes algoritmos.main.cpp
fill_circle.hpp
fill_circle.cpp
fonte
Para evitar a geração repetida de pixels nos eixos, vale a pena iniciar loops a partir de 1 e desenhar linhas centrais (ix == 0 ou linha == 0) em um loop separado.
Observe que também existe o algoritmo inteiro Bresenham para gerar pontos de circunferência.
fonte
Tudo bem, primeiro calculamos o quadrado interno do círculo. A fórmula para isso é simples:
Agora, todos os pontos entre
(-h, -h)
e(+h, +h)
estão dentro do círculo. Aqui está uma imagem do que quero dizer:A parte azul restante é um pouco complicada, mas também não é muito complicada. Começamos no topo do círculo azul
(x = 0, y = -radius)
. Em seguida, caminhamos à direita (x++
) até deixarmos o perímetro do círculo (atéx²+y² < r²
que não segure mais). Tudo entre (0, y) e (x, y) está dentro do círculo. Devido à simetria, podemos estender 8 vezes maisagora descemos 1 linha (
y--
) e repetimos as etapas acima (mantendo o valor mais recente dex
). Adicione o centro do círculo a cada um dos pontos e pronto.Aqui está uma visualização. Existem alguns artefatos devido ao aumento de escala. O ponto vermelho mostra o que estamos testando a cada iteração:
Aqui está o código completo (usando o opencv para desenhar o material):
fonte
Essa é uma otimização que reduz 1/4 da dimensão da pesquisa:
ou melhor, melhorando o desempenho da iteração do segundo círculo,
for
evitando aif
condiçãoBem, eu acho que outra opção é uma pesquisa binária para o limite superior:
A idéia da pesquisa binária é encontrar o limite superior de maneira ideal, evitando a
if
condição e os cálculos dentro dofor
ciclo. Para isso, é verificado qual é o maior número inteiro que faz a distância entre o ponto atual e o raio dentro do círculo.PD: Desculpe meu inglês.
fonte
Código
Com base na ideia do @ScottHunter , criei o seguinte algoritmo:
Algoritmo explicado
Este algoritmo realiza um número minucioso de verificações. Especificamente, ele verifica apenas em cada linha até que o primeiro ponto que faz parte do círculo seja atingido. Além disso, ele pulará os pontos à esquerda do ponto identificado anteriormente na próxima linha. Além disso, usando simetria, apenas metade das linhas (
n/2 + 1/2
como começamos em 0) são verificadas.Esta é uma visualização do algoritmo que criei. O contorno vermelho indica o quadrado que teria sido verificado anteriormente e os pixels pretos indicam o círculo real (com o pixel vermelho no meio sendo o centro). O algoritmo verifica os pontos (marcados em azul) e passa pelos pontos válidos (marcados em verde).
Como você pode ver, o número de pixels azuis no final é minuto, ou seja, existem apenas alguns pontos em loop que não fazem parte do círculo. Além disso, observe que apenas o primeiro pixel verde de cada vez precisa de uma verificação, os outros são repetidos, e é por isso que eles aparecem instantaneamente.
Notas
Os eixos podiam facilmente ser revertidos, obviamente.
Isso pode ser otimizado aproveitando ainda mais a simetria, ou seja, que as linhas serão iguais às colunas (passar por todas as linhas é o mesmo que passar por todas as colunas, da esquerda para a direita, de cima para baixo, vice-versa, vise vera) e descer apenas um quarto das linhas do centro seria suficiente para determinar exatamente quais pontos farão parte do círculo. No entanto, sinto que o pequeno aumento no desempenho que isso dará não vale o código adicional.
Se alguém quiser codificá-lo, proponha uma edição para esta resposta.
Código com comentários
fonte
O problema tem uma complexidade fixa de O (n ^ 2), em que n é o raio do círculo. A mesma complexidade que um quadrado ou qualquer forma 2D regular
Não há como ignorar o fato de que você não pode reduzir o número de pixels em um círculo, mesmo que você aproveite a simetria, a complexidade permanece a mesma.
Portanto, ignorando a complexidade e procurando otimização.
Na sua pergunta, você declara que
abs
é um pouco caro demais por pixel (ou quarto pixel)Uma vez por linha é melhor que uma vez por pixel
Você pode reduzi-lo para 1 raiz quadrada por linha. Para um raio de círculo 256 que 128 raízes quadradas
Para aproveitar melhor, você pode criar uma tabela de pesquisa para os cálculos da raiz do sqrt.
Todo inteiro
Como alternativa, você pode usar a variação na linha bresenham que substitui a raiz quadrada por toda a matemática inteira. No entanto, é uma bagunça e não seria de nenhum benefício, a menos que o dispositivo não possua uma unidade de ponto flutuante.
fonte
Você pode desenhar um quadrado que se encaixe dentro do círculo e é bastante simples descobrir se o ponto se encaixa.
Isso resolverá a maioria dos pontos (2 * r ^ 2) no tempo O (1), em vez de pesquisar todos os pontos (4 * r ^ 2).
Editar: Nos demais pontos, você não precisa repetir todos os outros pixels. Você precisa fazer um loop para os 4 retângulos dimensionados com as dimensões [(2r / sqrt (2)), r- (r / sqrt (2))] nos 4 lados (norte, leste, sul, oeste) do quadrado que é dentro. Isso significa que você nunca precisa procurar os quadrados nos cantos. Como é completamente simétrico, podemos pegar os valores absolutos dos pontos de entrada e pesquisar se o ponto está dentro de meio quadrado no lado positivo do plano de coordenadas. O que significa que fazemos loop apenas uma vez em vez de 4.
A complexidade geral do código é O ((rr / sqrt (2)) * (r / sqrt (2))). Que é apenas um loop para metade de um único retângulo (simetria de 8 direções) que fica entre o quadrado interno e a borda externa do cirle.
fonte
O(n^2)
não é a queO((r-r/sqrt(2))* (r/sqrt(2)))
você fornece uma notação quadrática e grande de O não deve incluir os coeficientes e todos, exceto a potência mais alta, devem ser ignorados. Esse problema não pode ser simplificado abaixoO(n^2)