Está en la página 1de 119

biOps: un paquete de procesamiento de

imágenes en R

Matı́as Bordese
Walter Daniel Alini
Director: Dr. Oscar Humberto Bustos

30 de noviembre de 2007

Facultad de Matemática, Astronomı́a y Fı́sica

Universidad Nacional de Córdoba


“No se qué hace, pero está muy bueno.”

Nicolás Wolovick
Clasificación:

I.4 Image Processing and Computer Vision

Palabras clave:

R, procesamiento de imágenes, detección de bordes, clasificación, FFT

UNIVERSIDAD NACIONAL DE CÓRDOBA

Facultad de Matemática, Astronomı́a y Fı́sica

Licenciatura en Ciencias de la Computación

biOps: un paquete de procesamiento de imágenes en R

por
Matı́as Bordese
Walter Daniel Alini

Resumen
El presente trabajo describe un paquete de procesamiento de imágenes realizado en R, un lengua-
je y entorno computacional libres, enfocado en estadı́stica y gráficos estadı́sticos. Las distintas
funciones del paquete, denominado biOps, fueron especificadas utilizando la notación Z -un len-
guaje formal de especificaciones usado para describir y modelar sistemas de computación- e
implementadas usando R mediante la codificación e integración de código C.

El paquete se compone de operaciones geométricas, morfológicas, aritméticas, lógicas, de tablas


de reemplazo, de detección de bordes y de convolución. Incluye también filtros en el espacio
de frecuencias a partir de la Transformada Rápida de Fourier y métodos no supervisados de
clasificación de imágenes. Se describen y detallan las implementaciones, sus fundamentos teóricos
y aplicaciones más frecuentes.

biOps fue liberado bajo licencia libre GPL y aceptado por la comunidad de R para formar parte
de su repositorio oficial de paquetes.
Agradecimientos
Al Dr. Oscar H. Bustos, por la dirección del trabajo.
Al Dr. Pedro R. D’Argenio, por su apoyo, consejos y opiniones.
A la Dra. Laura Alonso y al MSc. Maximiliano Cristiá, por su desinteresada colaboración.
A Kurt Hornik y Uwe Ligges, del R Development Core Team, nuestros R-gurús.
A nuestros familiares y grupo de amigos.

iii
Índice general

Resumen II

Agradecimientos III

Listado de Figuras VII

1. Introducción 1

2. R 4
2.1. Antecedente: El lenguaje S . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2. R como implementación de S . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3. Interfaz contra lenguajes compilados . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4. R puro vs. interfaz C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.5. Colaboración a CRAN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

3. Z 12
3.1. Las especificaciones formales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.2. El lenguaje de especificación Z . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.3. Definiciones en Z . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3.1. Declaraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3.2. Abreviaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.3.3. Definiciones axiomáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.3.4. Definiciones genéricas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.3.5. Esquemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.4. f uzz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.5. Especificación en Z . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.5.1. Especificación de reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.5.2. Resto de las especificaciones . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4. Imagen digital 21
4.1. Representación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2. Resolución espacial y de profundidad . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.3. Modelos de color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.3.1. RGB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.3.2. CYM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.3.3. HSI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.4. Nuestra implementación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.4.1. Especificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

5. El procesamiento digital de imágenes 27


5.1. Orı́genes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

iv
Índice general v

5.2. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.2.1. Astronomı́a y exploración del espacio . . . . . . . . . . . . . . . . . . . . . 29
5.2.2. Inteligencia y aplicación militar . . . . . . . . . . . . . . . . . . . . . . . . 29
5.2.3. Ciencias de la tierra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.2.4. Gobierno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.2.5. Visualización de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.2.6. Entretenimiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.2.7. Medicina . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.2.8. Procesamiento de documentos . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.2.9. Aplicaciones industriales y visión de máquinas . . . . . . . . . . . . . . . 31
5.2.10. Aplicaciones hogareñas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

6. biOps: un paquete de procesamiento de imágenes para R 32


6.1. Otros paquetes R de manejo de imágenes . . . . . . . . . . . . . . . . . . . . . . 32
6.2. Estructura del paquete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.3. Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.4. biOpsGUI: el principio de una interfaz gráfica de usuario . . . . . . . . . . . . . . 36
6.5. Próximos capı́tulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.6. Formato Digital . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

7. Operaciones por pixel 38


7.1. Look-up tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
7.1.1. Modificación de contraste . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.1.2. Modificación de intensidad . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.1.3. Otras modificaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.2. Operaciones aritméticas y lógicas . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.3. Histogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.4. Generación de ruido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

8. Operaciones geométricas 48
8.1. Mapeo de valores: “hacia adelante” vs. “hacia atrás” . . . . . . . . . . . . . . . . 48
8.2. Interpolación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
8.2.1. Interpolación por el vecino más cercano . . . . . . . . . . . . . . . . . . . 49
8.2.2. Interpolación bilineal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
8.2.3. Interpolación por B-Spline . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
8.2.4. Interpolación convolucional cúbica . . . . . . . . . . . . . . . . . . . . . . 51
8.3. Operaciones implementadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
8.3.1. Escalar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
8.3.2. Encoger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
8.3.3. Rotar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.3.4. Espejar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.3.5. Trasladar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.3.6. Recortar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

9. Operaciones por vecino 58


9.1. Convolución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
9.1.1. Blurring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
9.1.2. Sharpening . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
9.2. Filtro por mediana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
9.3. Filtro por mı́nimo/máximo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

10.Algoritmos de detección de bordes 64


10.1. Generalidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
10.2. Técnicas sencillas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Índice general vi

10.3. Técnicas por convolución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66


10.3.1. Detección de bordes por gradiente (Gradient Edge Detection) . . . . . . . 67
10.3.2. Detección de bordes por compás (Compass Edge Detection) . . . . . . . . 68
10.4. Técnicas avanzadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
10.4.1. Marr Hildreth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
10.4.2. Canny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
10.4.3. Shen Castan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
10.5. Detección de bordes en color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

11.Filtros en el espacio de frecuencias 74


11.1. Espacio de frecuencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
11.2. Transformada de Fourier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
11.3. Convolución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
11.4. Filtros por frecuencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

12.Operaciones morfológicas 82
12.1. Operaciones sobre imágenes binarias . . . . . . . . . . . . . . . . . . . . . . . . . 82
12.1.1. Dilatación binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
12.1.2. Erosión binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
12.1.3. Apertura y clausura binarias . . . . . . . . . . . . . . . . . . . . . . . . . 86
12.2. Operaciones sobre imágenes en escala de grises . . . . . . . . . . . . . . . . . . . 88

13.Clasificación de imágenes 90
13.1. Conceptos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
13.2. Clasificación supervisada y no supervisada . . . . . . . . . . . . . . . . . . . . . . 91
13.3. Métodos de clasificación no supervisados . . . . . . . . . . . . . . . . . . . . . . . 92
13.3.1. K-means . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
13.3.1.1. Complejidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
13.3.2. Isodata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

14.Conclusiones 99
14.1. Trabajo futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
14.2. Estadı́sticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

A. Profiling 103

Bibliografı́a 110
Índice de figuras

4.1. Matriz imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22


4.2. Modelos de color RGB y CYM . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.3. Modelo de color HSI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

6.1. Estructura biOps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7.1. Look-up tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39


7.2. Decrementar contraste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.3. Incrementar contraste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.4. Decrementar intensidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.5. Incrementar intensidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.6. Negativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.7. Thresholding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.8. Transformación Gamma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.9. Aplicación de imgDiffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.10. Histograma de una imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.11. Ruido “sal y pimienta” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

8.1. Rotación de imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54


8.2. Operación de espejado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.3. Operación de traslación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

9.1. Convolución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
9.2. Aplicación de sharpening . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
9.3. Aplicación de filtro por mediana . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

10.1. Operador de homogeneidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65


10.2. Operador por diferencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.3. Aplicación de operador por diferencia . . . . . . . . . . . . . . . . . . . . . . . . 66
10.4. Borde y derivadas en una dimensión . . . . . . . . . . . . . . . . . . . . . . . . . 66
10.5. Aplicación de Sobel (threshold = 40, negativo) . . . . . . . . . . . . . . . . . . . 68
10.6. Aplicación de Canny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

11.1. Transformada de Fourier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78


11.2. Filtros FFT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
11.3. Filtro por frecuencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

12.1. Representación gráfica de una imagen binaria . . . . . . . . . . . . . . . . . . . . 83


12.2. Dilatación binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
12.3. Dilatación binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
12.4. Erosión binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
12.5. Erosión binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
12.6. Apertura y clausura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

vii
List of Figures viii

13.1. Clasificación por k-means . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94


13.2. Kd-tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
13.3. Nearest Neighbor Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Capı́tulo 1

Introducción

El procesamiento digital es el conjunto de técnicas computacionales que se aplican sobre las


imágenes con el objetivo de mejorar la calidad, alterar su morfologı́a, facilitar su interpretación o
proporcionar herramientas para la búsqueda de información. Aparece tardı́amente en la historia
de la computación, debido a los requisitos de hardware y los sistemas gráficos que permitieran
desarrollarla. El abaratamiento de los costos y la evolución de los equipos le dio un fuerte impulso
en los últimos tiempos.

En la actualidad existen muchas aplicaciones de software que permiten el procesamiento digital


de imágenes, ası́ como librerı́as para los diferentes lenguajes de programación. R, un lenguaje
libre destinado principalmente al análisis estadı́stico de datos, es quizá una excepción a la regla.
Las alternativas que se presentan para el manejo multipropósito de imágenes son escasas.

La posibilidad de integrar funcionalidad para el procesamiento de imágenes en un entorno es-


tadı́stico, libre y con una comunidad muy bien organizada y en constante crecimiento, sumado
a las ventajas que suponen las utilidades estadı́sticas (cálculo de medias, desviaciones, histogra-
mas), nos impulsaron a la realización de este proyecto. El objetivo fue, entonces, el de investigar,
estudiar, especificar e implementar un conjunto de algoritmos para R, que provea un entorno
funcional, útil y general para el procesamiento de imágenes, colaborando con la comunidad de
Software Libre, es decir, permitiendo de esta forma su libre uso y modificación.

Presentamos en este escrito el resumen de varios meses de trabajo. Intentamos ser precisos al
introducir los conceptos manejados, para que el lector tenga una buena lectura preparatoria, y
analizar en detalle las especificaciones, utilidades e implementaciones de los algoritmos elegidos
para formar parte del paquete.

Se realizó el estudio, análisis, especificación, implementación y testeo de técnicas para el manejo


de imágenes, que concluyeron con la creación y publicación de un paquete R, denominado biOps,
liberado bajo la licencia GPL y que se encuentra disponible en el repositorio oficial de paquetes
del lenguaje R. Además se comenzó con el trabajo de una interfaz gráfica de usuario, biOpsGUI,
para brindar una mejor experiencia de usuario.

1
Capı́tulo 1. Introducción 2

Creemos que el paquete obtenido es una importante colaboración con la comunidad R, que no
contaba con paquetes multipropósito de importancia en el procesamiento de imágenes. biOps, en
este sentido, resulta de gran utilidad, fácilmente extensible y con una amplia gama de algoritmos.

Consideramos que los trabajos futuros para la mejora del paquete debieran considerar la exten-
sión de la interfaz gráfica, diversificar los formatos de imagen soportados, reconsiderar el manejo
en memoria de la representación de imágenes y añadir algoritmos para ampliar su utilidad (al-
goritmos supervisados de clasificación de imágenes, filtros, reconocimiento de patrones, machine
vision, etc.).

Estructura de este trabajo

Este texto se compone de dos partes principales: los capı́tulos 2 al 5 introducen conceptos re-
lacionados a las etapas previas a la codificación. Se presentan la notación Z, lenguaje utilizado
para las especificaciones formales en este trabajo, y el lenguaje R, sobre el cual se implementaron
los algoritmos estudiados. Se desarrollan además, los conceptos relacionados con imágenes, sus
representaciones, modelos de color y los usos en las diversas áreas de aplicación. El capı́tulo 6
presenta una descripción de las secciones posteriores (capı́tulos 7 al 13), en los cuales se profun-
dizan los conceptos, utilidades, especificación e implementación correspondientes a cada una de
las divisiones del paquete.

Para una visión global de este trabajo, recomendamos la lectura de los capı́tulos 1 (esta In-
troducción), 6 (descripción del paquete, contenidos del trabajo y capı́tulos posteriores) y 14
(recapitulación, evaluación, conclusiones y desafı́os para el trabajo futuro). Quien desee profun-
dizar acerca de los lenguajes y notaciones utilizados, puede concentrarse en los capı́tulos 2 (el
lenguaje R, y sus interfaz con el lenguaje C) y 3 (la notación Z, de especificación de modelos
de sistemas computacionales). A los interesados en conceptos o implementaciones en una deter-
minada rama del procesamiento digital de imágenes que hayan sido tratados en este trabajo,
sugerimos la lectura del capı́tulo correspondiente.

A continuación se presenta un breve resumen del contenido de los capı́tulos de este trabajo:

R [Cap. 2]: R es un lenguaje interpretado, de scripting, y un conjunto de librerı́as destinadas


principalmente al análisis estadı́stico de datos. El “Comprehensive R Archive Network” es
una red de sitios con las librerı́as que están disponibles para el uso en R. Se realiza una
breve descripción del lenguaje, sus procedimientos para colaborar con su comunidad, su
red de archivos y las interfaces para comunicarlo con otros lenguajes de programación. Se
comparan además algoritmos codificados en R y en C (mediante interfaz en R).

Z [Cap. 3]: Z es el nombre de la notación que utilizamos para la especificación de nuestro traba-
jo. Se presentan los conceptos básicos, definiciones de objetos necesarios para comprender
esta notación e implementación de los algoritmos del paquete y de una representación de los
números reales, basado esto último en la publicación de R. D. Arthan [Art96]. Se menciona
también a f uzz, con el cual realizamos la verificación de tipos de estas especificaciones.
Capı́tulo 1. Introducción 3

Imagen digital [Cap. 4]: Se presentan los conceptos necesarios para comprender la represen-
tación computacional de imágenes: la resolución espacial y de profundidad (detalles en una
imagen) y los modelos de color más conocidos (RGB, CYM y HSI). Se detalla además la
representación elegida para este trabajo e implementación para la obtención de imágenes
mediante R.

Procesamiento digital de imágenes [Cap. 5]: El procesamiento de imágenes es una rama


que data de principios de siglo pasado. Se relata su origen y las principales aplicaciones en
las diversas áreas donde es utilizado.

biOps: un paquete de procesamiento de imágenes en R [Cap. 6]: biOps es el nombre


del paquete que desarrollamos y que se encuentra publicado en el repositorio oficial de
paquetes R. Se detallan estructura, componentes y el comienzo de la implementación de su
interfaz gráfica: biOpsGUI. También se presenta una comparación contra otros paquetes R
de manejo de imágenes y una visión global de los capı́tulos posteriores:

Operaciones por pixel [Cap. 7]: Algoritmos de “tabla de reemplazos”, operaciones


aritméticas, lógicas, de representación gráfica (histogramas) y generación de ruidos.
Operaciones geométricas [Cap. 8]: Operaciones de rotación, escalado, espejado,
crop, shrink y traslación. Además, diversas formas de interpolación (vecino más cer-
cano, bilineal, cúbica y por spline).
Operaciones por vecino [Cap. 9]: Concepto de convolución y aplicación de filtros
lineales y no lineales.
Algoritmos de detección de bordes [Cap. 10]: Algoritmos sencillos y rápidos
(homogeneidad y diferencia), métodos basados en convolución (Sobel, Prewitt, Roberts,
etc.) y algunas técnicas avanzadas (Shen Castan, Marr Hildreth, etc.).
Filtros en el espacio de frecuencias [Cap. 11]: Filtros mediante la transformada
rápida de Fourier.
Operaciones morfológicas [Cap. 12]: Operaciones para imágenes binarias y de
escala de grises, de erosión, dilatación y sus combinaciones: apertura y clausura.
Clasificación de imágenes [Cap. 13]: Se dividen en algoritmos supervisados y no
supervisados. Se detallan Isodata y K-Means (no supervisados).

Conclusiones [Cap. 14]: Una recapitulación, evaluación de herramientas y breve comenta-


rio de lo realizado. Se incluyen algunas estadı́sticas y los trabajos futuros que a nuestro
entender deberı́an ser prioritarios para mejorar el paquete.
Capı́tulo 2

R es un lenguaje interpretado, de scripting, y un conjunto de librerı́as destinadas principalmente


al análisis estadı́stico de datos. Es una implementación libre del lenguaje estadı́stico S , creado
a mediados de la década del ’70 por los Laboratorios Bell, aunque se ve influenciado también
por el lenguaje Scheme. Se distribuye sin costo y bajo la licencia GPL, y es el lenguaje sobre el
cual se ha llevado a cabo la implementación de los diversos algoritmos que forman parte de este
trabajo. R está construido principalmente sobre el lenguaje de programación C , aunque mucha
funcionalidad está escrita en R mismo. Además puede integrarse con otros lenguajes mediante
el uso de funciones especı́ficas, lo que nos permite una diversidad de opciones a la hora de tomar
decisiones de implementación. Se codificaron algunos algoritmos, objeto de este trabajo, tanto
con acceso a código realizado en C como a uso explı́cito de este lenguaje, y se compararon los
datos de eficiencia mediante algunas herramientas de profiling. La gran diferencia encontrada a
favor de las implementaciones con llamadas a código C , cuyas causas se mencionan, influenció en
su mayor uso en el resto de los algoritmos.

El “Comprehensive R Archive Network” es una red de sitios con las librerı́as que están disponibles
para el uso en R. Para colaborar con CRAN es necesario cumplir con una serie de requisitos que
hacen que los paquetes puedan funcionar correctamente y estar documentados de una manera
homogénea.

La comunidad R, en constante crecimiento, ha realizado diversas herramientas y comandos para


aliviar la tarea de los programadores que deseen colaborar con el proyecto. Entre ellas están los
comandos check y build , que se explican brevemente.

2.1. Antecedente: El lenguaje S

Desde la segunda parte del siglo XX, y gracias al incremento del poder de cálculo de la compu-
tación, la estadı́stica se ha visto sustancialmente impactada. Este impacto ha traı́do dos con-
secuencias fundamentales: por un lado, la automatización del cálculo para los viejos métodos
estadı́sticos; y por el otro, el resurgimiento del interés en métodos menos estudiados, como los

4
Capı́tulo 2. R 5

no lineales, encabezados por las redes neuronales y los árboles de decisión. La abundancia en
recursos ha causado también el renacer de nuevos modelos lineales descartados con anterioridad.

Alrededor del año 1980 comienzan a surgir los lenguajes de programación especializados en
análisis estadı́sticos. Hoy en dı́a hay tantos como programadores emprendedores hubo en las
últimas décadas.

Entre los lenguajes que más popularidad han logrado, se encuentra S . La historia de este lenguaje
se remonta a mediados de los ’70, en los Laboratorios Bell. Hasta ese entonces, mucho de los
investigadores se valı́an de librerı́as del lenguaje Fortran (acrónimo de For mula Translator) para
realizar sus cálculos, sobre todo la librerı́a SCS (Statistical Computing Subroutines), rutinas
que se extendı́an según las necesidades. El impulso a realizar cálculos más simplistas que los que
proponı́a esta librerı́a, sumado a la paulatina disminución de código Fortran necesario para los
cálculos, hacen que Rick Becker, Allan Wilks y John Chambers comiencen el desarrollo de S
como una unidad independente.

S no fue el primer lenguaje con funcionalidad estadı́stica realizado por los Laboratorios Bell, pero
sı́ el primero en ser implementado. La primera implementación data del 1976 y funcionaba sobre
el sistema operativo GCOS (General Comprehensive Operating System). El nombre ’S’ (escrito
en un principio ası́, con comillas simples) fue elegido por ser esta letra comúnmente usada en
computación estadı́stica, siendo consistente con otros lenguajes de programación desarrollados
en la misma institución (léase el lenguaje de programación C , de uso frecuente en nuestros dı́as).

Tras una mutación no demasiado importante que hizo que pudiera empezar a utilizarse en el sis-
tema operativo UNIX , por el año 1988, S sufre una serie de cambios de peso (en implementación
y, sobre todo, en sintaxis) y en 1991 se introduce el concepto de notación de fórmulas.

Este “nuevo” lenguaje es bastante parecido a las implementaciones actuales: S − Plus (versión
comercial de S , también conocida como S +), desarrollado por la empresa Insightful , y R (versión
libre), objeto de nuestro estudio, y en el cual centraremos toda la atención.

R también fue influenciado, sobre todo en lo que se refiere a implementación subyacente y


semántica, por el lenguaje Scheme 1 , desarrollado por Guy L. Steele y Gerald Jay Sussman en
los años ’70.

Actualmente, además de S − Plus 2 existen otras alternativas comerciales, que si bien no son
objeto de estudio en este trabajo, vale la pena mencionarlas: SAS 3 , Minitab 4 y SPSS 5 .

2.2. R como implementación de S

La primera implementación de S como proyecto de software libre fue diseñada por Ross Ihaka
y Robert Gentleman en el Departamento de Estadı́sticas de la Universidad de Aukland, Nueva
1 http://www.schemers.org
2 http://www.insightful.com/products/splus
3 http://www.sas.com
4 http://www.minitab.com
5 http://www.spss.com
Capı́tulo 2. R 6

Zelanda. Le llamaron R, que surge por un juego con S , principal antecesor, y el primer nombre
de ambos autores.

Un gran grupo de personas han contribuido con el desarrollo de R mediante el aporte de código y
reportes de bugs desde su creación. Hacia mediados de 1997 se creó un grupo de desarrolladores
con permisos de modificación de las fuentes de R, el “R Core Team”, que se compone actualmente
de 17 personas, entre ellas sus primeros programadores Ihaka y Gentleman.

R es, en pocas palabras, la suma de un lenguaje de scripting, un intérprete y un conjunto muy


completo de módulos built-in para el manejo de datos y trabajos estadı́sticos. Consta de dos
componentes principales: el lenguaje propiamente dicho y el intérprete, con los cuales se puede
manejar gráficos, efectuar tareas de depuración y debugging, ası́ como también acceder a algunas
funciones del sistema y correr scripts desde código guardado en archivos.

R integra programas para la manipulación de datos, cálculo y gráficos. Dispone de una gran
cantidad de librerı́as, con un fuerte hincapié en el manejo de datos y funcionalidades estadı́sticas.
Cuenta además con:

Almacenamiento y manipulación eficaz de datos

Operadores para variables indexadas, en particular matrices (y arreglos, es decir, matrices


unidimensionales)

Una amplia colección integrada de herramientas para el análisis de datos

Funcionalidad de impresión gráfica en pantalla o impresora

El lenguaje de programación incluye condicionales, ciclos, funciones recursivas y de entrada/sa-


lida. Muchas de las funcionalidades que provee están escritas en R mismo, si bien gran parte de
las librerı́as básicas están escritas en C .

R puede integrarse con distintas bases de datos y existen librerı́as que facilitan su utilización
desde lenguajes de programación interpretados (como Perl y Python) o desde lenguajes de código
compilado (como C , C + + y Fortran), como veremos más adelante para el caso particular que
nos interesa. La lista de los lenguajes en los cuales pueden agregarse funcionalidad está creciendo
con el correr del tiempo, a medida que éstos aumentan en eficiencia o popularidad, y a medida
que R crece como utilidad para el usuario.

Una amplia colección de librerı́as se encuentran en CRAN 6 (Comprehensive R Archive Network),


una red de sitios que cuentan con idéntico contenido (mirrors), tanto de código como de docu-
mentación y de archivos binarios, y que mantienen la información que rodea a R actualizada
y a disposición de toda la comunidad. En CRAN se mantienen, también, una lista de correo
electrónico y un sistema de seguimientos de bugs.

R se utiliza mucho en la investigación biomédica, la bioinformática y la matemática financiera.


Los proyectos más conocidos basados en R son Bioconductor 7 , destinado al análisis de datos en
6 http://cran.r-project.org
7 http://www.bioconductor.org
Capı́tulo 2. R 7

genética y biologı́a molecular, y Rmetrics 8 , dedicado al análisis de técnicas de mercadotecnia y


evaluación de instrumentos financieros.

R se distribuye bajo la licencia GNU GPL y está disponible para la mayorı́a de los sistemas
operativos existentes (incluidas excentricidades como adaptaciones para funcionar en la consola
PlayStation2 y otras)

R tiene su propio formato de documentación, similar al reconocido LATEX. Esta documentación


es obligatoria para la aceptación de paquetes en CRAN , lo que hace que los agregados tengan
la chance de ofrecer documentación comprensible en varios formatos.

La distribución de R cuenta con muchos procedimientos con fines estadı́sticos, entre los que se en-
cuentran: modelos lineales y generalizados, modelos de regresión no lineales y análisis de tiempos
de series, asi como también funcionalidad de gráficos y representaciones de datos. Es relativa-
mente sencillo agregar nuevas utilidades, mediante lo que se denominan “add-on”s, módulos de
propósitos especı́ficos.

2.3. Interfaz contra lenguajes compilados

R nos ofrece la posibilidad de acceder a código compilado que haya sido linkeado previamente.
Este link se puede realizar en tiempo de creación del módulo o bien dinámicamente mediante
la función dyn.load . A través de la función .C se genera una interfaz a código compilado en C
o C + +. Los argumentos que se le pasan a esta función son generalmente copiados antes de la
ejecución del código, y también son copiados a una lista de argumentos en R cuando la función a
la cual accedemos ha retornado su valor. Los argumentos pueden pasarse con nombre, de forma
tal de tener un fácil acceso a ellos en su posterior manejo. R tiene un mecanismo de pasajes de
parámetros por defecto que transforma cada tipo del código en un tipo del código C . La lista de
tipos para los cuales R conoce mecanismos de transformación es acotada, pero puede extenderse,
en caso de requerirse, de una manera sencilla. Para este último caso, es preferible el uso de otras
funciones de ejecución de código compilado. La función .Call es la que se utiliza generalmente,
y que da un mecanismo para pasar directamente a C algunos tipos más complejos de R como
las listas. En el caso del lenguaje C , de interés para este trabajo, podemos ver en la siguiente
tabla la tranformación que sufren los principales modos de almacenamiento:

Mapeo de tipos
R C
logical int∗
integer int∗
double double∗
complex Rcomplex ∗
character char ∗ ∗
raw char ∗
8 http://www.itp.phys.ethz.ch/econophysics/R
Capı́tulo 2. R 8

Con type∗ se denota al puntero a type, es decir, la dirección de memoria de una variable de tipo
type. Rcomplex se refiere a una estructura en C incluida en los archivos de cabecera que provee
el lenguaje R.

2.4. R puro vs. interfaz C

La facilidad que presenta R de escribir add − ons en otros lenguajes (nombrados de forma breve
anteriormente) se enfrenta con las ventajas que encuentran algunos desarrolladores de basar sus
módulos sin la intervención explı́cita de otros lenguajes. La mayor parte de las librerı́as de R
están escritas en C , por la indiscutible eficiencia de este lenguaje.

Existe una forma de generar un análisis estadı́stico de un script en R que muestre el uso de
procesador y el porcentaje de tiempo de ejecución que cada parte del script ha utilizado. Lo
anterior es mucho más fácil de decir en inglés, para lo cual tenemos una palabra que lo expresa:
profiling.

Para hacer profiling en R puede llamarse a la función Rprof , entre cuyos argumentos se encuen-
tran el tiempo (medido en segundos) a esperar para hacer un muestreo del stack del proceso (en
general, este número debe ser cercano a 15/20 milisegundos, ya que un número menor harı́a que
el tiempo necesario para recolectar la información se vea superpuesto con la siguiente consulta al
stack, y un número mayor perjudicarı́a la precisión del análisis), y el nombre del archivo en el cual
(sobre)escribir la información recolectada. De esta manera, si bien el script que se está corriendo
baja un poco su performance, es posible identificar las partes en que la ejecución ha invertido
más o menos tiempo. Los mecanismos que se usan para el profiling son los mismos que usa el
lenguaje C, con lo que estas herramientas no pueden usarse conjuntamente.

Los test para Windows y sistemas operativos UNIX puede que arrojen resultados distintos,
puesto que el intervalo fijo que se establece para el muestreo del stack corresponde a uso del
tiempo del CPU en UNIX , y simplemente tiempo nominal en Windows. Sin embargo, ante igual
carga de CPU, los resultados no deberı́an variar para los distintos sistemas operativos.

La función Rprof consulta el estado de la ejecución periódicamente y escribe en el archivo


indicado el estado encontrado. El archivo generado puede tratarse de varias formas. Entre las
que nos ofrece la distribución de R se encuentran:

Mediante un script en Perl (comando de R) llamado también Rprof .

Una función del lenguaje llamada summaryRprof que devuelve un objeto en R que puede
ser analizado.

