7.2. O que o ML mudou

O módulo image carrega um punhado de métodos de detecção legadosfind_features() para detecção de rostos por cascata de Haar, find_eye() para o localizador de pupila fixo, find_hog() para resumos de direção de gradiente, os caminhos find_keypoints() e find_lbp() para pontos-chave arbitrários. Todos eles ainda funcionam; todos eles foram superados pelo pipeline de aprendizado de máquina.

7.2.1. A divisão clássica: resumos projetados à mão, decisões aprendidas

Um pipeline clássico de visão era algo de duas etapas. A primeira etapa transformava pixels brutos em um conjunto compacto de números escolhidos para resumir o que havia na imagem – não os valores dos pixels em si, mas uma descrição mais curta de quais padrões apareceram e onde. A segunda etapa pegava esse resumo e tomava uma decisão: rosto ou não, este objeto ou aquele, mesmo alvo ou diferente.

A divisão importava porque as duas etapas tinham autores diferentes. A primeira etapa era escrita por um humano. Alguém sentava e decidia que a diferença de brilho entre dois retângulos específicos era um bom resumo de uma região de olho, que a direção de borda dominante em cada célula de uma grade era um bom resumo do contorno de uma pessoa em pé, que o padrão claro-ou-escuro ao redor de cada pixel era um bom resumo da textura local. Cada uma dessas escolhas era um algoritmo escrito à mão – escrito, depurado e publicado. Os métodos legados acima eram todos resumos desse tipo que haviam se tornado ferramentas padrão:

  • find_features() resume uma janela da imagem somando o brilho dentro de vários retângulos e comparando os totais. Os layouts dos retângulos foram escolhidos porque rostos humanos apresentam contrastes confiáveis de claro contra escuro: sobrancelhas contra as bochechas, cavidades oculares contra a testa, nariz contra a pele ao redor.

  • find_hog() resume uma imagem percorrendo uma grade de pequenas células e registrando qual direção de borda domina em cada célula. A grade foi escolhida porque o contorno de uma pessoa em pé produz um padrão reconhecível de direções de borda, independentemente da roupa ou da iluminação.

  • find_lbp() resume a vizinhança de cada pixel codificando quais dos pixels ao seu redor são mais claros e quais são mais escuros. A codificação foi escolhida porque esses padrões de mais-claro-que / mais-escuro-que capturam a textura de uma superfície independentemente da iluminação geral.

  • find_keypoints() encontra pontos de canto na imagem e descreve a área ao redor de cada canto de uma forma que permanece a mesma quando o canto é rotacionado. O esquema de canto e rotação foi escolhido porque os mesmos cantos reaparecem quando uma cena é vista de um ângulo diferente.

Uma vez que um resumo havia sido escrito à mão, uma pequena etapa de aprendizado sobre ele podia combinar os números em uma decisão. O algoritmo de detecção de rostos acoplava uma etapa de aprendizado ao resumo de diferença de retângulos, treinando-o em imagens rotuladas de rosto e não rosto para aprender quais combinações de diferenças sinalizam um rosto. O resumo de direção de borda alimentava uma etapa de aprendizado treinada em imagens rotuladas de pessoa e não pessoa. Os descritores de canto alimentavam uma etapa de correspondência que aprendia quanto peso dar a cada canto. Cada uma dessas segundas etapas é um algoritmo de aprendizado – pequeno pelos padrões modernos, mas um algoritmo de aprendizado.

A divisão da contribuição é o que importava. O humano contribuía com o resumo. A máquina aprendia a combinação. Adicionar um novo alvo significava escrever um novo resumo.

7.2.2. O que as redes neurais mudaram

Uma rede neural apaga a divisão. As primeiras camadas da rede fazem o trabalho de resumo que os algoritmos escritos à mão costumavam fazer – detectar bordas, cantos, barras orientadas, texturas, exatamente as coisas que os métodos legados listados acima eram cada um ajustados para detectar – mas não são escritas à mão. Elas são aprendidas a partir dos mesmos dados de treinamento de onde a etapa de decisão é aprendida, em uma única passagem de treinamento que ajusta ambas as metades da rede de uma só vez. As camadas mais profundas fazem a combinação que a pequena etapa de aprendizado sobre os resumos escritos à mão costumava fazer, também aprendida, na mesma passagem.

A mudança em quem projeta o quê é total:

  • O humano projeta a entrada – quadros capturados de um dado tamanho e formato.

  • O humano projeta a saída – o layout do tensor de resultado (uma pontuação por classe para classificação, uma lista de caixas para detecção, uma grade de pontos-chave para pontos de referência).

  • O humano fornece dados de treinamento rotulados – exemplos suficientes do alvo e exemplos suficientes de não alvos para que o processo de treinamento tenha algo com que aprender.

Tudo entre a entrada e a saída é gerado pelo processo de treinamento. Não há uma etapa separada de escrita de resumos. As camadas iniciais se acomodam como detectoras de bordas e texturas não porque alguém as escreveu dessa forma, mas porque a detecção de bordas e texturas é o que faz as previsões da rede coincidirem com os rótulos. As camadas mais profundas se acomodam como detectoras de formas e objetos pela mesma razão. Ambas as metades são treinadas juntas, o que permite que os resumos que cada camada produz sejam exatamente os resumos de que a camada seguinte precisa – não os genéricos com que um pipeline escrito à mão tinha de se contentar.

7.2.3. Compondo com o módulo image

Os pipelines de redes neurais ainda capturam através das mesmas APIs de sensor, desenham resultados através das mesmas primitivas draw_rectangle() e draw_circle(), e delimitam o trabalho através das mesmas ROIs (x, y, w, h). Um pipeline típico captura um quadro, opcionalmente encontra um alvo grosseiro com um detector clássico como find_blobs() e passa sua caixa delimitadora para a inferência como uma ROI, executa a inferência e anota as detecções retornadas de volta no quadro original. As primitivas clássicas são o substrato; a rede é a nova etapa no meio.