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 lenguaje 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 lenguaje 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 2. R 2.1. 2.2. 2.3. 2.4. 2.5. 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 5 7 8 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 12 13 14 14 15 15 15 16 16 17 17 20 4. Imagen digital 4.1. Representación . . . . . . . . . . . . 4.2. Resolución espacial y de profundidad 4.3. Modelos de color . . . . . . . . . . . 4.3.1. RGB . . . . . . . . . . . . . . 4.3.2. CYM . . . . . . . . . . . . . 4.3.3. HSI . . . . . . . . . . . . . . 4.4. Nuestra implementación . . . . . . . 4.4.1. Especificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 21 22 23 23 24 24 25 26 5. El procesamiento digital de imágenes 5.1. Orı́genes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 27 Antecedente: El lenguaje S . . . . . R como implementación de S . . . . Interfaz contra lenguajes compilados R puro vs. interfaz C . . . . . . . . Colaboración a CRAN . . . . . . . . 3. Z 3.1. Las especificaciones formales . . . 3.2. El lenguaje de especificación Z . . 3.3. Definiciones en Z . . . . . . . . . . 3.3.1. Declaraciones . . . . . . . . 3.3.2. Abreviaciones . . . . . . . . 3.3.3. Definiciones axiomáticas . . 3.3.4. Definiciones genéricas . . . 3.3.5. Esquemas . . . . . . . . . . 3.4. f uzz . . . . . . . . . . . . . . . . . 3.5. Especificación en Z . . . . . . . . . 3.5.1. Especificación de reales . . 3.5.2. Resto de las especificaciones iv Índice general v 5.2. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . 5.2.1. Astronomı́a y exploración del espacio . . . . . . 5.2.2. Inteligencia y aplicación militar . . . . . . . . . 5.2.3. Ciencias de la tierra . . . . . . . . . . . . . . . 5.2.4. Gobierno . . . . . . . . . . . . . . . . . . . . . 5.2.5. Visualización de datos . . . . . . . . . . . . . . 5.2.6. Entretenimiento . . . . . . . . . . . . . . . . . 5.2.7. Medicina . . . . . . . . . . . . . . . . . . . . . 5.2.8. Procesamiento de documentos . . . . . . . . . . 5.2.9. Aplicaciones industriales y visión de máquinas 5.2.10. Aplicaciones hogareñas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 29 29 29 30 30 30 30 31 31 31 6. biOps: un paquete de procesamiento de imágenes para R 6.1. Otros paquetes R de manejo de imágenes . . . . . . . . . . 6.2. Estructura del paquete . . . . . . . . . . . . . . . . . . . . . 6.3. Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4. biOpsGUI: el principio de una interfaz gráfica de usuario . . 6.5. Próximos capı́tulos . . . . . . . . . . . . . . . . . . . . . . . 6.6. Formato Digital . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 32 33 34 36 36 37 7. Operaciones por pixel 7.1. Look-up tables . . . . . . . . . . . 7.1.1. Modificación de contraste . 7.1.2. Modificación de intensidad 7.1.3. Otras modificaciones . . . . 7.2. Operaciones aritméticas y lógicas . 7.3. Histogramas . . . . . . . . . . . . . 7.4. Generación de ruido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 39 40 41 41 43 45 46 8. Operaciones geométricas 8.1. Mapeo de valores: “hacia adelante” vs. “hacia atrás” 8.2. Interpolación . . . . . . . . . . . . . . . . . . . . . . 8.2.1. Interpolación por el vecino más cercano . . . 8.2.2. Interpolación bilineal . . . . . . . . . . . . . . 8.2.3. Interpolación por B-Spline . . . . . . . . . . . 8.2.4. Interpolación convolucional cúbica . . . . . . 8.3. Operaciones implementadas . . . . . . . . . . . . . . 8.3.1. Escalar . . . . . . . . . . . . . . . . . . . . . 8.3.2. Encoger . . . . . . . . . . . . . . . . . . . . . 8.3.3. Rotar . . . . . . . . . . . . . . . . . . . . . . 8.3.4. Espejar . . . . . . . . . . . . . . . . . . . . . 8.3.5. Trasladar . . . . . . . . . . . . . . . . . . . . 8.3.6. Recortar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 48 49 49 50 50 51 51 51 52 54 55 55 56 9. Operaciones por vecino 9.1. Convolución . . . . . . . . . 9.1.1. Blurring . . . . . . . 9.1.2. Sharpening . . . . . 9.2. Filtro por mediana . . . . . 9.3. Filtro por mı́nimo/máximo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 58 60 61 62 63 10.Algoritmos de detección de bordes 10.1. Generalidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2. Técnicas sencillas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 64 65 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Índice general 10.3. Técnicas por convolución . . . 10.3.1. Detección de bordes por 10.3.2. Detección de bordes por 10.4. Técnicas avanzadas . . . . . . . 10.4.1. Marr Hildreth . . . . . . 10.4.2. Canny . . . . . . . . . . 10.4.3. Shen Castan . . . . . . 10.5. Detección de bordes en color . vi . . . . . . . . . . . . . . . . . . . . . gradiente (Gradient Edge Detection) compás (Compass Edge Detection) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.Filtros en el espacio de frecuencias 11.1. Espacio de frecuencias . . . . . . . 11.2. Transformada de Fourier . . . . . . 11.3. Convolución . . . . . . . . . . . . . 11.4. Filtros por frecuencia . . . . . . . . . . . . . . . . . . . . 12.Operaciones morfológicas 12.1. Operaciones sobre imágenes binarias . . 12.1.1. Dilatación binaria . . . . . . . . 12.1.2. Erosión binaria . . . . . . . . . . 12.1.3. Apertura y clausura binarias . . 12.2. Operaciones sobre imágenes en escala de . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . grises 13.Clasificación de imágenes 13.1. Conceptos . . . . . . . . . . . . . . . . . . 13.2. Clasificación supervisada y no supervisada 13.3. Métodos de clasificación no supervisados . 13.3.1. K-means . . . . . . . . . . . . . . . 13.3.1.1. Complejidad . . . . . . . 13.3.2. Isodata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 67 68 69 70 70 72 73 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 74 75 78 80 . . . . . 82 82 83 85 86 88 . . . . . . 90 90 91 92 93 96 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Modelos de color RGB y CYM . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3. Modelo de color HSI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 23 25 6.1. Estructura biOps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 7.1. Look-up tables . . . . . . . 7.2. Decrementar contraste . . . 7.3. Incrementar contraste . . . 7.4. Decrementar intensidad . . 7.5. Incrementar intensidad . . . 7.6. Negativo . . . . . . . . . . . 7.7. Thresholding . . . . . . . . 7.8. Transformación Gamma . . 7.9. Aplicación de imgDiffer . . 7.10. Histograma de una imagen 7.11. Ruido “sal y pimienta” . . . . . . . . . . . . . . 39 40 41 42 42 42 43 43 45 45 47 8.1. Rotación de imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2. Operación de espejado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3. Operación de traslación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 55 56 9.1. Convolución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2. Aplicación de sharpening . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3. Aplicación de filtro por mediana . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 61 62 10.1. Operador de homogeneidad . . . . . . . . . . . 10.2. Operador por diferencia . . . . . . . . . . . . . 10.3. Aplicación de operador por diferencia . . . . . 10.4. Borde y derivadas en una dimensión . . . . . . 10.5. Aplicación de Sobel (threshold = 40, negativo) 10.6. Aplicación de Canny . . . . . . . . . . . . . . . . . . . . . 65 65 66 66 68 72 11.1. Transformada de Fourier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2. Filtros FFT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.3. Filtro por frecuencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 79 81 12.1. Representación gráfica de una 12.2. Dilatación binaria . . . . . . 12.3. Dilatación binaria . . . . . . 12.4. Erosión binaria . . . . . . . . 12.5. Erosión binaria . . . . . . . . 12.6. Apertura y clausura . . . . . 83 84 85 86 86 87 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . imagen binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . List of Figures 13.1. Clasificación por k-means . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2. Kd-tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.3. Nearest Neighbor Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii 94 95 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 estadı́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, histogramas), 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 extensió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 (algoritmos 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 relacionados 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 profundizan 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 Introducció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 profundizar 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 determinada 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 trabajo. 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 representació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 cercano, 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 comentario 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 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 computación, la estadı́stica se ha visto sustancialmente impactada. Este impacto ha traı́do dos consecuencias 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 sistema 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/salida. 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 documentació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 encuentran: 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 relativamente 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 encuentran 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 (decrementar 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 99.79 99.78 ... 0.21 0.21 ... 0.06 ... total seconds 1973.70 1973.55 % self 48.40 ... 0.06 0.05 ... self seconds 957.30 % self 0.00 48.40 self seconds 0.00 957.30 name " r_ de c_c on tr as t " " r _ l o o k _u p _ t a b l e " 4.20 4.20 0.00 0.00 0.00 0.00 " imgDecreaseContrast " " . imgContrast " 1.20 0.06 1.20 ".C" 1.20 0.90 % total 99.78 0.06 0.05 total seconds 1973.55 1.20 0.90 name " r _ l o o k _u p _ t a b l e " ".C" " 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 99.61 ... 0.39 0.39 0.29 ... total seconds 3578.40 % self self seconds 14.10 14.10 10.35 % self 53.47 0.00 0.00 0.29 % total self seconds 1920.90 0.00 0.00 10.35 total seconds name " r_imgDiffer " ". imgArithmeticOperator " " imgDiffer " ".C" name Capı́tulo 2. R 53.47 ... 0.29 0.25 ... 10 1920.90 10.35 9.00 99.61 0.29 0.25 3578.40 10.35 9.00 " r_imgDiffer " ".C" ":" 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 optimizaciones 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 bugtracking, 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 Z Las especificaciones pueden ser provechosas en muchos sentidos: describen propiedades sin inmiscuirse 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 comprenderla. 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 algoritmos 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 provee, 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, definiciones 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. 3.5.1. Especificación en Z 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á fuertemente 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 T N = {s : Z | 0 ∈ s ∧ {i : s • i + 1} ⊆ s} ∼ 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 especificaciones 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 amplitud. 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 (muestreo), 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 menor 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ágenes 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 intensidad. 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 ) (r1,0 , g1,0 , b1,0 ) .. . (r0,1 , g0,1 , b0,1 ) (r1,1 , g1,1 , b1,1 ) .. . (r0,2 , g0,2 , b0,2 ) (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, visualizació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 aplicaciones: 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). Actualmente 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 procesamiento 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 computadora 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 procedimientos 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 degradadas 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 tales 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 procesamiento 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 medidas 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 5.2. 29 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 convolució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 5.2.4. 30 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 planeamiento 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 caracteres 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 especializado, 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 repositorios 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 paquetes, 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 investigació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 organizació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 manejo 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 basados 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 rimage 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 representaciones 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, escrito 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 variadas 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 algunos 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 6.4. 36 biOpsGUI: el principio de una interfaz gráfica de usuario 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 implementar 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 especificació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 (mapeo 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 ajustar 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 transformació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 frecuencias (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 modificables. 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 reemplazos” 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 7.1. 39 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 f (x ) = x − min limit 255 n < min limit min limit ≤ x ≤ max limit 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: f (x ) = x × max desired − min desired +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: 7.1.3. f+ (x ) = min(255, x + (x × percentage)) (7.4) f− (x ) = max (0, x − (x × percentage)) (7.5) 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ámico 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 f (x ) = 255 x < thr value x ≥ thr value (7.7) Figura 7.7: Thresholding f (x ) = b( x 1/gamma ) × 255c 255 (7.8) 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: r (x , y) = a1 (x , y) + a2 (x , y) + ... + an (x , y) n (7.10) 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 (a) Imagen anterior 45 (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 (digitalizació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 realizarse, 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: p(z ) = √ 1 2πσ × e −(z −µ) 2 /2σ 2 (7.11) 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 transformada 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 generará 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: xs = xd 2 ys = yd 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 8.2.2. 50 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ı́: 2 1 | x |3 − | x |2 + 0 ≤| x |< 1 3 2 4 1 f (x ) = 3 2 − | x | + | x | −2 | x | + 1 ≤| x |< 2 6 3 0 2 ≤| x | (8.1) 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 8.2.4. 51 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 f (x ) = a | x |3 −5a | x |2 +8a | x | −4a 0 0 ≤| x |< 1 1 ≤| x |< 2 (8.2) 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 8.3. 8.3.1. = (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) Operaciones implementadas 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 8.3.3. 54 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 completamente, 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 importante 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 izquierda 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áscaras (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: g(x , y) = a b X X w (s, t)f (x + s, y + t) (9.1) s=−a t=−b donde a = (m − 1) 2 yb= (n − 1) . 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 totalmente 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 convolució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 especificado 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 intensidad 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 ordenando 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, eliminando 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 implementació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 | xmed − xi | ≤ i=1 N X | y − xi | (9.3) 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. Distancei = N X (| 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 computadoras 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 complemento 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 diferencias 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 df (i ) d (i ) 67 = f (i + 1) − f (i ) (10.1) El cálculo de esta fórmula es equivalente a una convolución de la función con [-1 1]. De manera 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: | G |= q 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 describen 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 1 0 Sobelx = 2 0 1 0 0 0 Robertsx = 0 0 1 0 1 0 Prewittx = 1 0 1 0 1 √ FreiChenx = 2 1 −1 1 −2 −1 Sobely = 0 −1 −1 0 0 −1 −1 −1 2 0 −2 0 0 Robertsy = 0 0 1 −1 √ 0 − 2 0 −1 0 68 1 Prewitty = 0 −1 0 1 FreiCheny = 0 −1 1 0 −1 √ 2 0 √ − 2 1 0 −1 0 0 −1 1 0 −1 1 0 −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, mientras 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 Prewitt = 1 −2 1 1 5 −3 Kirsch = 5 5 0 −3 1 0 Robinson3Level = 1 0 1 0 1 0 Robinson5Level = 2 0 1 0 10.4. −1 −1 −1 −3 −3 −3 −1 −1 −1 −1 −2 −1 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 zerocrossings 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: LoG(x , y) = 1 πσ 4 1 − x 2 + y2 2σ 2 e −(x 2 +y 2 )/2σ 2 (10.5) 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: DoG(x , y) = e −(x 2 +y 2 )/2σ12 2πσ12 − e −(x 2 +y 2 )/2σ22 2πσ22 s (10.6) 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 convolució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: f [i , j ] = (1 − b)b |i|+|j | 1+b (10.7) 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 segunda 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 transformació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 transformada 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 transformada 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 donde j es el número imaginario √ (11.1) −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(x , y)e −j 2π(ux +vy) dx dy H (u, v ) = −∞ (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: H (u, v ) = 1 MN M −1 N −1 X X h(x , y)e −j 2π(ux /M +vy/N ) (11.5) x =0 y=0 y la inversa: h(x , y) = M −1 N −1 X X 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) | H (u, v ) |= [R 2 (u, v ) + I 2 (u, v )]1/2 (11.8) tenemos φ(u, v ) = tan −1 I (u, v ) R(u, v ) (11.9) 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 representació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 completar 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 representació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 (a) Imagen original 81 (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 corresponden 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 sobre 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 B1 ⊕ B2 = 84 (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) + (0,0) (c) 12.2(a) + (0,1) (d) 12.2(b) + 12.2(c) (e) Ventana 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 (a) Imagen original 85 (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) Ventana Figura 12.4: Erosión binaria Análogamente a la dilatación, se implementó la función imgStdBinaryErosion, aplicación particular 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 denomina 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 aplicaciones 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 implementada, 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 operaciones 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 permitido, 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 definen 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 representan 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, corresponden 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 supervisada. 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 13.3. 92 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 comportamiento 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: da,b v um uX =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 correspondiente. 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 (a) Imagen original 94 (b) Imagen clasificada. Las clases 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 (a) Árbol en 2 dimensiones. No se indican los planos 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 95 (b) Representación del árbol anterior como un kd-tree 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 l X 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 13.3.2. 97 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 diferencia 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 correspondiente. 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 manipulació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 especificaciones 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 actualmente 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 posibilidad 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áquinas 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 documentar 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 99.64 22.53 12.31 6.98 3.65 0.36 0.36 0.26 0.24 0.05 0.05 0.04 0.03 0.03 total seconds 3544.50 801.30 438.00 248.40 129.90 12.75 12.75 9.30 8.55 1.80 1.80 1.35 0.90 0.90 % self 53.93 22.53 12.31 6.98 3.65 0.00 0.00 0.26 0.24 0.05 0.00 0.02 0.00 0.03 self seconds 1918.35 801.30 438.00 248.40 129.90 0.00 0.00 9.30 8.55 1.80 0.00 0.75 0.00 0.90 name " r_imgAdd " "[" " [ <- " " <= " "+" ". imgArithmeticOperator " " imgAdd " ".C" ":" " as . vector " " array " " imagedata " " as . integer " " as . integer . default " % self 53.93 22.53 12.31 6.98 3.65 0.26 0.24 0.05 0.03 0.02 self seconds 1918.35 801.30 438.00 248.40 129.90 9.30 8.55 1.80 0.90 0.75 % total 99.64 22.53 12.31 6.98 3.65 0.26 0.24 0.05 0.03 0.04 total seconds 3544.50 801.30 438.00 248.40 129.90 9.30 8.55 1.80 0.90 1.35 name " r_imgAdd " "[" " [ <- " " <= " "+" ".C" ":" " as . vector " " as . integer . default " " 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 99.53 30.59 16.19 3.48 0.47 0.33 0.16 0.14 0.09 0.09 0.08 0.07 0.05 0.05 total seconds 2653.20 815.40 431.70 92.85 12.45 8.85 4.35 3.60 2.40 2.40 2.10 1.95 1.35 1.35 % self 48.85 30.59 16.19 3.48 0.04 0.33 0.08 0.14 0.09 0.00 0.08 0.07 0.00 0.05 self seconds 1302.30 815.40 431.70 92.85 1.05 8.85 2.10 3.60 2.40 0.00 2.10 1.95 0.00 1.35 name " r_imgAverage " "[" " [ <- " "+" " imgAverage " ":" " imagedata " ".C" " as . vector " " array " "/" " list " " as . integer " " as . integer . default " % self 48.85 30.59 16.19 3.48 0.33 0.14 0.09 0.08 0.08 0.07 0.05 0.04 self seconds 1302.30 815.40 431.70 92.85 8.85 3.60 2.40 2.10 2.10 1.95 1.35 1.05 % total 99.53 30.59 16.19 3.48 0.33 0.14 0.09 0.16 0.08 0.07 0.05 0.47 total seconds 2653.20 815.40 431.70 92.85 8.85 3.60 2.40 4.35 2.10 1.95 1.35 12.45 name " r_imgAverage " "[" " [ <- " "+" ":" ".C" " as . vector " " imagedata " "/" " list " " as . integer . default " " 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 99.79 99.78 25.06 25.02 1.27 0.21 0.21 0.10 0.06 0.05 0.05 0.03 0.03 0.02 total seconds 1973.70 1973.55 495.75 494.85 25.05 4.20 4.20 1.95 1.20 0.90 0.90 0.60 0.60 0.45 % self 0.00 48.40 25.06 25.02 1.27 0.00 0.00 0.08 0.06 0.05 0.00 0.03 0.01 0.02 self seconds 0.00 957.30 495.75 494.85 25.05 0.00 0.00 1.65 1.20 0.90 0.00 0.60 0.15 0.45 % self 48.40 25.06 self seconds 957.30 495.75 % total 99.78 25.06 total seconds 1973.55 495.75 name " r_ de c_c on tr as t " " r _ l o o k _u p _ t a b l e " "[" " [ <- " "+" " imgDecreaseContrast " " . imgContrast " " imagedata " ".C" " as . vector " " array " ":" " as . integer " " as . integer . default " name " r _ l o o k _u p _ t a b l e " "[" Apéndice A. Profiling 25.02 1.27 0.08 0.06 0.05 0.03 0.02 0.01 494.85 25.05 1.65 1.20 0.90 0.60 0.45 0.15 105 25.02 1.27 0.10 0.06 0.05 0.03 0.02 0.03 494.85 25.05 1.95 1.20 0.90 0.60 0.45 0.60 " [ <- " "+" " imagedata " ".C" " as . vector " ":" " as . integer . default " " 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 99.76 99.75 25.61 24.89 1.51 0.24 0.24 0.12 0.06 0.06 0.06 0.04 0.03 0.02 0.01 total seconds 1992.45 1992.30 511.50 497.10 30.15 4.80 4.80 2.40 1.20 1.20 1.20 0.75 0.60 0.45 0.15 % self 0.00 47.71 25.61 24.89 1.51 0.00 0.00 0.10 0.06 0.06 0.00 0.04 0.01 0.02 0.00 self seconds 0.00 952.80 511.50 497.10 30.15 0.00 0.00 1.95 1.20 1.20 0.00 0.75 0.15 0.45 0.00 name " r _ d e c _ in t e n s i t y " " r _ l o o k _u p _ t a b l e " " [ <- " "[" "+" " imgDecreaseIntensity " " . imgIntensity " " imagedata " ".C" " as . vector " " array " ":" " as . integer " " as . integer . default " " max " % self 47.71 25.61 24.89 1.51 0.10 0.06 0.06 0.04 0.02 0.01 self seconds 952.80 511.50 497.10 30.15 1.95 1.20 1.20 0.75 0.45 0.15 % total 99.75 25.61 24.89 1.51 0.12 0.06 0.06 0.04 0.02 0.03 total seconds 1992.30 511.50 497.10 30.15 2.40 1.20 1.20 0.75 0.45 0.60 name " r _ l o o k _u p _ t a b l e " " [ <- " "[" "+" " imagedata " ".C" " as . vector " ":" " as . integer . default " " 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 99.61 22.99 12.32 5.13 2.98 2.47 0.39 0.39 0.29 total seconds 3578.40 825.75 442.65 184.20 107.10 88.80 14.10 14.10 10.35 % self 53.47 22.99 12.32 5.13 2.98 2.47 0.00 0.00 0.29 self seconds 1920.90 825.75 442.65 184.20 107.10 88.80 0.00 0.00 10.35 name " r_imgDiffer " "[" " [ <- " " <= " " <" "-" ". imgArithmeticOperator " " imgDiffer " ".C" Apéndice A. Profiling 0.25 0.05 0.05 0.04 0.02 0.02 % self 53.47 22.99 12.32 5.13 2.98 2.47 0.29 0.25 0.05 0.03 0.02 9.00 1.95 1.95 1.50 0.75 0.75 self seconds 1920.90 825.75 442.65 184.20 107.10 88.80 10.35 9.00 1.95 1.05 0.75 106 0.25 0.05 0.00 0.03 0.00 0.02 % total 99.61 22.99 12.32 5.13 2.98 2.47 0.29 0.25 0.05 0.04 0.02 9.00 1.95 0.00 1.05 0.00 0.75 ":" " as . vector " " array " " imagedata " " as . integer " " as . integer . default " total seconds 3578.40 825.75 442.65 184.20 107.10 88.80 10.35 9.00 1.95 1.50 0.75 name " r_imgDiffer " "[" " [ <- " " <= " " <" "-" ".C" ":" " as . vector " " imagedata " " 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 99.77 99.77 25.50 24.97 1.39 0.23 0.11 0.07 0.07 0.06 0.03 0.02 0.02 total seconds 1985.70 1985.55 507.60 496.95 27.60 4.50 2.25 1.35 1.35 1.20 0.60 0.45 0.45 % self 0.01 47.87 25.50 24.97 1.39 0.00 0.08 0.07 0.00 0.06 0.03 0.00 0.02 self seconds 0.15 952.80 507.60 496.95 27.60 0.00 1.50 1.35 0.00 1.20 0.60 0.00 0.45 name " r_gamma " " r _ l o o k _u p _ t a b l e " "[" " [ <- " "+" " imgGamma " " imagedata " " as . vector " " array " ".C" ":" " as . integer " " as . integer . default " % self 47.87 25.50 24.97 1.39 0.08 0.07 0.06 0.03 0.02 0.01 self seconds 952.80 507.60 496.95 27.60 1.50 1.35 1.20 0.60 0.45 0.15 % total 99.77 25.50 24.97 1.39 0.11 0.07 0.06 0.03 0.02 99.77 total seconds 1985.55 507.60 496.95 27.60 2.25 1.35 1.20 0.60 0.45 1985.70 name " r _ l o o k _u p _ t a b l e " "[" " [ <- " "+" " imagedata " " as . vector " ".C" ":" " as . integer . default " " 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 99.78 99.78 25.59 24.98 1.44 0.22 0.22 0.11 0.06 0.06 0.06 0.05 0.02 0.02 seconds 1985.25 1985.25 509.10 497.10 28.65 4.35 4.35 2.10 1.20 1.20 1.20 1.05 0.45 0.45 self 47.72 0.00 25.59 24.98 1.44 0.00 0.00 0.08 0.06 0.06 0.00 0.05 0.00 0.02 seconds 949.35 0.00 509.10 497.10 28.65 0.00 0.00 1.50 1.20 1.20 0.00 1.05 0.00 0.45 % self 47.72 25.59 24.98 1.44 0.08 0.06 0.06 0.05 0.02 self seconds 949.35 509.10 497.10 28.65 1.50 1.20 1.20 1.05 0.45 % total 99.78 25.59 24.98 1.44 0.11 0.06 0.06 0.05 0.02 total seconds 1985.25 509.10 497.10 28.65 2.10 1.20 1.20 1.05 0.45 name " r _ l o o k _u p _ t a b l e " " r_ in c_c on tr as t " "[" " [ <- " "+" " imgIncreaseContrast " " . imgContrast " " imagedata " ".C" " as . vector " " array " ":" " as . integer " " as . integer . default " name " r _ l o o k _u p _ t a b l e " "[" " [ <- " "+" " imagedata " ".C" " as . vector " ":" " 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 99.78 99.77 25.33 25.21 1.43 0.22 0.22 0.10 0.07 0.07 0.06 0.05 0.03 0.03 0.01 total seconds 2005.05 2004.90 508.95 506.70 28.80 4.50 4.50 2.10 1.50 1.50 1.20 1.05 0.60 0.60 0.15 % self 0.00 47.74 25.33 25.21 1.43 0.00 0.00 0.07 0.07 0.00 0.06 0.05 0.00 0.03 0.00 self seconds 0.00 959.40 508.95 506.70 28.80 0.00 0.00 1.35 1.50 0.00 1.20 1.05 0.00 0.60 0.00 name " r _ i n c _ in t e n s i t y " " r _ l o o k _u p _ t a b l e " "[" " [ <- " "+" " imgIncreaseIntensity " " . imgIntensity " " imagedata " " as . vector " " array " ".C" ":" " as . integer " " as . integer . default " " min " % self 47.74 25.33 25.21 1.43 0.07 0.07 0.06 0.05 0.03 self seconds 959.40 508.95 506.70 28.80 1.50 1.35 1.20 1.05 0.60 % total 99.77 25.33 25.21 1.43 0.07 0.10 0.06 0.05 0.03 total seconds 2004.90 508.95 506.70 28.80 1.50 2.10 1.20 1.05 0.60 name " r _ l o o k _u p _ t a b l e " "[" " [ <- " "+" " as . vector " " imagedata " ".C" ":" " 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 99.71 61.69 30.43 15.93 0.36 0.29 0.11 0.06 0.06 0.06 0.05 0.05 0.05 total seconds 2830.80 1751.25 864.00 452.25 10.35 8.10 3.00 1.80 1.65 1.65 1.50 1.35 1.35 % self 21.69 31.25 30.43 15.93 0.36 0.02 0.11 0.06 0.06 0.00 0.04 0.00 0.05 self seconds 615.75 887.25 864.00 452.25 10.35 0.45 3.00 1.80 1.65 0.00 1.05 0.00 1.35 name " r_imgMaximum " " max " "[" " [ <- " ":" " imgMaximum " ".C" " list " " as . vector " " array " " imagedata " " as . integer " " as . integer . default " % self 31.25 30.43 21.69 15.93 0.36 0.11 0.06 0.06 0.05 0.04 0.02 self seconds 887.25 864.00 615.75 452.25 10.35 3.00 1.80 1.65 1.35 1.05 0.45 % total 61.69 30.43 99.71 15.93 0.36 0.11 0.06 0.06 0.05 0.05 0.29 total seconds 1751.25 864.00 2830.80 452.25 10.35 3.00 1.80 1.65 1.35 1.50 8.10 name " max " "[" " r_imgMaximum " " [ <- " ":" ".C" " list " " as . vector " " as . integer . default " " imagedata " " 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 99.57 99.57 27.15 27.04 1.65 0.28 0.15 0.14 0.14 0.10 0.10 0.07 0.03 0.01 0.01 total seconds 1857.60 1857.60 506.55 504.45 30.75 5.25 2.85 2.70 2.70 1.80 1.80 1.35 0.60 0.15 0.15 % self 43.70 0.00 27.15 27.04 1.65 0.00 0.10 0.00 0.14 0.10 0.00 0.07 0.03 0.00 0.01 self seconds 815.25 0.00 506.55 504.45 30.75 0.00 1.95 0.00 2.70 1.80 0.00 1.35 0.60 0.00 0.15 % self self seconds % total total seconds name " r _ l o o k _u p _ t a b l e " " r_ ne gat iv e_ lu t " " [ <- " "[" "+" " imgNegative " " imagedata " " r_negative " "-" " as . vector " " array " ".C" ":" " as . integer " " as . integer . default " name Apéndice A. Profiling 43.70 27.15 27.04 1.65 0.14 0.10 0.10 0.07 0.03 0.01 815.25 506.55 504.45 30.75 2.70 1.95 1.80 1.35 0.60 0.15 109 99.57 27.15 27.04 1.65 0.14 0.15 0.10 0.07 0.03 0.01 1857.60 506.55 504.45 30.75 2.70 2.85 1.80 1.35 0.60 0.15 " r _ l o o k _u p _ t a b l e " " [ <- " "[" "+" "-" " imagedata " " as . vector " ".C" ":" " 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 99.76 99.76 25.53 24.84 1.54 0.24 0.12 0.07 0.07 0.06 0.02 0.02 0.02 total seconds 1970.25 1970.25 504.15 490.50 30.45 4.65 2.40 1.35 1.35 1.20 0.45 0.45 0.30 % self 47.84 0.00 25.53 24.84 1.54 0.00 0.08 0.07 0.00 0.06 0.00 0.02 0.02 self seconds 944.85 0.00 504.15 490.50 30.45 0.00 1.65 1.35 0.00 1.20 0.00 0.45 0.30 name " r _ l o o k _u p _ t a b l e " " r_threshold " "[" " [ <- " "+" " imgThreshold " " imagedata " " as . vector " " array " ".C" " as . integer " " as . integer . default " ":" % self 47.84 25.53 24.84 1.54 0.08 0.07 0.06 0.02 0.02 self seconds 944.85 504.15 490.50 30.45 1.65 1.35 1.20 0.45 0.30 % total 99.76 25.53 24.84 1.54 0.12 0.07 0.06 0.02 0.02 total seconds 1970.25 504.15 490.50 30.45 2.40 1.35 1.20 0.45 0.30 name " r _ l o o k _u p _ t a b l e " "[" " [ <- " "+" " imagedata " " as . vector " ".C" " as . integer . default " ":" 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