Este tipo de análisis se utilizan para identificar “cuellos de botella” o partes de código en R que
pueda ser beneficioso reemplazar por código compilado. Para que los resultados sean provechosos,
es necesario que las corridas sean lo suficientemente grandes como para que el tiempo en que el
lenguaje realiza garbage collections sean depreciables; caso contrario es posible que encontremos
resultados que no sean demostrativos para la experiencia que realizamos.
Capı́tulo 2. R 9

La bibliografı́a consultada es redundante en cuanto a la mayor eficiencia de las implementaciones


en código compilado en C contra las implementaciones puras en el lenguaje R. Sin embargo,
parte de nuestro interés era comparar cuantitativamente estas diferencias para algunos casos de
nuestro proyecto, de forma tal de tomar una decisión al respecto basada en la aplicación directa
de nuestras implementaciones.

Para ello, codificamos una selección de algoritmos tanto con acceso a código C como sin él
(y aquı́ hablamos de “sin acceso explı́cito”), para realizar luego un análisis con la herramienta
anteriormente mencionada.

A continuación se muestran los resultados obtenidos para un algoritmo de Look-up tables (de-
crementar contraste, función imgDecreaseContrast), que se detallan en 7.1.1, y, para uno de
operaciones aritméticas (diferencia de imágenes, función imgDiffer ), detallados en 7.2. El resto
de los resultados pueden encontrarse en el Apéndice A:
r_ de c_ con tr as t vs . i m g D e c r e a s e C o n t r a s t
Each sample represents 0.15 seconds .
Total run time : 1 9 7 7 . 9 0 0 0 0 0 0 0 0 4 7 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.79 1973.70 0.00 0.00 " r_ de c_c on tr as t "
99.78 1973.55 48.40 957.30 " r _ l o o k _u p _ t a b l e "
...
0.21 4.20 0.00 0.00 " imgDecreaseContrast "
0.21 4.20 0.00 0.00 " . imgContrast "
...
0.06 1.20 0.06 1.20 ".C"
...

% self % total
self seconds total seconds name
48.40 957.30 99.78 1973.55 " r _ l o o k _u p _ t a b l e "
...
0.06 1.20 0.06 1.20 ".C"
0.05 0.90 0.05 0.90 " as . vector "
...

r_imgDiffer vs . imgDiffer
Each sample represents 0.15 seconds .
Total run time : 3 5 9 2 . 5 0 0 0 0 0 0 0 1 4 5 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.61 3578.40 53.47 1920.90 " r_imgDiffer "
...
0.39 14.10 0.00 0.00 ". imgArithmeticOperator "
0.39 14.10 0.00 0.00 " imgDiffer "
0.29 10.35 0.29 10.35 ".C"
...

% self % total
self seconds total seconds name
Capı́tulo 2. R 10

53.47 1920.90 99.61 3578.40 " r_imgDiffer "


...
0.29 10.35 0.29 10.35 ".C"
0.25 9.00 0.25 9.00 ":"
...

En el primero de los listados de estos resultados se encuentran las funciones llamadas en la


ejecución, ordenadas por el porcentaje de tiempo ocupado dentro de cada una (y de aquellas a
las cuales ha llamado). El segundo listado corresponde al orden según el porcentaje del tiempo
ocupado sólo por la función (y no por las llamadas anidadas).

Notamos para el caso de la función de decrementar contraste (r dec contrast vs. imgDecreaseContrast)
que la relación de uso de CPU fue de aproximadamente 475 a 1 (475.1904) y para la función de
diferencia de imágenes (r imgDiffer vs. imgDiffer ) fue de 255 (255.4102) a 1, en ambos casos a
favor de las implementaciones con acceso a código C.

No resta demasiado análisis por hacer. Lo que valdrı́a preguntarse es el por qué de semejante
diferencia. La respuesta puede buscarse de entre las siguientes justificaciones:

Lo principal es recordar que C es un lenguaje compilado y R uno interpretado, con lo


que hay una capa de abstracción (al menos) de diferencia. Además, muchas de las optimi-
zaciones a código fuente que hacen los códigos compilados se pierden para el caso de los
interpretados.

Las funciones de acceso a algunas estructuras de datos en R verifican ciertas condiciones


(como la validez del lugar de memoria a acceder), lo cual hace que las estructuras de R
subyacentes (implementadas en C ) sean más complejas y tengan chequeos que no nos eran
necesarios realizar en nuestro código C (esto hace a R un lenguaje más robusto que C ,
pagando el precio de la eficiencia).

El uso, en algunos casos, de funciones no del todo adecuadas pero que se pegaban más a
las especificaciones de los algoritmos. Por caso, en las look-up tables, se usa una estructura
de memoria contigua (tal como lo describen los algoritmos). Sin embargo, esta razón no es
del todo válida: una evaluación para estos casos (cambiando es uso de memoria contigua
por las funciones mapply y el uso de funciones en los parámetros) arrojó, para el caso de
decrementar contraste, una relación de 433.78 a 1. Es decir, del mismo orden de magnitud
que las pruebas anteriores.

2.5. Colaboración a CRAN

La colaboración con la comunidad R puede hacerse de diversas formas. Existen sistemas de bug-
tracking, para el reporte y discusión de bugs, manejo de versiones, utilidades diversas como de
testeo de nuevos paquetes, interfaz de intérprete por web y un largo etcétera. La comunidad
R crece a un ritmo sorprendente, y es uno de los mejores ejemplos de cómo la colaboración de
anónimos puede hacer crecer el software libre muy por encima de los programas de software
privativo.
Capı́tulo 2. R 11

CRAN (explicado brevemente en la sección anterior) recibe las colaboraciones de paquetes. Antes
de subir un paquete nuevo, es necesario seguir ciertos pasos que garanticen su funcionabilidad y
documentación, entre otras cosas. El grupo de desarrollo de R ha creado un comando a tal fin:
check . Este comando verifica que el paquete pueda instalarse, que los ejemplos corran y que la
documentación con la cual debe liberarse exista, esté completa y pueda ser procesada por los
formateadores (la documentación de un paquete se crea en los formatos de texto plano, HTML
y TEX). Si es necesario compilar código, también chequea que esto pueda hacerse correctamente.
Se verifica además que la estructura de archivos y directorios sea la adecuada: es necesario que
existan ciertos archivos de configuración y de ayuda, los cuales usualmente contienen scripts de
verificación de librerı́as requeridas e información acerca de las licencias y caracterı́sticas generales.
Este comando debe finalizar su ejecución sin errores ni advertencias para que el paquete sea
aceptado en el repositorio. Con el comando build puede generarse un archivo comprimido listo
para liberar una versión de nuestro paquete. La “entrega” se realiza mediante la carga del
archivo a un repositorio temporario (FTP ) de paquetes y el envı́o de un correo electrónico a los
mantenedores de CRAN .
Capı́tulo 3

Las especificaciones pueden ser provechosas en muchos sentidos: describen propiedades sin in-
miscuirse en implementaciones, son referencia constante para todos los individuos involucrados
de una u otra forma en el proceso de creación de software (investigadores, codificadores, testers,
documentadores, clientes, etc.) y forman la estructura básica para la etapa de codificación. La
matemática ha ayudado a formalizar estos conceptos a través del concepto de tipos.

Z es el nombre de la notación que utilizamos para la especificación de nuestro trabajo. En este


capı́tulo se presentan las notaciones básicas y definiciones de objetos necesarios para compren-
derla. Ellos son: definiciones, abreviaciones, definiciones axiomáticas, definiciones genéricas y
esquemas.

Z es un lenguaje tipado, lo que permite la creación de algoritmos para la verificación automática


de tipos y ámbito de variables. Entre todas las herramientas disponibles a tal fin, elegimos f uzz
para este trabajo, por tener una notación simple y adaptaciones para su impresión en formatos
como LATEX.

Al disponer sólo del tipo de los números enteros (caracterı́stica de Z ), vimos la necesidad de
definir el tipo que represente los números reales (y varios de sus subconjuntos), de modo de
clarificar notaciones y hacer nuestras especificaciones de lectura natural e intuitiva. Para ello nos
basamos en una publicación de R. D. Arthan que axiomatiza este conjunto de forma precisa.

Con esta extensión fue posible definir nuestro esquema de representación de una imagen y a
partir de allı́ modelar los algoritmos que componen este trabajo, y que serán tratados en los
sucesivos capı́tulos.

3.1. Las especificaciones formales

Las especificaciones formales usan la notación matemática para describir de una forma precisa las
propiedades que debe tener un sistema de información, sin restringir excesivamente la forma en
que estas propiedades son alcanzadas. Describen qué debe hacer el sistema sin decir cómo debe

12
Capı́tulo 3. Z 13

hacerlo. Esta abstracción hace de la especificación formal una herramienta útil en el proceso de
desarrollo de sistemas de computación, porque permiten que las preguntas acerca de lo que hace
el sistema puedan ser respondidas de una manera confiable, sin la necesidad de desenmarañar la
información de una masa de código detallada, o especular acerca del significado de frases en una
descripción en prosa imprecisa.

Una especificación formal puede servir como un punto de referencia simple y confiable para
quienes investiguen las necesidades de los clientes, para quienes implementen los programas para
satisfacer esas necesidades, para aquellos que testeen los resultados y para aquellos que escriban
la documentación del sistema. En definitiva, es una herramienta que puede ser útil para todos
los integrantes del proceso de desarrollo.

Al ser independiente del código del programa, la especificación formal de un sistema puede ser
realizada en las primeras etapas del proceso de desarrollo. Aún cuando cambie a medida que se
gane en comprensión del problema y percepción de la evolución de las necesidades del cliente,
puede ser una media apreciable para promover un entendimiento común entre todos los roles
involucrados en el sistema.

Una forma en que la notación matemática puede ayudar a alcanzar estos objetivos es a través
del modelo de tipos de datos matemáticos del sistema. Estos tipos de datos no están orientados a
la representación computacional, pero responden a un conjunto de leyes que hacen posible sacar
conclusiones efectivas acerca del comportamiento que tendrá un sistema especificado.

3.2. El lenguaje de especificación Z

Z es un lenguaje de especificación que trabaja a altos niveles de abstracción. Esto permite que
aún comportamientos complejos puedan ser descriptos precisa y consisamente. Originalmente
propuesto por Jean-Raymond Abrial en 1977 con la ayuda de Steve Schuman y Bertrand Meyer,
fue desarrollado por el grupo de Investigación de Programación de la Universidad de Oxford.
Ha sido sometido en los últimos años a estandarización de la Organizacion Internacional de
Estandarización (ISO).

La semántica de Z es matemática; de esta manera las fórmulas pueden ser manipuladas algebraica
y lógicamente.

En Z usamos la notación de predicados lógicos para describir abstractamente el efecto de cada


operación del sistema, de una forma que permite sacar conclusiones y hacer análisis acerca de
su comportamiento.

La notación está basada en teorı́a de conjuntos y lógica matemática. La teorı́a de conjuntos


usada incluye operadores de conjunto básicos y por comprensión, productos cartesianos y partes
de conjuntos. La lógica matemática es un cálculo de predicados de primer orden. Juntos, forman
un lenguaje matemático que es fácil de entender y, sobre todo, de llevar a la práctica.

Otro aspecto es cómo se puede estructurar este lenguaje. En Z esto se responde con el concepto de
esquemas: una declaración de patrones y restricciones. El lenguaje de esquemas puede ser usado
Capı́tulo 3. Z 14

para describir el estado del sistema, y las formas en que este estado puede cambiar. También
puede describir propiedades del sistema y ayudar a pensar acerca de posibles refinamientos del
diseño.

Los esquemas se utilizan para describir aspectos dinámicos y estáticos. Estos últimos incluyen:

los estados que ocupa; y

las relaciones invariantes que son mantenidas en el movimiento de estado a estado en el


sistema

Los aspectos dinámicos incluyen:

las operaciones posibles;

la relación entre las entradas y las salidas; y

los cambios de estados que pueden ocurrir

Una de las caracterı́sticas principales de Z es el uso de tipos. Además de ser esto un enlace de
extrema utilidad para el momento de la codificación, puede ser sujeto de chequeos automáticos.
Existen varias herramientas a tal fin, entre las que se encuentra f uzz, la cual describiremos
brevemente más adelante (sección 3.4).

Otro aspecto es el uso del lenguaje natural: usamos el lenguaje matemático para determinar el
problema y eventualmente encontrar soluciones, e incluso para probar que los diseños cumplen
con la especificación. El uso del lenguaje natural relaciona la matemática con los objetos de la
vida real, y es esencial para hacer que las especificaciones sean realmente obvias para el lector.

3.3. Definiciones en Z

A modo introductivo presentamos algunos de los conceptos sobre los cuales se basa el lenguaje
de especificación Z , que serán de utilidad para la comprensión de las especificaciones del trabajo.

3.3.1. Declaraciones

Es la forma más simple de declarar un objeto en Z . Se utiliza en especial para tipos básicos o
conjuntos dados. Se denotan por una declaración del nombre entre corchetes:

[A]

Este tipo de declaraciones introduce un nuevo tipo, con lo que podremos declarar variables con
ese tipo en el futuro:
Capı́tulo 3. Z 15

0:A

3.3.2. Abreviaciones

Es la manera en que se puede definir un objeto a partir de otros existentes, cuando sus objetos
y estados son iguales:

VALUE == MinValue . . MaxValue

3.3.3. Definiciones axiomáticas

Se pueden introducir objetos con restricciones, como las que deben asumirse cuando un sı́mbolo
es usado. Estas restricciones se interpretan como axiomas del objeto:

declaracion

predicado

donde predicado simboliza las restricciones del objeto u objetos declarados en declaracion.

Por ejemplo:

TopValue : N

TopValue = MaxValue + 1

Introduce un nuevo sı́mbolo, TopValue, que satisface el predicado que se menciona. Como en
este ejemplo, las declaraciones pueden restringirse hasta el punto que se denote sólo un objeto.

3.3.4. Definiciones genéricas

Se utilizan para definir una familia de constantes globales, parametrizadas por algún conjunto:

[Y ]
y :Y

predicado
Capı́tulo 3. Z 16

introduce una constante genérica de tipo Y, satisfaciendo el predicado predicado. Notar que Y
es, en este caso, un parámetro formal: puede considerarse como un tipo básico con visibilidad en
esta definición genérica.

A modo de ejemplo, tenemos la definición utilizada en el trabajo para obtener el largo de una
secuencia:

[X ]
# : seq X "N
#hi = 0
∀ i : seq X | i 6= hi • # i = 1 + # (tail i)

3.3.5. Esquemas

Además del lenguaje matemático, en Z tenemos el lenguaje de esquemas, usado principalmente


para rejuntar partes de información, encapsularlas y nombrarlas para su futura reutilización. Este
último aspecto es de vital importancia para las técnicas formales: con ello podemos mantener
nuestras descripciones flexibles y manejables.

La forma general de los esquemas es esta:

NombreDeEsquema
declaraciones

predicados

A modo de ejemplo, nuestro esquema para representar una imagen:

Image
v : VALUES
width, height : N

dom v = {a : N × N | 0 ≤ first a < width ∧ 0 ≤ second a < height}

3.4. f uzz

f uzz es un conjunto de herramientas de formateo e impresión de especificaciones en Z , y al-


goritmos para verificaciones de alcance y reglas de tipos conforme a la especificación de este
lenguaje.

Entre las herramientas de formateo se incluyen archivos de estilo para LATEX, y la definición
de un conjunto con sı́mbolos especiales propios de estas especificaciones. Para su uso f uzz pro-
vee, entre otros, de los siguientes entornos, los cuales fueron mencionados en la sección 3.3: zed ,
Capı́tulo 3. Z 17

axdef , gendef y schema, respectivamente para texto en prosa y fuera de estructuras, definicio-
nes axiomáticas, definiciones genéricas y esquemas. Existen otros entornos disponibles que no
mencionaremos en este breve resumen.

Para este trabajo hicimos uso de sus dos funcionalidades principales. En la impresión actual se
utilizaron las herramientas que permiten que los diagramas y sı́mbolos especiales puedan verse
correctamente y mezclarse con texto en prosa, como es caracterı́stico en muchos formatos de
especificación. Y para la diagramación del código Z para los algoritmos implementados, hicimos
uso del chequeador de tipos y alcance de variables, lo cual es mı́nimamente necesario en cualquier
chequeo de especificaciones.

El comando f uzz puede configurarse para tener dos tipos de salida: con la opción −v obtenemos
un reporte en código ASCII de una representación de cada párrafo en Z ; y con la opción −t se
listan el tipo de cada nombre definido globalmente, en una representación fácil de leer. Además,
los esquemas son expandidos, para que resulte claro ver qué componente tiene cada uno. La
salida de esta última opción se incluye en formato digital con este trabajo (tal como se describe
en la sección 6.6).

3.5. Especificación en Z

3.5.1. Especificación de reales

En la especificación de software generalmente vienen incluidas ciertas nociones de tipos. En Z ,


esta noción es muy acotada: un tipo es un conjunto maximal, al menos para los lı́mites de la
especificación en cuestión. Esto trae como consecuencia que cada valor x en una especificación
esté asociado exactamente a un tipo: el conjunto más grande s para el cual x ∈ s.

La notación Z tiene un solo tipo built − in (esto es, propio de la notación): el conjunto de todos
los enteros Z . Cualquier otro tipo puede construirse a base de éste, o de valores de tipos básicos
(sobre los cuales no pueden asumirse ninguna propiedad).

Muchos de los algoritmos que presentamos en nuestra implementación requieren de una precisión
que los enteros no nos brindan de forma natural. Es fácil determinar una biyección entre los
números enteros y los reales de precisión acotada, pero el manejo de los mismos se torna tedioso
y la representación no obedece a las costumbres sobre el manejo de valores que arrastramos
en la educación que recibimos. Por esta razón, y por la estructura de imágenes que creimos
conveniente utilizar (aunque esta estructura y la representación de valores están ı́ntimamente
relacionadas) y que mencionaremos en esta sección, es que necesitamos la especificación de un
tipo que represente más fidelignamente a los reales.

Para tal fin nos basamos en la publicación de [Art96], “Arithmetics for Z”, la cual está fuertemen-
te inspirada en el estándar [Dep95]. La especificación que realizamos incluye la axiomatización
necesaria para definir el conjunto de los números reales y sus operaciones básicas (de acuerdo a
lo que nos resultaba excluyente disponer).

La axiomatización se caracteriza por tres propiedades de los números reales:


Capı́tulo 3. Z 18

1. Los reales forman un campo

2. El campo de los reales puede ordenarse linealmente de forma que este orden sea compatible
con la suma y la multiplicación. Para definir dicho orden es suficiente con encontrar un
conjunto R, cerrado por multiplicación y suma, tales que Rp , Rn y {0} conformen una
partición del campo.

3. Cualquier subconjunto no vacı́o de los reales, acotado inferiormente con respecto al orden
establecido en el punto anterior, tiene una cota inferior maximal.

Estas propiedades caracterizan a los reales (o cualquier isomorfismo) y una consecuencia de ello
es la existencia de un anillo incluido en este conjunto, que es isomorfo a los enteros.

Esta axiomatización es similar a las vistas en los libros de cálculo. Comenzamos con un conjunto
maximal, que llamamos A

[A]

A partir de él, definimos el conjunto Z (el cual “redefinimos”),

Z : A

y dos de sus elementos:

0:A
1:A

El resto de operaciones y axiomas se detallan a continuación:

+ :A×AA

:AA
N : Z

(Z × Z )  ( + ) ∈ Z × Z "Z
Z (∼
)∈Z "Z
{0, 1} ⊆ Z
∀ i , j , k : Z • (i + j ) + k = i + (j + k )
∧i +j =j +i
∧ i + ∼i = 0
∧i +0=i
∀ h : Z • 1 ∈ h ∧ (∀ i, j : h • i + j ∈ h ∧ ∼ i ∈ h) ⇒ h = Z
N = {s : Z | 0 ∈ s ∧ {i : s • i + 1} ⊆ s}
T

1∈
/N
Capı́tulo 3. Z 19

− :A×AA

(Z × Z )  ( − ) ∈ Z × Z "Z

∀ i, j : Z • i − j = i + ( j )

≤ , < , ≥ , > :A#A

∀ i , j : Z • (i ≤ j ⇔ j − i ∈ N )
∧ (i < j ⇔ i + 1 ≤ j)
∧ (i ≥ j ⇔ j ≤ i )
∧ (i > j ⇔ j < i )

∗ :A×AA

(Z × Z )  ( ∗ ) ∈ Z × Z "Z
∀ i , j , k : Z • (i ∗ j ) ∗ k = i ∗ (j ∗ k )
∧i ∗j =j ∗i
∧ i ∗ (j + k ) = i ∗ j + i ∗ k
∧1∗i=i

div , mod : A × A  A

(Z × Z \ {0})  ( div ) ∈ Z × Z " Z


(Z × Z \ {0})  ( mod ) ∈ Z × Z " Z
∀ i : Z • ∀ j : Z \ {0} • i = (i div j) ∗ j + i mod j
∧ (0 ≤ i mod j < j ∨ 0 ≥ i mod j > j)

R : 1 A
/ :A×AA

(R × R)  ( + ) ∈ R × R " R
(R × R)  ( ∗ ) ∈ R × R " R
(R × R \ {0})  ( / ) ∈ R × R \ {0} " R
R  (∼ ) ∈ R " R
Z ⊆R
∀ x , y, z : R • (x + y) + z = x + (y + z )
∧x +y =y +x
∧ x + ∼x = 0
∧x +0=x
∀ x , y, z : R • (x ∗ y) ∗ z = x ∗ (y ∗ z )
∧x ∗y =y ∗x
∧ x ∗ (y + z ) = x ∗ y + x ∗ z
∧1∗x=x
∀ x : R • ∀ y : R \ {0} • (x / y) ∗ y = x
Capı́tulo 3. Z 20

Rp, Rn : 1 A

(Rp × Rp)  ( + ) ∈ Rp × Rp " Rp


(Rp × Rp)  ( ∗ ) ∈ Rp × Rp " Rp
Rn = (∼ )Rp 
Rn ∩ Rp = 
R = Rn ∪ {0} ∪ Rp
∀ x , y : R • x ≤ y ⇔ y + ∼ x ∈ Rp ∪ {0}

Con esta “creación” del tipo R, muchas de las operaciones sobre imágenes que fueron especificadas
(y que se mostrarán pertinentemente, a medida que lo consideremos necesario) resultaron más
claras e intuitivas.

3.5.2. Resto de las especificaciones

A partir de nuestro esquema de imagen

Image
v : VALUES
width, height : N

dom v = {a : N × N | 0 ≤ first a < width ∧ 0 ≤ second a < height}

se especificaron las operaciones sobre imágenes que corresponden al presente trabajo. Las mismas
se presentarán en las secciones particulares de los algoritmos, cuando creamos necesario hacer
alguna aclaración. De todas formas, los archivos correspondientes a estas descripciones pueden
encontrarse en formato digital, con el material que acompaña este impreso (ver sección 6.6 para
más detalles).

Nótese que no se hacen diferencias de acuerdo a la cantidad de canales que tenga la imagen en
cuestión. Esto fue una decisión arbitraria y responde a una necesidad de claridad de notación y en
algunos casos a similitudes en los diversos canales de una imagen. Vale decir que las especificacio-
nes realizadas en Z nos guiaron a través de nuestro desarrollo, pero no nos restringieron. Es por
eso que algunas caracterı́sticas esperadas en las imágenes resultantes de la aplicación de algún
algoritmo sólo se describe a través de una definición axiomática y algunas otras directamente se
asumen como disponibles para su uso.
Capı́tulo 4

Imagen digital

Cuando se captura una imagen del mundo real a través de una computadora, la continuidad de
tamaño, intensidad y colores es truncada. La combinación de caracterı́sticas fı́sicas continuas que
nuestra mente se encarga de manejar deben ser convertidas en números finitos para ser utilizados
por una computadora. Esa visión continua debe ser discretizada para obtener una imagen digital.
En esa conversión se determinan la resolución espacial y la profundidad de color.

La representación de imágenes color se basa en los denominados espacios de color, modelos


matemáticos para especificar los colores. La mayorı́a de estos modelos en uso están orientados
o bien hacia el hardware o bien hacia aplicaciones en que la manipulación de los colores es el
principal objetivo.

4.1. Representación

Una imagen se puede definir como una función de dos dimensiones, f (x , y), donde x , y son
coordenadas espaciales, en el plano, y la amplitud de f en cualquier par de coordenadas (x , y)
se llama intensidad de la imagen en ese punto. La denominación escala de grises se usa para
referirse a la intensidad en imágenes monocromáticas. Las imágenes en color están formadas por
la combinación de imágenes 2-D. Por ejemplo, en el sistema de color RGB (red, green, blue),
una imagen consiste de tres imágenes componentes individuales (rojo, verde, azul). Por esta
razón, muchas de las técnicas desarrolladas para imágenes monocromáticas se pueden extender
a imágenes color mediante el procesamiento de cada una de las componentes individuales. En
general hablaremos en términos de imágenes en escala de grises, haciendo las aclaraciones y
distinciones para extender a imágenes color cuando sea necesario.

Una imagen puede ser continua respecto a los ejes de coordenadas, como ası́ también en am-
plitud. Convertir dicha imagen a formato digital requiere que tanto las coordenadas como la
intensidad sean digitalizadas. El proceso de digitalizar las coordenadas se llama sampling (mues-
treo), mientras que el de digitalizar la amplitud se llama quantization. De esta manera, cuando
x , y, y la amplitud de f son valores finitos y discretos tenemos una imagen digital.

21
Capı́tulo 4. Imagen digital 22

El resultado de sampling y quantization es una matriz de números reales. Asumiendo que f (x , y)


es muestreada a una imagen que tiene M filas y N columnas, decimos que la imagen tiene tamaño
M × N . El origen de la imagen lo definimos en (x , y) = (0, 0). La siguiente coordenada a lo largo
de la primera fila es (x , y) = (0, 1). Es decir, que de acuerdo con la notación de matrices, el eje
vertical, y, recorre la imagen de arriba hacia abajo. El eje horizontal, x , la recorre de izquierda a
derecha. De esta manera podemos representar nuestra imagen digital como una matriz M × N :

Figura 4.1: Matriz imagen

El lado derecho de la igualdad es por definición una imagen digital. Cada elemento de esta matriz
se llama pixel (picture element). Usaremos los términos imagen y pixel de aquı́ en adelante para
denotar una imagen digital y sus elementos, respectivamente.

En el proceso de digitalización se deben tomar decisiones sobre los valores de M , N , y para


el número L de niveles de gris permitidos para cada pixel. No hay restricciones sobre M y N ,
sólo que deben ser enteros positivos. Sin embargo, debido al tipo de procesos, almacenamiento y
hardware de sampling, el número de niveles de gris es en general un entero potencia de 2: L = 2k .
Se asume también que estos niveles son equidistantes y que son enteros en el intervalo [0, L − 1].

4.2. Resolución espacial y de profundidad

El sampling determina la resolución espacial de una imagen. La resolución espacial define el me-
nor detalle discernible en una imagen. Supongamos que tenemos un cuadro con lı́neas verticales
de ancho W , con un espacio entre estas lı́neas también de ancho W . Un par consiste de una
lı́nea y el correspondiente espacio adyacente. Entonces el ancho de un par es 2W , y hay 1/2W
pares por unidad de distancia. Una definición de resolución es simplemente el menor número de
pares discernibles por unidad de distancia; por ejemplo, 100 pares por milı́metro.

Hay que tener en cuenta que cada pixel no representa sólo un punto en la imagen, sino una región
rectangular. De esta forma, con pixels grandes no sólo la resolución espacial es baja, sino que el
valor del nivel de gris correspondiente hace aparecer discontinuidades en los bordes de los pixels.
A medida que los pixels se hacen más pequeños, el efecto se hace menos pronunciado, hasta el
punto en que se tiene la sensación de una imagen continua. Esto sucede cuando el tamaño de
los pixels es menor que la resolución espacial de nuestro sistema visual. Para una tarea dada el
tamaño de pixel deberı́a ser lo suficientemente pequeño de acuerdo a los objetos que queramos
estudiar de la imagen.

La resolución de profundidad se refiere a la cantidad de bits que se utilizan para representar


la intensidad de un pixel, es decir el menor cambio distinguible en el nivel de gris. Como ya se
Capı́tulo 4. Imagen digital 23

ha dicho, principalmente debido a restricciones de hardware, en general el número de niveles de


gris es un entero potencia de 2, comúnmente 8 bits, aunque algunas aplicaciones que requieren
mucha precisión en este sentido pueden llevarlo a 16.

4.3. Modelos de color

