Introducción al Procesamiento de Imágenes

por Juan Manuel Vuletich
Volver al estudio de Juan


Trabajo Práctico 0

Ejercicio 1

Introducción

    Se solicito implementar un Tipo de Dato Abstracto para manejar imágenes, incluyendo la implementacion de un conjunto de operaciones. Elegí desarrollar en Smalltalk porque facilita el desarrollo rápido, con código compacto y modularizado, y por el poderoso ambiente de desarrollo que proporciona.

Requerimientos

    Se requiere manejar imágenes bidimensionales, donde los valores de los pixels pueden ser Enteros, Reales o Bytes (que valen entre 0 y 255). También es necesario manejar imagenes donde los pixels son vectores (de la misma dimensión para todos los pixeles de una imagen), de cualquiera de los tipos citados. Estas últimas también pueden considerarse como compuestas por varias "bandas" o "planos" con pixeles escalares. También pueden entenderse como imágenes tridimensionales, con coordenadas (x, y, z). Los distintos métodos implementados permiten considerar a las imágenes de pixeles vectoriales de cualquiera de estas maneras.

Diseño

    La primera decisión de diseño tomada fue utilizar para el almacenaje de los pixels un Array ya preparado para el tipo de dato de los pixels escalares. Esto significa almacenar en cada posición del array exactamente un pixel. Entonces, usamos una instancia de IntegerArray, FloatArray o ByteArray para ésto. Esto nos permite que una sola clase Image pueda manejar los tipos de pixels (escalares) necesarios, en vez de hacer falta implementar tres clases distintas. Solo es necesario decir el tipo a utilizar al construir el objeto Image. En todos los casos, el pixel (x, y) se almacena en la posición ((y-1) * width + x) del array.

    Para las imágenes vectoriales desarrollollamos una subclase de Image llamada MultiPlaneImage. Esto es así porque una imagen de pixels vectoriales ES UNA imagen. La relación ES UN(A) es lo que define la herencia en Smalltalk. Esto permite heredar tanto el protocolo como la implementación de la superclase. Dado que MultiPlaneImage ya hereda la variable de instancia donde almacenar los pixels, almacenamos todos los pixels en el mismo array, ubicando el pixel (x, y, z) en la posición ((y-1) * width + (x-1)) * planeCount + z del array, donde las coordenadas comienzan en 1, asi como la indexación del array.

     Con referencia al iterador, se solicitaba que reciba una función. En Smalltalk es usual para ésto el uso de Bloques. No es preciso que el bloque tome como parámetro la imagen en cuestión, sino sólo las coordenadas de cada pixel. La implementación que hicimos permite, por ejemplo:

anImage xyIndexesDo: [ :x :y |
    anImage
        atX: x y: y
        put: ((anImage atX: x y: y)procesadoDeAlgunaManera) ].

este segmento de código opera tanto sobre imagenes escalares como sobre los pixels vectoriales de una imagen vectorial. Si, por el contrario, se desea operar sobre las componenetes escalares de una imagen vectorial, es posible:

anImage xyzIndexesDo: [ :x :y :z |
    anImage
        atX: x
        y: y
        z: z
        put: ((anImage atX:x y:y z:z)procesadoDeAlgunaManera) ].

este segmento de código opera únicamente sobre imagenes vectoriales

    Para mostrar por pantalla las imágenes escalares de tipo Byte, los pixels se consideran tonos de gris. Para las imagenes Float, se hace stretching para adaptar el rango de los pixels realmente contenidos en la imagen a tonos de gris. Para las Integer, se toma la parte mas significativa como rojo, la del centro como verde, y la menos significativa como azul. Para mostrar por pantalla las imágenes vectoriales, se puede tomar 3 bandas como componentes Rojo, Verde y Azul, o tomar la norma de los vectores como tonos de gris. En ambos casos, la implementación lo que hace es solicitar mostrar por pantalla una imagen color de 32 bits, que es manejada por la máquina virtual del Smalltalk, para mostrarla sobre la pantalla realmente disponible. En este sentido, cabe mencionar que yo soy el implementador de la máquina virtual de Squeak para el sistema operativo OS/2. Esto incluye la conversión de las distintas profundidades de color a la disponible en la pantalla física, incluyendo modos de video paletizados, y técnicas de dithering para dar la ilusión de más colores que los efectivamente disponibles.

    Implementamos los métodos correspondientes a las operaciones solicitadas. Asimismo se implementó la lectura y grabación de los formatos de archivo solicitados. También es posible leer archivos en formato BMP, GIF y JPG. Se incluyó en los comentarios de ambas clases una selección de ejemplos que ilustran el funcionamiento de las operaciones solicitadas. Implementamos también una interfaz al usuario para interactuar con las imagenes. Esta interfaz, juntamente con las clases Image y MultiPlaneImage ya descriptas, puede la base de una aplicación completa para el procesamiento de imágenes.

Ejercicio 2

Introducción

    Se solicito implementar un Tipo de Dato Abstracto para manejar histogramas de imágenes, tanto estáticos como dinámicos, incluyendo la implementacion de un conjunto de operaciones.

Diseño

    Un histograma es estático cuando incluye entradas para todos los valores de los pixels posibles. Esto significa que al modificarse las entradas no será necesario ajustar estructuras de datos. Resulta práctico cuando los valores posibles de los pixels no son muchos. Nosotros decidimos implementar histogramas estáticos únicamente para imágenes con pixels Byte. Las entradas se guardan en un Array de 256 entradas, indexado por el valor de los pixels.

    Un histograma dinámico incluye entradas únicamente para los valores de los pixels realmente presentes en una imagen. Esto hace posible manejar histogramas de imágenes con gran cantidad de valores posibles. Por ejemplo, las imágenes Integer o Float pueden tener 232 valores posibles. Es claro que una imagen de tamaño razonable utilizará únicamente un subconjunto pequeño de estos valores, ya que la cantidad de pixels sera mucho menor a 232. Adicionalmente, resulta imposible manejar arreglos de este tamaño, pero si la imagen entra en memoria, un histograma dinámico también será manejable. Sin embargo, la implementación de histogramas dinámicos resulta más trabajosa, necesitándose recurrir a una tabla de hash. En mi implementación usé una instancia de la clase Dictionary, que es justamente una tabla de hash.

    La implementación la hice en dos clases, ImageHistogram y una subclase llamada StaticImageHistogram. ImageHistogram es un histograma dinámico, y acepta cualquier objeto como posible valor de los pixels. StaticImageHistogram hereda de ella, y redefine los métodos necesarios para funcionar como histograma estático, únicamente para imágenes con pixels de tipo Byte, por las razones ya expuestas.

    Implementamos también una interfaz al usuario atractiva, integrada a la realizada para manejar las imágenes y accesible desde ella, que permite visualizar el histograma de una imagen.