Problema
Quiero obtener todos los píxeles que están dentro de un círculo de un radio dado sobre un punto dado, donde los puntos solo pueden tener coordenadas enteras , es decir, píxeles en un lienzo.
Así que quiero obtener todos los puntos en el área amarilla dada (x, y)
y r
.
Enfoques
La forma más eficiente que se me ocurre es recorrer un cuadrado (x, y)
y verificar la distancia euclidiana para cada punto:
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.
}
}
}
Sin embargo, esto significa que este algoritmo verificará los (r * 2)^2 * (4 - pi) / 4
píxeles que no son parte del círculo. dx * dx + dy * dy <= r * r
, que parece bastante caro, se llama redundantemente casi todo 1 / 4
el tiempo.
Integrar algo como lo que se propuso aquí podría mejorar el rendimiento:
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.
}
}
}
Sin embargo, como el propio autor señaló, es probable que esto no sea más rápido cuando la mayoría de los puntos estarán dentro del círculo (especialmente debido a abs
), que pi / 4
son en este caso.
No pude encontrar ningún recurso sobre esta pregunta. Estoy buscando específicamente una solución en C ++ y no algo en SQL .
Respuestas:
Bien, aquí están los puntos de referencia que prometí.
Preparar
Utilicé google benchmark y la tarea consistía en insertar todos los puntos dentro del perímetro del círculo en a
std::vector<point>
. Comparo un conjunto de radios y un centro constante:La exactitud de los resultados de cada algoritmo se prueba (en comparación con la salida del algoritmo OP).
Hasta ahora, los siguientes algoritmos están comparados:
enclosing_square
.containing_square
.edge_walking
.binary_search
.Resultados
Código
El código de prueba completo está abajo, puede copiarlo y pegarlo y probarlo usted mismo.
fill_circle.cpp
contiene la implementación de los diferentes algoritmos.main.cpp
fill_circle.hpp
fill_circle.cpp
fuente
Para evitar la generación repetida de píxeles en los ejes, vale la pena iniciar bucles desde 1 y dibujar líneas centrales (ix == 0 o line == 0) en un bucle separado.
Tenga en cuenta que también existe un algoritmo de Bresenham entero puro para generar puntos de circunferencia.
fuente
Muy bien, primero que nada calculamos el cuadrado interno del círculo. La fórmula para ello es sencilla:
Ahora, cada punto entre
(-h, -h)
y se(+h, +h)
encuentra dentro del círculo. Aquí hay una imagen de lo que quiero decir:La parte azul restante es un poco complicada, pero tampoco demasiado complicada. Comenzamos en la parte superior del círculo azul
(x = 0, y = -radius)
. A continuación, caminamos a la derecha (x++
) hasta que dejamos el perímetro del círculo (hasta quex²+y² < r²
ya no se mantenga). Todo entre (0, y) y (x, y) está dentro del círculo. Debido a la simetría podemos extender este 8 vecesahora bajamos 1 línea (
y--
) y repetimos los pasos anteriores (manteniendo el valor más reciente dex
). Agregue el centro del círculo a cada uno de los puntos y listo.Aquí hay una visualización. Hay algunos artefactos debido a la ampliación. El punto rojo muestra lo que estamos probando en cada iteración:
Aquí está el código completo (usando opencv para dibujar las cosas):
fuente
Esta es una optimización que reduce 1/4 de la dimensión de búsqueda:
o mejor, mejorando el rendimiento la iteración del segundo círculo
for
evitando elif
condicionalBueno, creo que otra opción es una búsqueda binaria para el límite superior:
La idea de la búsqueda binaria es encontrar el límite superior de manera óptima, evitando la
if
condición y los cálculos dentro delfor
ciclo. Para esto, se verifica cuál es el número entero más grande que hace la distancia entre el punto actual y el radio dentro del círculo.PD: Perdón, mi inglés.
fuente
Código
Basado en la idea de @ScottHunter , se me ocurrió el siguiente algoritmo:
Algoritmo explicado
Este algoritmo realiza una cantidad minuto de comprobaciones. Específicamente, solo verifica en cada fila hasta que se alcanza el primer punto que es parte del círculo. Además, saltará puntos a la izquierda del punto previamente identificado en la siguiente fila. Además, al usar la simetría, solo
n/2 + 1/2
se verifica la mitad de las filas ( como comenzamos en 0).Esta es una visualización del algoritmo que creé. El contorno rojo indica el cuadrado que previamente se habría verificado y los píxeles negros indican el círculo real (con el píxel rojo en el centro como centro). El algoritmo verifica los puntos (marcados en azul) y recorre los puntos válidos (marcados en verde).
Como puede ver, el número de píxeles azules al final es minuto, es decir, solo hay unos pocos puntos en bucle que no forman parte del círculo. Además, tenga en cuenta que solo el primer píxel verde cada vez necesita una verificación, los otros solo se repiten, por lo que aparecen instantáneamente.
Notas
Los ejes podrían invertirse fácilmente, obviamente.
Esto podría optimizarse aprovechando aún más la simetría, es decir, que las filas serán las mismas que las columnas (pasar por todas las filas es lo mismo que pasar por todas las columnas, de izquierda a derecha, de arriba a abajo, viceversa, tornillo de banco) y bajar solo una cuarta parte de las filas desde el centro sería suficiente para determinar exactamente qué puntos van a formar parte del círculo. Sin embargo, creo que el pequeño aumento de rendimiento que esto va a dar no vale el código adicional.
Si alguien quiere codificarlo, proponga una edición para esta respuesta.
Código con comentarios
fuente
El problema tiene una complejidad fija de O (n ^ 2) donde n es el radio del círculo. La misma complejidad que un cuadrado o cualquier forma 2D normal
No se puede pasar por alto el hecho de que no puede reducir la cantidad de píxeles en un círculo, incluso si aprovecha la simetría, la complejidad sigue siendo la misma.
Ignorando la complejidad y buscando la optimización.
En su pregunta, afirma que
abs
es un poco demasiado caro por píxel (o cuarto píxel)Una vez por fila es mejor que una vez por píxel
Puede reducirlo a 1 raíz cuadrada por fila. Para un radio de círculo 256 que 128 raíces cuadradas
Para sacarle más provecho, puede crear una tabla de búsqueda para los cálculos de raíz sqrt.
Todo entero
Alternativamente, puede usar la variación en la línea de bresenham que reemplaza la raíz cuadrada con todas las matemáticas enteras. Sin embargo, es un desastre y no sería beneficioso a menos que el dispositivo no tenga una unidad de coma flotante.
fuente
Puedes dibujar un cuadrado que encaje dentro del círculo y es bastante sencillo encontrar si el punto cae.
Esto resolverá la mayoría de los puntos (2 * r ^ 2) en el tiempo O (1), en lugar de buscar todos los puntos (4 * r ^ 2).
Editar: para el resto de los puntos, no necesita hacer un bucle con todos los demás píxeles. Necesita hacer un bucle para los 4 rectángulos dimensionados con dimensiones [(2r / sqrt (2)), r- (r / sqrt (2))] en los 4 lados (norte, este, sur, oeste) del cuadrado que es dentro. Significa que nunca tienes que buscar los cuadrados en las esquinas. Dado que es completamente simétrico, podemos tomar los valores absolutos de los puntos de entrada y buscar si el punto está dentro de medio cuadrado en el lado positivo del plano de coordenadas. Lo que significa que solo hacemos un bucle una vez en lugar de 4.
La complejidad general del código es O ((rr / sqrt (2)) * (r / sqrt (2))). Que solo se repite en la mitad de un solo rectángulo (simetría de 8 vías) que se encuentra entre el cuadrado interior y el borde exterior del círculo.
fuente
O(n^2)
no esO((r-r/sqrt(2))* (r/sqrt(2)))
que se dé una notación cuadrática y grande de O no debe incluir los coeficientes y se debe ignorar todo excepto la potencia más alta. Este problema no se puede simplificar a continuaciónO(n^2)