7.7. Normalização#

ml.Model.predict() aceita uma lista de entradas porque algumas redes têm mais de um tensor de entrada, mas a lista não tem forma de transportar argumentos por entrada em linha – não existe nenhum slot kwarg para «recortar esta entrada a (x, y, w, h) mas deixar as outras entradas em paz». ml.preprocessing.Normalization é o wrapper que preenche essa lacuna. Uma instância de Normalization contém os parâmetros para uma entrada; o script passa a entrada envolvida na lista do predict sempre que necessita de algo diferente dos valores predefinidos.

A razão mais comum para o utilizar é recortar uma região específica do fotograma capturado para a rede em vez da imagem inteira.

7.7.1. Parâmetros#

Normalization(scale=(0.0, 1.0),
              mean=(0.0, 0.0, 0.0),
              stdev=(1.0, 1.0, 1.0),
              roi=None)
  • roi – retângulo (x, y, w, h) no fotograma de origem a recortar antes do redimensionamento. Por omissão, corresponde ao fotograma inteiro. A maioria das utilizações de Normalization define apenas este parâmetro.

  • scale – o intervalo (min, max) que os tensores de entrada em ponto flutuante esperam após a normalização. O intervalo de pixel 0..255 é mapeado linearmente para este intervalo. Valores comuns são (0.0, 1.0) para redes treinadas com ReLU e (-1.0, 1.0) para redes normalizadas simetricamente.

  • mean – média (R, G, B) por canal subtraída à imagem após o escalonamento. Corresponde às estatísticas de canal utilizadas no treino da rede – (0.485, 0.456, 0.406) para redes derivadas do ImageNet é o exemplo canónico. As redes em escala de cinzentos reduzem a média para um valor de luminância usando o padrão 0.299*R + 0.587*G + 0.114*B.

  • stdev – desvio padrão (R, G, B) por canal pelo qual a imagem é dividida após a subtração da média, correspondendo igualmente às estatísticas de treino da rede. Reduzido para luminância da mesma forma para redes em escala de cinzentos.

7.7.2. Quando os parâmetros são relevantes#

scale, mean e stdev são ignorados quando o input_dtype da rede é int8 ou uint8. Para redes com entrada inteira, os bytes da imagem recortada são escritos diretamente no tensor e o input_scale e o input_zero_point da rede tratam da conversão de inteiro para real. Os três parâmetros só são relevantes quando a rede espera entrada em ponto flutuante.

roi é lido em todos os casos – controla qual a parte do fotograma de origem que chega à rede, independentemente do dtype de entrada.

7.7.3. ROI e redimensionamento#

O ROI é escalado bilinearmente das suas dimensões de origem para as dimensões de entrada da rede. A imagem é centrada no destino e o escalonamento preenche o destino – não preserva a proporção da imagem. Um ROI não quadrado alimentado a uma entrada de rede quadrada fica esticado horizontal ou verticalmente.

Se o esticamento é relevante depende da rede. Os modelos de deteção facial e de pontos de referência como a família MediaPipe (BlazeFace, FaceLandmarks, HandLandmarks, MoveNet) foram treinados com recortes quadrados e degradam rapidamente quando a proporção da entrada está incorreta; para estes, a aplicação precisa de lhes fornecer um ROI quadrado – seja capturando numa resolução quadrada através de window() ou recortando com o parâmetro roi=. Os detetores de objetos da família YOLO são tipicamente treinados com aumentação que inclui esticamentos aleatórios e aceitam ROIs não quadrados sem grande perda de precisão; passar o fotograma capturado inteiro diretamente costuma funcionar bem.

Quando as dimensões de entrada da rede correspondem exatamente ao ROI, o escalonamento colapsa para uma cópia, que é o caso mais económico.

7.7.4. Substituir o valor predefinido#

predict() envolve automaticamente cada entrada image.Image com Normalization() – os parâmetros predefinidos acima. A maioria dos modelos que acompanham a câmara foram treinados contra intervalos de pixel que os valores predefinidos já cobrem, pelo que o caso comum é passar a imagem diretamente:

result = model.predict([img])

Para utilizar um ROI personalizado – a substituição mais comum – construa uma Normalization com o ROI definido e associe a imagem a ela:

from ml.preprocessing import Normalization

norm = Normalization(roi=(80, 60, 160, 120))
result = model.predict([norm(img)])

Para corresponder às estatísticas de canal em tempo de treino de uma rede, defina os parâmetros em ponto flutuante:

norm = Normalization(scale=(0.0, 1.0),
                     mean=(0.485, 0.456, 0.406),
                     stdev=(0.229, 0.224, 0.225))

result = model.predict([norm(img)])

Chamar a instância Normalization sobre a imagem devolve uma nova instância associada a partir da qual o motor preenche o tensor. A instância associada é o que o predict aceita em vez da imagem bruta e, como é um objeto por entrada, uma rede com múltiplas entradas pode misturar imagens com ROIs diferentes na mesma lista do predict.

Para redes que esperam entradas que a aplicação já produziu em forma de tensor – um buffer de um periférico, um ndarray calculado por outra pipeline, dados numéricos não-imagem – ignore Normalization inteiramente e passe o ndarray ou um callable que o produza. predict() passa esses diretamente para o motor sem os envolver.