7.7. Normalisation

ml.Model.predict() prend une liste d’entrées parce que certains réseaux ont plus d’un tenseur d’entrée, mais la liste n’a aucun moyen de transporter des arguments par entrée en ligne – il n’y a pas d’emplacement de kwarg pour « recadrer cette entrée à (x, y, w, h) mais laisser les autres entrées intactes ». ml.preprocessing.Normalization est l’enveloppe qui comble ce manque. Une instance de Normalization contient les paramètres d’une entrée ; le script passe l’entrée enveloppée dans la liste de predict chaque fois qu’il a besoin d’autre chose que les valeurs par défaut.

La raison la plus courante d’y recourir est de recadrer une région spécifique de la trame capturée vers le réseau au lieu de l’image entière.

7.7.1. Paramètres

Normalization(scale=(0.0, 1.0),
              mean=(0.0, 0.0, 0.0),
              stdev=(1.0, 1.0, 1.0),
              roi=None)
  • roi – rectangle (x, y, w, h) dans la trame source à recadrer avant le redimensionnement. Par défaut, la trame entière. La plupart des usages de Normalization ne règlent que ce paramètre.

  • scale – la plage (min, max) que les tenseurs d’entrée en virgule flottante attendent après normalisation. La plage de pixels 0..255 est mappée linéairement dans cette plage. Les valeurs courantes sont (0.0, 1.0) pour les réseaux entraînés avec ReLU et (-1.0, 1.0) pour les réseaux normalisés symétriquement.

  • mean – moyenne par canal (R, G, B) soustraite de l’image après la mise à l’échelle. Correspond aux statistiques de canaux sur lesquelles le réseau a été entraîné – (0.485, 0.456, 0.406) pour les réseaux dérivés d’ImageNet en est l’exemple canonique. Les réseaux en niveaux de gris réduisent la moyenne à une valeur de luminance en utilisant la formule standard 0.299*R + 0.587*G + 0.114*B.

  • stdev – écart-type par canal (R, G, B) par lequel l’image est divisée après la soustraction de la moyenne, correspondant là encore aux statistiques d’entraînement du réseau. Réduit à la luminance de la même manière pour les réseaux en niveaux de gris.

7.7.2. Quand les paramètres importent

scale, mean et stdev sont ignorés lorsque le input_dtype du réseau est int8 ou uint8. Pour les réseaux à entrée entière, les octets de l’image recadrée sont écrits directement dans le tenseur et les propres input_scale et input_zero_point du réseau gèrent la conversion entier-vers-réel. Les trois paramètres n’importent que lorsque le réseau attend une entrée en virgule flottante.

roi est lu dans tous les cas – il contrôle quelle partie de la trame source atteint le réseau quel que soit le dtype d’entrée.

7.7.3. ROI et redimensionnement

La ROI est mise à l’échelle de manière bilinéaire de ses dimensions source vers les dimensions d’entrée du réseau. L’image est centrée dans la destination et la mise à l’échelle remplit la destination – elle ne préserve pas le rapport d’aspect. Une ROI non carrée fournie à une entrée de réseau carrée ressort étirée horizontalement ou verticalement.

Que l’étirement importe dépend du réseau. Les modèles de détection de visages et de points de repère comme la famille MediaPipe (BlazeFace, FaceLandmarks, HandLandmarks, MoveNet) ont été entraînés sur des recadrages carrés et se dégradent rapidement lorsque le rapport d’aspect de l’entrée est faussé ; pour ceux-là, l’application doit leur fournir une ROI carrée – soit en capturant à une taille de trame carrée via window(), soit en recadrant avec le paramètre roi=. Les détecteurs d’objets de la famille YOLO sont généralement entraînés avec une augmentation qui inclut des étirements aléatoires et acceptent des ROI non carrées sans grande perte de précision ; passer directement la trame capturée entière convient généralement.

Lorsque les dimensions d’entrée du réseau correspondent exactement à la ROI, la mise à l’échelle se réduit à une copie, ce qui est le cas le moins coûteux.

7.7.4. Remplacement de la valeur par défaut

predict() enveloppe automatiquement chaque entrée image.Image avec Normalization() – les paramètres par défaut ci-dessus. La plupart des modèles livrés avec la caméra ont été entraînés sur des plages de pixels que les valeurs par défaut couvrent déjà, de sorte que le cas courant est de passer l’image directement:

result = model.predict([img])

Pour utiliser une ROI personnalisée – le remplacement le plus courant – construisez une Normalization avec la ROI définie et liez-y l’image:

from ml.preprocessing import Normalization

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

Pour correspondre aux statistiques de canaux du temps d’entraînement d’un réseau, définissez les paramètres en virgule flottante:

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)])

Appeler l’instance Normalization sur l’image renvoie une nouvelle instance liée à partir de laquelle le moteur remplit le tenseur. L’instance liée est ce que predict accepte à la place de l’image brute, et comme il s’agit d’un objet par entrée, un réseau à entrées multiples peut mélanger des images avec des ROI différentes dans la même liste de predict.

Pour les réseaux qui attendent des entrées que l’application a déjà produites sous forme de tenseur – un tampon issu d’un périphérique, un ndarray calculé par un autre pipeline, des données numériques non issues d’une image – sautez entièrement Normalization et passez le ndarray ou un appelable qui le produit. predict() les transmet au moteur sans les envelopper.