7.7. Normalización¶
ml.Model.predict() recibe una lista de entradas porque algunas redes tienen más de un tensor de entrada, pero la lista no tiene forma de transportar argumentos por entrada en línea – no hay un espacio de kwarg para «recorta esta entrada a (x, y, w, h) pero deja las demás entradas intactas». ml.preprocessing.Normalization es el envoltorio que llena ese vacío. Una instancia de Normalization contiene los parámetros para una entrada; el script pasa la entrada envuelta en la lista de predict siempre que necesite algo distinto de los valores por defecto.
La razón más común para recurrir a ella es recortar una región específica del fotograma capturado hacia la red en lugar de la imagen completa.
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– rectángulo(x, y, w, h)en el fotograma de origen que se recorta antes de redimensionar. Por defecto es el fotograma completo. La mayoría de los usos deNormalizationestablecen solo este parámetro.scale– el rango(min, max)que los tensores de entrada de punto flotante esperan tras la normalización. El rango de píxeles0..255se mapea linealmente a este rango. Los valores comunes son(0.0, 1.0)para redes entrenadas con ReLU y(-1.0, 1.0)para redes normalizadas simétricamente.mean– media por canal(R, G, B)que se resta de la imagen tras el escalado. Coincide con las estadísticas de canal con las que se entrenó la red –(0.485, 0.456, 0.406)para redes derivadas de ImageNet es el ejemplo canónico. Las redes en escala de grises reducen la media a un valor de luma usando el estándar0.299*R + 0.587*G + 0.114*B.stdev– desviación estándar por canal(R, G, B)por la que se divide la imagen tras restar la media, coincidiendo de nuevo con las estadísticas de entrenamiento de la red. Se reduce a luma de la misma manera para las redes en escala de grises.
7.7.2. Cuándo importan los parámetros¶
scale, mean y stdev se ignoran cuando el input_dtype de la red es int8 o uint8. Para redes con entrada entera, los bytes de la imagen recortada se escriben directamente en el tensor y el propio input_scale y input_zero_point de la red gestionan la conversión de entero a real. Los tres parámetros importan solo cuando la red espera entrada de punto flotante.
roi se lee en todos los casos – controla qué parte del fotograma de origen llega a la red independientemente del dtype de entrada.
7.7.3. ROI y redimensionado¶
La ROI se escala bilinealmente desde sus dimensiones de origen a las dimensiones de entrada de la red. La imagen se centra en el destino y el escalado rellena el destino – no conserva la relación de aspecto. Una ROI no cuadrada alimentada a una entrada de red cuadrada sale estirada horizontal o verticalmente.
Que el estiramiento importe o no depende de la red. Los modelos de detección de rostros y puntos de referencia como la familia MediaPipe (BlazeFace, FaceLandmarks, HandLandmarks, MoveNet) se entrenaron con recortes cuadrados y se degradan rápidamente cuando la relación de aspecto de la entrada es incorrecta; para esos, la aplicación necesita darles una ROI cuadrada – ya sea capturando con un framesize cuadrado mediante window() o recortando con el parámetro roi=. Los detectores de objetos de la familia YOLO suelen entrenarse con aumentos que incluyen estiramientos aleatorios y aceptan ROI no cuadradas sin mucha pérdida de precisión; pasar el fotograma capturado completo directamente suele estar bien.
Cuando las dimensiones de entrada de la red coinciden exactamente con la ROI, el escalado se reduce a una copia, que es el caso más económico.
7.7.4. Reemplazar el valor por defecto¶
predict() envuelve automáticamente cada entrada image.Image con Normalization() – los parámetros por defecto anteriores. La mayoría de los modelos que vienen con la cámara se entrenaron con rangos de píxeles que los valores por defecto ya cubren, así que el caso común es pasar la imagen directamente:
result = model.predict([img])
Para usar una ROI personalizada – el reemplazo más común – construye una Normalization con la ROI establecida y vincula la imagen a ella:
from ml.preprocessing import Normalization
norm = Normalization(roi=(80, 60, 160, 120))
result = model.predict([norm(img)])
Para coincidir con las estadísticas de canal de entrenamiento de una red, establece los parámetros de punto flotante:
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)])
Llamar a la instancia de Normalization sobre la imagen devuelve una nueva instancia vinculada desde la que el motor rellena el tensor. La instancia vinculada es lo que predict acepta en lugar de la imagen en bruto, y como es un objeto por entrada, una red de varias entradas puede mezclar imágenes con distintas ROI en la misma lista de predict.
Para redes que esperan entradas que la aplicación ya ha producido en forma de tensor – un búfer de un periférico, un ndarray calculado por otra canalización, datos numéricos no provenientes de imágenes – omite por completo Normalization y pasa el ndarray o un invocable que lo produzca. predict() los pasa al motor sin envolverlos.