5.27. Búsqueda de círculos y rectángulos

Las líneas y los segmentos abarcan los bordes rectos del fotograma capturado, pero muchas características del mundo real que la cámara busca no son rectas. Una moneda sobre un escritorio es un círculo. Una etiqueta impresa, una nota adhesiva o la parte superior de una caja vista en ángulo es un cuadrilátero. El módulo image expone un detector dedicado para cada caso: una búsqueda de tipo Hough para círculos y una búsqueda derivada de AprilTag para formas de cuatro lados.

Ambos métodos siguen la misma plantilla que los detectores de líneas: threshold controla cuántos votos necesita una detección, roi reduce la búsqueda y los objetos devueltos llevan tanto una posición como una magnitud de confianza; pero los espacios de parámetros y los valores predeterminados adecuados difieren lo suficiente como para merecer una cobertura explícita.

5.27.1. Círculos de Hough

find_circles() ejecuta la variante circular de la transformada de Hough. Cada píxel de borde del pre-paso de Sobel vota por cada círculo que podría pasar a través de él; se devuelven los círculos que reúnen suficientes votos.

circles = img.find_circles(threshold=3500,
                            x_margin=10, y_margin=10, r_margin=10,
                            r_min=10, r_max=80, r_step=2)

for c in circles:
    img.draw_circle((c.x, c.y, c.r), color=(255, 0, 0))

threshold es la suma mínima de las magnitudes de borde de Sobel a lo largo del círculo candidato. Los círculos más grandes recorren más píxeles y, por tanto, necesitan umbrales más altos para pasar; un valor que encuentra una moneda de 20 píxeles de radio también se disparará con el ruido alrededor de un borde de 100 píxeles, mientras que un valor ajustado para la moneda grande no detectará la pequeña. Cuando se conoce el rango de radios objetivo, el umbral adecuado escala con la circunferencia: aproximadamente threshold = 50 * 2 * pi * r ofrece un punto de partida razonable, y el valor correcto se obtiene tras un breve paso de ajuste.

r_min, r_max y r_step establecen la búsqueda de radio. Sin límites, el detector buscaría cada radio desde unos pocos píxeles hasta la mitad del ancho de la imagen, lo cual es lento y propenso a falsos positivos. Establecer r_min y r_max para acotar el tamaño objetivo esperado con un margen generoso (p. ej. r_min=15, r_max=25 para una moneda que se sabe que ronda los 20 píxeles) reduce sustancialmente el trabajo y mejora la relación señal-ruido de los votos. r_step controla la granularidad de la búsqueda; pasos más grandes se ejecutan más rápido y pueden pasar por alto un círculo cuyo radio real cae entre dos valores muestreados. El valor predeterminado r_step=2 es un compromiso razonable.

x_margin, y_margin y r_margin controlan la fusión de detecciones cercanas, igual que theta_margin y rho_margin lo hacen para la detección de líneas. Un único círculo físico en la imagen vota por un grupo de círculos candidatos cuyos centros y radios coinciden con una diferencia de unos pocos píxeles; los márgenes colapsan cada grupo a su pico antes de construir la lista de resultados. Márgenes más grandes devuelven menos detecciones pero más fiables; márgenes más pequeños devuelven más detecciones con posibles casi duplicados.

x_stride y y_stride avanzan el escaneo de votación, del mismo modo que en los otros detectores. Los valores predeterminados de 2 y 1 son adecuados para la mayoría de las imágenes; subir ambos a 4 es el compromiso de velocidad estándar para una imagen que se sabe que contiene círculos grandes.

Cada Circle devuelto lleva x, y, r (el centro y el radio) y magnitude (el total de votos, útil como puntuación de confianza para ordenar o filtrar). Dibujar la detección de vuelta en el fotograma es una sola llamada: draw_circle() toma la tupla de 3 elementos (x, y, r), disponible directamente como (c.x, c.y, c.r) desde el resultado.

5.27.2. Rectángulos

find_rects() toma prestado el detector de cuadriláteros de la canalización de AprilTag: la misma rutina que localiza el cuadrado negro alrededor de una etiqueta se expone por sí sola como un buscador de rectángulos de uso general.

rects = img.find_rects(threshold=12000)

for r in rects:
    img.draw_rectangle(r.rect, color=(0, 255, 0))
    for corner in r.corners:
        img.draw_circle((corner[0], corner[1], 4),
                        color=(0, 255, 0))

threshold es la suma mínima de las magnitudes de borde alrededor del perímetro del rectángulo. Un rectángulo negro sobre blanco impreso en un fotograma bien iluminado supera fácilmente 10000; un rectángulo tenue sobre un fondo con textura puede necesitar bajar a 2000, sacrificando falsos positivos por sensibilidad. Al igual que con el detector de círculos, el valor adecuado se obtiene tras un rápido paso de ajuste con los objetivos previstos a la vista.

El detector es proyectivo: encuentra cuadriláteros cuyos lados son rectos pero no necesariamente paralelos o alineados con los ejes. Una etiqueta vista en ángulo parece un trapecio en la imagen, y el detector de rectángulos la encuentra correctamente; un rectángulo alineado con los ejes es simplemente el caso degenerado en el que las cuatro esquinas resultan formar una caja en ángulo recto. roi restringe la búsqueda; el resto de los argumentos por palabra clave toman sus valores predeterminados de la canalización de AprilTag y rara vez necesitan ajuste.

Cada Rect devuelto lleva el cuadro delimitador alineado con los ejes (x, y, w, h, además de la tupla de 4 elementos rect que draw_rectangle() espera) y las cuatro esquinas detectadas como corners. El cuadro delimitador es lo que la aplicación utiliza para la posición y el tamaño aproximados; las esquinas describen el cuadrilátero proyectivo en sí. Cuando la cámara observa un objetivo plano desde un ángulo y la aplicación necesita deshacer el efecto trapezoidal (leer texto en una etiqueta, muestrear color de una zona plana), las esquinas se pasan directamente a rotation_corr() con la palabra clave corners= (véase corrección de lente y perspectiva), y la salida es el rectángulo rectificado listo para el análisis que venga a continuación.

Advertencia

Dado que el detector está ajustado para lo que necesita la canalización de AprilTag (cuadriláteros con bordes fuertes y de alto contraste, como el contorno de una etiqueta negra sobre papel blanco), no es un paso que encuentre todos los rectángulos. Los rectángulos con poco contraste, bordes con textura o entornos abarrotados pueden quedar sin detectar por completo. Lo bien que funcione depende de la situación: pruébelo con los objetivos reales pronto, antes de construir una canalización a su alrededor.

5.27.3. Cuando el detector falla

Los círculos en particular se benefician de un pre-filtro sobre la entrada. Un fotograma ruidoso produce muchos píxeles de borde dispersos que votan, y el espacio de Hough resultante tiene picos amplios y difusos que el fusionador tiene dificultad para separar. Un paso de gaussian() o mean() antes de find_circles() suaviza el ruido y deja los bordes reales intactos; el detector devuelve picos más limpios en menos tiempo.

Para los rectángulos, el modo de fallo común es el opuesto: el bajo contraste entre el rectángulo y su fondo significa que la suma de magnitudes de borde nunca supera threshold. Un paso de histeq() para redistribuir el rango de brillo a lo largo de toda la extensión de 0 a 255 restaura el contraste que el detector necesita. (El contraste debe existir en algún punto de la imagen; la ecualización del histograma solo puede amplificar lo que ya está presente).