Lo que los humanos percibimos como color es una combinación de caracterı́sticas fı́sicas. Un
modelo (o espacio) de color es una representación matemática de esas caracterı́sticas. El objetivo
es también facilitar la especificación de colores mediante alguna forma estándar y aceptada. En
esencia se tratan de sistemas de coordenadas y subespacios en que cada color se representa por
un único punto.

Brevemente repasaremos estos distintos esquemas. Si bien la mayorı́a de los procesos con imáge-
nes digitales trabajan en RGB, muchas aplicaciones requieren la conversión a otros espacios de
color.

4.3.1. RGB

Todos los espacios de color son sistemas ortogonales tridimensionales de coordenadas, es decir
que los tres ejes (en este caso las intensidades de rojo, verde y azul) son perpendiculares entre
sı́. La intensidad del rojo empieza en cero y se incrementa en uno de los ejes. Análogamente
para el verde y el azul en sus correspondientes ejes. Asumiendo 8 bits de profundidad, cada color
puede tener un valor máximo de 255, dando como resultado una estructura cúbica. La escala de
grises (puntos de valores RGB iguales) se extiende desde el negro hasta el blanco, a lo largo de
la diagonal que une estos dos puntos.

Figura 4.2: Modelos de color RGB y CYM


Capı́tulo 4. Imagen digital 24

De esta manera tenemos un modelo matemático que nos permite definir cualquier color dando
sus valores de rojo, verde y azul, es decir coordenadas en el cubo. El RGB es un espacio de color
aditivo, porque su origen está en el negro y cualquier otro color se deriva sumando valores de
intensidad. Es el modelo usado en la práctica para los monitores color y muchas cámaras de
video.

4.3.2. CYM

Este espacio de color es el inverso exacto del RGB. En este caso, el origen es blanco y los ejes
primarios son cyan, amarillo y magenta. Ası́, el color rojo es una combinación de amarillo y
magenta, el verde de amarillo y cyan, y el azul de cyan y magenta. A continuación se detallan
las ecuaciones que permiten pasar de un sistema a otro:

c = max − r m = max − g y = max − b


r = max − c g = max − m b = max − y

(max es el valor máximo de intensidad)

Si se muestra una imagen en CYM como si fuera RGB veremos una imagen con sus colores
invertidos o negativos. El CYM se usa principalmente en la industria de la impresión, donde
las imágenes empiezan sobre un papel blanco y la tinta se aplica para obtener los colores. Se
han desarrollado técnicas para obtener imágenes de mayor calidad y a un menor costo. Uno de
estos avances es el llamado “under color removal” que modifica CYM en CYMK, donde la K
representa al negro.

Este proceso, sabiendo que todo color tiene un gris subyacente, es decir una misma cantidad de
cyan, magenta y amarillo, genera esa componente con tinta negra (más barata) y utiliza menor
cantidad de tinta de color para lograr el tono correcto.

4.3.3. HSI

La visión humana tiende a observar los colores de una forma diferente. No vemos las cosas como
una mezcla de colores primarios en una proporción particular, sino como tonos (hue), saturación
(saturation) e intensidad (intensity). Todavı́a se trata de un espacio tridimensional, aunque
bastante diferente del RGB o CYM.

En la imagen 4.3 vemos un eje que recorre el centro del cono, que representa la intensidad. Sobre
este eje se encuentran todos los valores de gris, con el negro en el origen del cono y el blanco
en la base. Cuanto mayor es la distancia sobre esta lı́nea al origen, la intensidad es mayor, más
brillante.

Si vemos la base del cono desde arriba, se convierte en un cı́rculo. Los diferentes tonos están
definidos por posiciones especı́ficas alrededor del cı́rculo. Los tonos están dados por su posición
angular en esta rueda.
Capı́tulo 4. Imagen digital 25

Figura 4.3: Modelo de color HSI

La saturación, o riqueza de color, está definida como la distancia perpendicular al eje de intensi-
dad. Los colores más cercanos al eje central tienen menor saturación y se ven pastel. Los colores
cercanos al borde del cono tienen mayor saturación y son más marcados en apariencia. A veces
es preferible modificar una imagen en HSI en lugar de RGB. Por ejemplo, si quisiéramos cambiar
el color amarillo de un auto a azul, pero sin afectar el brillo ni las sombras. Esto es relativamente
sencillo en HSI. Basta cambiar el valor de tono, sin modificar la intensidad ni la saturación.

4.4. Nuestra implementación

Siguiendo el esquema visto hasta aquı́ elegimos representar una imagen digital mediante una
matriz. Nos inclinamos por usar matrices de R, de dos dimensiones si la imagen tiene un único
nivel de profundidad de color o tres dimensiones si se trata de imágenes RGB, el espacio de color
base del cual partimos. Sin embargo, esta decisión también afectarı́a nuestra forma de trabajar
en el lenguaje C.

Esta elección significarı́a manejar arreglos lineales en C con una distribución particular de los
datos, que es la forma en que R hace la conversión de matrices. Para hacer más comprensible el
manejo de ı́ndices sobre dicho arreglo se definió una macro que hace la traducción de coordenadas
en la imagen a ı́ndices en ese arreglo lineal.

Dada la siguiente matriz imagen

(r0,0 , g0,0 , b0,0 ) (r0,1 , g0,1 , b0,1 ) (r0,2 , g0,2 , b0,2 ) ···
(r1,0 , g1,0 , b1,0 ) (r1,1 , g1,1 , b1,1 ) (r1,2 , g1,2 , b1,2 ) ···
.. .. .. ..
. . . .
Capı́tulo 4. Imagen digital 26

el correspondiente arreglo lineal que se obtiene en C tras la traducción es:

r0,0 r1,0 ... r0,1 r1,1 ··· g0,0 g1,0 ··· b0,0 b1,0 ···

Los formatos de imagen soportados son jpeg, a través de la librerı́a libjpeg, y tiff, mediante libtiff.
A partir de ellas se desarrollaron las funciones para leer y escribir archivos de imágenes. libjpeg es
una librerı́a escrita en C que implementa un codificador/decodificador JPEG. Es mantenida por
el Grupo JPEG Independiente 1 . La versión actual es la 6b. Similarmente, libtiff2 es una librerı́a
que permite leer y escribir archivos en formato TIFF. Actualmente la última versión estable es la
3.8.2. Ambas librerı́as son libres, y se distribuyen tanto su código fuente como versiones binarias
para distintas plataformas.

4.4.1. Especificación

A lo largo del trabajo se explican las distintas técnicas y filtros mediante especificaciones en el
lenguaje Z. A continuación se describen los esquemas que caracterizan a la representación de
imagen elegida.

Existen un valor mı́nimo y un valor máximo. Para el caso de imágenes de 8 bits de profundidad,
tendremos MinValue = 0 y MaxValue = 255.

MinValue, MaxValue : N

Los posibles valores para cada pixel oscilan en el intervalo determinado por el mı́nimo y máximo
dados.

VALUE == MinValue . . MaxValue

VALUES define el espacio que va de un par (que representa las coordenadas de la imagen) en
un VALUE . Especifica el espacio de las matrices imagen.

VALUES == (N × N  VALUE )

El esquema estado de una imagen está dado por una matriz, y las dimensiones de alto y ancho.
En este caso se trata de imágenes con una sola componente de color.

Image
v : VALUES
width, height : N

dom v = {a : N × N | 0 ≤ first a < width ∧ 0 ≤ second a < height}

1 http://www.ijg.org/
2 http://www.remotesensing.org/libtiff
Capı́tulo 5

El procesamiento digital de
imágenes

La vista es el más avanzado de nuestros sentidos, tal es ası́ que las imágenes tienen un papel
importante en la percepción humana. Sin embargo, a diferencia del ser humano que está limitado
a la banda visual del espectro electromagnético, las máquinas pueden cubrir distintas bandas,
desde las ondas gamma hasta las de radio. Pueden trabajar con imágenes generadas a partir de
fuentes que los humanos no están acostumbrados a asociar con imágenes: ultrasonido, visualiza-
ción de modelos matemáticos o visión por computadora, por citar algunos ejemplos. El campo
del procesamiento digital de imágenes se refiere al proceso de trabajar con imágenes digitales
mediante computadoras. Cubre una amplia gama de técnicas, utilizadas en numerosas aplica-
ciones: para mejorar o distorsionar una imagen, destacar ciertas caracterı́sticas, crear una nueva
imagen desde otras o restaurar una imagen degradada (por transmisión, adquisición). Actual-
mente puede ser llevada a cabo por cualquier persona con una computadora personal. De esta
manera se observa el uso de técnicas de procesamiento de imágenes entre artistas, cientı́ficos y
otros, aún sin conocimientos especı́ficos.

5.1. Orı́genes

Una de las primeras aplicaciones de las imágenes digitales fue en la industria de los periódicos,
cuando se enviaban fotos a través de un cable submarino entre Londres y Nueva York. De esta
forma se redujo la transmisión de una foto a través del Atlántico, en 1920, de más de una semana
a menos de tres horas. Un sistema de impresión especializado recibı́a y reconstruı́a las imágenes
codificadas enviadas a través del cable. Algunos de los problemas iniciales fueron mejorar la
calidad visual de estas imágenes en función los procedimientos de impresión y la distribución de
los niveles de intensidad.

Hasta ese momento tenemos ejemplos que involucran imágenes digitales, pero que no pueden
considerarse como ejemplos de procesamiento digital de imágenes, ya que no habı́a computadoras

27
Capı́tulo 5. El procesamiento digital de imágenes 28

en la generación de las mismas. Entonces, la historia del procesamiento de imágenes se encuentra


ligada al desarrollo de las computadoras y la tecnologı́a asociada (almacenamiento, visualización,
transmisión).

Las primeras computadoras suficientemente poderosas para ejecutar tareas significativas de pro-
cesamiento de imágenes aparecieron en la década del ’60. El nacimiento de lo que consideramos
el procesamiento digital de imágenes se puede remontar a la disponibilidad de esas máquinas y
el desarrollo del programa espacial de ese perı́odo. La combinación de estos dos factores sacó a la
luz el potencial del campo de procesamiento de imágenes. El uso de técnicas con computadoras
para mejorar imágenes espaciales empezó en el Jet Propulsion Laboratory (California) en 1964,
donde las imágenes de la Luna transmitidas por el Ranger 7 fueron procesadas por una compu-
tadora para corregir diferentes distorsiones inherentes a la cámara de televisión utilizada. Estas
técnicas constituyeron la base para nuevos métodos que se utilizarı́an más tarde para mejorar y
restaurar imágenes de misiones posteriores.

En paralelo a las aplicaciones espaciales, las técnicas de procesamiento digital de imágenes se


comenzaron a usar en medicina, observaciones remotas de la Tierra y astronomı́a (1960-70). La
invención de la tomografı́a computada es uno de los hechos más importantes de la aplicación del
procesamiento de imágenes en el diagnóstico médico. Desde 1960 hasta nuestros dı́as, el campo
del procesamiento de imágenes ha crecido de forma importante. Además de su aplicación en la
medicina y las actividades espaciales, se ha extendido a múltiples áreas. Se usan procedimien-
tos por computadora para realzar el contraste o codificar los niveles de intensidad en colores
para facilitar la interpretación de imágenes de rayos X y otros tipos utilizados en la industria,
la medicina y la biologı́a. Los geógrafos usan técnicas similares para estudiar los patrones de
contaminación del aire e imágenes satelitales.

Los procedimientos para mejorar y restaurar imágenes se utilizan para procesar imágenes de-
gradadas de objetos irrecuperables o resultados experimentales demasiados costosos de repetir.
En arqueologı́a, por ejemplo, se usan estos métodos para restaurar imágenes con ruido que son
el único registro de artı́culos raros, perdidos o dañados después de ser fotografiados. En fı́sica
y campos relacionados se usan técnicas para procesar imágenes de experimentos en áreas ta-
les como plasma de alta energı́a y microscopı́a del electrón. Y de la misma manera se pueden
encontrar casos de aplicación en astronomı́a, biologı́a, medicina nuclear, defensa o en la industria.

Todos estos ejemplos ilustran la utilidad de los resultados del procesamiento de imágenes con
la finalidad de la interpretación del hombre. La segunda mayor área de aplicación del proce-
samiento de imágenes es en el tratamiento de problemas relacionados con la percepción de las
máquinas. En estos casos el interés se centra en procedimientos para extraer información de una
imagen para ser utilizada por una máquina, y por lo tanto, no necesariamente estos resultados
tienen que ver con las formas de interpretación humana. Ejemplos de información utilizada por
las máquinas son los momentos estadı́sticos, los coeficientes de la transformada de Fourier y me-
didas de distancias multidimensionales. Problemas tı́picos en este campo son el reconocimiento
automático de caracteres, visión de máquinas, aplicaciones militares, procesamiento de huellas
digitales, visualización de rayos X y muestras de sangre, y procesamiento de imágenes satelitales
para la predicción del clima y análisis del medio ambiente.
Capı́tulo 5. El procesamiento digital de imágenes 29

5.2. Aplicaciones

El uso del procesamiento digital de imágenes se ha ido extendiendo a distintas áreas, y ha dejado
de ser una actividad exclusiva de un grupo de cientı́ficos, para ir teniendo cada vez mayor impacto
en nuestra vida cotidiana. A continuación se describen algunas aplicaciones especı́ficas.

5.2.1. Astronomı́a y exploración del espacio

Este campo ha sido desde el comienzo una de las áreas más activas en el desarrollo de técnicas
y avances en el procesamiento digital de imágenes. Debido a las señales débiles en la captura de
imágenes de los objetos celestes, se debieron desarrollar métodos para extraer información; es
ası́ como surgen muchos de los filtros disponibles hoy: promedio de imágenes, filtros de convolu-
ción y transformadas de Fourier, por ejemplo.

Los sistemas de imágenes diseñados en esta área, en general, atribuyen menor importancia al
color, buscando el detalle. Es por eso que en gran medida se trabaja con imágenes en escala de
grises, aunque en algunos casos se añaden colores para resaltar determinada información.

5.2.2. Inteligencia y aplicación militar

En este caso se utiliza como herramienta para la interpretación de fotografı́as, con el objetivo
de identificar áreas de interés y extraer toda la información posible de la imagen. Puede ser en
búsqueda de instalaciones militares, facilidades para la investigación, complejos industriales o
estructuras residenciales. Una de las principales necesidades es la velocidad. Se hace zoom sobre
determinadas zonas de una imagen, rotaciones para lograr una perspectiva particular, o puede
ser necesario mejorar el contraste de la fotografı́a. Adicionalmente también se requiere hacer
anotaciones sobre la imagen.

Otro uso en este campo es la combinación de mapas digitalizados e imágenes satelitales para el
mejor conocimiento de una zona dada, sumado a la reconstrucción del terreno y animaciones,
que permiten conocer las caracterı́sticas topográficas del lugar.

5.2.3. Ciencias de la tierra

Los geólogos pueden aprender mucho de imágenes tomadas de la superficie. Pueden identificar
fácilmente fallas en la corteza de la Tierra, especialmente a partir de imágenes multiespectrales,
es decir cuando se cuenta con muchas imágenes capturadas de una misma área en diferentes
espectros electromagnéticos.

Las imágenes multiespectrales se utilizan también en la explotación de petróleo y minerales. Se


pueden determinar los mejores lugares para perforar o minar estudiando las macro estructuras
donde tienden a encontrarse el gas natural o los metales preciosos. Con sensores y radares se
pueden capturar y mapear imágenes del fondo del océano. También se utilizan sensores para
buscar patrones en las imágenes del clima, incrementando las capacidades de pronóstico.
Capı́tulo 5. El procesamiento digital de imágenes 30

5.2.4. Gobierno

Ası́ como se aplica el procesamiento de imágenes para el mapeo y exploración de recursos,


los gobiernos pueden utilizar las mismas técnicas con otros propósitos. Una industria que ha
crecido mucho son los denominados Sistemas de Información Geográfica (GIS, por sus siglas en
inglés). Los usos de GIS son amplios y variados. Se puede hacer seguimiento de proyectos de
construcción mediante fotografı́as aéreas. Mapas de centros de población se pueden relacionar
con el cubrimiento de determinados servicios. A partir de información hidrográfica y un mapa
de elevación del terreno se pueden definir potenciales zonas de inundación. Todas estas funciones
requieren distintas técnicas de procesamiento que combinan imágenes con información gráfica y
textual.

Este tipo de análisis puede ayudar a los gobiernos a estimar el crecimiento urbano y el planea-
miento de facilidades y servicios. La representación visual de los datos abstractos en general
ofrece una mejor vista de situaciones del mundo real que los números y las estadı́sticas.

5.2.5. Visualización de datos

Mucho del trabajo de cientı́ficos e ingenieros dedicados a la investigación involucra simulaciones


de problemas fı́sicos reales o potenciales usando modelos matemáticos. Es razonable presentar
estos datos numéricos de una manera visual. Ası́ se usan histogramas para analizar datos en una
dimensión. Para el caso de dos dimensiones se puede utilizar alguna forma gráfica o incluso una
imagen, en que la ubicación de un pixel es función de los parámetros de entrada y la intensidad
representa la magnitud u otro resultado de algún cálculo.

5.2.6. Entretenimiento

La industria del entretenimiento se ha convertido en los últimos años en una de las principales
usuarias del procesamiento de imágenes. Los efectos visuales no se usan sólo en pelı́culas y
televisión, sino también en parques temáticos y eventos especiales. El uso de computadoras
transformó la industria y abrió la posibilidad al desarrollo de la creatividad. De hecho, el uso del
procesamiento digital de imágenes en la industria del entretenimiento impulsa el avance de los
lı́mites tecnológicos en lo que a computadoras y almacenamiento de datos se refiere.

5.2.7. Medicina

La medicina ha usado imágenes digitales durante muchos años, y nuevas técnicas hacen que
esta tendencia vaya en aumento. Los métodos en este campo son limitados, aunque hay que
tener en cuenta que deben proveer gran precisión y confiabilidad puesto que en muchos casos
está la vida en juego. Podemos citar por caso el uso de rayos X, como un método no intrusivo
que permite investigar un cuerpo, mostrando detalles finos de sus estructuras internas y que
se utiliza para diagnóstico y tratamiento. Actualmente estas imágenes se pueden digitalizar, y
Capı́tulo 5. El procesamiento digital de imágenes 31

además de integrar esa información en bases de datos, se tiene la posibilidad de realzar, escalar,
rotar, filtrar y manipular los datos de distintas maneras.

5.2.8. Procesamiento de documentos

Existen diversas técnicas especializadas para operar sobre este tipo de datos. Una de las áreas
de mayor investigación es la de la compresión. Sin embargo, muchas veces contamos con esa
información en forma de imagen. Ası́ surge la necesidad de convertir una imagen digital en ca-
racteres ASCII. Este proceso se denomina Reconocimiento Óptico de Caracteres (OCR). Usando
distintas operaciones y filtros sobre la imagen, ésta se puede reducir a sus partes mı́nimas y luego
aplicar técnicas de búsqueda de patrones para distinguir los caracteres.

5.2.9. Aplicaciones industriales y visión de máquinas

Ası́ como los robots se han hecho cargo de tareas repetitivas o peligrosas, también se les ha dado
la habilidad de “ver” y tomar decisiones basadas en esas observaciones.

Una aplicación es el ordenar y reconocer objetos, por ejemplo los productos que vienen en una
cinta transportadora. Se toma una captura de imagen, y usando filtros de contraste, threshold
y otras técnicas, se pueden aislar e inspeccionar objetos individuales mediante un software es-
pecializado, y determinar la corrección de un objeto para pasar a una siguiente etapa en el
proceso.

5.2.10. Aplicaciones hogareñas

Finalmente el procesamiento digital de imágenes ha llegado también al hogar. A medida que se


va haciendo más común el uso de cámaras digitales, surge para el usuario la necesidad, a través
de su computadora personal, de manipular las imágenes capturadas. En general se trata de
operaciones por punto y procesos por vecino para el filtrado, corrección de color y composición.
Capı́tulo 6

biOps: un paquete de
procesamiento de imágenes para
R

biOps 1 , acrónimo de Basic Image Operations, es el nombre del paquete publicado en los reposi-
torios de R con los algoritmos que en su mayorı́a se decriben en este trabajo. El nombre se ha
instaurado por razones históricas, al ser la primer idea del proyecto la publicación de varios pa-
quetes, con el mismo contenido que el actual dividido de acuerdo a su funcionalidad. Esta idea se
descartó por razones de experiencia en el uso de los paquetes y de dependencias y funcionalidades
en común entre ellos.

En este capı́tulo se describen otros paquetes R para el manejo de imágenes, parte de la investi-
gación previa al desarrollo de biOps. A continuación se detallan los componentes del paquete y
una introducción a su interfaz gráfica de usuario (biOpsGUI), el testing realizado, la estructura
y el contenido del material en formato digital que acompaña el presente impreso y la organiza-
ción de los próximos capı́tulos, en donde profundizaremos conceptos, teorı́a y codificación de los
algoritmos implementados.

Para una visión global del contenido y funcionalidad provista por el paquete, recomendamos la
lectura de este capı́tulo. Para entrar en detalle en algún algoritmo o área particular, puede ser
conveniente la lectura del capı́tulo correspondiente.

6.1. Otros paquetes R de manejo de imágenes

Nuestro estudio previo incluyó un rastreo y análisis de paquetes de R relacionados con el ma-
nejo y procesamiento de imágenes. En la actualidad, no hay muchos antecedentes en CRAN, el
repositorio oficial de paquetes R (analizado en la sección 2.2). Aquı́ una lista de paquetes que
analizamos y un breve comentario de ellos:
1 http://cran.r-project.org/src/contrib/Descriptions/biOps.html

32
Capı́tulo 6. biOps: un paquete de procesamiento de imágenes para R 33

adimpro 2 : maneja formatos de imágenes pgm, ppm y pnm, los cuales no serán tratados en
este trabajo. Si se tiene instalada la librerı́a ImageMagick soporta más formatos y cambio
de representaciones (algo que analizamos en 4.3 ya que esta librerı́a también resultó del
interés de biOps, como se detalla en 14.1). Provee funcionalidad de rotar imagen, unos pocos
métodos de detección de bordes y extracción de máscaras para aplicación de algoritmos
mediante el Propagation-Separation approach 3 , un enfoque de imágenes que se basa en
adaptación estructural, las cuales usan aproximaciones por modelos parámetricos. Este
último enfoque es central en los algoritmos de este paquete.

edci 4 : provee algunos métodos de detección de puntos en bordes mediante algoritmos ba-
sados en M-estimators, un concepto que utiliza la librerı́a de modelado en Java, JVMA.

PET 5 : algoritmos para escalar y rotar imágenes en formatos pet y fif. Pueden utilizarse
más formatos, pero requieren del paquete adimpro. Provee también implementaciones de
algunas transformaciones, como la de Hough, Radon y Radon inversa6

rimage 7 : un paquete con implementación de algoritmos multi propósito para imágenes jpeg.
Provee métodos de lectura de archivos, filtros pasalto y pasabajo, un par de algoritmos de
detección de bordes (Sobel y Laplace), filtro por transformada de Fourier y de impresión
de imágenes por pantalla.

biOps es más abarcativo que los paquetes mencionados, tanto en ramas del procesamiento digital
de imágenes y diversidad de algoritmos, como en alternativas de implementación (interpolación
-capı́tulo 8- y generalidad en detección de bordes -capı́tulo 10-, por ejemplo). El paquete rima-
ge es, actualmente, el único que presenta algunos algoritmos multi propósito, pero no ha sido
actualizado desde principios de 2005.

6.2. Estructura del paquete

La estructura de biOps (y en general, salvo algunas excepciones, de los paquetes R) es la siguiente:

ChangeLog
configure
data /
DESCRIPTION
inst /
LICENSE
man /
biOps-package . Rd
imgAdd . Rd
...
NAMESPACE
R/
arithmetics . R

2 http://cran.r-project.org/src/contrib/Descriptions/adimpro.html
3 http://www.wias-berlin.de/project-areas/stat/projects/aws.html
4 http://cran.r-project.org/src/contrib/Descriptions/edci.html
5 http://cran.r-project.org/src/contrib/Descriptions/PET.html
6 http://eivind.imm.dtu.dk/staff/ptoft/ptoft papers.html
7 http://cran.r-project.org/src/contrib/Descriptions/rimage.html
Capı́tulo 6. biOps: un paquete de procesamiento de imágenes para R 34

convolution . R
...
README
src /
arithmetics . c
convolution . c
...

Los archivos ChangeLog, DESCRIPTION, LICENSE y README contienen información acerca


de los cambios entre las versiones del paquete, la descripción que aparecerá en el repositorio, una
copia de la licencia e información de ayuda, respectivamente.

En configure y NAMESPACE se incluyen directivas para la instalación (compilado, linkeado,


chequeo de dependencias, etc.) y órdenes para la carga dinámica del paquete.

Dentro de los directorios, se incluyen:

data: archivos que pueden ser cargados con la función de R data(). Estos son representacio-
nes de objetos o código R. En nuestro caso incluimos un objeto que representa la imagen
del logo de la comunidad.

inst: Se ubican los directorios que requieren ser copiados en la instalación. En nuestro caso,
ubicamos aquı́ algunas imágenes de muestra.

man: páginas del manual. Cada función pública en R debe tener su correspondiente archivo
en este directorio, en un formato similar a LATEX, donde se indican (entre otros) tipos,
descripción y ejemplos de uso. El comando check de R usa estos archivos para correr los
ejemplos en cada función, y detectar posibles errores en la página de manual o en las
implementaciones.

R: archivos de código R. En nuestro caso, son los algoritmos implementados (descriptos en


2.4) en R y los que utilizan funciones implementadas en C.

src: código C de las implementaciones de nuestros algoritmos.

En la figura 6.1 puede verse un diagrama con la organización del paquete. Cada rectángulo
representa una de nuestras divisiones: los nombres que se incluyen corresponden a los archivos
en código C, de los cuales el código R actúa como interfaz. Se indica además, en qué capı́tulo se
trata cada uno de estas divisiones.

6.3. Testing

Para verificar el correcto funcionamiento de los algoritmos implementados se utilizó un script, es-
crito en R, que permite correr casos de prueba evaluando los resultados obtenidos en la aplicación
de las funciones provistas por el paquete.

Un caso de prueba consiste de una matriz numérica que representa una posible imagen, de la
cual conocemos de antemano el resultado de una determinada operación. De esta manera, se
Capı́tulo 6. biOps: un paquete de procesamiento de imágenes para R 35

Figura 6.1: Estructura biOps

ejecuta la función correspondiente a la operación y se chequea que el resultado obtenido sea el


esperado.

Esta metodologı́a se puso en práctica para aquellos algoritmos que consideramos susceptibles de
esta forma de testeo, en particular en los casos de las operaciones por pixel, aritméticas, lógicas,
por vecino, morfológicas y geométricas. Mientras que, por ejemplo, en el caso de la clasificación
de imágenes, donde intervienen factores probabilı́sticos y aleatorios, y los resultados están sujetos
a la interpretación del usuario según su necesidad, no fue posible su verificación mediante este
tipo de testeo.

En todos los casos se efectuaron pruebas y aplicaciones de la implementación con imágenes va-
riadas obteniendo resultados esperados. Por otra parte, desde su primera publicación, el paquete
ha estado a disposición de los usuarios quienes pueden hacer llegar sus reportes de uso a través
de la lista de correo de la comunidad R. Al momento, sólo se han recibido comentarios de algu-
nos inconvenientes con la instalación de biOps en el sistema operativo Windows, que han sido
subsanados en la recientemente liberada versión 0.2.
Capı́tulo 6. biOps: un paquete de procesamiento de imágenes para R 36

6.4. biOpsGUI: el principio de una interfaz gráfica de usua-


rio

Con el objetivo de brindar una mejor experiencia de usuario, comenzamos con la implementación
de una interfaz gráfica de usuario para biOps, llamada biOpsGUI. Este paquete requiere para su
uso de RGtk2 8 , versión portada a R de GTK 9 , un conjunto de herramientas para crear interfaces
de usuario.

La interfaz gráfica estuvo fuera del planeamiento de este proyecto, sin embargo pudimos imple-
mentar funciones para mostrar una imagen, manteniendo su tamaño original, y utilidades para
visualizar las coordenadas y valores de los pixels de una imagen.

Es nuestro deseo el continuar desarrollando este paquete, como explicamos en la sección 14.1.

6.5. Próximos capı́tulos

Los próximos capitulos desarrollan la teorı́a detrás de los algoritmos y los detalles de especifica-
ción e implementación. La distribución de capı́tulos es la siguiente:

Operaciones por pixel [Cap. 7]: Son, quizá, las modificaciones más simples que pueden
realizarse: el valor de un pixel destino sólo depende del correspondiente pixel fuente. Se
presentan algoritmos implementados mediante “tabla de reemplazos” o look-up tables (ma-
peo de valores en valores) y operaciones aritméticas y lógicas. Se introduce también una
representación gráfica de los valores de una imagen: los histogramas, útiles para ajus-
tar parámetros en diversos algoritmos. Por último, se desarrolla el concepto de ruido en
imágenes, y se describen dos formas de generarlo: Gaussiana e impulsiva.

