Trabajo Práctico 2 — Museo de Pantallas#
Introducción#
El Museo de Pantallas prepara una sala interactiva sobre tecnología de visualización. La idea es que un visitante suba una foto y el sistema le muestre cómo se habría visto esa imagen en dos pantallas de otra época:
- la pantalla de una Game Boy, con sus 4 tonos de verde, y
- el tubo de un televisor CRT, con sus líneas de barrido y sus colores apenas corridos.
El resultado no es solo la imagen filtrada: el museo quiere ver la imagen puesta dentro de una foto del aparato, ocupando exactamente la zona de la pantalla.
Imágenes#
Las imágenes se representan como matrices de píxeles. En una imagen en escala de grises, esta matriz tiene dos dimensiones (ancho y alto) con valores entre 0 y 255, donde 0 representa el negro y 255 el blanco. En el caso de imágenes a color, se utilizan tres matrices correspondientes a los canales rojo, verde y azul (RGB), cada uno con valores en el mismo rango. La combinación de estos tres canales define el color final de cada píxel.
Para trabajar con imágenes en este proyecto se puede utilizar la función Image.open de la librería Pillow en conjunto con numpy. También se permite usar Image.resize para redimensionar imágenes. No se pueden usar funciones que resuelvan un ejercicio entero (por ejemplo, filtros ya hechos de Pillow o de otras librerías): la idea es implementar cada transformación a mano.
El filtro Game Boy#
La pantalla de la Game Boy (modelo original) mide 160×144 píxeles y solo puede mostrar 4 tonos. Este filtro lleva cualquier imagen a ese formato.
Paleta de la Game Boy (del tono más oscuro al más claro):
Cada tupla representa un color RGB. Por ejemplo, (15, 56, 15) es Rojo=15, Verde=56, Azul=15.
Pasos para implementar este filtro:
-
Ajustar la resolución. Redimensionar la imagen para que entre en una caja de 160×144 píxeles. Se puede usar
Image.resize. -
Calcular el brillo de cada píxel como el promedio de sus tres canales:
\[ \text{brillo}(x, y) = \frac{R + G + B}{3} \] -
Dithering ordenado. Como hay solo 4 tonos, para simular los valores intermedios se usa un patrón de puntos que depende de la posición del píxel (así se lograban los degradados en esa consola). Se usa la matriz de Bayer 4×4:
Con \(\text{paso} = 64\), a cada píxel se le suma un ajuste que depende de su posición:
\[ \text{ajuste}(x, y) = \left( \frac{\text{BAYER_4}[\,y \bmod 4\,][\,x \bmod 4\,] + 0.5}{16} - 0.5 \right) \times \text{paso} \]\[ \text{brillo}(x, y) \leftarrow \text{brillo}(x, y) + \text{ajuste}(x, y) \]El ajuste vale aproximadamente entre \(-32\) y \(+32\): hace que píxeles cercanos con el mismo brillo caigan en tonos distintos según su posición, formando la trama.
Nota
El operador \(\bmod\) devuelve el resto de la división entera. Por ejemplo, \(7 \bmod 4 = 3\) y \(-1 \bmod 4 = 3\). En Python se puede usar el operador
%para calcularlo. -
Cuantizar a 4 tonos. Calcular el índice del tono y recortarlo al rango \([0, 3]\):
\[ \text{indice} = \left\lfloor \frac{\text{brillo}(x, y)}{\text{paso}} \right\rfloor \]Esto devuelve un número entero entre 0 y 3, que indica qué color de la paleta usar.
-
Pintar el píxel con
GAMEBOY[indice].
El filtro CRT#
Este filtro simula cómo se veía una imagen en un televisor de tubo. Recibe un parámetro entero, desplazamiento (por defecto 3).
Pasos para implementar este filtro:
-
Ajustar la resolución. Redimensionar la imagen para que entre en 320×240 píxeles (resolución típica de un televisor de la época). Para esto se puede usar
Image.resize. -
Desplazamiento de canales. Los tubos no alineaban perfectamente los colores. Para cada píxel \((x, y)\):
- el canal rojo toma el valor del píxel que está
desplazamientoposiciones a la izquierda:imagen[y, x - desplazamiento, 0] - el canal azul toma el valor del píxel que está
desplazamientoposiciones a la derecha:imagen[y, x + desplazamiento, 2] - el canal verde queda igual
- si la posición de origen cae fuera de la imagen, ese canal se deja como estaba
- el canal rojo toma el valor del píxel que está
-
Scanlines. El haz del tubo dibujaba líneas horizontales con huecos entre ellas. Para simularlo, oscurecer una fila de cada dos: para toda fila con índice par (0, 2, 4, …), multiplicar todos los píxeles de esas filas por \(0.5\) y redondear los resultados para que queden enteros.
Insertar la imagen en la pantalla#
Junto con el enunciado se entregan cuatro archivos (ver Materiales):
| Foto del aparato | Máscara de la pantalla |
|---|---|
gameboy.png |
gameboy_mascara.png |
tv.png |
tv_mascara.png |
La máscara es una imagen en escala de grises, del mismo tamaño que la foto, que vale blanco (255) donde está la pantalla y negro (0) en el resto.
Pasos para insertar imagen (el resultado de un filtro) en foto usando mascara:
- Encontrar el rectángulo de la pantalla. Buscar la primera y la última fila que tengan algún píxel blanco (\(= 255\)) en la máscara, y lo mismo para las columnas. Eso da las esquinas \((x_0, y_0)\) y \((x_1, y_1)\) del rectángulo que contiene a la pantalla.
-
Estirar la imagen filtrada al tamaño exacto de ese rectángulo: \(\text{ancho} = x_1 - x_0 + 1\), \(\text{alto} = y_1 - y_0 + 1\) (con
Image.resize). -
Copiar. Armar una imagen nueva cuyos pixeles serán:
- Si la máscara es blanca (\(= 255\)), tomar el píxel de la imagen después del filtro.
- Si la máscara es negra (\(= 0\)), tomar el píxel de la foto original (de la TV o de la Game Boy).
Para esto se puede copiar la foto de la TV o de la Game Boy y luego reemplazar solamente los píxeles donde la máscara es blanca.
Requerimientos del Programa#
Se te pide escribir un programa en Python que:
-
Solicite al usuario:
- La ruta de la imagen a procesar.
- La pantalla a usar: gameboy o crt.
- Si eligió crt, el desplazamiento de canales (entero positivo, por defecto 3).
- La ruta donde guardar el resultado.
-
Procese la imagen:
- Si la pantalla es gameboy, aplicar el filtro Game Boy (con dithering) e insertar el resultado en la pantalla de
gameboy.pngusandogameboy_mascara.png. - Si la pantalla es crt, aplicar el filtro CRT e insertar el resultado en la pantalla de
tv.pngusandotv_mascara.png.
- Si la pantalla es gameboy, aplicar el filtro Game Boy (con dithering) e insertar el resultado en la pantalla de
-
Guarde la imagen resultante en la ruta indicada.
El programa debe validar los errores:
- Si no se encuentra la imagen de entrada.
- Si la pantalla elegida no es
gameboynicrt. - Si el desplazamiento no es un número entero positivo.
Se espera que la salida por pantalla sea un texto legible, claro y bien formateado.
Ejemplos de ejecución del programa
Materiales#
Se pueden descargar las fotos de los aparatos, sus máscaras y algunas imágenes de prueba de esta carpeta.
Entrega#
El trabajo se realizará en grupos de hasta dos estudiantes. El desarrollo del trabajo se llevará a cabo en un repositorio privado de Github, el cual debe ser compartido con los docentes de la materia (se pueden encontrar los usuarios de los docentes aquí). La entrega del trabajo se realizará subiendo el link del repositorio a la tarea correspondiente en el campus del curso. La fecha de entrega se encuentra en la misma tarea y en el calendario del curso.
El código debe estar modularizado en varios archivos y utilizar únicamente las herramientas vistas en clase
junto con Pillow y numpy.
Importante: Se recuerda a los estudiantes que las entregas deben ser un producto original de cada grupo, por lo que se les pide revisar la sección 6 del programa de la materia y el Código de Honor y Ética. En caso de sospecha de copia, se citará a los grupos involucrados para una defensa oral para verificar la autoría de la entrega. En caso de no poder defender la autoría, los profesores estarán obligados a elevar el caso al comité de ética de la universidad.










