5.27. Encontrando círculos e retângulos¶
Linhas e segmentos cobrem as bordas retas no quadro capturado, mas muitas das características do mundo real que a câmera procura não são retas. Uma moeda sobre uma mesa é um círculo. Um rótulo impresso, um post-it, o topo de uma caixa vista fora do eixo é um quadrilátero. O módulo image expõe um detector dedicado para cada caso: uma busca no estilo Hough para círculos e uma busca derivada do AprilTag para formas de quatro lados.
Ambos os métodos seguem o mesmo modelo dos detectores de linha – threshold controla quantos votos uma detecção precisa, roi restringe a busca e os objetos retornados carregam tanto uma posição quanto uma magnitude de confiança –, mas os espaços de parâmetros e os valores padrão corretos diferem o suficiente para merecer uma cobertura explícita.
5.27.1. Círculos de Hough¶
find_circles() executa a variante circular da transformada de Hough. Cada pixel de borda da pré-passagem Sobel vota em todo círculo que poderia passar por ele; os círculos que acumulam votos suficientes são retornados.
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 é a soma mínima das magnitudes de borda Sobel ao longo do círculo candidato. Círculos maiores percorrem mais pixels e por isso precisam de limiares mais altos para passar; um valor que encontra uma moeda de raio de 20 pixels também dispara com ruído ao redor de uma borda de 100 pixels, enquanto um valor ajustado para a moeda grande deixará escapar a pequena. Quando a faixa de raio alvo é conhecida, o limiar correto escala com a circunferência – aproximadamente threshold = 50 * 2 * pi * r fornece um ponto de partida razoável, e o valor correto resulta de uma rápida passagem de ajuste.
r_min, r_max e r_step definem a busca de raio. Sem limites, o detector buscaria todo raio desde alguns pixels até a metade da largura da imagem, o que é tanto lento quanto uma receita para falsos positivos. Definir r_min e r_max para enquadrar o tamanho alvo esperado com uma margem generosa (por exemplo, r_min=15, r_max=25 para uma moeda conhecida por ter cerca de 20 pixels) reduz substancialmente o trabalho e melhora a relação sinal-ruído dos votos. r_step controla a granularidade da busca; passos maiores executam mais rápido e podem deixar escapar um círculo cujo raio verdadeiro caia entre dois valores amostrados. O padrão r_step=2 é um compromisso razoável.
x_margin, y_margin e r_margin controlam a fusão de detecções próximas, da mesma forma que theta_margin e rho_margin fazem para a detecção de linhas. Um único círculo físico na imagem vota em um agrupamento de círculos candidatos cujos centros e raios concordam dentro de alguns pixels; as margens colapsam cada agrupamento em seu pico antes de a lista de resultados ser construída. Margens maiores retornam menos detecções, porém mais confiáveis; margens menores retornam mais detecções com possíveis quase-duplicatas.
x_stride e y_stride dão passos na varredura de votação, da mesma forma que fazem nos outros detectores. O padrão de 2 e 1 é adequado para a maioria das imagens; aumentar ambos para 4 é o trade-off de velocidade padrão para uma imagem que sabidamente contém círculos grandes.
Cada Circle retornado carrega x, y, r (o centro e o raio) e magnitude (o total de votos, útil como pontuação de confiança para ordenar ou filtrar). Desenhar a detecção de volta no quadro é uma única chamada – draw_circle() recebe a tupla de 3 elementos (x, y, r), disponível como (c.x, c.y, c.r) diretamente a partir do resultado.
5.27.2. Retângulos¶
find_rects() toma emprestado o detector de quadriláteros do pipeline do AprilTag – a mesma rotina que localiza o quadrado preto ao redor de uma tag é exposta por si só como um localizador de retângulos de propósito geral.
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 é a soma mínima das magnitudes de borda ao redor do perímetro do retângulo. Um retângulo impresso preto sobre branco em um quadro bem iluminado supera facilmente 10000; um retângulo tênue sobre um fundo texturizado pode precisar cair para 2000 – trocando falsos positivos por sensibilidade. Como no detector de círculos, o valor correto resulta de uma rápida passagem de ajuste com os alvos pretendidos em vista.
O detector é projetivo – ele encontra quadriláteros cujos lados são retos, mas não necessariamente paralelos ou alinhados aos eixos. Um rótulo visto fora do eixo parece um trapézio na imagem, e o detector de retângulos o encontra corretamente; um retângulo alinhado aos eixos é apenas o caso degenerado em que os quatro cantos por acaso formam uma caixa de ângulos retos. roi restringe a busca; o restante dos argumentos nomeados assume os valores padrão do pipeline do AprilTag e raramente precisa de ajuste.
Cada Rect retornado carrega a caixa delimitadora alinhada aos eixos – x, y, w, h, além da tupla de 4 elementos rect que draw_rectangle() espera – e os quatro cantos detectados como corners. A caixa delimitadora é o que a aplicação usa para posição e tamanho aproximados; os cantos descrevem o próprio quadrilátero projetivo. Quando a câmera está visualizando um alvo plano a partir de um ângulo e a aplicação precisa desfazer o efeito de distorção trapezoidal – ler texto em um rótulo, amostrar a cor de um trecho plano –, os cantos alimentam diretamente rotation_corr() com o argumento corners= (veja correção de lente e perspectiva), e a saída é o retângulo retificado pronto para qualquer análise que venha a seguir.
Aviso
Como o detector é ajustado para o que o pipeline do AprilTag precisa – quadriláteros com bordas fortes e de alto contraste, como o contorno preto de uma tag em papel branco –, ele não é uma passagem que encontra todos os retângulos. Retângulos com contraste suave, bordas texturizadas ou arredores movimentados podem passar totalmente despercebidos. O quão bem ele funciona depende da situação: teste-o contra os alvos reais cedo, antes de construir um pipeline em torno dele.
5.27.3. Quando o detector falha¶
Os círculos em particular se beneficiam de um pré-filtro na entrada. Um quadro ruidoso produz muitos pixels de borda dispersos que todos votam, e o espaço de Hough resultante tem picos largos e indefinidos que o mesclador tem dificuldade em separar. Uma passagem de gaussian() ou mean() antes de find_circles() suaviza o ruído e deixa as bordas verdadeiras intactas; o detector retorna picos mais limpos em menos tempo.
Para retângulos, o modo de falha comum é o oposto: o baixo contraste entre o retângulo e seu fundo faz com que a soma das magnitudes de borda nunca supere threshold. Uma passagem de histeq() para redistribuir a faixa de brilho por toda a extensão de 0 a 255 restaura o contraste de que o detector precisa. (O contraste precisa existir em algum lugar da imagem; a equalização de histograma só pode amplificar o que já está presente.)