Operaciones geométricas [Cap. 8]: Modifican la ubicación de los pixels mediante una trans-
formación geométrica. Se introduce el concepto de interpolación, necesaria para “cubrir”
vacı́os propios de estos mapeos. Si bien no es un proceso geométrico, es usado en muchas
de las transformaciones de este capı́tulo. Se detallan las operaciones de rotación, escalado,
espejado, recortado (crop), encogido (shrink ) y traslación.

Operaciones por vecino [Cap. 9]: Generan el pixel destino a partir del pixel fuente y
sus vecinos. Se introducen el concepto de convolución (suma con peso de los pixels de
una sección de imagen, llamada ventana) y los filtros que pueden ser aplicados con ella.
También se describen filtros no lineales: mediana, mı́nimo y máximo.

Algoritmos de detección de bordes [Cap. 10]: Los bordes son los lı́mites entre objetos, y
entre objetos y fondo en una imagen. Existen aplicaciones para su detección en muchas de
las ramas del procesamiento digital de imágenes. Se revisarán algoritmos sencillos y rápidos
(homogeneidad y diferencia), métodos clásicos basados en convolución (Sobel, Prewitt,
Roberts, etc.) y técnicas avanzadas (Shen Castan, Marr Hildreth, etc.).
8 http://cran.r-project.org/src/contrib/Descriptions/RGtk2.html
9 http://www.gtk.org
Capı́tulo 6. biOps: un paquete de procesamiento de imágenes para R 37

Filtros en el espacio de frecuencias [Cap. 11]: Se presentan filtros en el espacio de fre-


cuencias (tasa de cambio en la intensidad de los pixels) de una imagen. La transformación
elegida es la difundida transformada rápida de Fourier. Con esta representación es posible
la aplicación de filtros, útiles para reemplazar a la convolución con máscaras grandes. Se
desarrollan a fondo estos conceptos y la implementación en biOps.

Operaciones morfológicas [Cap. 12]: Son operaciones matemáticas sobre una representación
de una imagen mediante un conjunto, y se utilizan para resaltar aspectos especı́ficos de la
forma. Se tratarán las operaciones básicas, para imágenes binarias y de escala de grises,
de erosión, por la cual se borran ciertos pixels, dilatación, donde se establece un patrón
alrededor de un pixel, y sus combinaciones: apertura y clausura.

Clasificación de imágenes [Cap. 13]: Se trata de obtener una nueva imagen, donde los pixels
han sido discriminados en diferentes categorı́as. Se estudian los conceptos de clasificación
supervisada y no supervisada, desarrollando los algoritmos no supervisados de Isodata y
K-Means, ofreciendo para este último varias alternativas de implementación.

6.6. Formato Digital

Un CD acompaña este impreso. El contenido es el siguiente:


biOps /
biOpsGUI /
output /
packages /
report /
samples /
spec /

biOps y biOpsGUI : los paquetes descriptos en esta sección.

output: se incluyen la salida de f uzz, con la opción -t, para los archivos de especificación
(como se vio en la subsección 3.5.1) y las salidas completas del profiling (introducido en la
sección 2.4 y ampliado en el apéndice A).

packages: algunos de los paquetes que se describieron en este escrito: fuzz, R y rGTK

report: este impreso en varios formatos, y la documentación de biOps y biOpsGUI.

samples: algunas imágenes de ejemplo

spec: los archivos de especificación en Z para este proyecto (introducidos en la subsección


3.5.1)
Capı́tulo 7

Operaciones por pixel

Las operaciones por pixel son, quizá, las más simples de las modificaciones que puedan sufrir
las imágenes. Esto es porque, para determinar el valor de un pixel en la imagen destino, sólo es
necesario tener en cuenta el valor para el mismo pixel en la imagen fuente, independientemente
del resto de los valores para los demás componentes.

La implementación de estas funcionalidades suelen ser bastante genéricas y fácilmente modifi-


cables. Este tipo de operaciones son, generalmente, unarias o binarias, aunque presentaremos
casos de número ilimitado de parámetros (por ejemplo, para la funcionalidad de promedio de
imágenes).

Dentro de esta categorı́a se encuentran algoritmos de implementación mediante “tabla de reem-


plazos” o look-up tables, mapeos de valores en valores que resultan en operaciones como el cambio
de intensidad y contraste, transformación a negativo, etc., y que tienen múltiples utilidades, que
intentaremos explicar y justificar.

Componen también esta categorı́a las operaciones aritméticas y lógicas, manipulaciones naturales
que se realizan sobre valores numéricos.

Los histogramas son representaciones gráficas de la distribución del rango de valores de una
imagen, que tiene utilidad para determinar parámetros para muchas de las operaciones que se
implementaron en este trabajo.

El ruido es un vicio propio de cualquier señal, y las imágenes no escapan a este problema. En este
trabajo estudiaremos algunos métodos para eliminarlo y en este capı́tulo, dos para generarlo: el
Gaussiano y el impulsivo. Estos métodos son útiles para evaluar la validez de filtros de eliminación
o para mejorar otros algoritmos.

A priori, este tipo de procesamiento puede parecer banal, pero no debe minimizarse el potencial
que presenta, como trataremos de mostrar en este capı́tulo.

38
Capı́tulo 7. Operaciones por pixel 39

7.1. Look-up tables

El primer grupo de algoritmos que analizaremos son los que utilizan una “tabla de reemplazos”
como estructura de datos, mejor definida en inglés como look-up table, o LUT . Responden a
transformaciones numéricas, descriptas genéricamente por la siguiente ecuación:

d (x , y) = lut(f (x , y)) (7.1)

donde d (x , y) y f (x , y) representan los pixels de la imagen destino y fuente, respectivamente,


en la coordenada (x , y). Las look-up tables son, en general, arreglos sencillos en donde se usa
el valor del pixel actual para obtener el valor del nuevo pixel (esto es, un mapeo de valores en
valores, lut). La imagen de destino se construye repitiendo este proceso para todos los pixels de
la imagen.

La ventaja de este tipo de implementaciones se basa en el ahorro del cálculo repetido: como la
LUT se llena completamente, no es necesario hacer reiteradas veces un mismo cálculo. El cálculo
realizado es constante, independientemente del tamaño de la imagen. La polı́tica seguida para los
valores que se exceden de los lı́mites permitidos para un pixel es la de forzar su ingreso ajustando
el valor al más cercano permitido. Ası́, en nuestro caso, todo valor que supere 255 (máximo valor
para un pixel) será ajustado a 255. Similarmente para los valores que desciendan más allá del
mı́nimo (en nuestro caso 0, que se llevan a este valor). Es importante notar que la misma imagen
que tomamos como parámetro puede usarse para llenar el buffer de la imagen de retorno.

El procedimiento es sencillo: para cada pixel en la imagen

Tomar el valor v del pixel

Consultar el valor v 0 de la LUT en el ı́ndice v

Establecer a v 0 el valor de la posición del pixel en cuestión para la imagen resultado

Este proceso puede verse en la figura 7.1. Usar la misma imagen como entrada y salida trae
aparejado un ahorro importante en la cantidad de memoria utilizada.

Esta transformación numérica puede escribirse en notación de función, como veremos en las
aplicaciones de esta sección. Muchas veces resultan más fácil de visualizar si se las representa
gráficamente. Por eso acompañamos para algunos casos un mapeo: el eje horizontal representa
el valor del pixel de entrada, y el eje vertical el resultado de la aplicación de la operación.

Figura 7.1: Look-up tables


Capı́tulo 7. Operaciones por pixel 40

Cualquier función que pueda ser descripta en términos matemáticos (y que mapee valores en
valores), puede ser implementada como una tabla de reemplazos. Para el trabajo hicimos una
elección arbitraria de ellas, incluyendo las que nos parecı́an más representativas y útiles. De
todas formas, queda la implementación de nuestra función en R llamada r look up table, por
la cual puede fácilmente extenderse este trabajo a la inclusión de alguna otra función deseada.
La sencillez del procedimiento queda reflejado en la implementación de esta función:
r_look _ u p _ t a bl e <- function ( imgdata , table ) {
for ( i in 1: length ( imgdata ) ) {
imgdata [ i ] <- table [ imgdata [ i ]+1]
}
imgdata
}

7.1.1. Modificación de contraste

El contraste en una imagen es su distribución de pixels claros y oscuros. Las imágenes con
poco contraste son en general mayormente claras, mayormente oscuras o mayormente “medio
tono”. Aquellas con mayor contraste tienen regiones de claros y oscuros, dado que usan más
ampliamente el rango de valores.

El problema con las imágenes de alto contraste es que tienen grandes regiones de oscuros y de
claros. Por ejemplo, la fotografı́a de una persona parada delante de una ventana en un dı́a de
sol tiene alto contraste: la persona está oscura y la ventana brillante. Las imágenes con buen
contraste exhiben un amplio rango de valores de pixels. Ninguno domina exageradamente por
sobre el resto, sino que todo el rango de valores es utilizado.

Nuestra implementación para el incremento y decremento de contraste son un tanto distintas.


Para el caso del incremento (función imgIncreaseContrast), los valores entre los lı́mites dados
por parámetro son mapeados en una distribución lineal en el rango de los valores. El resto de los
valores se mapean al más cercano hacia el máximo o mı́nimo. Visualmente la idea es la siguiente:
las zonas oscuras se hacen más oscuras y las claras aún más claras, lo que hace que la diferencia
de áreas quede más pronunciada. La función es la siguiente:


0
 n < min limit
f (x ) = x − min limit min limit ≤ x ≤ max limit

255 x > max limit

(7.2)

Figura 7.2: Decrementar contraste

Para el decremento de contraste (función imgDecreaseContrast) se usa el razonamiento inverso,


si bien estas operaciones, como puede verse, no son inversas, con lo que la aplicación en cascada
Capı́tulo 7. Operaciones por pixel 41

de algún orden de estas dos funciones no resulta en la misma imagen que al comienzo. Toma los
valores máximo y mı́nimo que deseamos que tenga la imagen resultado, y distribuye los valores
linealmente sobre esos parámetros:

max desired − min desired


f (x ) = x × +min desired
256
(7.3)

Figura 7.3: Incrementar contraste

Si bien no entra en la categorı́a de LUT s, nos gustarı́a nombrar también la implementación de


imgNormalize, operación que hace que los valores de la imagen ocupen todo el rango disponible.
Esto trae como consecuencia un decremento del contraste de la imagen, como mencionamos
anteriormente. Esta funcionalidad será de utilidad para las transformaciones que se requieren en
los algoritmos que trabajan con la Transformada Rápida de Fourier (como se verá en el capı́tulo
11).

7.1.2. Modificación de intensidad

La intensidad es el nivel de color (o de gris, para imágenes en escala de grises) de una imagen.
Visualmente, el cambio de la intensidad da una sensación de alteración en el brillo de la imagen.
Los procedimientos que implementamos (funciones imgIncreaseIntensity e imgDecreaseIntensity)
toman como parámetro el porcentaje de intensidad que deseamos modificar en la imagen en
cuestión. Las funciones subyacentes de estas transformaciones son:

f+ (x ) = min(255, x + (x × percentage)) (7.4)

f− (x ) = max (0, x − (x × percentage)) (7.5)

7.1.3. Otras modificaciones

Una de las más simples modificaciones que se suele realizar es la de inversión de los valores
de una imagen para obtener su negativo (imgNegative). La función relacionada y el gráfico de
mapeo se muestra en la figura 7.1.3.

A modo ilustrativo mostramos además el esquema de especificación en Z correspondiente a esta


aplicación: los valores de alto y ancho permanecen sin modificar, y la función de valores se
modifica invirtiendo cada componente.
Capı́tulo 7. Operaciones por pixel 42

Figura 7.4: Decrementar intensidad Figura 7.5: Incrementar intensidad

f (x ) = 255 − x (7.6)

Figura 7.6: Negativo

Negative
∆Image

∀ a : dom v • v 0 a = MaxValue − v a
width 0 = width
height 0 = height

Muchas veces es útil separar regiones de una imagen correspondientes a objetos que son de
nuestro interés con respecto a objetos que son parte del fondo de la imagen. El thresholding
(figura 7.1.3) es en general conveniente para este tipo de acción. Se establece un umbral o lı́mite
por el cual los valores que lo superen serán mapeados al valor máximo disponible, y los que no
al valor mı́nimo.

La modificación gamma se trata de un mapeo exponencial. Se usa para cambiar el rango dinámi-
co de una imagen. El resultado visual de esta aplicación es el de resaltar los valores con alta
intensidad en la imagen (figura 7.1.3).
Capı́tulo 7. Operaciones por pixel 43

