5.25. Deteção de manchas¶
A limiarização transformou o fotograma capturado numa máscara binária: cada pixel ou passa no teste de limiar ou não passa. Isso responde à pergunta que cores relevantes para a aplicação aparecem na cena, mas não onde – a máscara é apenas um conjunto de 1s e 0s. O passo seguinte é a deteção de manchas: percorrer a máscara, encontrar regiões contíguas de pixels que passaram o teste e devolver cada uma como um objeto com uma posição, um tamanho, uma orientação e outras propriedades sobre as quais a aplicação pode actuar.
find_blobs() é o método central para esse passo e é o ponto de entrada mais comum no mundo dos objetos de resultado do módulo de imagem. Rastrear uma bola colorida, seguir uma linha pintada no chão, contar quantos pontos brilhantes um sensor térmico deteta, decidir se um LED azul está ligado ou desligado – a mesma chamada cobre todos estes casos. As entradas mudam (os limiares, a região pesquisada, os filtros aplicados ao resultado), mas o padrão de chamada é o mesmo.
5.25.1. A chamada básica¶
find_blobs recebe uma lista de limiares e devolve uma lista de objetos de resultado de mancha:
thresholds = [(30, 100, 15, 127, 15, 127)] # LAB threshold for red
blobs = img.find_blobs(thresholds)
for b in blobs:
img.draw_rectangle(b.rect, color=(255, 0, 0))
img.draw_cross(b.cx, b.cy, color=(255, 0, 0))
Cada tuplo de limiar tem a mesma forma que os limiares passados a binary() – seis entradas (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) para uma imagem RGB565 (os limites estão em LAB), duas entradas (lo, hi) para uma imagem em escala de cinzentos. Podem ser fornecidos até 32 limiares numa única chamada, o que torna find_blobs() tão flexível: balizas vermelha, verde e azul podem ser rastreadas simultaneamente, cada uma contribuindo com as suas próprias manchas para a lista devolvida, e a propriedade code de cada mancha identifica qual o limiar que correspondeu.
As chamadas a draw_rectangle() e draw_cross() acima anotam o fotograma capturado para a pré-visualização no IDE. O resultado da mancha já contém b.rect (a caixa delimitadora como um tuplo de 4 elementos) e b.cx / b.cy (o centróide em número inteiro), portanto desenhar a deteção de volta no fotograma resume-se a duas chamadas de método.
5.25.2. O que o resultado contém¶
Cada Blob é um tuplo de atributos que agrupa tudo o que o detetor mediu sobre a região. As propriedades dividem-se em quatro grupos.
O grupo de caixa delimitadora e centróide – x, y, w, h, rect, cx, cy, cxf, cyf – descreve a posição da mancha. rect é o tuplo (x, y, w, h) de 4 elementos que os métodos de desenho esperam; cx e cy são o centróide em coordenadas de pixel inteiras; cxf e cyf são o centróide em coordenadas float de sub-pixel, úteis quando uma calibração a montante necessita de posições fracionadas.
Os descritores de forma – pixels, area, density, perimeter, roundness, elongation, compactness, rotation – descrevem a aparência da mancha. pixels é o número de pixels que passaram o teste; area é a área da caixa delimitadora alinhada com os eixos (w * h); density é a razão entre os dois, que se aproxima de 1.0 para um rectângulo sólido e desce para 0.0 para um traço diagonal fino. roundness e compactness avaliam ambos a redondeza da mancha, a partir de diferentes pontos de vista geométricos (roundness a partir dos momentos de segunda ordem, compactness a partir da razão perímetro-área); elongation é 1.0 - roundness por conveniência. rotation é a orientação do eixo principal em radianos, mais precisa em manchas alongadas e com maior ruído em manchas quase redondas (um eixo ambíguo não tem direcção bem definida).
Os metadados de limiar e fusão – code, count – identificam qual o limiar que correspondeu e quantas manchas de origem foram fundidas na devolvida. code é um mapa de bits de 32 bits com um bit definido por limiar correspondente (um único limiar dá code == 1; uma mancha multi-cor fundida pode ter vários bits definidos); count é 1 a menos que merge=True tenha combinado várias deteções numa só.
O grupo de cantos – corners, min_corners – fornece a geometria rotada da mancha. corners é o tuplo de 4 extremos (x, y) extraídos do contorno da mancha, ordenados no sentido horário a partir do canto superior esquerdo; min_corners é o tuplo de 4 cantos para o rectângulo rotado de área mínima que envolve a mancha. O rectângulo de área mínima é o ajuste apertado; o rect alinhado com os eixos é o ajuste largo alinhado com a grelha de pixels. Ambos são úteis consoante uma fase a jusante necessite de uma caixa orientada ou de uma caixa simples.
Uma mancha contém a caixa delimitadora alinhada com os eixos (rect, x, y, w, h), o centróide (cx, cy ou sub-pixel cxf, cyf), o rectângulo rotado de área mínima (min_corners mais rotation), e as linhas dos eixos maior/menor opcionais calculadas pelas funções auxiliares ao nível do módulo descritas abaixo.¶
5.25.3. Filtrar a pesquisa¶
Um fotograma capturado contém tipicamente pixels que correspondem ao limiar por razões diferentes do objecto que a aplicação pretende detetar: reflexos especulares, objectos de fundo distantes, pixels de ruído de imagem que por acaso caem no intervalo LAB. Os argumentos de palavra-chave de find_blobs() são a primeira linha de defesa.
roi restringe a pesquisa a uma região do fotograma, tal como fazem todos os outros métodos do módulo de imagem. Uma aplicação que sabe que o objecto só pode aparecer na metade inferior do campo de visão passa roi=(0, h//2, w, h//2) e ignora tudo acima; o tempo poupado reverte para a taxa de fotogramas.
area_threshold e pixels_threshold filtram ambos manchas demasiado pequenas para serem relevantes. area_threshold descarta manchas cuja caixa delimitadora tem menos pixels de área do que esse valor (útil para filtrar ruído disperso); pixels_threshold descarta manchas com menos pixels aprovados do que esse valor (útil para filtrar manchas grandes mas esparsas, como um padrão de pontilhado limiarizado com um ou dois pixels a corresponder aqui e ali). Os valores predefinidos são ambos 10; aumentá-los para centenas quando o alvo em primeiro plano tem alguns centímetros de tamanho elimina todos os pequenos pontos de ruído.
x_stride e y_stride definem o passo em pixels que o scanner dá enquanto procura uma mancha para começar a traçar. O passo não é a resolução do traçado – o traçado segue sempre o contorno real da mancha com detalhe de pixel único – mas controla a rapidez com que a varredura encontra um pixel inicial. Quando se sabe que as manchas são grandes (um alvo colorido do tamanho de um punho a trinta centímetros da câmara, facilmente uma centena de pixels de largura), x_stride=4, y_stride=4 reduz o tempo de varredura dezasseis vezes sem perda prática de deteção. Quando as manchas são pequenas (uma baliza LED distante, com alguns pixels de largura), os passos têm de permanecer em 1 para evitar saltá-las completamente. invert inverte o teste de limiar: a correspondência passa a ser não-correspondência e a rotina devolve manchas de pixels reprovados.
threshold_cb é uma callback Python invocada em cada mancha após a limiarização mas antes de a lista de resultados final ser construída. A callback recebe a mancha e devolve True para a manter ou False para a descartar. Este é o lugar para aplicar filtros arbitrários ao nível do Python sobre propriedades que os argumentos de palavra-chave não expõem directamente – uma densidade mínima, um intervalo de rotação específico, uma combinação personalizada de bits de código após a fusão. Os argumentos de palavra-chave são filtros em código nativo e executam rapidamente; a callback executa em Python e é mais lenta mas ilimitada no que pode expressar.
5.25.4. Fundir manchas sobrepostas¶
merge=True pós-processa a lista de resultados para combinar manchas cujos rectângulos delimitadores se sobrepõem. O uso natural é detectar um alvo cuja cor a câmara vê como múltiplas regiões limiarizadas devido a reflexos especulares, linhas de sombra ou iluminação desigual sobre o objecto: uma bola vermelha pode ser devolvida como três ou quatro manchas vermelhas pequenas que, tomadas em conjunto, delineiam a bola. Com merge=True, as três manchas tornam-se uma única mancha grande, o rect cobre a união, o code é o OR bit-a-bit dos códigos das manchas fundidas (pelo que uma fusão multi-cor identifica quais as cores que contribuíram), e count relata quantas manchas de origem foram combinadas.
margin aumenta ou diminui os rectângulos delimitadores antes do teste de sobreposição. Com margin=2, manchas cujos rectângulos delimitadores ficam a 2 pixels um do outro ainda se fundem; com margin=-2, apenas se fundem manchas cujos rectângulos delimitadores se sobrepõem em pelo menos 2 pixels. A afinação natural: margem positiva para lidar com manchas que o limiar fragmentou em peças adjacentes; margem negativa para manter objectos distintos muito próximos separados.
merge_cb executa em cada par candidato antes de a fusão ocorrer. A callback recebe as duas manchas e devolve True para permitir a fusão ou False para a impedir. Esta é a ferramenta certa para verificar fusões que a regra geométrica falha – por exemplo, recusar fundir duas manchas cujos ângulos de rotation diferem mais do que um limiar, ou recusar fundir uma mancha pequena numa muito maior se a pequena for apenas ruído.
5.25.5. Histogramas de projecção¶
x_hist_bins_max e y_hist_bins_max associam histogramas de projecção opcionais a cada mancha. Um histograma de projecção é a contagem de pixels aprovados ao longo de um eixo: o histograma do eixo X totaliza os pixels aprovados por coluna dentro da caixa delimitadora da mancha, e o histograma do eixo Y totaliza por linha. Ambos têm valor predefinido de zero – os histogramas não são calculados a menos que seja fornecido um max diferente de zero, pois caso contrário adicionariam trabalho a cada deteção.
Quando são calculados, os histogramas fornecem um sinal 1-D económico sobre o qual uma aplicação pode efectuar análises adicionais: detectar a posição de uma faixa vertical dentro da mancha, encontrar o ponto de divisão de um alvo com duas cores, contar quantos hiatos aparecem ao longo do eixo longo. São preenchidos como as propriedades x_hist_bins e y_hist_bins em cada Blob.
5.25.6. Auxiliares geométricos adicionais¶
Um conjunto de medidas geométricas adicionais existe como funções ao nível do módulo que recebem uma mancha e devolvem a medição pedida:
image.get_solidity()devolve a solidez da mancha – pixels divididos pela área do fecho convexo. Uma região preenchida sólida está próxima de1.0; uma mancha com concavidades (uma ferradura, uma mão com dedos abertos) desce bem abaixo.image.get_convexity()devolve a convexidade – o perímetro do fecho convexo dividido pelo perímetro da mancha. Uma mancha perfeitamente convexa é1.0; manchas irregulares ou com entalhes são inferiores.image.get_major_axis_line()eimage.get_minor_axis_line()devolvem objectosLineao longo dos eixos maior e menor da mancha, derivados do rectângulo rotado de área mínima.image.get_enclosing_circle()devolve umCircleque envolve a mancha – útil quando uma fase a jusante quer um círculo para desenhar ou testar.image.get_enclosed_ellipse()devolve o tuplo de 5 elementos(cx, cy, rx, ry, rotation)para uma elipse inscrita no rectângulo de área mínima da mancha. Os valores são passados directamente adraw_ellipse().
5.25.7. Aprender automaticamente um limiar¶
Um detetor de manchas é tão bom quanto os limiares com que é executado, e o trabalho de encontrar o limiar correcto para uma cor alvo é um problema por si só. Dois padrões comuns reduzem esse trabalho.
O primeiro é a selecção interactiva no IDE: capturar um fotograma, arrastar um rectângulo à volta de um exemplo da cor alvo e deixar o editor de limiares do IDE reportar os limites LAB que vê. Esses limites entram no script como os limiares de find_blobs() e o detetor está pronto.
O segundo é a aprendizagem automática programática: uma rotina de calibração a correr na câmara captura um fotograma, calcula um histograma de uma região conhecida onde o alvo se encontra (get_histogram() com roi=), e lê o intervalo de valores da região a partir do histograma com get_percentile(). O percentil 5 define o limite inferior de cada canal e o percentil 95 o limite superior, ignorando pixels aberrantes em ambas as extremidades. Numa imagem RGB565, uma chamada ao percentil relata os três canais LAB de uma vez, pelo que as duas chamadas produzem os seis números que find_blobs() espera:
h = img.get_histogram(roi=patch)
lo = h.get_percentile(0.05)
hi = h.get_percentile(0.95)
threshold = (lo.l_value, hi.l_value,
lo.a_value, hi.a_value,
lo.b_value, hi.b_value)