5.5. Regiones y máscaras

De forma predeterminada, cada operación del módulo image afecta a todos los píxeles de su imagen de origen. Ese es el comportamiento más sencillo de describir, y el correcto cuando la tarea del algoritmo realmente abarca todo el fotograma: una corrección de color uniforme, un histograma global, una pasada de codificación para su transmisión. Pero en la práctica la mayoría de los algoritmos quieren analizar menos que eso. Un rastreador de manchas (blob) que vigila un marcador de color se interesa por la parte de la escena en la que el marcador puede aparecer, no por la pared que hay detrás. Una pasada de limpieza morfológica solo es segura sobre los píxeles que una etapa anterior marcó como candidatos. Un detector de rostros podría ejecutarse únicamente dentro del cuadro delimitador que un detector más burdo ya acotó. El módulo image admite ese trabajo mediante dos mecanismos que limitan el alcance de una operación a un subconjunto de píxeles: las regiones de interés rectangulares y las máscaras binarias. Se combinan con total libertad, y casi todos los métodos que tocan píxeles aceptan uno u otro, o ambos, como argumento de palabra clave.

5.5.1. Regiones de interés

Una región de interés es un rectángulo de píxeles nombrado mediante la cuádrupla (x, y, w, h) presentada en la página de coordenadas. Alrededor de treinta métodos de la superficie aceptan un argumento de palabra clave roi; cuando está presente, la operación se ejecuta solo sobre los píxeles que hay dentro de ese rectángulo y deja intacto el resto de la imagen. Cuando roi es None o se omite, la operación se ejecuta sobre toda la imagen, igual que si se hubiera pasado roi=(0, 0, width, height).

En el código, la palabra clave se sitúa junto a los demás argumentos que recibe la operación:

# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))

Lo primero que aportan las ROI es el control de falsos positivos. Un rastreador de color que solo mira la mesa nunca se activará por la camisa de alguien que pasa por delante; un detector de bordes que solo se ejecuta dentro del área de trabajo definida nunca informará de los bordes del propio soporte de la cámara. Reducir el área de búsqueda a la parte de la escena que de verdad le importa al algoritmo es la mejora más barata que una canalización puede hacer a su propia fiabilidad.

Lo segundo que aportan es la canalización de lo grueso a lo fino. Los objetos resultado de las detecciones (una blob, un rect, un apriltag, etc.) exponen sus cuadros delimitadores como la misma cuádrupla (x, y, w, h) que acepta roi. Así, una primera etapa burda puede devolver un cuadro delimitador, el cuadro encaja directamente en el roi de la siguiente etapa y la segunda etapa se ejecuta sobre el área más estrecha. Cada estrechamiento progresivo acelera la siguiente etapa y hace sus resultados más fiables, porque el espacio de búsqueda ya se ha filtrado.

5.5.2. Máscaras binarias

Un rectángulo es la forma adecuada cuando el área de interés está alineada con los ejes. Cuando no lo está (una región curva, una no convexa, los píxeles que alguna etapa anterior clasificó como «coincidencias»), hay que indicar a la operación que limite su alcance a un patrón arbitrario de píxeles. El mecanismo para ello es una máscara binaria: una Image aparte, con las mismas dimensiones que el origen, usada como un interruptor de encendido/apagado por píxel. Un píxel distinto de cero en la máscara dice «incluye el píxel de origen correspondiente»; un píxel cero dice «deja en paz el píxel de origen.»

Una máscara suele ser una imagen BINARY (el formato de un bit por píxel que existe precisamente con este propósito), pero cualquier imagen de un solo canal funcionará, porque el consumidor trata cualquier valor distinto de cero como encendido.

Los métodos de filtrado, umbralización y aritmética aceptan un argumento de palabra clave mask. La forma es la misma en cada uno: una imagen binaria asignada por separado, con las mismas dimensiones que el origen, que se pasa como argumento.

Las ROI y las máscaras se combinan. Pasa ambas y la operación se ejecutará solo sobre los píxeles que estén dentro de la ROI y encendidos en la máscara. Los dos mecanismos ofrecen al código de la aplicación palancas independientes (una para el área rectangular de interés y otra para el patrón arbitrario dentro de ella) sin que ninguna de las dos formas herede restricciones de la otra.

Una pequeña cuadrícula que representa una imagen. Un rectángulo de línea discontinua trazado sobre la parte central superior de la cuadrícula etiqueta la ROI: solo se consideran los píxeles que hay dentro de este rectángulo. Dentro de la ROI, un conjunto de celdas rellenas de forma aproximadamente circular etiqueta la máscara: solo esas celdas rellenas se modifican realmente. Las celdas restantes aparecen sombreadas ligeramente para indicar que no se tocan.

Una ROI confina una operación a un rectángulo alineado con los ejes. Una máscara la acota aún más a un patrón arbitrario de píxeles. Las dos se combinan: solo se modifican los píxeles que están dentro de la ROI y encendidos en la máscara.

5.5.3. Construcción de máscaras

Tres métodos de Image construyen geometrías de máscara comunes in situ poniendo a cero los píxeles que quedan fuera de la región elegida:

Cada uno recibe (x, y, w, h) (para el rectángulo y la elipse) o (x, y, radius) (para el círculo). Llamar a cualquiera de ellos sin argumentos centra la geometría y la dimensiona para llenar la imagen, que es la forma a la que recurre una aplicación cuando el objetivo es un óvalo o un círculo simple de imagen completa que no oculta nada salvo las esquinas.

mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear()              # start from all zeros
mask.mask_ellipse()       # centred, full-size oval

Las máscaras interesantes rara vez provienen de los métodos mask_* por sí solos. Provienen de etapas anteriores de la canalización: una pasada de umbralización produce una imagen binaria cuyos píxeles distintos de cero marcan las coincidencias, exactamente la forma adecuada para alimentar el argumento mask= de la siguiente etapa. Una pasada de limpieza morfológica refina esa máscara sin cambiar su forma. Cualquier cosa que acabe siendo una imagen de un solo canal es en sí misma una máscara válida.

5.5.4. Cómo modifican la imagen las operaciones

Un patrón visible en cada fragmento de código de las últimas páginas (la operación que devuelve el mismo img para encadenar) merece destacarse explícitamente para no tener que repetirlo cada vez que se introduce un método nuevo. En la superficie de Image aparecen tres familias de métodos, cada una de las cuales trata la imagen de origen de forma distinta:

  • Los métodos de operación modifican los píxeles del origen in situ y devuelven la misma imagen para encadenar. Las familias de dibujo, aritmética, umbral y filtro se comportan todas de este modo. img.gaussian(1) desenfoca img y devuelve el mismo img; reasignar, img = img.gaussian(1), es inofensivo pero innecesario.

  • Los métodos de conversión operan in situ de forma predeterminada igual que los métodos de operación, pero aceptan copy=True y copy_to_fb=True para asignar una imagen resultado aparte cuando hay que preservar el origen. Las conversiones de formato y las copias geométricas son los principales miembros de esta familia.

  • Los métodos de inspección leen los píxeles y devuelven un objeto resultado (una lista de características detectadas, un histograma, un conjunto de estadísticas) sin modificar en absoluto la imagen de origen.

Esa tricotomía es coherente en toda la superficie. Saber a qué familia pertenece un método le indica a la aplicación qué esperar de una llamada: si los píxeles del origen sobrevivirán intactos, si se asignará una imagen resultado aparte y si el valor devuelto es el propio origen o algo distinto.