(
0 x < thr value
f (x ) = (7.7)
255 x ≥ thr value

Figura 7.7: Thresholding

x 1/gamma
f (x ) = b( ) × 255c (7.8)
255

Figura 7.8: Transformación Gamma

7.2. Operaciones aritméticas y lógicas

Como las imágenes digitales se componen de valores numéricos, resulta natural aplicar aritmética
sobre ellos. Estas operaciones en general son binarias, y pueden expresarse con la siguiente
ecuación:
c(x , y) = a(x , y)hoperacionib(x , y) (7.9)

donde c es la imagen resultado (o destino), a y b son las imágenes de entrada, y hoperacioni


es la operación aritmética efectuada; léase: suma (función imgAdd ), resta (imgDiffer ), división
(imgDivide) o multiplicación (imgMultiply). En estos casos el valor de los pixels resultantes es
también independiente del resto de los pixels de las imágenes, con lo que seguimos en el campo de
las operaciones por pixel. En más de un caso resulta necesario, como vimos en las LUT s, ajustar
el valor resultado para que permanezca dentro del rango aceptado para nuestra representación.

Aquı́ la especificación en Z general de estas aplicaciones binarias:


Capı́tulo 7. Operaciones por pixel 44

BinaryOp
∆Image
op? : VALUE × VALUE " VALUE
input? : Image

∀ x : (dom v ) ∩ (dom input?.v ) • v 0 x = clipPixel (op? (v x , input?.v x ))


∀ x : dom v 0 | x ∈
/ (dom v ) ∩ (dom input?.v ) • v 0 x = v x
width 0 = width
height 0 = height

Otras de las operaciones que implementamos son las de promedio (imgAverage), aunque esta
no necesariamente es una operación binaria: toma como parámetro una lista de imágenes de
la misma profundidad de color y calcula el valor promedio coordenada a coordenada, y la de
máximo (imgMaximum), que toma el máximo de cada coordenada entre dos imágenes y que se
usará en implementaciones que veremos en los próximos capı́tulos.

Las aplicaciones de estas funcionalidades son variadas. Por ejemplo, el promedio entre imágenes
se utiliza en la eliminación de ruido, pixels superfluos claros u oscuros que no son fiel reflejo
de la realidad. Estos “intrusos” aparecen en distintas intensidades y posiciones dentro de una
imagen (en general, puede asumirse que el ruido es aleatorio). Este hecho puede ser aprovechado
para eliminar el ruido: si se cuenta con una determinada cantidad de imágenes del mismo objeto
(como suele suceder con las fotos planetarias o satelitales, por ejemplo), se procede a obtener el
promedio de todas ellas:

a1 (x , y) + a2 (x , y) + ... + an (x , y)
r (x , y) = (7.10)
n

Se experimentan buenos resultados al promediar al menos tres o cuatro imágenes, aunque con
dos imágenes pueden obtenerse comportamientos aceptables.

La diferencia entre imágenes es común en aplicaciones de machine vision o aplicaciones robóticas.


Por ejemplo, es común tener objetos pasando por una cinta transportadora. Se toma una imagen
de referencia, cuando no hay objetos presentes. Luego, tomando la diferencia entre esta imagen
y otra con elementos presentes en la cinta es posible, mediante la operación de diferencia, aislar
estos objetos para ser analizados posteriormente. La resta entre imágenes también es usada para
la detección de cambios: si ésta es mayormente cero, se puede deducir que no hubo cambios.
Si, por otro lado, hubo movimientos entre las escenas, se verán diferencias significativas y se
podrá deducir qué ha sido modificado. Un ejemplo de esto puede verse en la figura 7.9. En
7.9(c) puede verse el resultado de la diferencia negada de dos momentos de una distribución de
herramientas (7.9(a) y 7.9(b)).

La suma y diferencia contra imágenes constantes suele utilizarse también para la corrección de
brillo de una imagen. Esto está fuertemente relacionado con las operaciones de intensidad, vistas
en la sección anterior, ası́ como las operaciones de multiplicación y división, que modifican el
contraste de la imagen cuando son operadas contra imágenes constantes.
Capı́tulo 7. Operaciones por pixel 45

(a) Imagen anterior (b) Imagen posterior (c) Diferencia negada

Figura 7.9: Aplicación de imgDiffer

Similarmente a las operaciones aritméticas se implementaron operaciones lógicas ∧ (imgAND), ∨


(imgOR) y xor (imgXOR). Estos operadores son funcionalmente completos para las operaciones
lógicas, puesto que cualquier otro puede obtenerse a partir de combinaciones de los anteriores.
Los operadores de ∧ y ∨ son usados para masking, esto es, para seleccionar subimágenes de una
imagen. Esto es tambı́en posible con la multiplicación de imágenes.

La implementación en C de estas operaciones aprovecha los operadores lógicos entre bits: & (∧),
| (∨) y ∧ (xor )

7.3. Histogramas

El histograma de una imagen se refiere al histograma de los valores de intesidad de sus pixels.
Esto es, un gráfico que muestra el número de pixels de una imagen en cada intensidad encontrada.

La implementación es sumamente sencilla. Se escanea la imagen y se va contando la cantidad


de pixels que tienen cada una de las intensidades posibles. Al finalizar se construye el gráfico en
cuestión. Esto puede observarse en la implementación de la función de R imgHistogram. En la
figura 7.10 podemos ver una imagen y su respectivo histograma.

(a) Imagen (b) Histograma

Figura 7.10: Histograma de una imagen


Capı́tulo 7. Operaciones por pixel 46

El uso de los histogramas es realmente amplio. Uno de los más comunes es decidir el valor por el
cual aplicar la operación de thresholding (7.1.3). Si es conveniente aplicar esta operación a una
imagen, es común que el histograma sea “separable” en dos grandes grupos de valores (lo que se
denomina histogramas bimodales). Entonces, un buen valor para pasarle a la función podrı́a ser
uno entre los dos “picos” que se darán en el histograma.

Dos operadores que están relacionados con los histogramas son la normalización de contraste
(estiramiento de los valores para que ocupen todo el rango, como se vio en 7.1.1), ya que para
que esta operación tenga sentido debe cumplirse que haya extremos en el rango de valores que
no estén siendo utilizados, y la ecualización de histogramas, métodos para modificar el rango
dinámico y el contraste de una imagen mediante la alteración de las intensidades del histograma,
ecualizaciones sobre las cuales no hemos hecho hincapié en este trabajo.

7.4. Generación de ruido

Todo proceso de señales tiene que tratar un evento aleatorio de fondo como es el ruido. Las
principales fuentes de ruido en las imágenes digitales se presentan durante la adquisición (digita-
lización) y/o la transmisión. No es parte de las señales ideales y puede ser causado por diversos
factores, entre ellos la variación en la sensibilidad de los detectores, alteraciones en el ambiente,
radiaciones, errores de transmisión, etc. Las caracterı́sticas del ruido dependen de su origen,
aunque lo mismo ocurre para el operador que mejor reduce sus efectos.

La generación de ruido consiste en corromper deliberadamente una imagen. Esto puede reali-
zarse, por ejemplo, para probar la resistencia de algún operador al ruido o de intentar mejorar
los filtros existentes para la eliminación del mismo.

La caracterización del ruido se hace mediante la función probabilı́stica de densidad (PDF , por
sus siglas en inglés de probability density function). Dos de los más comunes los presentaremos
a continuación, por haber sido los elegidos para este trabajo: el ruido Gaussiano y el ruido
impulsivo (salt & pepper o sal y pimienta).

El ruido Gaussiano es matemáticamente dócil, por lo cual se lo utiliza mucho en la práctica. El


PDF de una variable aleatoria Gaussiana z está dado por:

1 2
/2σ 2
p(z ) = √ × e −(z −µ) (7.11)
2πσ

donde µ representa la media y σ el desvı́o estándar. Para introducir ruido de este tipo (función
imgGaussianNoise) utilizamos el método de Box-Muller, el cual usa una técnica de transforma-
da inversa para pasar de dos variables aleatorias uniformemente distribuidas a dos aleatorias
normales de media 0 y varianza 1, X e Y , las cuales pueden ser fácilmente modificables para los
diferentes valores de media y varianza (σ 2 ) usando la siguiente relación:

X 0 = µ + σ2 × X (7.12a)
Capı́tulo 7. Operaciones por pixel 47


Y 0 = µ + σ2 × Y (7.12b)

estas variables se suman a los pixels de a dos por vez, X 0 para el primero e Y 0 para el segundo.

El ruido impulsivo, también llamado salt & pepper se caracteriza por ocurrencias aleatorias de
valores mı́nimos o máximos en los canales de la imagen. Para imágenes de un solo canal, estos
valores corresponden a las tonalidades de blanco y negro, con lo que visualmente resulta en
“salpicados” blancos y negros, lo que da origen al nombre que recibe.

La implementación (imgSaltPepperNoise) toma un valor que representa el porcentaje de pixels a


ser “contaminados”. Mediante el uso de variables aleatorias se determina si el pixel se transforma
y en tal caso si lo hace al valor máximo o al mı́nimo. En la figura 7.11 puede observarse una
aplicación de esta función, con un parámetro de 5 (es decir, 5 % de los pixels contaminados).

(a) Imagen original (b) Ruido agregado (5 %)

Figura 7.11: Ruido “sal y pimienta”


Capı́tulo 8

Operaciones geométricas

Los procesos geométricos modifican la ubicación de los pixels basados en alguna transformación
geométrica. La idea es mover los pixels alrededor de la imagen sin alterar, idealmente, sus valores.
Sin embargo, si algún proceso intenta mapear un pixel desde una ubicación que no existe, se ge-
nerará un nuevo pixel. Este proceso de generación se conoce como interpolación. La interpolación
propiamente dicha no es un proceso geométrico, pero es usado en muchas de las transformaciones
que veremos en este capı́tulo. Se presentarán los conceptos básicos de los procesos geométricos
y las diferentes funciones que se utilizaron en la implementación de los métodos.

En esta sección se detallan la implementación de las funciones de rotar, escalar, espejar, recortar
(crop), encoger y trasladar ; para muchas de las cuales, como veremos, puede elegirse el método
de interpolación a aplicar.

8.1. Mapeo de valores: “hacia adelante” vs. “hacia atrás”

En las operaciones geométricas se utiliza el mapeo inverso: a partir de las coordenadas de la


imagen destino se determinan las coordenadas de la imagen fuente de las cuales obtener los
valores para realizar la transformación.

Transferir el pixel de entrada hacia un pixel de salida a través de una función se denomina mapeo
“hacia adelante” (forward mapping). Esta alternativa trae aparejado ciertos problemas: agujeros
y solapamientos. Los agujeros son pixels cuyos valores no están definidos, y el pixel destino no
tiene en estos casos su correspondiente pixel fuente. Los solapamientos ocurren cuando dos (o
más) pixels se mapean al mismo pixel de destino. ¿Qué valor se le asigna en esos casos?

Para resolver estos problemas se utiliza otro tipo de mapeo, “hacia atrás” (reverse mapping).
Notar que en este caso surgen los mismos inconvenientes que en el mapeo “hacia adelante”, pero
no son problemas ya que cada pixel de la imagen destino tiene un valor asociado (es decir, los
agujeros quedarán en la imagen fuente, y los solapamientos no son problema al quedar los pixels
de la imagen destino con el mismo valor).

48
Capı́tulo 8. Operaciones geométricas 49

Por esta razón es que se hace imprescindible el uso del mapeo “hacia atrás”, que se utilizará en
las implementaciones de las operaciones geométricas de este capı́tulo.

8.2. Interpolación

El mapeo a veces genera problemas. Por ejemplo: ¿qué pasa si nuestra función de mapeo calcula
una dirección de pixel no entera? Para que esto resulte más visible, consideremos la siguiente
transformación:

xd yd
xs = ys =
2 2

xs e ys denotan las coordenadas x e y del pixel fuente (respectivamente) y xd e yd las del pixel
destino.

El pixel para (0, 0) del destino vendrá del (0, 0) del fuente. Pero, ¿qué pasa con el pixel (1, 1)
del destino? La transformación reversa buscarı́a en (0.5, 0.5) del fuente, que no existe.

Para este tipo de problemas disponemos de una técnica que se denomina interpolación, un
proceso para generar valores de direcciones que se ubican “entre pixels”. Existen varias técnicas
de interpolación; la más adecuada para usar depende mucho de la aplicación en cuestión: los
algoritmos más sofisticados mejoran la calidad de la imagen, pero hacen el proceso más complejo
y computacionalmente más costoso (y lo opuesto pasa para los algoritmos más sencillos).

A continuación presentamos los métodos de interpolación que pueden aplicarse en las operaciones
(que lo requieren) de este capı́tulo.

8.2.1. Interpolación por el vecino más cercano

La idea para el vecino más cercano es la de asignar como salida el pixel que minimice la distancia
a la dirección generada (sin considerar en absoluto el resto de los pixels). La implementación de
esta técnica consiste en redondear la fracción obtenida al entero más cercano. La suma en 0.5 y el
redondeo logran este cometido. En el siguiente código C puede verse una posible implementación:
fx = map ( x_dest ) ;
fy = map ( y_dest ) ;
x_src = ( int ) ( fx + 0.5) ;
y_src = ( int ) ( fy + 0.5) ;

Como no se genera ningún pixel, todos los valores son obtenidos del conjunto de entrada. En
general, a mayor cantidad de pixels asignados a uno mismo de entrada, mayor es la imprecisión
que se logra en la imagen final. Esto puede verse, por ejemplo, en el escalado de imágenes cuando
el factor de escala es muy grande.
Capı́tulo 8. Operaciones geométricas 50

8.2.2. Interpolación bilineal

Otra técnica común de interpolación es la bilineal. El pixel generado es una suma de pesos de los
cuatro vecinos más cercanos. Los pesos son determinados linealmente. Cada peso es directamente
proporcional a la distancia a cada pixel existente.

Esta técnica requiere tres interpolaciones lineales. Una de las formas de proceder, como veremos
en el siguiente código, es interpolar linealmente el par de pixels ubicado más arriba y el par
ubicado más abajo. Con ellos, se realiza la tercera interpolación lineal, para obtener el valor
deseado:
pesoEO = fx - floor ( x ) ;
pesoNS = fy - floor ( y ) ;

/* 1 ra interpolacion */
EOarriba = NO + pesoEO * ( NE - NO ) ;
/* 2 da interpolacion */
EOabajo = SO + pesoEO * ( SE - SO ) ;

/* 3 ra interpolacion */
dest = EOarriba + pesoNS * ( EOabajo - EOarriba ) ;

La interpolación bilineal resulta en una imagen más suave y lisa, en comparación a la que se
obtiene con la interpolación por vecino más cercano. Sin embargo, al realizar tres interpolaciones
lineales, requiere claramente más computación que la mencionada anteriormente.

8.2.3. Interpolación por B-Spline

El método del vecino más cercano requiere un pixel de entrada. La interpolación bilineal requiere
cuatro pixels de entrada. En este caso, veremos un método de orden más alto, que requiere de
los 16 pixels más cercanos. Se trata de B-Spline. La función está definida ası́:


 1 2
| x |3 − | x |2 +


 0 ≤| x |< 1
2 3


f (x ) = 1 3 2
4 (8.1)
−
 6 | x | + | x | −2 | x | + 1 ≤| x |< 2


 3
0 2 ≤| x |

El principio es el mismo que para el resto de las interpolaciones de alto orden (que, salvo por la
convolucional cúbica, no serán profundizadas en este trabajo): la función se centra en el punto de
interés y sus valores en los puntos de muestra son multiplicados por los valores de la función. La
suma de estos productos es el nuevo pixel generado. Se opera primero en cada fila, obteniendo
un resultado por cada una. Estos valores vuelven a procesarse, obteniendo un solo valor, que
corresponde al resultado de la interpolación.
Capı́tulo 8. Operaciones geométricas 51

8.2.4. Interpolación convolucional cúbica

Al igual que B-Spline, la interpolación cúbica utiliza los 16 pixels más cercanos para generar el
nuevo pixel. En este caso, la familia de funciones está definida de la siguiente manera:




(a + 2) | x |3 −(a + 3) | x |2 +1 0 ≤| x |< 1

f (x ) = a | x |3 −5a | x |2 +8a | x | −4a 1 ≤| x |< 2 (8.2)



0 2 ≤| x |

El valor de la constante a es arbitrario, aunque se sugieren -0.5, -0.75 y -1.0. Las pruebas han
demostrado que para resultados visuales, el valor -1.0 es la mejor opción.

Este método es quizá el que más agudice la diferencia de valores. Una de las caracterı́sticas
notables es que puede tomar valores negativos o excederse de nuestro rango de valores. La salida
en estos casos deberá ser alterada para satisfacer nuestras especificaciones.

Un detalle de implementación: para ahorrar computación en algunos casos fue conveniente la


aplicación de la regla de Horner, método recursivo para transformar polinomios a la forma
monomial. Tal es el caso de expresiones como x 3 + 2x 2 + 3x + 4. Para evitar la operación de
exponenciación, costosa en sentido computacional, puede aplicarse esta regla, de la siguiente
forma:

x 3 + 2x 2 + 3x + 4
= (x 3 + 2x 2 + 3x ) + 4
= x (x 2 + 2x + 3) + 4
= x ((x 2 + 2x ) + 3) + 4
= x (x (x + 2) + 3) + 4
= (((x + 2)x + 3)x + 4)

8.3. Operaciones implementadas

8.3.1. Escalar

El escalar es la función por la cual se lleva la imagen a un tamaño (mayor) deseado. Esta
operación recibe muchos nombres: magnificar, zoom, estiramiento, etc. Hay dos cosas que deben
tenerse en cuenta cuando escalamos: la primera es que no se mejorará la resolución de la imagen
original. No tenemos más información de la que nos brinda la imagen original. Lo que sı́ puede
hacerse es una interpolación que promedie de alguna manera e “invente” esos datos que estarán
faltando. La segunda cuestión es que, a menos que todos los escalados se realicen a partir de la
imagen original, los resultados serán siempre más degradados. Al escalar, se están creando pixels
Capı́tulo 8. Operaciones geométricas 52

“artificiales”, con lo que las sucesivas aplicaciones generarán nuevos pixels a partir de estos, ya
creados anteriormente.

La implementación de esta operación es sencilla: recorremos la imagen de destino (mapeo hacia


atrás) y obtenemos los valores a partir de las divisiones de las coordenadas actuales con los
respectivos factores de escala. El resultado puede obtenerse aplicando algunas de las funciones
de interpolación.

Esta operación, y aquellas que requieren de interpolación para determinar sus valores, fueron
implementadas utilizando las operaciones mencionadas en la sección anterior. Para el caso de
escalar una imagen, puede llamarse a la función imgScale con, además de la imagen en cuestión y
los factores de escala, alguno de las siguientes secuencia de caracteres, que identifican la operación
de interpolación a utilizar:

“nearestneighbor” (vecino más cercano)

“bilinear” (bilineal)

“cubic” (convolucional cúbica)

“spline” (B-Spline)

Esta identificación de métodos es una constante a lo largo del trabajo. Es posible también invocar
directamente a un método en particular: esto se hace a través de las funciones

imgNearestNeighborScale (vecino más cercano)

imgBilinearScale (bilineal)

imgCubicScale (convolucional cúbica)

imgSplineScale (B-Spline)

Estas operaciones no restringen su utilización para reducir el tamaño de una imagen; aunque
para ello, como veremos, es conveniente el uso de funciones especı́ficas para encoger.

8.3.2. Encoger

En esta sección se analizan dos algoritmos implementados para la reducción del tamaño de una
imagen. El uso tı́pico de esta operación es la creación de imágenes en miniatura (comúnmente
conocidas como thumbnails), y la idea que manejan es la de representar un conjunto de pixels
con un único pixel. Para ello disponemos de varias técnicas, entre las que elegimos las dos más
usadas: la de representación por mediana y por promedio.

Ambas técnicas toman una ventana de n × n que van “deslizando” por sobre la imagen. El
valor de n depende del factor de reducción que busquemos en la imagen: estos son inversamente
proporcionales, puesto que se requiere una ventana más grande para determinar una cantidad
menor de pixels.
Capı́tulo 8. Operaciones geométricas 53

En la representación por mediana (imgMedianShrink ) se ordenan los pixels de la ventana y se


elige el valor de la mediana, es decir, el que se encuentra en “el medio” del orden de valores
por magnitud. Esta técnica requiere mucho tiempo de computación debido a que el cálculo de
la mediana no es sencillo. Existen algoritmos que mejoran por mucho el algoritmo ordinario de
cálculo: para nuestra implementación usamos quick select, que tiene la idea del ordenamiento
quick sort.

La idea de fondo es la misma. Echemos un vistazo al pseudocódigo:


quick_select ( L ) {
elegir x en L
particionar L en L1 <x , L2 =x , L3 > x
quick_sort ( L1 )
quick_sort ( L3 )
concatenar L1 , L2 , L3 en L ’
devolver k-esimo de L ’
}

Esto tiene el mismo orden que quick sort, O(n × log(n)). Podemos notar que si k es menor que
la longitud de L1, no es necesario ordenar L3. Lo mismo si k es mayor que la concatenación de
L1 y L2. De esta forma podemos ahorrar un poco de cálculo. También podemos ahorrar (pero
no mucho) si no hacemos la concatenación, simplemente mirando en el lugar que corresponda:
quick_select ( L ) {
elegir x en L
particionar L en L1 <x , L2 =x , L3 > x
if ( k <= longitud ( L1 ) ) {
quick_sort ( L1 )
devolver k-esimo de L1
} else if ( k > longitud ( L1 ) + longitud ( L2 ) ) {
quick_sort ( L3 )
devolver ( k - longitud ( L1 ) - longitud ( L2 ) ) - esimo de L3
} else {
devolver x
}
}

Esto sigue siendo O(n×log(n)), pero con una constante menor. Podemos hacer una nueva mejora:
el código de cada rama if ordena la lista y devuelve la posición que corresponde, exactamente el
problema que estamos resolviendo. Luego, podemos hacer las mismas mejoras que hasta ahora:
quick_select (L , k ) {
elegir x en L
particionar L en L1 <x , L2 =x , L3 > x
if ( k <= longitud ( L1 ) ) {
devolver quick_select ( L1 , k )
} else if ( k > longitud ( L1 ) + longitud ( L2 ) ) {
devolver quick_select ( L3 , k - longitud ( L1 ) - longitud ( L2 ) )
} else {
devolver x
}
}

La representación por promedio (imgAverageShrink ) utiliza el mismo concepto que la de por


mediana, pero toma el valor del promedio de los de la ventana. Esta no es una operación tan
lenta como la de mediana, y los resultados son, en el caso general, igualmente aceptables.
Capı́tulo 8. Operaciones geométricas 54

8.3.3. Rotar

La operación básica de rotar es la siguiente:

xs = xd ∗ cos(α) + yd ∗ sin(α) (8.3)


ys = yd ∗ cos(α) + xd ∗ sin(α) (8.4)

De nuevo, xs e ys denotan respectivamente las coordenadas x e y del pixel fuente y xd e yd las


del pixel destino. Esta fórmula rotará la imagen sobre (0,0). Para rotar una imagen con respecto
a su centro (centrox , centroy ), debemos modificar las ecuaciones 8.3 y 8.4:

xs = (xd − centrox ) ∗ cos(α) + (yd − centroy ) ∗ sin(α) (8.5)


ys = (yd − centroy ) ∗ cos(α) + (xd − centrox ) ∗ sin(α) (8.6)

La operación de rotar cambiará las dimensiones de la imagen para que ésta pueda verse completa-
mente, completando los vacı́os que deje la rotación con algún color predeterminado (tı́picamente
negro -caso de nuestra implementación-). En la figura 8.1 pueden verse los sectores de la imagen
que no tendrán valor asociado ante una rotación de A grados. Además se indica con diferentes
colores los altos y anchos de la imagen original y de la rotada.

Figura 8.1: Rotación de imagen

Una vez que se determinaron estos valores, deben ser interpolados. Para ello implementamos,
como en el resto de las operaciones que lo requerı́an, funciones con las diversas interpolaciones:
imgNearestNeighborRotate, imgBilinearRotate, imgSplineRotate e imgCubicRotate. Lo impor-
tante para esta operación es considerar los valores de xs e ys que caen dentro de los lı́mites de
la imagen fuente.
Capı́tulo 8. Operaciones geométricas 55

Si el ángulo de rotación α es un múltiplo de 90o , no es una buena idea aplicar las ecuaciones vistas
anteriormente, ya que lo único que se precisa es una reubicación de pixels; más precisamente una
trasposición de filas y columnas. Para ello se implementaron las rotaciones de 90o en sentido
horario (imgRotate90Clockwise) y antihorario (imgRotate90CounterClockwise).

8.3.4. Espejar

Espejar una imagen es, simplemente, darla vuelta sobre algunos de los ejes. El espejado horizontal
(imgHorizontalMirroring) voltea la imagen en el eje y. Ası́, los objetos que antes aparecı́an a la iz-
quierda de la imagen, ahora aparecerán a la derecha. El espejado vertical (imgVerticalMirroring)
da vuelta la imagen en el eje x , con lo que los objetos que aparecı́an en la parte superior de la
imagen, aparecerán ahora en la parte inferior, y viceversa.

Es importante destacar que en esta operación no hay intervención de interpolación, puesto que
el espejado es un mero reacomodo de la posición de los pixels en la imagen.

En la figura 8.2 puede verse la imagen original y sus espejados en ambos ejes.

(a) Original (b) Espejado vertical (c) Espejado horizontal

Figura 8.2: Operación de espejado

8.3.5. Trasladar

La traslación consiste en mover un sector de una imagen a otra parte. Para ello debe utilizarse un
buffer secundario, de modo de no sobreescribir información que sea útil en la misma operación.
El uso de un único buffer para este tipo de operaciones es un error común que puede causar
operaciones recursivas sobre la imagen.

La implementación de la operación de trasladar, imgTranslate, toma como parámetros, además


de la imagen en cuestión, las coordenadas del borde superior izquierdo del bloque fuente y destino,
y el ancho y alto del bloque a mover. En caso de que estos bloques sean demasiado grandes (es
decir, que los parámetros indiquen que el bloque excede los lı́mites de la imagen), éstos serán
corregidos automáticamente para hacer que la operación sea válida.

En la figura 8.3 puede verse una imagen de 512 por 512 pixels (reducida para este impreso),
donde se ha trasladado un rectángulo de 110 (ancho) por 40 (alto) pixels desde la posición (245,
Capı́tulo 8. Operaciones geométricas 56

245) hasta la posición (245, 285), produciendo la duplicación de ojos de la bella Lenna, famosa
imagen utilizada en procesamiento de imágenes. En la figura 8.3(b) se demarcan los sectores de
destino y fuente de la operación.

(a) Original (b) Posiciones de movimiento

(c) Trasladado

Figura 8.3: Operación de traslación

8.3.6. Recortar

El recortado, o crop, es quizá la operación más sencilla de entre las geométricas. Consiste en
reducir una imagen a una parte de la misma. El tamaño en general es alterado y se requiere de
un segundo buffer para almacenar el resultado. Es una operación muy común a la hora de hacer
zoom de una imagen o, simplemente, de eliminar bordes que no son deseados. La implementación
de esta función, imgCrop, toma como parámetros las coordenadas de inicio del rectángulo que
deseamos conservar, y el ancho y alto correspondientes. Notar que este ancho y alto será el
tamaño final de la imagen, como puede verse en la especificación Z de la operación:
Capı́tulo 8. Operaciones geométricas 57

ImageCrop
∆Image
x ?, y? : N
width?, height? : N

0 ≤ x? < width
0 ≤ y? < height
0 ≤ width? < (width − x? + 1)
0 ≤ height? < (height − y? + 1)
width 0 = width?
height 0 = height?
∀ x , y : N | x ∈ 0 . . (width? − 1) ∧ y ∈ 0 . . (height? − 1) •
v 0 (x , y) = v (x ? + x , y? + y)

Podemos notar en este esquema, que se exige que el ancho y alto que se pasan por parámetro
(width? y height? en este caso) no se excedan de los lı́mites que disponemos en la imagen
(habiendo fijado las coordenadas correspondientes a la margen superior izquierda del rectángulo
que deseamos conservar).
Capı́tulo 9

Operaciones por vecino

Las operaciones por vecino, también denominadas procesos de imágenes por área, toman por
entrada un pixel y los pixels alrededor de éste para generar el valor del pixel de salida.

Entre estas operaciones tenemos los llamados filtros espaciales lineales que trabajan sobre una
ventana de la imagen y una máscara o kernel del tamaño de esa ventana. El término filtro proviene
del procesamiento de señales en el espacio de frecuencias, a partir de la transformada de Fourier,
que veremos más detalladamente en 11.3. Aquı́ veremos filtros que operan directamente en los
pixels de la imagen, implementados a partir de la convolución de la imagen de entrada con un
kernel predefinido.

Describiremos algunos filtros no lineales, que también operan sobre ventanas de la imagen. Sin
embargo, la operación de filtrado se basa en los valores de los pixels en la ventana y no se usa
una máscara con coeficientes para operar con ellos. Es el caso de los filtros por mediana, mı́nimo
y máximo.

9.1. Convolución

La convolución se usa en distintos filtros para el procesamiento de imágenes. Una convolución


consiste en una suma con pesos del pixel de entrada y sus vecinos. Los pesos están determinados
por una matriz, la matriz (o kernel) de convolución. En general las dimensiones de esta matriz
son impares, de tal manera de poder determinar un centro. La ubicación del centro corresponde
a la ubicación del pixel de salida.

Entonces se mantiene una ventana corrediza que se centra en cada pixel de la imagen de entrada
y se generan nuevos pixels de salida. Cada nuevo valor se calcula multiplicando los pixels en
la ventana por su correspodiente peso en la matriz de convolución y sumando esos productos
(figura 9.1). Es importante guardar los valores obtenidos en una nueva imagen, para calcular los
subsiguientes valores a partir de los pixels originales de la imagen.

La suma de los pesos de una máscara de convolución afectan la intensidad global de la imagen
resultante. Muchas máscaras tienen coeficientes cuya suma es igual a 1. En estos casos la imagen

58
Capı́tulo 9. Operaciones por vecino 59

Figura 9.1: Convolución

producto de la convolución tendrá el mismo promedio de intensidad que la original. Otras másca-
ras (por ejemplo las de detección de bordes, ver 10.3) tienen coeficientes negativos y suman 0.
De esta forma se pueden obtener valores de pixel negativos. A ese valor se le suma una constante
(como la mitad de la máxima intensidad); si el resultado todavı́a es negativo, el pixel se pone a
0.

En general, dada una imagen f de tamaño M × N y una máscara w de tamaño m × n, la imagen


resultado de la convolución g está definida por:

a
X b
X
g(x , y) = w (s, t)f (x + s, y + t) (9.1)
s=−a t=−b

(m − 1) (n − 1)
donde a = yb= .
2 2
Uno de los problemas que se plantean al momento de implementar filtros por convolución es
cómo tratar los bordes de la imagen. Cuando la ventana de convolución se centra en el pixel
(0, 0), qué valores se deben multiplicar con los coeficientes de la máscara que quedan fuera de la
imagen? Existen distintas alternativas para manejar esta situación.

Una es tratar las celdas vacı́as de la ventana como ceros (zero padding). Es una solución fácil,
pero le resta importancia a los bordes de la imagen.

Otra posibilidad es iniciar la convolución en la primera posición tal que la ventana queda to-
talmente dentro de la imagen. Es decir, si la máscara es 3 × 3 empezarı́a en (1, 1). Es simple
de implementar, y se suele copiar los bordes de la convolución para obtener una imagen con las
mismas dimensiones que la original.

Hay alternativas que se basan en extender la imagen original antes de aplicar el filtro. Una forma
es duplicar los bordes. Si se usa una máscara 3 × 3, se duplican las filas de los bordes superior
Capı́tulo 9. Operaciones por vecino 60

e inferior, y las columnas de los bordes izquierdo y derecho. Esta es la variante que elegimos en
nuestra implementación.

Otro método es “envolver” (wrap) la imagen. O sea, si quisiéramos aplicar una convolución a
una imagen de 512 × 512 con una máscara 3 × 3, la primera ventana operarı́a sobre los pixels
(511, 511), (0, 511), (1, 511), (511, 0), (0, 0), (1, 0), (511, 1), (0, 1), (1, 1).

Algo para tener en cuenta también es el hecho de que a medida que crece la máscara de convo-
lución crece exponencialmente la carga computacional.

Nuestro esquema Z para la operación de convolución es el siguiente:

Convolution
∆Image
mask ? : Mask
op? : Mask × VALUES " VALUE
bias? : VALUE

width 0 = width
height 0 = height
∀ c : dom v 0 • v 0 (c) =
clipPixel (op? (mask ?, getSlice (v , width, height, first c,
second c, mask ?.width, mask ?.height)) + bias?)

donde op? es la función que aplica la convolución propiamente dicha a partir de la máscara dada
(mask ?) y la ventana de la imagen con las dimensiones de la máscara correspondiente a un pixel
dado (el resultado de getSlice); al valor devuelto por op? se le suma bias?, un valor constante,
como se describió anteriormente. Y finalmente clipPixel garantiza que el valor del pixel final
esté en el rango válido.

Al trabajar con imágenes color tenemos dos opciones. Una, operar sobre el canal de intensidad
en el modelo de color HSI. La otra es operar sobre cada uno de los canales de una imagen
RGB. El primero tiene la ventaja de que preserva la información de tonos original, pero requiere
conversiones de un modelo a otro. El método más popular es el de hacer la convolución sobre los
canales RGB, y es la alternativa que seguimos. Qué técnica es mejor depende del objetivo de la
aplicación y los filtros.

Nuestro paquete ofrece una función de convolución, imgConvolve, que aplica el filtro especi-
ficado por una máscara de entrada, definida por el usuario, sobre la imagen dada. También
se implementaron algunos filtros predefinidos para blurring (imgBlur en biOps) y sharpening
(imgSharpen).

9.1.1. Blurring

El blurring es un filtro pasobajo que se aplica en la representación espacial de una imagen.


Remueve los detalles finos de una imagen. Se usa, por ejemplo, para simular una cámara fuera
Capı́tulo 9. Operaciones por vecino 61

de foco o quitarle importancia al fondo.

En general se utilizan máscaras cuyos coeficientes son iguales. En una máscara 3 × 3 todos los
elementos son iguales a 1/9; en una 5×5, a 1/25. Como se puede ver se trata de un promedio entre
los vecinos. Cuanto mayor es la máscara, mayor será el efecto y el tiempo de cálculo requerido.
El blurring es una forma efectiva de reducir el ruido Gaussiano de una imagen, no ası́ para ruido
impulsivo (i.e. cuando no hay una correlación con el valor original del pixel). Además se reducen
los valores extremos en cada ventana, y por lo tanto tiende a disminuir el contraste de la imagen.

Otra máscara usada es la que elige los coeficientes de tal manera de no afectar el promedio de
intensidad de la imagen, aproximando un perfil Gaussiano y haciendo la suma de los coeficientes
igual a 1.

El problema de usar filtros pasobajo para reducir el ruido de una imagen es que los bordes de los
objetos en la imagen se tornan difusos. Cuando se busca filtrar el ruido de una imagen el filtro
de mediana puede ser una mejor alternativa, ya que preserva mejor los bordes.

9.1.2. Sharpening

El sharpening produce el efecto opuesto al blurring. El sharpening enfatiza los detalles de una
imagen. Si una imagen es difusa puede llevarse a un nivel aceptable mediante este filtro. Claro
que también tiende a amplificar el ruido y se incrementa el contraste.

(a) Imagen original (b) Imagen filtrada

Figura 9.2: Aplicación de sharpening

La máscara de convolución usada tiene un coeficiente positivo en el centro y mayorı́a negativos


en los bordes. El sharpening se basa en los filtros pasoalto que remueven los componentes de
baja frecuencia. Otro método para obtener un filtro pasoalto es restar a la imagen original la
imagen filtrada por pasobajo. Se conoce por unsharp.

Una alternativa al sharpening es el denominado filtro high-boost:

HighBoost = αOriginal − Pasobajo (9.2)

Cuando α = 1, el resultado es una imagen pasoalto. Si α > 1, una fracción de la imagen original se
añade al resultado del pasoalto, lo que restablece algunos de los componentes de baja frecuencia.
El filtro high-boost retiene más información del fondo de la imagen original. A medida que se
Capı́tulo 9. Operaciones por vecino 62

incrementa α, la imagen se torna más clara, ya que una mayor proporción de la imagen original
se suma al resultado y entonces los valores de los pixels son mayores.

9.2. Filtro por mediana

Ya hemos mencionado que un filtro pasobajo puede resultar útil para remover ruido Gaussiano,
pero no impulsivo. Una imagen con ruido impulsivo tiene pixels corruptos con valores de inten-
sidad de 0 o 255. Una manera efectiva de remover el ruido impulsivo es el filtro por mediana
(figura 9.3). Una de las ventajas de este filtro sobre el pasobajo es que preserva mejor los bordes
y detalles.

(a) Ruido agregado (5 %) (b) Imagen filtrada

Figura 9.3: Aplicación de filtro por mediana

El filtro por mediana se aplica llevando una ventana corrediza sobre la imagen original y or-
denando los pixels en la ventana en orden ascendente. La mediana (el pixel del centro en ese
ordenamiento) será el valor del pixel correspodiente en la imagen resultado. La función principal
es forzar a los puntos cuya intensidad es muy distinta de sus vecinos a parecerse a ellos, elimi-
nando picos de intensidad. Al implementar el algoritmo surge el mismo inconveniente que con la
convolución: cómo tratar las celdas de la ventana que no caen dentro de la imagen? Además de
las alternativas presentadas, se puede considerar una más, que fue la elegida en nuestra imple-
mentación (imgBlockMedianFilter ): ignorar las celdas vacı́as y operar sólo sobre los valores de
la imagen en la ventana.

El procedimiento para filtrar imágenes color es diferente. El algoritmo para ordenar los pixels
debe ser distinto. Una posibilidad serı́a aplicar el filtro descripto en cada uno de los canales y
combinar las salidas. Esto tiene el problema de que se pierde la correlación entre los componentes
de color. Además una de las caracterı́sticas del filtro es que no se introducen nuevos valores en
la salida, sino que cada valor de pixel en el resultado se corresponde con alguno en la imagen de
entrada.
Capı́tulo 9. Operaciones por vecino 63

Sin embargo hay una propiedad de la mediana que podemos aprovechar en este caso. La suma
de las diferencias entre un valor de mediana y todos los demás valores en un conjunto será menor
que la suma de las diferencias para cualquier otro valor del conjunto:

N
X N
X
| xmed − xi | ≤ | y − xi | (9.3)
i=1 i=1

N es el número de elementos en el conjunto (serı́a 9 para un filtro mediana 3 × 3); y es un valor


arbitrario de ese conjunto; xmed es la mediana.

Entonces ahora podemos considerar sumas de diferencias en lugar de preocuparnos por cómo
ordenar los pixels color. Para cada pixel en nuestra ventana sumamos la diferencia entre los
componentes rojo, verde y azul con el resto de los pixels. El pixel con la menor suma es el valor
de salida. Es decir que para cada uno de los N pixels de la ventana se debe calcular la suma de
las diferencias para cada componente.

N
X
Distancei = (| redi − redj | + | greeni − greenj | + | bluei − bluej |) (9.4)
j =1

Donde i es el pixel que se está procesando y j representa los demás pixels en la ventana; la menor
distancia, i , corresponderá al pixel de salida xi .

Esta técnica funciona bien tanto para ventanas de dimensiones impares como pares, aunque
tradicionalmente se utilizan dimensiones impares.

9.3. Filtro por mı́nimo/máximo

Los filtros por mı́nimo (imgMinimumFilter ) y máximo (imgMaximumFilter ) son similares al


filtro por mediana. En lugar de reemplazar el pixel del centro de la ventana por la mediana, se
usan el valor mı́nimo o máximo, respectivamente.

El filtro por mı́nimo remueve picos de blanco. De esta manera, un pixel es representado por el
más oscuro de la ventana, y por lo tanto la intensidad de la imagen resultante se verá reducida
respecto de la original. El filtro por máximo remueve los picos oscuros, y la intensidad de la
imagen de salida será mayor que la de la original.

Ambos filtros fallan a la hora de remover ruido impulsivo, ya que cada uno realza los picos
negativos (mı́nimo) o los picos positivos (máximo). Una cascada de filtros por máximo y mı́nimo
pueden servir para eliminar este ruido ”salt & pepper”. Un filtro por máximo seguido por uno
por mı́nimo se llama filtro de closing, mientras que uno por mı́nimo seguido por uno por máximo
es llamado filtro de opening.
Capı́tulo 10

Algoritmos de detección de
bordes

Los bordes en una imagen suministran mucha información acerca de la misma. Por ejemplo
marcan los lı́mites entre un objeto y el fondo, y entre distintos objetos. Es decir que si se pueden
identificar los bordes con precisión, se pueden localizar objetos y determinar algunas propiedades
básicas como área, perı́metro o forma.

Existen numerosas aplicaciones para la detección de bordes, por ejemplo en visión de compu-
tadoras o en el proceso de identificar regiones en una imagen (segmentación).

A lo largo de esta sección revisamos distintos algoritmos para la detección de bordes: algunos
métodos sencillos y rápidos, los métodos tradicionales basados en máscaras de convolución y
también algunas técnicas avanzadas.

10.1. Generalidades

Diremos que existe un borde donde la intensidad de la imagen pasa de un valor bajo a uno alto
o viceversa. Como los bordes consisten principalmente de frecuencias altas, podrı́amos detectar
bordes aplicando un filtro pasoalto en el espacio de Fourier (ver 11.4), o aplicando una convolución
con una máscara apropiada en la representación espacial. En la práctica se suele utilizar esta
última alternativa, ya que es computacionalmente menos costosa y se obtienen muchas veces
mejores resultados.

Hay un número infinito de orientaciones, anchos y formas de bordes. Y hay muchas técnicas
para su detección, cada una con sus ventajas y desventajas. En algunos casos la experimentación
ayuda a determinar cuál es la mejor técnica para aplicar en cada caso.

La salida de un operador de detección de bordes se denomina mapa de bordes. Como comple-


mento a la detección de bordes se puede aplicar una operación de threshold para enfatizar los
bordes más fuertes y disimular los débiles. Se pueden dar uno o dos niveles de threshold. Si se

64
Capı́tulo 10. Algoritmos de detección de bordes 65

especifica sólo uno, los pixels cuyos valores estén por encima se setean al máximo valor posible,
y aquellos que estén por debajo se setean a cero. Si se definen un valor de threshold superior y
uno inferior, los valores por debajo del inferior se setean a cero, aquellos entre los dos valores
dados no cambian y los que están por encima del valor superior se setean al máximo posible.

10.2. Técnicas sencillas

Los detectores de bordes más simples y rápidos determinan el máximo valor a partir de una serie
de diferencias entre pixels. El operador de homogeneidad calcula la diferencia entre cada uno de
los 8 pixels y el del centro de una ventana de 3 × 3. El valor del pixel de salida es el máximo
entre los valores absolutos de las diferencias (ver figura 10.1). Puede ser necesario utilizar un
offset para acomodar los valores en la imagen final. En biOps está implementado bajo el nombre
imgHomogeneityEdgeDetection.

(a) Operador (b) Ejemplo

res = max {| 11−11 |, | 11−13 |, | 11−15 |, | 11−16 |, | 11−11 |, | 11−16 |, | 11−12 |, | 11−11 |} = 5

Figura 10.1: Operador de homogeneidad

Similar al operador de homogeneidad se define el detector de bordes por diferencia (en biOps,
imgDifferenceEdgeDetection). Es más rápido porque requiere cuatro restas por pixel. Las dife-
rencias que se calculan son superior izquierda - inferior derecha, medio izquierda - medio derecha,
inferior izquierda - superior derecha, y medio superior - medio inferior (figura 10.2).

(a) Operador (b) Ejemplo

res = max {| 11 − 11 |, | 13 − 12 |, | 15 − 16 |, | 11 − 16 |} = 5

Figura 10.2: Operador por diferencia


Capı́tulo 10. Algoritmos de detección de bordes 66

Estos métodos son rápidos, pero a veces se necesitan técnicas más complejas. En la figura 10.3
se puede ver un ejemplo de una aplicación del operador por diferencia.

(a) Imagen original (b) Detección de bordes

Figura 10.3: Aplicación de operador por diferencia

10.3. Técnicas por convolución

Los operadores de gradiente encuentran bordes horizontales y verticales, es decir que podemos
usar las derivadas de la imagen. Se puede ver que la posición de los bordes puede estimarse a
partir del máximo de la primera derivada o a partir de los llamados zero-crossings de la segunda
derivada (puntos en que la función cruza el cero). Por lo tanto, necesitamos una forma de calcular
la derivada de una imagen.

Figura 10.4: Borde y derivadas en una dimensión

Para una función discreta de una dimensión la primera derivada se puede aproximar por:
Capı́tulo 10. Algoritmos de detección de bordes 67

df (i )
= f (i + 1) − f (i ) (10.1)
d (i )

El cálculo de esta fórmula es equivalente a una convolución de la función con [-1 1]. De mane-
ra similar, la segunda derivada se puede estimar convolviendo f (i ) con [1 -2 1]. Entonces los
operadores por gradiente los podemos obtener por convolución.

Existen diferentes máscaras de detección de bordes basadas en la fórmula descripta, que nos
permiten calcular la primera o segunda derivada de una imagen. Hay dos aproximaciones para
estimar la primera derivada de una imagen: gradient edge detection y compass edge detection.

Los coeficientes de estas máscaras suman 0. Si esto no fuera ası́, entonces al convolver con una
imagen constante obtendrı́amos una imagen distinta de 0, lo que implicarı́a erronéamente la
existencia de bordes.

10.3.1. Detección de bordes por gradiente (Gradient Edge Detection)

Es una de las técnicas más utilizadas. Se aplican dos máscaras de convolución sobre la imagen,
una que estima el gradiente en la dirección de x (Gx ), y otra en la dirección de y (Gy ). La
magnitud absoluta del gradiente está dada por:

q
| G |= Gx2 + Gy2 (10.2)

y por lo general se aproxima por:

| G |=| Gx | + | Gy | (10.3)

También se puede determinar la orientación de los bordes por:

θ = arctan(Gx /Gy ) − 3π/4 (10.4)

Las máscaras más comunes, y que fueron implementadas, son Sobel (imgSobel , ver figura 10.5),
Roberts (imgRoberts), Prewitt (imgPrewitt) y Frei-Chen (imgFreiChen). A continuación se des-
criben las correspondientes máscaras, tanto para la dirección horizontal como vertical. Notar que
una es la rotación de 90o de la otra.
Capı́tulo 10. Algoritmos de detección de bordes 68

   
1 0 −1 1 2 1
   
Sobelx = 
 2 0 −2 
 Sobely = 
 0 0 0 

1 0 −1 −1 −2 −1

   
0 0 −1 0 0 0
   
Robertsx = 
 0 1 0 
 Robertsy = 
 0 1 0 

0 0 0 0 0 −1

   
1 0 −1 1 1 1
   
Prewittx = 
 1 0 −1 
 Prewitty = 
 0 0 0 

1 0 −1 −1 −1 −1

   √ 
1 0 −1 1 2 1
 √ √   
FreiChenx =  2
 0 − 2  FreiCheny = 
 0 0 0 
 √ 
1 0 −1 −1 − 2 −1

Figura 10.5: Aplicación de Sobel (threshold = 40, negativo)

10.3.2. Detección de bordes por compás (Compass Edge Detection)

Los operadores por compass gradient encuentran bordes en ocho direcciones diferentes. Esto
requiere convolver la imagen con un conjunto de (en general ocho) máscaras, cada una sensible
a distintas orientaciones. La salida de la operación corresponde al máximo de las convoluciones
aplicadas.

Hay que tener en cuenta que cuanto menor son las máscaras, son más sensibles al ruido, mien-
tras que las máscaras más grandes no pueden resolver detalles finos, además de ser el cálculo
computacionalmente más costoso.
Capı́tulo 10. Algoritmos de detección de bordes 69

En este caso implementamos las máscaras de Prewitt (imgPrewittCompassGradient), Kirsch


(imgKirsch) y Robinson (imgRobinson3Level , imgRobinson5Level ). A continuación se detallan
las máscaras base. Las restantes se obtienen rotando 45o sucesivamente.

 
1 1 −1
 
Prewitt = 
 1 −2 −1 

1 1 −1

 
5 −3 −3
 
Kirsch = 
 5 0 −3 

5 −3 −3

 
1 0 −1
 
Robinson3Level = 
 1 0 −1 

1 0 −1

 
1 0 −1
 
Robinson5Level = 
 2 0 −2 

1 0 −1

10.4. Técnicas avanzadas

Los operadores por gradiente vistos hasta aquı́ producen una respuesta grande a lo largo del área
donde hay bordes. Idealmente, un detector de bordes deberı́a determinar el centro de los bordes.
Este concepto se denomina localización. Si un detector de bordes devuelve bordes de varios pixels
de ancho es difı́cil definir el centro de los bordes. Se hace necesario aplicar un proceso de thinning
para reducir el ancho de los bordes a un pixel. Los detectores de bordes basados en la segunda
derivada proveen una mejor localización, importante en visión de máquinas.

Otra ventaja de los operadores de segunda derivada es que los bordes detectados son curvas
cerradas, importante para el proceso de segmentación. Además, no responden ante áreas de
variaciones lineales leves en la intensidad.

El operador de Laplacian es un buen ejemplo. Se trata de un operador omnidireccional, que


además produce bordes más finos que los métodos anteriores. El resultado presenta un cambio
de signo en los bordes de la imagen, los ya mencionados zero-crossings. Por lo tanto, después
de la convolución, la imagen debe ser procesada para encontrar estos puntos y setear la salida
correspondiente.

Un problema con Laplacian es que es un operador susceptible al ruido, y entonces los zero-
crossings pueden indicar más bordes que los esperados. En estos casos se debe aplicar un threshold
para filtrar el resultado.
Capı́tulo 10. Algoritmos de detección de bordes 70

Otro operador de segunda derivada, menos susceptible al ruido, es el Laplacian of Gaussian


(LoG). Éste aplica un suavizado gaussiano antes del operador de Laplacian. Ambas operaciones
se pueden resolver mediante una máscara de la siguiente forma:

 
1 x 2 + y2 2
+y 2 )/2σ 2
LoG(x , y) = 1 −  e −(x (10.5)
πσ 4 2σ 2

Cuanto más ancha sea la función, más ancho serán los bordes detectados; una función más
angosta detectará bordes más finos y mayor detalle. Mientras mayor sea el σ, mayor será la
máscara de convolución necesaria. Por otro lado, la detección de bordes basados en suavizado
gaussiano, al reducir el ruido en la imagen, reducen el número de bordes falsos detectados.

Como aproximación al LoG se suele usar el Difference of Gaussian (DoG) que tiene un menor
costo computacional para ser calculado:

2
+y 2 )/2σ12 2
+y 2 )/2σ22
e −(x e −(x
DoG(x , y) = − s (10.6)
2πσ12 2πσ22

Este operador convuelve una imagen con una máscara que resulta de la diferencia de dos máscaras
Gaussianas con diferentes valores de σ. El cociente σ1 /σ2 = 1,6 da una buena aproximación a
LoG. Variando los valores de σ1 y σ2 se puede especificar el ancho de los bordes a detectar.

10.4.1. Marr Hildreth

Este algoritmo (1970, Marr y Hildreth) está basado en el LoG. Consiste de los siguientes pasos:

1. Convolver la imagen I con una máscara Gaussiana

2. Aplicar el operador LoG (o DoG)

3. Los pixels correspondientes a bordes son los zero-crossings del resultado anterior

Este método tiene un par de limitaciones. En primer lugar, produce “falsos bordes”, es decir
genera respuestas donde no existen bordes; por otro lado, tampoco tiene buena localización. Fue
implementado en la función imgMarrHildreth, que tiene por argumentos una imagen y un valor
para el σ de la máscara Gaussiana.

10.4.2. Canny

El detector Canny (1986, John Canny) está definido a partir de una serie de objetivos a cumplir:

Tasa de error: Debe responder sólo a bordes y debe encontrarlos todos;

Localización: La distancia entre los bordes detectados y los reales debe ser mı́nima;
Capı́tulo 10. Algoritmos de detección de bordes 71

Respuesta: No se deben detectar múltiples pixels de borde cuando sólo existe uno;

Para satisfacer estos criterios se utiliza el cálculo de variaciones, que permite encontrar la función
que optimiza un funcional dado. En el caso de Canny, esa función se describe como la suma de
cuatro términos exponenciales; sin embargo se puede aproximar por la primera derivada de una
Gaussiana.

El algoritmo esta definido por las siguientes etapas:

1. Convolución con Gaussiana en las direcciones x , y


La derivada de una Gaussiana es susceptible al ruido; por esta razón se aplica una con-
volución con una máscara Gaussiana, para obtener una imagen con un ligero borroneado
(blurring) que disminuya el ruido. El σ de esta Gaussiana es parámetro del algoritmo. Se
aplica como dos convoluciones de una dimensión por separado, dando por resultado las
imágenes componentes por dirección, Ix , Iy .

2. Convolución con las derivadas Gaussianas en las direcciones x , y


También se aplican por separado en cada dirección, y a la correspondiente componente,
para obtener Ix0 , Iy0 .

3. Calcular la magnitud del gradiente


Las componentes se combinan para obtener la magnitud del gradiente en cada pixel.

4. Aplicar eliminación de puntos no máximos (nonmaximal suppression)


Los pixels de borde tienen una dirección asociada; la magnitud del gradiente en pixel de
borde debe ser mayor que la magnitud del gradiente de los pixels a cada lado del borde.
Los pixels que no son máximos locales son eliminados. Desde el pixel en cuestión, seguir la
dirección del gradiente hasta encontrar otro pixel; éste es el primer vecino. Luego, desde el
pixel original, dirigirse en la dirección opuesta hasta encontrar un nuevo pixel, el segundo
vecino. Moviéndose de un vecino al otro se pasa a través del pixel de borde, cruzando el
borde, por lo tanto la magnitud del gradiente deberı́a ser mayor en este último pixel.

5. Threshold por Hysteresis


Canny sugiere aplicar hysteresis en lugar de simplemente elegir un valor de threshold para
toda la imagen. Hysteresis usa un valor de máximo de threshold, Th , y un valor mı́nimo,
Tl . Cualquier pixel en la imagen con un valor mayor que Th se marca como borde; luego,
cualquier pixel conectado a éste, y que tenga un valor mayor a Tl , también se selecciona
como borde. Este proceso se puede hacer de forma recursiva, o mediante múltiples pasadas
por la imagen.

En biOps se invoca a través de la función imgCanny (ver figura 10.6), que toma como parámetros
además de la imagen sobre la cual aplicar el algoritmo, el σ del filtro Gaussiano, y opcionalmente
los valores de threshold para el proceso de hysteresis.
Capı́tulo 10. Algoritmos de detección de bordes 72

Figura 10.6: Aplicación de Canny

10.4.3. Shen Castan

El concepto de optimalidad es relativo, y por lo tanto es posible definir un detector de bordes


mejor que Canny en ciertas circunstancias. El algoritmo de Shen Castan (1992, Shen y Castan)
coincide con Canny en la forma general: convolución con una máscara suavizante, seguida de una
búsqueda de pixels de borde. Sin embargo busca optimizar una función diferente para la tasa
de error, y en lugar de la derivada de una Gaussiana usa el filtro exponencial simétrico infinito
(ISEF, infinite symmetric exponential filter), que en dos dimensiones y para el caso discreto es:

(1 − b)b |i|+|j |
f [i , j ] = (10.7)
1+b

donde b es el factor de suavizado usado por el filtro, y toma valores reales entre 0 y 1.

1. Sea I la imagen original. Aplicar ISEF y obtener la imagen filtrada, S

2. Calcular una aproximación del operador Laplacian (bandlimitedLaplacian), B = S − I

3. Obtener BLI (binary Laplacian image)


Se obtiene de B seteando los pixels positivos a 1 y los demás a 0. Los pixels borde candidatos
son los lı́mites de las regiones en la imagen obtenida, que corresponden a los zero-crossings.
Si bien este podrı́a ser el resultado, quedan un par de pasos para mejorar la calidad de los
pixels identificados.

4. Eliminar falsos zero-crossings


Análogo al proceso de eliminación de puntos no máximos (nonmaximal suppression) del
algoritmo de Canny. En la posición de un pixel borde habrá un zero-crossing en la se-
gunda derivada de la imagen filtrada. Es decir que el gradiente en ese punto es o bien un
máximo o un mı́nimo. Si la segunda derivada cambia de signo de positivo a negativo, se
Capı́tulo 10. Algoritmos de detección de bordes 73

llamará un zero-crossing positivo; y si pasa de negativo a positivo, zero-crossing negativo.


Los zero-crossings permitidos son aquellos que son positivos y tienen gradiente positivo, o
los negativos con gradiente negativo. Los demás zero-crossings serán considerados falsos y
no correspondientes a un borde.

5. Aplicar threshold por gradiente adaptativo


Una ventana de ancho fijo W se centra en cada pixel borde candidato en la imagen BLI.
Si se trata efectivamente de un pixel borde, entonces la ventana contendrá dos regiones de
diferente nivel de gris separadas por un borde. La mejor estimación del gradiente en ese
punto deberı́a ser la diferencia de nivel entre las dos regiones, correspondientes una a los
pixels de valor 0 y la otra a los de valor 1 en la BLI.

6. Hysteresis
Es el mismo método que en Canny, pero adaptado para el caso en que los bordes están
representados por zero-crossings.

Este algoritmo puede correrse sobre una imagen a través de la función imgShenCastan que toma
argumentos para definir el factor de suavizado, un factor de thinning, el tamaño de la ventana
del threshold por gradiente adaptativo, un porcentaje que indica la cantidad de pixels que debe
haber por encima del valor de threshold máximo, y un booleano que determina si se aplica
hysteresis o no.

10.5. Detección de bordes en color

La detección de bordes en imágenes color depende de la definición de borde. Si se define como


la discontinuidad en la luminosidad de la imagen, entonces deberı́amos hacer la detección en el
canal de intensidad, en el espacio de color HSI.

Otra definición sostiene que un borde existe si está presente en los tres canales, rojo, verde
y azul. En este caso se puede hacer la detección en cada componente y después combinarlas,
obteniendo una imagen resultado color. También podrı́a hacerse la detección por componente y
luego sumarlas para crear una imagen en escala de grises.

Está visto que la gran mayorı́a de los bordes encontrados en las componentes de color de una
imagen también se encuentran en la componente de intensidad. De esta manera serı́a suficiente
hacer la detección de bordes sobre el canal de intensidad. Sin embargo hay casos en imágenes
de bajo contraste en que existen bordes que no se detectan por luminosidad pero sı́ en las
componentes cromáticas. La decisión entonces dependerá principalmente de la aplicación. En
nuestro caso, los algoritmos implementados trabajan sobre las componentes de color, trabajando
con imágenes en representación RGB.
Capı́tulo 11

Filtros en el espacio de
frecuencias

Gran parte del procesamiento digital de señales se hace en un espacio matemático conocido como
espacio de frecuencias. El espacio de frecuencias de una imagen se refiere a la tasa de cambio en la
intensidad de los pixels. Para representar la información en este espacio es necesario aplicar algún
tipo de transformación. Una de las más difundidas y estudiadas en este caso es la transformada
de Fourier.

En el caso particular de las imágenes introducimos una variante de la transformada de Fourier,


la denominada transformada de Fourier discreta. Sin embargo, el cálculo de esta transforma-
ción es costoso computacionalmente. Por esta razón se desarrolló un método más eficiente para
computarla, la llamada transformada rápida de Fourier, que es el utilizado en el procesamiento
digital.

Una vez que tenemos la representación de la imagen en el espacio de frecuencias podemos analizar
su espectro de frecuencias, aplicar distintos filtros en este espacio e incluso, por una propiedad
de la transformada de Fourier, calcular mediante el producto de matrices complejas lo que en
representación espacial hacı́amos por convolución, lo que es especialmente útil para máscaras de
convolución grandes.

11.1. Espacio de frecuencias

Una transformación es simplemente un mapeo de un conjunto de coordenadas en otro. La trans-


formada de Fourier convierte coordenadas espaciales en frecuencias. Cualquier curva o superficie
se puede expresar como la suma de senos y cosenos. En el espacio de frecuencias (o espacio de
Fourier) una imagen se representa como los parámetros de funciones seno y coseno. La transfor-
mada de Fourier es el método para pasar de una representación a otra.

Se denomina espacio de frecuencias porque los parámetros del seno son amplitud y frecuencia.
El hecho de que una imagen se pueda convertir al espacio de frecuencias implica que se puede

74
Capı́tulo 11. Filtros en el espacio de frecuencias 75

reconocer información de baja y alta frecuencia. Una zona de la imagen que cambia lentamente
a lo largo de las columnas corresponde en el espacio de frecuencias a una función seno o coseno
con baja frecuencia. Por otro lado, si cambia rápidamente, como un borde, tendrá componentes
con frecuencias altas.

El espacio de frecuencias de una imagen se refiere a la tasa en que la intensidad de los pixels
cambia. Las frecuencias altas se caracterizan por los grandes cambios de amplitud, mientras que
las bajas por zonas de valores casi constantes.

De esta manera es posible construir filtros para remover o realzar determinadas frecuencias en una
imagen, lo que permite en ciertas ocasiones producir efectos de restauración. De hecho, el ruido
consiste principalmente de información de frecuencias altas, y entonces filtrar las frecuencias
altas deberı́a producir una reducción del ruido. Sin embargo, en este caso, también se obtiene
una reducción de los bordes.

11.2. Transformada de Fourier

La transformada de Fourier convierte una imagen (o una señal, en una dimensión) en un conjunto
de componentes de seno y coseno. Es importante mantener estas componentes separadas, y por
esta razón se suele usar vectores de la forma (coseno, seno) para cada punto de la representación
en el espacio de frecuencias de una imagen. Una forma de representar estos vectores es mediante
números complejos. Cada número complejo consiste de una parte real y una parte imaginaria, y
puede ser pensado como un vector. Un número complejo tiene la siguiente forma:

z = (x , j y) = x + j y (11.1)


donde j es el número imaginario −1. El exponencial de un número complejo se puede repre-
sentar como la suma de un seno y un coseno, que es exactamente lo que queremos:

e j θ = cos(θ) + j sin(θ) (11.2)

Esta representación es la utilizada en la transformación.

La transformada de Fourier opera sobre funciones continuas de longitud infinita. Para una función
de dos dimensiones:

Z ∞ Z ∞
H (u, v ) = h(x , y)e −j 2π(ux +vy) dx dy (11.3)
−∞ −∞

También es posible pasar del espacio de frecuencias a la representación espacial, mediante la


transformada de Fourier inversa:

Z ∞ Z ∞
h(u, v ) = H (u, v )e j 2π(ux +vy) du dv (11.4)
−∞ −∞
Capı́tulo 11. Filtros en el espacio de frecuencias 76

Sin embargo al trabajar con imágenes no tenemos funciones continuas, sino que contamos con
un número finito de pixels que tienen valores discretos. Por lo tanto necesitamos definir una
transformación de Fourier discreta (DFT, Discrete Fourier Transformation), que no es más que
un caso especial de la continua. La fórmula para computar la DFT de una imagen de M × N es:

M −1 N −1
1 X X
H (u, v ) = h(x , y)e −j 2π(ux /M +vy/N ) (11.5)
MN x =0 y=0

y la inversa:

M
X −1 N
X −1
h(x , y) = H (u, v )e j 2π(ux /M +vy/N ) (11.6)
u=0 v =0

Si representamos H (u, v ) en coordenadas polares:

H (u, v ) =| H (u, v ) | e −j φ(u,v ) (11.7)

tenemos

| H (u, v ) |= [R 2 (u, v ) + I 2 (u, v )]1/2 (11.8)

 
I (u, v )
φ(u, v ) = tan −1   (11.9)
R(u, v )

donde R(u, v ) e I (u, v ) son la parte real e imaginaria de H (u, v ), respectivamente. A | H (u, v ) |
se le llama magnitud o espectro de la transformación, y a φ(u, v ), ángulo de fase. A la hora de
trabajar con imágenes se usa especialmente el espectro.

El cálculo de la DFT es computacionalmente intensivo. Trabajando con imágenes 2D de M × M


se requieren M 4 multiplicaciones de complejos. Afortunadamente se desarrolló, por el año 1942,
una técnica “divide & conquer” para obtener la DFT que se denominó Transformada Rápida de
Fourier (FFT, Fast Fourier Transformation).

No entraremos en más detalles acerca del cálculo e implementación de FFT, ya que irı́an más
allá de lo necesario para la comprensión de este capı́tulo. En nuestro desarrollo utilizamos FFTW
(Fast Fourier Transformation in the West), una librerı́a libre bajo licencia GPL para calcular la
FFT en una o más dimensiones. Esta librerı́a puede manejar arreglos de tamaños arbitrarios, y
nos permite obtener rápidamente la DFT de una imagen.

Ahora que podemos obtener la transformación de una imagen queremos mostrar la información.
Sin embargo existen algunas complicaciones que debemos superar para mostrar el espectro de
una imagen. Uno de los problemas que tenemos es que cada punto está representado por un
Capı́tulo 11. Filtros en el espacio de frecuencias 77

número flotante, que no necesariamente está en el rango 0 - 255. Una solución usual es tomar el
logaritmo del espectro, es decir:

D(u, v ) = c log[1+ | H (u, v ) |] (11.10)

donde c es una constante, que representa el parámetro de escala; además se suma 1 a cada pixel
para evitar pasar el valor 0 a la función logaritmo.

Una imagen del espectro tiene la componente cero en la esquina superior izquierda, como se ve
en 11.1(b). La forma convencional de mostrar el espectro es hacer un remapeo de los cuadrantes,
haciendo un intercambio (o “shift”) horizontal de la imagen en la mitad del ancho, y vertical en
la mitad del alto (11.1(c)).

¿Cómo interpretamos esta información? Cada pixel en el espectro (11.1(d)) representa un cambio
en el espacio de frecuencias de un ciclo por ancho de la imagen. El origen, en el centro del espectro
cuando éste está ordenado, es el término constante. Si todos los pixels de la imagen fueran grises
entonces habrı́a un único valor en el espectro de frecuencias, y estarı́a en el origen. El siguiente
pixel a la derecha del origen representa un ciclo por ancho de la imagen, el siguiente 2 ciclos
por ancho de imagen y ası́ sucesivamente. Es decir que las amplitudes de las frecuencias bajas
se encuentran en las esquinas del espectro, mientras que las altas están alrededor del centro (el
origen del espectro).

biOps en este campo ofrece funciones para hacer la transformación (imgFFT ) y su inversa
(imgFFTInv ). Se puede decidir la organización de los cuadrantes tanto al momento de aplicar
FFT como una vez obtenido el resultado mediante la función imgFFTShift. Todas las funciones
mencionadas, a excepción de imgFFTInv que devuelve una imagen, trabajan con matrices de
números complejos. Para obtener una imagen del espectro se puede invocar a imgFFTSpectrum,
y para generar la imagen de la información de fase, imgFFTPhase.

A continuación se presentan los esquemas utilizados para representar las transformaciones y la


matriz resultado de FFT, y en los que se basan las especificaciones de los filtros en el espacio de
frecuencias que se detallan en las próximas secciones.

FFTMatrix
matrix : N × N  Complex
width, height : N

dom matrix = {a : N × N | 0 ≤ first a < width ∧ 0 ≤ second a < height}

fft : Image " FFTMatrix

∀ x : Image
• (∃1 y : FFTMatrix • fft (x ) = y ∧ x .width = y.width ∧ x .height = y.height)
Capı́tulo 11. Filtros en el espacio de frecuencias 78

(a) Imagen original (b) Espectro FFT original

(c) Remapeo de cuadrantes (d) Espectro FFT remapeado

Figura 11.1: Transformada de Fourier

fftInv : FFTMatrix " Image


∀ x : FFTMatrix
• (∃1 y : Image • fftInv (x ) = y ∧ x .width = y.width ∧ x .height = y.height)

11.3. Convolución

Una razón por la cual es útil generar la información de frecuencia de una imagen es para aplicarle
filtros. Hemos visto filtros por convolución en la representación espacial (ver 9.1). Una convolución
en la representación espacial es equivalente a una multiplicación de espectros en el espacio de
frecuencias.

Sean F (u, v ) y H (u, v ) las FFT de f (x , y) y h(x , y), respectivamente. Denotaremos a la operación
de convolución por ∗. El teorema de convolución demuestra que f (x , y)∗h(x , y) y F (u, v )H (u, v )
constituyen un par FFT:

f (x , y) ∗ h(x , y) ⇔ F (u, v )H (u, v ) (11.11)


Capı́tulo 11. Filtros en el espacio de frecuencias 79

Figura 11.2: Filtros FFT

La flecha doble indica que la expresión de la izquierda (convolución espacial) puede ser obtenida
tomando la FFT inversa de la expresión de la derecha (el producto en el espacio de frecuencias).
De la misma forma la expresión de la derecha se obtiene mediante la FFT de la expresión de la
izquierda. Un resultado análogo es que la convolución en el espacio de frecuencias reduce a la
multiplicación en la representación espacial, y viceversa, es decir:

f (x , y)h(x , y) ⇔ F (u, v ) ∗ H (u, v ) (11.12)

Estos dos resultados resumen el teorema de convolución.

Entonces podemos sintetizar el proceso en los siguientes pasos (11.2):

1. Transformar una imagen al espacio de frecuencias mediante FFT

2. Multiplicar el espectro por una máscara

3. Aplicar la transformación FFT inversa

Necesitamos crear la máscara. Existen dos métodos: uno es partir de una máscara en representa-
ción espacial y hacer la transformación, y el otro directamente calcular la máscara en el espacio
de frecuencias.

Para utilizar una máscara en representación espacial, se debe centrar ésta en la imagen y comple-
tar con ceros de tal forma de cubrir la imagen. Luego, transformar esta máscara y multiplicarla
por la FFT de la imagen, mediante multiplicación de complejos. Al resultado se le aplica la FFT
inversa. La imagen obtenida es la misma que si se hubiera hecho la convolución en la represen-
tación espacial con la máscara original. Este método se usa en general cuando se trabaja con
máscaras muy grandes.

La función imgFFTConvolve computa la convolución en el espacio de frecuencias dada una


imagen ya transformada y una máscara en su representación espacial, la que rellena y transforma
para efectuar el cálculo. El resultado es una matriz compleja cuya FFT inversa es la imagen
resultado de la convolución.
Capı́tulo 11. Filtros en el espacio de frecuencias 80

Convolve
∆FFTMatrix
mask ? : Image

width 0 = width = mask ?.width


height 0 = height = mask ?.height
let fft mask == fft(mask ?) •
(∀ x : dom matrix • matrix 0 x = matrix x ∗C fft mask .matrix x )

11.4. Filtros por frecuencia

Existen muchos tipos de filtros por frecuencia pero la mayorı́a son una derivación o combinación
de los siguientes cuatro: pasobajo, pasoalto, bandpass y bandstop.

El filtro pasobajo deja pasar las frecuencias bajas atenuando las más altas. El pasoalto, en cambio,
atenua las más bajas mientras deja pasar las altas. Bandpass permite pasar sin modificaciones
una determinada banda de frecuencias, atenuando las frecuencias fuera del rango. Bandstop, por
el contrario, bloquea sólo una banda especı́fica de frecuencias, sin alterar aquellas fuera de esa
banda. Bandpass y bandstop se pueden obtener como combinación de sustracción y adición de
los resultados de los filtros pasobajo y pasoalto.

Los filtros provistos por biOps son: imgFFTLowPass (filtro pasobajo) y imgFFTHighPass (filtro
pasoalto), que toman por argumento, además de la transformada de la imagen, un valor de
radio por el cual filtrar las frecuencias; imgFFTBandPass e imgFFTBandStop, que esperan la
transformada y dos valores de radio que delimitan la banda.

A modo de ejemplo se muestra el esquema que describe el filtro de pasoalto y se muestra una
aplicación particular de este filtro (11.3).

HighPass
∆FFTMatrix
r? : R

width 0 = width
height 0 = height
∀ x : dom matrix | euclideanDistance(x , (width div 2, height div 2)) ≤ r?
• (matrix 0 x ).re = 0 ∧ (matrix0 x).im = 0
Capı́tulo 11. Filtros en el espacio de frecuencias 81

(a) Imagen original (b) Filtro pasoalto con r=10

Figura 11.3: Filtro por frecuencia


Capı́tulo 12

Operaciones morfológicas

Morfologı́a significa “la forma y estructura de un objeto”, o “la colocación e interrelación entre
las partes de un objeto”. A diferencia de otras operaciones vistas en este trabajo, diseñadas
para alterar la apariencia de una imagen, las morfológicas están relacionadas con la forma, y la
morfologı́a digital es una manera de describir o analizar la forma de un objeto digital.

La ciencia de la morfologı́a digital es relativamente reciente, aunque basa sus conceptos en


la teorı́a simple de conjuntos. Podemos pensar que las imágenes consisten en un conjunto de
elementos (pixels). Pueden usarse ciertas operaciones matemáticas sobre este conjunto para
resaltar aspectos especı́ficos de las formas para, por ejemplo, ser contadas o reconocidas.

Las operaciones básicas, y que se tratarán en este capı́tulo, son la erosión, por la cual se borran
pixels de la imagen que cumplan con ciertas condiciones, y dilatación, en donde se establece un
patrón alrededor de un pixel. A partir de éstas se definen la apertura u opening y la clausura o
closing.

Se tratarán sólo operaciones sobre dos tipos de imágenes: las denominadas binarias que corres-
ponden a las imágenes en “blanco y negro”, y las de canal único, o de “escala de grises”. Las
imágenes de color podrı́an tratarse como una generalización de escala de grises (trabajando so-
bre cada canal) o pensarse como dominios de aplicación separados por color. En ambos casos,
los resultados que se obtienen hacen que sea realmente difı́cil estructurarlos para llevar a cabo
una tarea particular. Sin embargo, este campo del procesamiento de imágenes está creciendo
rápidamente.

12.1. Operaciones sobre imágenes binarias

Las operaciones morfológicas sobre imágenes binarias se basan en imágenes de dos niveles: el
valor de cada pixel pertenece a un conjunto de dos elementos que contiene sólo el mı́nimo
y máximo aceptados (en nuestra especificación, MinValue y MaxValue, respectivamente, y en
nuestra implementación, 0 y 255). Este tipo de imágenes puede ser interpretado como un conjunto
matemático de pixels negros. Como cada pixel se identifica con sus coordenadas, decimos que

82
Capı́tulo 12. Operaciones morfológicas 83

es un punto en un espacio bidimensional (E 2 ). Ası́, por ejemplo, la imagen de la figura 12.1


puede representarse como {(0,0), (1,0), (1,1), (2,2)}, conjunto que llamaremos B1 , para futuras
referencias.

Figura 12.1: Representación gráfica de una imagen binaria

12.1.1. Dilatación binaria

Para definir la dilatación en términos de conjuntos, necesitamos antes algunas definiciones.

Se define a la traslación de un conjunto A por el punto x como:

(A)x = {c | c = a + x , a ∈ A} (12.1)

Para nuestro ejemplo de la imagen de la figura 12.1, tomando x = (1, 2), tendrı́amos que:

(B1 )(1,2) = {(1, 2), (2, 2), (2, 3), (3, 4)}

La reflexión de un conjunto A se define como:

 = {c | c = −a, a ∈ A} (12.2)

que es en realidad una rotación de A en 180o por el origen.

También usaremos algunas definiciones conocidas de la teorı́a de conjuntos:

Ac = {c | c ∈
/ A} (12.3)
A ∩ B = {c | c ∈ A ∧ c ∈ B } (12.4)
A ∪ B = {c | c ∈ A ∨ c ∈ B } (12.5)
A − B = {c | c ∈ A ∧ c ∈
/ B} (12.6)

La dilatación del conjunto A por el conjunto B es:

A ⊕ B = {c | c = a + b, a ∈ A, b ∈ B } (12.7)

donde A representa la imagen sobre la cual estamos trabajando y B un conjunto de pixels,


llamado elemento estructural, o simplemente ventana, y su composición define la naturaleza de
la dilatación. Para visualizarlo, sea B2 = {(0, 0), (0, 1)}. Tendremos que:
Capı́tulo 12. Operaciones morfológicas 84

B1 ⊕ B2 = (B1 + {(0, 0)}) ∪ (B1 + {(0, 1)})


= B1 ∪ {(0, 1), (1, 1), (1, 2), (2, 3)}
= {(0, 0), (0, 1), (1, 0), (1, 1), (1, 2), (2, 2), (2, 3)}

La figura 12.2 grafica la operación, mostrando el efecto causado para este caso.

(a) Original (b) 12.2(a) (c) 12.2(a) (d) 12.2(b) (e)


+ (0,0) + (0,1) + 12.2(c) Ven-
tana

Figura 12.2: Dilatación binaria

La forma en que se calcula la dilatación nos hace conjeturar que puede ser definida como la unión
de todas las traslaciones de los elementos de la ventana. Esto es:

[
A⊕B = (A)b (12.8)
b∈B

Como la dilatación es conmutativa (pues está definida en términos de operaciones conmutativas),


podemos expresar la ecuación 12.8 de la siguiente manera:

[
A⊕B = (B )a (12.9)
a∈A

Esto da un pista con respecto a la implementación para el operador de dilatación (en nuestro
código, imgBinaryDilation): cuando el centro de la ventana se alinea con un pixel negro de la
imagen, todos los pixels de la imagen que corresponden a un pixel negro de la ventana se marcan
para ser cambiados a negro. Cuando terminamos de recorrer la imagen, habremos marcado los
pixels que deben ser convertidos a negro. En general, y en nuestro caso particular, se usa un
buffer secundario (inicialmente en blanco) para ir cargando los valores de la imagen resultado.
Esto es beneficioso en términos de tiempos de ejecución, pero perjudicial en cuanto a uso de
memoria.

Una de las aplicaciones más comunes para este tipo de operación (y por la cual ha tomado este
nombre), es la de hacer que las zonas negras de una imagen crezcan, o se “dilaten”. Para ello
implementamos también la función imgStdBinaryDilation, que aplica el método anteriormente
analizado utilizando una ventana estándar igual a 0, con dimensión pasada por parámetro. Esta
operación genera pixels negros alrededor de los ya existentes, “engrosando” de esta manera a los
objetos presentes. Un ejemplo concreto, utilizando la ventana estándar de dimensión 5, puede
verse en la figura 12.3.
Capı́tulo 12. Operaciones morfológicas 85

(a) Imagen original (b) Dilatación con dimensión 5

Figura 12.3: Dilatación binaria

12.1.2. Erosión binaria

Ası́ como puede decirse que la dilatación resulta en agregar pixels negros en los objetos de las
imágenes binarias (o hacerlos más “gruesos” o “grandes”), la erosión resulta en sacar pixels
negros de los objetos (o hacerlos más “finos” o “pequeños”).

Con los conceptos introducidos en la subsección anterior, podemos definir la erosión de una
imagen A y un elemento estructural o ventana B como sigue:

A B = {c | (B )c ⊆ A} (12.10)

lo cual es el conjunto de pixels c tal que el elemento estructural B trasladado por c corresponde
al conjunto de pixels negros en A.

La definición queda más clara si analizamos la implementación de la función (imgBinaryErosion):


en la imagen resultado se establecen a negro todos los pixels que hacen que la ventana en ese
lugar coincida en todos los lugares que corresponden a la imagen. Es decir, un pixel determinado
quedará en valor negro, si al centrar la ventana en el pixel, la ventana y la porción de imagen
correspondiente coinciden en su totalidad.

Veamos un ejemplo: consideremos la imagen B1 y la ventana B2 vistas en la subsección anterior


y calculemos B1 B2 . Este conjunto es el de todas las traslaciones de B2 que alinean B2 sobre
un conjunto de pixels negros en B1 . Luego, no es necesario considerar el total de traslaciones,
sino aquellas que sitúan el origen de B2 en algún miembro de B1 . Tenemos cuatro con esas
caracterı́sticas:
Capı́tulo 12. Operaciones morfológicas 86

(B2 )(0,0) = {(0, 0), (0, 1)}


(B2 )(1,0) = {(1, 0), (1, 1)}
(B2 )(1,1) = {(1, 1), (1, 2)}
(B2 )(2,2) = {(2, 2), (2, 3)}

De los cuales sólo (B2 )(1,0) queda incluido en B1 y, por consiguiente, aparecerá en la erosión de
B1 . En la figura 12.4 se muestra esta operación.

(a) Original (b) Erosión (c)


Ven-
tana

Figura 12.4: Erosión binaria

Análogamente a la dilatación, se implementó la función imgStdBinaryErosion, aplicación parti-


cular de erosión utilizando una ventana estándar de dimensión parametrizada. Una aplicación
del método (para la figura 12.3(b)) puede verse en la figura 12.5.

(a) Imagen original (b) Erosión con dimensión 3

Figura 12.5: Erosión binaria

12.1.3. Apertura y clausura binarias

A partir de las operaciones vistas en la subsección anterior definiremos algunas más, que son de
uso cotidiano en el procesamiento de imágenes digitales.

Es importante destacar que las operaciones de erosión y dilatación no son inversas. Aunque haya
casos en que la aplicación en cascada de estas operaciones resulte en la imagen original, no es
Capı́tulo 12. Operaciones morfológicas 87

cierto en general. Las operaciones son duales en el siguiente sentido:

(A B )c = Ac ⊕ B̂ (12.11)

La aplicación de una erosión inmediatamente seguida de una dilatación usando el mismo elemento
estructural se llama de apertura (en inglés, opening). En nuestro paquete puede encontrarse con
el nombre de imgBinaryOpening. Es un nombre descriptivo ya que pareciera que la operación
tiende a “abrir” los pequeños espacios entre los objetos que se tocan en una imagen. Después
de la aplicación de apertura, los objetos parecen estar mejor aislados que en la imagen original.
Esta operación puede ser útil a la hora de contar o clasificar los objetos que se encuentran en
ella.

Otra aplicación es la eliminación de ruido. La operación de erosión quitará los pixels aislados y
algunos bordes de los objetos, pero (la mayor parte de) estos últimos podrán ser recuperados con
la operación de dilatación, sin recuperar en este caso los pixels extraños agregados por el ruido.
Es necesario aclarar, de todas formas, que esta técnica da buenos resultados para la eliminación
de puntos negros, pero no hará lo propio con puntos blancos.

Una clausura (closing, en inglés) es similar a una operación de apertura, salvo que la dilatación
se realiza antes que la erosión. La función de biOps que implementamos a tal fin se denomi-
na imgBinaryClosing. La operación tiende a “cerrar” o “rellenar” los pequeños espacios entre
objetos.

La clausura también puede usarse para suavizar los contornos de los objetos de una imagen y
disminuir la apariencia de “dentado” que suelen aparecer en los objetos de algunas imágenes,
sobre todo las que han pasado por un proceso de thresholding.

Para ambas operaciones, y al igual que en el caso de dilatación y erosión, se han implementado las
variantes de aplicación con ventana estándar: pueden usarse las funciones imgStdBinaryOpening
e imgStdBinaryClosing. Un ejemplo de cada una de estas funciones puede verse en la figura 12.6.

(a) Imagen original (b) Apertura (dim=3) (c) Apertura (dim=2) (d) Clausura (dim=3)

Figura 12.6: Apertura y clausura

Otra posibilidad es la aplicación repetida de dilatación seguido de la misma cantidad de aplicacio-


nes de erosión. Esta función fue implementada en biOps bajo en nombre de imgNDilationErosion
(imgStdNDilationErosion para la versión de ventana fija), y para un valor n de aplicaciones, la
operación resulta en el suavizado de irregularidades de tamaño n.
Capı́tulo 12. Operaciones morfológicas 88

La forma tradicional de aproximar la computación de una apertura de profundidad n es realizar


n operaciones de erosión seguido de n aplicaciones de dilatación. Esta operación también fue im-
plementada, y se denomina imgNErosionDilation para el caso general, e imgStdNErosionDilation
para la versión con ventana fija. Existen otros algoritmos que realizan esta misma operación, pero
no serán tratados en este trabajo.

12.2. Operaciones sobre imágenes en escala de grises

El uso de imágenes en escala de grises para las operaciones vistas en la sección anterior introduce
muchas complicaciones, tanto conceptuales como computacionales. La noción alrededor de la
teorı́a de conjuntos desaparece, puesto que los valores que pueden tomar los pixels se expande a
un rango notablemente más grande.

Haremos un acercamiento intuitivo a las operaciones morfológicas, con la esperanza de que tengan
sentido aplicarlas para obtener resultados satisfactorios.

En las imágenes que consideramos en la sección anterior, el valor de los pixels se restringı́a al
máximo o mı́nimo permitidos. Estos valores se distinguı́an uno del otro para aplicar las opera-
ciones de erosión y dilatación. Es posible realizar una analogı́a para las imágenes en escala de
grises.

Definimos la dilatación en escala de grises de una imagen A con un elemento estructural S como
sigue:

(A ⊕ S )[i , j ] = máx{A[i − r , j − c] + S [r , c], [i − r , j − c] ∈ A, [r , c] ∈ S } (12.12)

Esta definición puede computarse como sigue (implementación de la función imgGrayScaleDilation):

1. Posicionar la ventana sobre el primer pixel de A

2. Computar la suma de los pares conformados por cada valor de la imagen con el pixel
correspondiente de la ventana

3. Buscar el máximo de estas sumas, y establecer este valor como pixel de salida

4. Repetir para todos los pixels de la imagen

Para esta implementación debe tenerse presente que los valores pueden salirse del rango permi-
tido, en cuyo caso deberemos hacer el ajuste necesario para respetar nuestras especificaciones.

Podemos definir también la erosión en escala de grises de A con una ventana S , de modo tal
que respete la dualidad planteada en la ecuación 12.11, de la siguiente manera:

(A S )[i , j ] = mı́n{A[i − r , j − c] − S [r , c], [i − r , j − c] ∈ A, [r , c] ∈ S } (12.13)


Capı́tulo 12. Operaciones morfológicas 89

La implementación para biOps (imgGrayScaleErosion) es similar a la de dilatación: esta vez se


reemplaza el cálculo del máximo de las sumas por el del mı́nimo de las restas de la ventana con
su correspondiente pixel de la imagen a erosionar.

Las operaciones de apertura (imgGrayScaleOpening) y clausura (imgGrayScaleClosing) se defi-


nen e implementan de la misma manera que las imágenes binarias, con la salvedad que se utilizan
las correspondientes versiones de las funciones de dilatación y erosión.

El campo de aplicación de estas últimas operaciones es muy amplio. Se utilizan en la inspección


visual de objetos, ya que estos se tornan más visibles en caso de ser elementos cortantes o muy
lustrados, que saturan de brillo la imagen. También para remover brillos y oscuridades excesivas,
detección de bordes, reducción de ruidos, segmentación de texturas, distribución de tamaños de
objetos y muchos más.
Capı́tulo 13

Clasificación de imágenes

La clasificación es un área importante dentro del análisis de imágenes, de aplicación en campos


tales como la teledetección y el reconocimiento de patrones. En esta sección se introduce el
concepto de clasificación de imágenes digitales y se presentan distintas maneras de abordar el
problema.

Nuestro estudio se centra en los métodos de clasificación no supervisados, y más particularmente


en los algoritmos k-means e isodata. Tras una reseña general sobre la clasificación no supervisada,
se describen ambos algoritmos y se analizan diferentes implementaciones de k-means.

13.1. Conceptos

Dada una imagen, su clasificación consiste básicamente en obtener una nueva imagen, del mismo
tamaño y caracterı́sticas que la original, con la diferencia de que los valores de los pixels repre-
sentan una etiqueta que identifica la categorı́a asignada a cada pixel. Es importante considerar
que no pueden aplicarse ciertas operaciones estadı́sticas a una imagen clasificada, ya que, pese a
ser digital, no es una variable cuantitativa sino cualitativa.

En el proceso de clasificación digital se pueden distinguir las siguientes etapas:

1. Definición de las categorı́as (fase de entrenamiento)


Se trata de obtener el valor de pixel (o rango de valores) que identifica a cada categorı́a.
Este objetivo se logra seleccionando una muestra de pixels de la imagen que representen,
adecuadamente, a las categorı́as de interés. A partir de esos pixels se puede calcular el valor
medio y la variabilidad numérica de cada categorı́a.

2. Agrupación de los pixels de la imagen por categorı́as (fase de asignación)


Se trata de asociar cada uno de los pixels de la imagen a una de las clases previamente
seleccionadas. Esta asignación se realiza en función de los valores de cada pixel. El resultado
será una nueva imagen cuyos valores de pixel indican la categorı́a a la cual ha sido asignado.

90
Capı́tulo 13. Clasificación de imágenes 91

En nuestra implementación, los pixels resultado tienen el valor del pixel que representa a
la clase.

3. Comprobación y verificación de resultados


Toda clasificación conlleva un cierto margen de error, en función de la calidad de los datos
o de la rigurosidad del método empleado. Es por ello que existen métodos de verificación
estadı́stica que permiten cuantificar el error y valorar la calidad final del trabajo y su
aplicabilidad operativa.

13.2. Clasificación supervisada y no supervisada

Los métodos de clasificación se pueden dividir en dos categorı́as, supervisada y no supervisada, de


acuerdo a la forma en que son obtenidas las estadı́sticas de entrenamiento. El método supervisado
parte de un conocimiento previo de la imagen, a partir del cual se seleccionan las muestras para
cada una de las categorı́as. Por su parte, el método no supervisado procede a una búsqueda
automática de grupos de valores homogéneos en la imagen. Queda al usuario, en este caso,
encontrar correspondencias entre esos grupos y sus categorı́as de interés.

Suelen distinguirse dos tipos de clases: informacionales y espectrales. Las primeras son las que
constituyen la leyenda de trabajo que pretende deducir el intérprete. Las segundas, correspon-
den a los grupos de valores espectrales homogéneos en la imagen, en función de ofrecer una
reflectividad similar.

Idealmente habrı́a de producirse una correspondencia biunı́voca entre las dos, es decir, que a
cada clase de cobertura le corresponda un único grupo espectral, y que cada grupo espectral
corresponda a una sola clase temática. Este caso es poco frecuente. Normalmente se produce
alguna de las siguientes situaciones:

Una categorı́a de cubierta se manifiesta en varias clases espectrales: bastarı́a perfeccionar


el muestreo para corregir la dispersión espectral de cada clase, o subdividir la categorı́a
informacional en varias subclases y fundirlas tras la clasificación;

Dos o más categorı́as informacionales comparten una clase espectral: en este caso lo más
razonable es optar por una clave más general;

Varias clases informacionales comparten clases espectrales: frente a esta situación se puede
intentar con las soluciones anteriores, pero también puede ser necesario reconsiderar la
estrategia.

Como se puede ver, el método supervisado pretende definir clases informacionales, mientras el
no supervisado tiende a identificar las clases espectrales presentes en la imagen.

En nuestro trabajo se optó por desarrollar e implementar dos algoritmos de clasificación no super-
visada. En la siguiente sección se describen, de forma más detallada, los métodos no supervisados
en general y los algoritmos elegidos: K-means e Isodata.
Capı́tulo 13. Clasificación de imágenes 92

13.3. Métodos de clasificación no supervisados

Estos métodos están dirigidos a definir las clases espectrales presentes en la imagen. No implican
ningún conocimiento del área de estudio, por lo que la intervención humana se centra más en la
interpretación que en la consecución de los resultados.

Se asume que los valores de los pixels forman una serie de agrupaciones o conglomerados
(clusters), más o menos nı́tidos según los casos. Estos grupos equivaldrı́an a pixels con un com-
portamiento espectral homogéneo, y por tanto, deberı́an definir clases temáticas de interés. Sin
embargo, como ya vimos, estas categorı́as espectrales no siempre pueden equipararse a las clases
informacionales que el usuario pretende deducir, por lo que resta a éste interpretar el significado
temático de dichas categorı́as espectrales.

La idea general se puede expresar mediante la especificación en Z:

getCluster : Z × seq VALUE " VALUE

valueDistance : VALUE × VALUE "R

Classification
input? : Image
k? : Z
clusters? : seq1 VALUE
output! : Image

output!.width = input?.width
output!.height = input?.height
#clusters? = k ?
∀ x : dom output!.v •
let c == min{i : Z | 1 ≤ i ≤ k? • valueDistance(input?.v(x), getCluster(i, clusters?))} •
(∃ v : Z | getCluster (v , clusters?) = c • output!.v (x ) = v )

El método para definir los agrupamientos espectrales se basa en la selección de tres parámetros:

Variables que intervienen en el análisis


En este contexto, las variables son las bandas de la imagen. Los casos son los pixels que
componen la imagen. En este espacio multivariado se trata de encontrar los grupos de pixels
con valores similares, para luego equipararlos con alguna de las clases informacionales de
nuestra leyenda.

Criterio para medir la similitud o distancia entre casos


Capı́tulo 13. Clasificación de imágenes 93

Para medir la similitud entre pixels se han propuesto diversos criterios. El más utilizado
se basa en la distancia euclideana:
v
um
uX
da,b =t (Ia,i − Ib,i )2 (13.1)
i=1

donde da,b denota la distancia entre dos pixels cualesquiera a y b; Ia,i y Ib,i los valores de
cada pixel en la banda i , y m el número de bandas de la imagen.

Criterio para agrupar los casos similares


Las opciones son numerosas. En nuestro caso particular nos focalizamos en k-means e
isodata.

13.3.1. K-means

Dado un conjunto de n puntos (en nuestro caso particular, los pixels de la imagen) en un espacio
d -dimensional y un entero k , el problema consiste en determinar un conjunto de k puntos,
llamados centroides, tales que se minimiza la distancia cuadrada media entre cada punto y el
centroide más cercano a éste.

Este algoritmo, además de la imagen a clasificar, tiene por entrada un valor k , que representa el
número de clusters a construir, y un entero maxit, que denota el número máximo de iteraciones
a realizar.

El método de clasificación por k-means se puede resumir en los siguientes pasos:

1. Inicialización de centroides (un centroide es el valor medio de las muestras asociadas a un


cluster). Se toman k pixels aleatorios de la imagen.

2. Para cada pixel, encontrar el centroide más cercano. Asociar el pixel al cluster correspon-
diente.

3. Si no hubo cambios en los clusters o se alcanzó el lı́mite de iteraciones, detenerse.

4. Recalcular los centroides y volver a 2.

Este algoritmo es popular debido a su simplicidad de implementación, escalabilidad, velocidad


de convergencia y adaptabilidad. En la figura 13.1 se puede ver un ejemplo de su aplicación. En
biOps hemos implementado tres versiones: imgKMeans, imgKDKMeans e imgEKMeans.

La primera es la implementación directa del algoritmo a partir de la descripción. Sin embargo


puede resultar lenta en determinados casos, debido principalmente al costo de encontrar los
vecinos más cercanos (nearest neighbor search). Por esta razón decidimos analizar alternativas
para la codificación de este método de clasificación.

Al momento de buscar el centroide más cercano la implementación anterior revisa uno por uno
los k clusters. Sin embargo, existe una manera de estructurar la información de los centroides
Capı́tulo 13. Clasificación de imágenes 94

(a) Imagen original (b) Imagen clasificada. Las cla-


ses que se podrı́an deducir: zona
urbana, agua, vegetación

Figura 13.1: Clasificación por k-means

para evitar calcular la distancia a cada uno cada vez, guardando esos puntos en un kd-tree
[Moo91].

Sea un espacio acotado (bounding box ) de un conjunto de puntos en un espacio k-dimensional,


el menor hiperrectángulo que los contiene. Un kd-tree es un árbol binario, que representa una
subdivisión jerárquica a través de hiperplanos del espacio acotado correspondiente a un conjunto
de puntos dado. Cada nodo en un kd-tree tiene asociado un espacio cerrado (closed box ) dentro
del espacio acotado, llamado celda. La celda de la raı́z es el espacio que contiene a todos los
puntos del conjunto. Si una celda contiene a lo sumo un punto, entonces se trata de una hoja.
Caso contrario, estará dividida en dos hiperrectángulos por un hiperplano ortogonal. Los puntos
de la celda se ubican a un lado o al otro del hiperplano. De esta forma tenemos dos subceldas, los
hijos de la celda original (ver 13.2). Existen distintos criterios para elegir la coordenada por la
cual dividir una celda. En nuestra implementación decidimos dividir una celda en la coordenada
de la dimensión más extendida (lo que tiende a producir regiones cuadradas).

A partir de un kd-tree, y dado un punto x , queremos encontrar el vecino más cercano en el árbol.
Una primera aproximación es inicialmente la hoja cuya celda contiene a x . En la figura 13.3(a),
x está denotado por X y el punto dueño de la hoja que contiene a x está coloreado en negro.
Como se puede ver en este caso, la primera aproximación no es necesariamente el punto buscado
(i.e. no se trata del vecino más cercano) pero al menos sabemos que cualquier potencial vecino
más cercano debe estar más próximo, y por lo tanto dentro del cı́rculo centrado en x y que tiene
por radio la distancia de x al dueño del nodo. Subimos entonces al padre del nodo actual. En
la figura 13.3(b), el nodo negro. Calculamos si es posible una solución más cercana que la que
tenı́amos. En este caso no es posible, ya que el cı́rculo no interseca el espacio (sombreado) que
ocupa el otro hijo del nodo actual (el“hermano” de la hoja anterior). Si no puede existir un
vecino más cercano en el otro hijo, el algoritmo sigue hacia arriba en el árbol. El próximo nodo
padre deberá ser chequeado, es decir, considerar la distancia al punto dueño del nodo, puesto que
el área que le corresponde (norte de la lı́nea horizontal central) es intersecada por el cı́rculo. Esta
mecánica se aplica sucesivamente hasta alcanzar la raı́z del árbol. La descripción del algoritmo
Capı́tulo 13. Clasificación de imágenes 95

(a) Árbol en 2 dimensiones. No se indican los pla- (b) Representación del árbol anterior como un kd-tree
nos que dividen. El nodo (2,5) divide a lo largo del
plano por la coordenada y=5 y el nodo (3,8) del
plano por x=3

Figura 13.2: Kd-tree

para construir los kd-trees y efectuar la búsqueda del vecino más cercano, y algunos detalles de
implementación se encuentran en [Moo91].

(a) Primer paso (b) Segundo paso

Figura 13.3: Nearest Neighbor Search

A partir de esta estructura de datos se implementó imgKDKMeans que utiliza el kd-tree para
realizar las búsquedas de centroide más cercano. Esta variante no significó una mejora notable,
ya que en general el número de clusters no es alto (y por lo tanto el número de centroides contra
los que comparar tampoco). Existe otra implementación de k-means que no desarrollamos que
Capı́tulo 13. Clasificación de imágenes 96

utiliza kd-trees ligeramente modificados para mapear todos los puntos de la imagen y eficientizar
el algoritmo [KNW02].

Sin embargo, encontramos otra manera de optimizar el orden de complejidad de k-means [FSTR06].
En cada iteración el algoritmo calcula la distancia entre cada punto y todos los centroides. ¿Por
qué no usar la información de las iteraciones anteriores? Para cada punto podemos mantener la
distancia al centroide del cluster más cercano. En la siguiente iteración, calculamos la distancia
al nuevo centroide de ese cluster. Si la nueva distancia es menor o igual que la que habı́amos
guardado, el punto se queda en el cluster y no hay necesidad de calcular la distancia con los
demás centroides.

La idea surge del hecho de que k-means descubre clusters de forma esférica, cuyo centro se va
moviendo a medida que se agregan puntos al cluster. Esto hace que el centro esté más cerca de
algunos puntos, y de esa forma, esos puntos cercanos permanecen en el cluster y no es necesario
encontrar la distancia a los otros clusters. Los puntos más alejados pueden cambiar de cluster
y en esos casos sı́ se recalculan las distancias. La variante implementada bajo el nombre de
imgEKMeans realiza las 2 primeras iteraciones del algoritmo original y las siguientes aplicando
la mejora descripta.

13.3.1.1. Complejidad

El algoritmo k-means converge a un mı́nimo local. Antes de converger, se calculan los centroides
varias veces y se hace una redistribución de todos los puntos de acuerdo a los nuevos centroides.
Esto tiene O(nkl ), donde n esel número de puntos, k el número de clusters y l el número de
iteraciones.

La variante que usa kd-trees para resolver la búsqueda de vecino más cercano no cambia el orden
de complejidad, pero tiene un mejor caso promedio ya que en el mejor caso se hacen O(log k )
inspecciones; aunque en el peor caso siguen siendo necesarias las k distancias. Además tiene por
desventaja el hecho de que es necesario reconstruir el árbol en cada iteración y eso también tiene
un costo.

La última propuesta, para obtener los cluster iniciales requiere O(nk ). Luego, algunos puntos se
mantienen en un cluster y otros cambian. Si un punto se mantiene en el cluster, esto requiere
O(1); caso contrario, requiere O(k ). Si suponemos que la mitad de los puntos se cambian de
cluster, requiere O(nk /2); como el algoritmo converge a un mı́nimo local, el número de puntos
que cambian de cluster decrece en cada iteración. Entonces se espera que el costo total sea
Xl
nk 1/i . Incluso para un número grande de iteraciones, este valor es mucho menor que nkl , y
i=1
por lo tanto esta mejora nos provee aproximadamente un O(nk ).
Capı́tulo 13. Clasificación de imágenes 97

13.3.2. Isodata

Este algoritmo puede ser considerado como una mejora al enfoque de k-means. También busca
minimizar el error cuadrático asignando los pixels al centroide más cercano. Sin embargo, a dife-
rencia del anterior, no se maneja con un número fijo de clusters sino con k clusters, permitiendo
que k varı́e en un intervalo que contiene la cantidad de clusters pedida por el usuario. Esta
situación se debe a que se descartan los clusters con pocos elementos. Por otro lado, se combinan
clusters si hay muchos o si existen algunos muy cercanos (operación merge). También un cluster
se puede dividir si hay pocos clusters o si contiene pixels demasiado disı́miles (operación split).

Los parámetros requeridos por Isodata son:

no clusters: número deseado de clusters, y también el número inicial.

min elements: mı́nimo número de pixels requerido por cluster.

min dist: distancia mı́nima permitida entre los centroides de los clusters.

split sd: parámetro que controla la división de clusters.

iter start: máximo número de iteraciones de la primera parte del algoritmo.

max merge: máximo número de combinaciones de clusters por iteración.

iter body: máximo número de iteraciones del loop principal del algoritmo.

El uso y significado de estos parámetros se describen con mayor detalle a continuación, junto
con los pasos del algoritmo:

1. Inicialización de los centroides de los clusters.

2. Para cada pixel, encontrar el centroide más cercano. Asociar el pixel al cluster correspon-
diente.

3. Calcular los centroides de los clusters resultantes.

4. Si al menos un cluster cambió y el número de iteraciones es menos que iter start, volver a
2.

5. Descartar los clusters con menos de min elements pixels, y descartar esos pixels también.

6. Si el número de clusters es mayor o igual que 2 ∗ no clusters, ir a 7 (merge); sino, ir a 8.

7. Si la distancia entre dos centroides es menor que min dist, combinar estos clusters y
actualizar el centroide; caso contrario, ir a 8. Repetir hasta max merge veces e ir a 8.

8. Si el número de clusters es menor o igual a no clusters/2, o se trata de una iteración impar


y el número de clusters es menor que 2 ∗ no clusters, ir a 9 (split). Sino ir a 10.
Capı́tulo 13. Clasificación de imágenes 98

9. Encontrar un cluster que tenga desviación estándar para alguna variable, digamos x , que
sea mayor que split sd . De no haber, ir a 10. Sino, calcular la media para x en el cluster.
Separar los pixels del cluster en dos conjuntos, uno conteniendo aquellos pixels en los que x
es mayor o igual que la media, y el otro aquellos en que x es menor. Calcular los centroides
de estos dos nuevos clusters. Si la distancia entre ellos es mayor o igual que 1,1 ∗ min dist,
reemplazar el cluster original por los dos creados; caso contrario, el cluster no se divide.

10. Si este paso ha sido ejecutado iter body veces o no hubo cambios en los clusters desde su
última ejecución, detenerse. Sino, volver a 2.

La implementación de este algoritmo en biOps está dada por la función imgIsoData.


Capı́tulo 14

Conclusiones

Este proyecto concluye con la publicación del paquete biOps en los repositorios de R. La licencia
GPL garantiza, a quienes ası́ lo deseen, la posibilidad de usar, copiar, modificar y redistribuir
este paquete. Estimamos que se mantendrá y mejorará su utilidad con el correr del tiempo, tanto
por nuestro aporte como el de los desarrolladores de R. La cooperación que caracteriza a esta
filosofı́a hace que quienes comulgamos con ella trabajemos por códigos de calidad y de constante
evolución y corrección.

El paquete se encuentra disponible en http://cran.r-project.org/src/contrib/Descriptions/


biOps.html

Creemos que este trabajo resultó en un aporte importante a la comunidad R, y por extensión a la
comunidad del Software Libre. Los antecedentes en el procesamiento de imágenes en R, como se
vio en la sección 6.1 son escasos, y en su mayorı́a aportan a aspectos muy especı́ficos o áreas muy
particulares del manejo de imágenes. biOps, en este sentido, resulta un paquete multipropósito,
fácilmente extensible y con una amplia gama de algoritmos.

Se estudiaron, analizaron, especificaron, implementaron y testearon procesamientos para la ma-


nipulación de imágenes obteniendo operaciones:

geométricas

morfológicas

aritméticas

lógicas

de manipulación de frecuencias

de tablas de reemplazo

de detección de bordes

de convolución.

99
Capı́tulo 14. Conclusiones 100

de clasificación de imágenes

A lo largo del trabajo utilizamos diversas herramientas y lenguajes de programación. Creemos


válido un breve comentario de los más importantes:

El lenguaje R (tratado en el capı́tulo 2) es muy poderoso y completo en lo que se refiere a


manipulación de datos (principalmente numéricos). Sus interfaces con otros lenguajes hacen
que sea modificable y extensible sin necesidad de demasiados conocimientos especı́ficos,
permitiendo aprovechar las ventajas de otros lenguajes, sobre todo si son compilados. El
hecho de ser interpretado lo hace un poco más lento, como pudo verse en el análisis hecho
en 2.4, pero nos dejó una impresión general muy buena y satisfactoria.

La notación Z (vista en el capı́tulo 3) nos resultó útil como herramienta de especificación,


aunque debimos agregarle una representación de reales para que la notación no nos resultara
tan rebuscada. Además hemos evidenciado algunas falencias en su expresividad al tratar
de especificar ciertos comportamientos.

f uzz (sección 3.4) es una herramienta muy práctica para el chequeo de tipos de las especi-
ficaciones Z. Nos adaptamos muy rápidamente tanto a ella como a su paquete para el uso
en LATEX.

La comunidad R es muy grande y está muy bien organizada. El equipo de desarrolladores


respondió nuestras consultas acerca de la publicación de paquetes de manera rápida y
eficiente. Los comandos R facilitan mucho la tarea del programador: existen scripts para
instalación, desinstalación y control de la estructura de los paquetes que nos resultaron de
gran utilidad.

svn1 , el sistema de control de versiones de Tigris2 y trac3 , la wiki y sistema de seguimiento


de issues (asuntos, temas), nos resultaron muy prácticos para la organización de nuestras
actividades y nuestros archivos.

El trabajo nos resultó muy entretenido y enriquecedor. El tratamiento digital de imágenes no es


un área que esté en la currı́cula de las materias de nuestra carrera; sin embargo, encontramos su
estudio y análisis muy natural y nos pareció una tarea sumamente agradable.

14.1. Trabajo futuro

El área del procesamiento digital de imágenes es muy amplia y está en constante evolución.
A lo largo del proceso de desarrollo de este trabajo fuimos estudiando muchas ramas de esta
ciencia, profundizando en aquellos aspectos que consideramos más valiosos, de mayor interés y
que hicieran a la buena funcionalidad del paquete.

Por ello que muchas aplicaciones han quedado relegadas. A continuación describimos los puntos
en que creemos conveniente focalizar el trabajo futuro de este proyecto:
1 http://subversion.tigris.org
2 http://www.tigris.org
3 http://trac.edgewall.org
Capı́tulo 14. Conclusiones 101

Conversión entre espacios de color: Como vimos en 4.3, existen distintos modelos de color
que permiten trabajar sobre diferentes aspectos de una imagen. biOps se maneja actual-
mente en el espacio RGB, pero está pensado incorporar funciones para el cambio entre
espacios, además de adaptar las funciones que sean necesarias para la manipulación de las
distintas representaciones.

Selección manual de colores para las categorı́as de clasificación: Permitir modificar los
colores de las clases en el resultado según la voluntad del usuario, para identificar con
tonos arbitrarios una categorı́a espectral con su correspondiente categorı́a informacional.

Interfaz gráfica de usuario: La librerı́a gráfica Gtk ha sido portada a R, dando la posibi-
lidad de generar un entorno de trabajo mediante ventanas y botones haciendo más fácil
la experiencia del usuario. Al momento sólo se ha implementado una ventana para ver las
imágenes que brinda información adicional, como las coordenadas y valores de los pixels
(ver 6.4).

Implementación de nuevos algoritmos: Este trabajo se centró en áreas especı́ficas, pero


existen caminos que no han sido explorados: reconocimiento de patrones, visión de máqui-
nas y un largo etcétera. Por otro lado, queda pendiente la implementación de algoritmos de
clasificación supervisada, y la posibilidad de combinar algunas de las funciones existentes
para obtener nuevos filtros, principalmente en el espacio de frecuencias.

Extender soporte de formatos de archivo: Actualmente se permite leer y escribir archivos


en formatos jpg (libjpeg) y tiff (libtiff), a través de librerı́as libres y portables; hay una
librerı́a libre para el formato png (libpng) que no se incorporó. Serı́a bueno considerar
también el uso de las librerı́as de ImageMagick , que permitirı́an ampliar el soporte de
formatos y el cambio de representaciones. También existe la inquietud de leer archivos de
imágenes satelitales, multibandas.

Procesamiento de archivos grandes: Al trabajar con imágenes la necesidad de memoria para


su manipulación hace difı́cil operar con archivos muy grandes. En este sentido consideramos
que se podrı́a evaluar alternativas para evitar cargar toda la imagen en memoria y optimizar
su uso en la implementación.

14.2. Estadı́sticas

Algunos números de este proyecto:

∼1000 lı́neas de especificación

∼10500 lı́neas de código (∼4100 en R, ∼6400 en C)

∼3300 lı́neas de documentación

∼1100 horas de trabajo

∼15 libros, ∼20 publicaciones y ∼70 páginas webs consultadas


Capı́tulo 14. Conclusiones 102

∼20 herramientas, lenguajes y programas usados para codificar, especificar, testear y do-
cumentar
Apéndice A

Profiling

En la sección 2.4 hemos visto una comparación entre implementaciones de diversos algoritmos
usando solamente código R y usando llamadas a código C. A continuación se detallan estos
resultados:
% imgAdd
Each sample represents 0.15 seconds .
Total run time : 3 5 5 7 . 2 5 0 0 0 0 0 0 1 4 3 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.64 3544.50 53.93 1918.35 " r_imgAdd "
22.53 801.30 22.53 801.30 "["
12.31 438.00 12.31 438.00 " [ <- "
6.98 248.40 6.98 248.40 " <= "
3.65 129.90 3.65 129.90 "+"
0.36 12.75 0.00 0.00 ". imgArithmeticOperator "
0.36 12.75 0.00 0.00 " imgAdd "
0.26 9.30 0.26 9.30 ".C"
0.24 8.55 0.24 8.55 ":"
0.05 1.80 0.05 1.80 " as . vector "
0.05 1.80 0.00 0.00 " array "
0.04 1.35 0.02 0.75 " imagedata "
0.03 0.90 0.00 0.00 " as . integer "
0.03 0.90 0.03 0.90 " as . integer . default "

% self % total
self seconds total seconds name
53.93 1918.35 99.64 3544.50 " r_imgAdd "
22.53 801.30 22.53 801.30 "["
12.31 438.00 12.31 438.00 " [ <- "
6.98 248.40 6.98 248.40 " <= "
3.65 129.90 3.65 129.90 "+"
0.26 9.30 0.26 9.30 ".C"
0.24 8.55 0.24 8.55 ":"
0.05 1.80 0.05 1.80 " as . vector "
0.03 0.90 0.03 0.90 " as . integer . default "
0.02 0.75 0.04 1.35 " imagedata "

% imgAverage
Each sample represents 0.15 seconds .

103
Apéndice A. Profiling 104

Total run time : 2 6 6 5 . 6 5 0 0 0 0 0 0 0 8 9 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.53 2653.20 48.85 1302.30 " r_imgAverage "
30.59 815.40 30.59 815.40 "["
16.19 431.70 16.19 431.70 " [ <- "
3.48 92.85 3.48 92.85 "+"
0.47 12.45 0.04 1.05 " imgAverage "
0.33 8.85 0.33 8.85 ":"
0.16 4.35 0.08 2.10 " imagedata "
0.14 3.60 0.14 3.60 ".C"
0.09 2.40 0.09 2.40 " as . vector "
0.09 2.40 0.00 0.00 " array "
0.08 2.10 0.08 2.10 "/"
0.07 1.95 0.07 1.95 " list "
0.05 1.35 0.00 0.00 " as . integer "
0.05 1.35 0.05 1.35 " as . integer . default "

% self % total
self seconds total seconds name
48.85 1302.30 99.53 2653.20 " r_imgAverage "
30.59 815.40 30.59 815.40 "["
16.19 431.70 16.19 431.70 " [ <- "
3.48 92.85 3.48 92.85 "+"
0.33 8.85 0.33 8.85 ":"
0.14 3.60 0.14 3.60 ".C"
0.09 2.40 0.09 2.40 " as . vector "
0.08 2.10 0.16 4.35 " imagedata "
0.08 2.10 0.08 2.10 "/"
0.07 1.95 0.07 1.95 " list "
0.05 1.35 0.05 1.35 " as . integer . default "
0.04 1.05 0.47 12.45 " imgAverage "

% r_de c_ co ntr as t
Each sample represents 0.15 seconds .
Total run time : 1 9 7 7 . 9 0 0 0 0 0 0 0 0 4 7 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.79 1973.70 0.00 0.00 " r_ de c_c on tr as t "
99.78 1973.55 48.40 957.30 " r _ l o o k _u p _ t a b l e "
25.06 495.75 25.06 495.75 "["
25.02 494.85 25.02 494.85 " [ <- "
1.27 25.05 1.27 25.05 "+"
0.21 4.20 0.00 0.00 " imgDecreaseContrast "
0.21 4.20 0.00 0.00 " . imgContrast "
0.10 1.95 0.08 1.65 " imagedata "
0.06 1.20 0.06 1.20 ".C"
0.05 0.90 0.05 0.90 " as . vector "
0.05 0.90 0.00 0.00 " array "
0.03 0.60 0.03 0.60 ":"
0.03 0.60 0.01 0.15 " as . integer "
0.02 0.45 0.02 0.45 " as . integer . default "

% self % total
self seconds total seconds name
48.40 957.30 99.78 1973.55 " r _ l o o k _u p _ t a b l e "
25.06 495.75 25.06 495.75 "["
Apéndice A. Profiling 105

25.02 494.85 25.02 494.85 " [ <- "


1.27 25.05 1.27 25.05 "+"
0.08 1.65 0.10 1.95 " imagedata "
0.06 1.20 0.06 1.20 ".C"
0.05 0.90 0.05 0.90 " as . vector "
0.03 0.60 0.03 0.60 ":"
0.02 0.45 0.02 0.45 " as . integer . default "
0.01 0.15 0.03 0.60 " as . integer "

% r_d e c _ i n te n s i t y
Each sample represents 0.15 seconds .
Total run time : 1 9 9 7 . 2 5 0 0 0 0 0 0 0 4 8 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.76 1992.45 0.00 0.00 " r _ d e c _ in t e n s i t y "
99.75 1992.30 47.71 952.80 " r _ l o o k _u p _ t a b l e "
25.61 511.50 25.61 511.50 " [ <- "
24.89 497.10 24.89 497.10 "["
1.51 30.15 1.51 30.15 "+"
0.24 4.80 0.00 0.00 " imgDecreaseIntensity "
0.24 4.80 0.00 0.00 " . imgIntensity "
0.12 2.40 0.10 1.95 " imagedata "
0.06 1.20 0.06 1.20 ".C"
0.06 1.20 0.06 1.20 " as . vector "
0.06 1.20 0.00 0.00 " array "
0.04 0.75 0.04 0.75 ":"
0.03 0.60 0.01 0.15 " as . integer "
0.02 0.45 0.02 0.45 " as . integer . default "
0.01 0.15 0.00 0.00 " max "

% self % total
self seconds total seconds name
47.71 952.80 99.75 1992.30 " r _ l o o k _u p _ t a b l e "
25.61 511.50 25.61 511.50 " [ <- "
24.89 497.10 24.89 497.10 "["
1.51 30.15 1.51 30.15 "+"
0.10 1.95 0.12 2.40 " imagedata "
0.06 1.20 0.06 1.20 ".C"
0.06 1.20 0.06 1.20 " as . vector "
0.04 0.75 0.04 0.75 ":"
0.02 0.45 0.02 0.45 " as . integer . default "
0.01 0.15 0.03 0.60 " as . integer "

% r_imgDiffer
Each sample represents 0.15 seconds .
Total run time : 3 5 9 2 . 5 0 0 0 0 0 0 0 1 4 5 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.61 3578.40 53.47 1920.90 " r_imgDiffer "
22.99 825.75 22.99 825.75 "["
12.32 442.65 12.32 442.65 " [ <- "
5.13 184.20 5.13 184.20 " <= "
2.98 107.10 2.98 107.10 " <"
2.47 88.80 2.47 88.80 "-"
0.39 14.10 0.00 0.00 ". imgArithmeticOperator "
0.39 14.10 0.00 0.00 " imgDiffer "
0.29 10.35 0.29 10.35 ".C"
Apéndice A. Profiling 106

0.25 9.00 0.25 9.00 ":"


0.05 1.95 0.05 1.95 " as . vector "
0.05 1.95 0.00 0.00 " array "
0.04 1.50 0.03 1.05 " imagedata "
0.02 0.75 0.00 0.00 " as . integer "
0.02 0.75 0.02 0.75 " as . integer . default "

% self % total
self seconds total seconds name
53.47 1920.90 99.61 3578.40 " r_imgDiffer "
22.99 825.75 22.99 825.75 "["
12.32 442.65 12.32 442.65 " [ <- "
5.13 184.20 5.13 184.20 " <= "
2.98 107.10 2.98 107.10 " <"
2.47 88.80 2.47 88.80 "-"
0.29 10.35 0.29 10.35 ".C"
0.25 9.00 0.25 9.00 ":"
0.05 1.95 0.05 1.95 " as . vector "
0.03 1.05 0.04 1.50 " imagedata "
0.02 0.75 0.02 0.75 " as . integer . default "

% r_gamma
Each sample represents 0.15 seconds .
Total run time : 1 9 9 0 . 2 0 0 0 0 0 0 0 0 4 8 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.77 1985.70 0.01 0.15 " r_gamma "
99.77 1985.55 47.87 952.80 " r _ l o o k _u p _ t a b l e "
25.50 507.60 25.50 507.60 "["
24.97 496.95 24.97 496.95 " [ <- "
1.39 27.60 1.39 27.60 "+"
0.23 4.50 0.00 0.00 " imgGamma "
0.11 2.25 0.08 1.50 " imagedata "
0.07 1.35 0.07 1.35 " as . vector "
0.07 1.35 0.00 0.00 " array "
0.06 1.20 0.06 1.20 ".C"
0.03 0.60 0.03 0.60 ":"
0.02 0.45 0.00 0.00 " as . integer "
0.02 0.45 0.02 0.45 " as . integer . default "

% self % total
self seconds total seconds name
47.87 952.80 99.77 1985.55 " r _ l o o k _u p _ t a b l e "
25.50 507.60 25.50 507.60 "["
24.97 496.95 24.97 496.95 " [ <- "
1.39 27.60 1.39 27.60 "+"
0.08 1.50 0.11 2.25 " imagedata "
0.07 1.35 0.07 1.35 " as . vector "
0.06 1.20 0.06 1.20 ".C"
0.03 0.60 0.03 0.60 ":"
0.02 0.45 0.02 0.45 " as . integer . default "
0.01 0.15 99.77 1985.70 " r_gamma "

% r_in c_ co ntr as t
Each sample represents 0.15 seconds .
Total run time : 1 9 8 9 . 6 0 0 0 0 0 0 0 0 4 8 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
Apéndice A. Profiling 107

total seconds self seconds name


99.78 1985.25 47.72 949.35 " r _ l o o k _u p _ t a b l e "
99.78 1985.25 0.00 0.00 " r_ in c_c on tr as t "
25.59 509.10 25.59 509.10 "["
24.98 497.10 24.98 497.10 " [ <- "
1.44 28.65 1.44 28.65 "+"
0.22 4.35 0.00 0.00 " imgIncreaseContrast "
0.22 4.35 0.00 0.00 " . imgContrast "
0.11 2.10 0.08 1.50 " imagedata "
0.06 1.20 0.06 1.20 ".C"
0.06 1.20 0.06 1.20 " as . vector "
0.06 1.20 0.00 0.00 " array "
0.05 1.05 0.05 1.05 ":"
0.02 0.45 0.00 0.00 " as . integer "
0.02 0.45 0.02 0.45 " as . integer . default "

% self % total
self seconds total seconds name
47.72 949.35 99.78 1985.25 " r _ l o o k _u p _ t a b l e "
25.59 509.10 25.59 509.10 "["
24.98 497.10 24.98 497.10 " [ <- "
1.44 28.65 1.44 28.65 "+"
0.08 1.50 0.11 2.10 " imagedata "
0.06 1.20 0.06 1.20 ".C"
0.06 1.20 0.06 1.20 " as . vector "
0.05 1.05 0.05 1.05 ":"
0.02 0.45 0.02 0.45 " as . integer . default "

% imgIncreaseIntensity
Each sample represents 0.15 seconds .
Total run time : 2 0 0 9 . 5 5 0 0 0 0 0 0 0 4 9 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.78 2005.05 0.00 0.00 " r _ i n c _ in t e n s i t y "
99.77 2004.90 47.74 959.40 " r _ l o o k _u p _ t a b l e "
25.33 508.95 25.33 508.95 "["
25.21 506.70 25.21 506.70 " [ <- "
1.43 28.80 1.43 28.80 "+"
0.22 4.50 0.00 0.00 " imgIncreaseIntensity "
0.22 4.50 0.00 0.00 " . imgIntensity "
0.10 2.10 0.07 1.35 " imagedata "
0.07 1.50 0.07 1.50 " as . vector "
0.07 1.50 0.00 0.00 " array "
0.06 1.20 0.06 1.20 ".C"
0.05 1.05 0.05 1.05 ":"
0.03 0.60 0.00 0.00 " as . integer "
0.03 0.60 0.03 0.60 " as . integer . default "
0.01 0.15 0.00 0.00 " min "

% self % total
self seconds total seconds name
47.74 959.40 99.77 2004.90 " r _ l o o k _u p _ t a b l e "
25.33 508.95 25.33 508.95 "["
25.21 506.70 25.21 506.70 " [ <- "
1.43 28.80 1.43 28.80 "+"
0.07 1.50 0.07 1.50 " as . vector "
0.07 1.35 0.10 2.10 " imagedata "
0.06 1.20 0.06 1.20 ".C"
0.05 1.05 0.05 1.05 ":"
0.03 0.60 0.03 0.60 " as . integer . default "
Apéndice A. Profiling 108

% imgMaximum
Each sample represents 0.15 seconds .
Total run time : 2 8 3 8 . 9 0 0 0 0 0 0 0 0 9 9 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.71 2830.80 21.69 615.75 " r_imgMaximum "
61.69 1751.25 31.25 887.25 " max "
30.43 864.00 30.43 864.00 "["
15.93 452.25 15.93 452.25 " [ <- "
0.36 10.35 0.36 10.35 ":"
0.29 8.10 0.02 0.45 " imgMaximum "
0.11 3.00 0.11 3.00 ".C"
0.06 1.80 0.06 1.80 " list "
0.06 1.65 0.06 1.65 " as . vector "
0.06 1.65 0.00 0.00 " array "
0.05 1.50 0.04 1.05 " imagedata "
0.05 1.35 0.00 0.00 " as . integer "
0.05 1.35 0.05 1.35 " as . integer . default "

% self % total
self seconds total seconds name
31.25 887.25 61.69 1751.25 " max "
30.43 864.00 30.43 864.00 "["
21.69 615.75 99.71 2830.80 " r_imgMaximum "
15.93 452.25 15.93 452.25 " [ <- "
0.36 10.35 0.36 10.35 ":"
0.11 3.00 0.11 3.00 ".C"
0.06 1.80 0.06 1.80 " list "
0.06 1.65 0.06 1.65 " as . vector "
0.05 1.35 0.05 1.35 " as . integer . default "
0.04 1.05 0.05 1.50 " imagedata "
0.02 0.45 0.29 8.10 " imgMaximum "

% imgNegative
Each sample represents 0.15 seconds .
Total run time : 1 8 6 5 . 5 50 0 0 0 0 0 0 4 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.57 1857.60 43.70 815.25 " r _ l o o k _u p _ t a b l e "
99.57 1857.60 0.00 0.00 " r_ ne gat iv e_ lu t "
27.15 506.55 27.15 506.55 " [ <- "
27.04 504.45 27.04 504.45 "["
1.65 30.75 1.65 30.75 "+"
0.28 5.25 0.00 0.00 " imgNegative "
0.15 2.85 0.10 1.95 " imagedata "
0.14 2.70 0.00 0.00 " r_negative "
0.14 2.70 0.14 2.70 "-"
0.10 1.80 0.10 1.80 " as . vector "
0.10 1.80 0.00 0.00 " array "
0.07 1.35 0.07 1.35 ".C"
0.03 0.60 0.03 0.60 ":"
0.01 0.15 0.00 0.00 " as . integer "
0.01 0.15 0.01 0.15 " as . integer . default "

% self % total
self seconds total seconds name
Apéndice A. Profiling 109

43.70 815.25 99.57 1857.60 " r _ l o o k _u p _ t a b l e "


27.15 506.55 27.15 506.55 " [ <- "
27.04 504.45 27.04 504.45 "["
1.65 30.75 1.65 30.75 "+"
0.14 2.70 0.14 2.70 "-"
0.10 1.95 0.15 2.85 " imagedata "
0.10 1.80 0.10 1.80 " as . vector "
0.07 1.35 0.07 1.35 ".C"
0.03 0.60 0.03 0.60 ":"
0.01 0.15 0.01 0.15 " as . integer . default "

% imgThreshold
Each sample represents 0.15 seconds .
Total run time : 1 9 7 4 . 9 0 0 0 0 0 0 0 0 4 7 seconds .

Total seconds : time spent in function and callees .


Self seconds : time spent in function alone .

% total % self
total seconds self seconds name
99.76 1970.25 47.84 944.85 " r _ l o o k _u p _ t a b l e "
99.76 1970.25 0.00 0.00 " r_threshold "
25.53 504.15 25.53 504.15 "["
24.84 490.50 24.84 490.50 " [ <- "
1.54 30.45 1.54 30.45 "+"
0.24 4.65 0.00 0.00 " imgThreshold "
0.12 2.40 0.08 1.65 " imagedata "
0.07 1.35 0.07 1.35 " as . vector "
0.07 1.35 0.00 0.00 " array "
0.06 1.20 0.06 1.20 ".C"
0.02 0.45 0.00 0.00 " as . integer "
0.02 0.45 0.02 0.45 " as . integer . default "
0.02 0.30 0.02 0.30 ":"

% self % total
self seconds total seconds name
47.84 944.85 99.76 1970.25 " r _ l o o k _u p _ t a b l e "
25.53 504.15 25.53 504.15 "["
24.84 490.50 24.84 490.50 " [ <- "
1.54 30.45 1.54 30.45 "+"
0.08 1.65 0.12 2.40 " imagedata "
0.07 1.35 0.07 1.35 " as . vector "
0.06 1.20 0.06 1.20 ".C"
0.02 0.45 0.02 0.45 " as . integer . default "
0.02 0.30 0.02 0.30 ":"
Bibliografı́a

[Art96] R. D. Arthan. Arithmetics for Z. ICL, Febrero 1996.

[Bec90] Richard A. Becker. A brief history of S. AT&T Bell Laboratories - Murray Hill -
New Jersey, 1990.

[Chu96] Emilio Chuvieco. Fundamentos de teledetección espacial. Ediciones RIALP, 1996.

[Cra97] Randy Crane. A simplified approach to image processing. Prentice Hall, 1997.

[Dep95] Department of Computing, University of Brighton. Z Standards Document D-172,


Marzo 1995.

[FPWW94] Bob Fisher, Simon Perkins, Ashley Walker, and Erik Wolfart. Hypermedia image
processing reference, Marzo 1994.

[FSTR06] Fahim, Salem, Torkey, and Ramadan. An efficient enhanced k-means clustering
algorithm. Journal of Zhejiang University, 2006.

[GJJ96] Earl Gose, Richard Johnsonbaugh, and Steve Jost. Pattern recognition and image
analysis. Prentice Hall, 1996.

[GW02] Rafael Gonzalez and Richard Woods. Digital Image Processing. Prentice Hall, 2002.

[KNW02] Tapas Kanungo, Nathan Netanyahu, and Angela Wu. An efficient enhanced k-means
clustering algorithm: Analysis and implementation. IEEE Transactions on pattern
analysis and machine intelligence, 2002.

[Moo91] Andrew Moore. Efficient Memory-based Learning for Robot Control. An introductory
tutorial on kd-trees. PhD thesis, Carnegie Mellon University, 1991.

[Par96] J. R. Parker. Algorithms for image processing and computer vision. Wiley Computer,
1996.

[Spi98] J. M. Spivey. The Z Notation: a reference manual. Prentice Hall, 1998.

[Tea00] R Development Core Team. Introducción a r, 2000.

[Tea06] R Development Core Team. Writing r extensions, 2006.

[WD95] Jim Woodcock and Jim Davies. Using Z. University of Oxford, 1995.

[ZWI] Wikipedia - Z Notation: http://en.wikipedia.org/wiki/Z notation.

110

También podría gustarte