Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías de Telecomunicación Herramienta de Reconocimiento de Imágenes en Python Autor: Samuel Moreno Fernández Tutor: Juan José Murillo Fuentes Dpto. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2020 iii Proyecto Fin de Carrera Ingeniería de Telecomunicación Herramienta de Reconocimiento de Imágenes en Python Autor: Samuel Moreno Fernández Tutor: Juan José Murillo Fuentes Catedrático de Universidad Dpto. de Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2020 v Trabajo Fin de Grado: Herramienta de Reconocimiento de Imágenes en Python Autor: Samuel Moreno Fernández Tutor: Juan José Murillo Fuentes El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2020 El Secretario del Tribunal vii A mi familia A mis amigos y compañeros ix Agradecimientos Quería agradecer a todas las personas que me han acompañado a lo largo de esta bonita etapa y me han ayudado a alcanzar la meta que me propuse hace tan solo cuatro años. A mis padres. Mi apoyo incondicional, mi fuente inspiración, quienes confían en mi cuando ni yo mismo soy capaz, desde el primer momento hasta el último de esta y de cualquier etapa de mi vida solo tuvieron la intención de ayudarme en no venirme abajo en ningún momento. Se los debo todo. Eternamente agradecido. A mi hermano, el pequeño de la casa, pero no por ello estoy continuamente aprendiendo de él. Una persona con un corazón tan grande, que en mis peores momentos incluso lo pasa peor que yo. Siempre estará a mi lado. A mis abuelos, se que los que no están aquí estarán profundamente orgullosos de mi desde el cielo, y que sin su esfuerzo no podría recoger los frutos que ahora estoy recogiendo. Mención especial a mis dos abuelas: una de ellas nos dejó durante mi etapa como estudiante universitario, y desde aquí le dedico este trabajo por su tremenda bondad y por ser una abuela ejemplar, Emilia, y a mi otra abuela quien reza cada vez que me examino para que apruebe, y que ha dado resultado, eres increíble Antonia. Mil gracias. A mis amigos, no fue fácil el momento en el que cada uno después de Bachillerato cogiera un camino distinto, pero hoy puedo decir orgulloso que son las personas con las que desconecto de todo, en quienes confio, me río con ellos y hacen que consiga ese pico de felicidad que hace tanta falta. A mis compañeros de piso, tantos lo que tuve el primer año como el resto de los años, compartiendo tantos buenos momentos y remando a contracorriente en las adversidades y épocas de exámenes. Grandes mis “Bros”. A mi tutor Juan José, por la atención recibida durante la pandemia vivida durante este curso. Samuel Moreno Fernández Sevilla, 2020 xi Resumen Este trabajo de fin de grado aporta al lector diferentes funciones para realizarle transformaciones a una imagen introducida por un usuario. A través de estas transformaciones, el usuario podrá encontrar diferentes objetivos que se encuentran en este trabajo: Transformada de Fourier, filtrado gaussiano, filtrado de mediana, su propia visualización 3D… El usuario tendrá una lista con todos los distintos tipos de tratamiento que se puede realizar sobre la imagen, y es el mismo usuario el que eligirá que transformación que realizará el programa. Una vez hecha una breve explicación sobre los objetivos, se tratará de explicar el lenguaje de programación en el que se implementa este trabajo, Python 3.8. Este lenguaje es el elegido para implementar el programa que llevará a cabo el tratamiento de las imágenes, y se utilizarán distintas librerías como NumPy y Matplotlib, para conseguir los resultados que queremos. Tras analizar Python y sus peculariedades, se explicarán las funciones definidas por el programador y todas las variables utilizadas a lo largo del programa, y el desarrollo de estas en el script GUI_img.py. Pero todas estas funciones creadas para transformar las imágenes tenemos que configurarlas de alguna manera para que interactúe con el usuario, y por ello se implementa una GUI (interfaz gráfica de usuario) usando la librería Tkinter en Python, siempre manteniendo cuidadosamente el tamaño de las imágenes, su relación de aspecto y sus propiedades para que se plasmen en la GUI sin ningún tipo de alteración. Finalmente, este programa se compilará para crear un archivo ejecutable .exe que funcione en Windows. Para ello se utilizará el compilador PyInstaller. xiii Abstract This final degree project provides the reader with different functions to perform transformations on an image entered by a user. Through these transformations, the user will be able to find different objectives found in this work: Fourier transform, Gaussian filtering, median filtering, their own 3D visualization… The user will have a list with all the different types of treatment that can be perform on the image, and it is the same user who will choose which transformation the program will perform. Once a brief explanation of the objectives has been made, the aim will be to explain the programming language in which this work is implemented, Python 3.8. This language is chosen to implement the program that will carry out the treatment of the images, and different libraries such as NumPy and Matplotlib will be used to achieve the results we want. After analyzing Python and its peculiarities, the functions defined by the programmer and all the variables used throughout the program will be explained, and the development of these in the GUI_img.py script. But all these functions created to transform the images we have to configure them in some way so that they interact with the user, and therefore a GUI (graphical user interface) is implemented using the Tkinter library in Python, always carefully maintaining the size of the images , its aspect ratio and its properties so that they are reflected in the GUI without any alteration. Finally, this program will be compiled to create an executable .exe file that works on Windows. For this, PyInstaller compiler will be used. Índice Agradecimientos ix Resumen xi Abstract xiii Índice xiv Índice de Figuras xvii 1 Introducción 1 1.1 Objetivos 1 1.2 Reconocimiento de imágenes 1 1.2.1 Transformada de Fourier 2 1.2.2 Filtrado de imágenes 2 1.2.3 Visualización 3D y Contorno 2 1.3 Estructura 3 2 Software y diseño 5 2.1 Software 5 2.2 Python 5 2.3 Entorno de Desarrollo Integrado(IDE) 6 2.4 Herramientas de Python 7 2.4.1 NumPy 7 2.4.2 SciPy 8 2.4.3 Os 8 2.5 Herramientas de procesado de imagen 9 2.5.1 OpenCV 9 2.5.2 Matplotlib 9 2.5.3 PIL 9 2.5.4 Imutils 10 2.6 Herramientas GUI 10 2.6.1 Tkinter 10 3 Herramienta y Desarrollo en Python 11 3.1 Objetivo GUI 11 3.2 Variables del programa 12 3.2.1 Definición 12 3.2.2 Variables en Python 12 3.2.3 Variables globales 13 3.2.4 Variables locales 13 3.3 Funciones usadas de librerias 13 3.3.1 Definición 13 3.3.2 Implementación 14 3.4 Diagrama de flujo 18 4 Implementación en Python 19 4.1 Fichero principal GUI_img.py 19 4.2 Variables definidas por usuario 20 xv 4.2.1 Variables globales 20 4.2.2 Variables locales 23 4.3 Módulo principal 23 4.4 Funciones definidas por usuario 26 4.4.1 Get_mouse_posn(event) 26 4.4.2 Update_sel_rect(event) 26 4.4.3 Contour_y_3d() 27 4.4.4 Histograma() 27 4.4.5 NoRecorta() 29 4.4.6 Recorta() 29 4.4.7 Limpiar() 31 4.4.8 ReconocimientoImagen() 31 4.4.9 Choose() 36 4.4.10 Save_file() 38 4.4.11 Imprime_label(event) 40 5 Compilación GUI 43 5.1 Introducción 43 5.2 PyInstaller 44 5.2.1 Compilación programa 45 6 Resultados de la GUI 47 7 Conclusiones y Líneas futuras 63 Referencias 66 Glosario 69 xvii ÍNDICE DE FIGURAS Figura 1. Ejecución fichero.py desde Pycharm. 7 Figura 2. Capas GUI. 12 Figura 3. Definición de función en Python. 14 Figura 4. Diagrama de flujos. 18 Figura 5. Fases de la compilación. 43 Figura 6. Análisis léxico. 43 Figura 7. Análisis sintáctico. 44 Figura 8. Instalación PyInstaller. 45 Figura 9. Compilación programa en Python. 45 Figura 10. Inicio de la GUI. 47 Figura 11. Introducción de la imagen a tratar. 48 Figura 12. Inicio con imagen introducida. 49 Figura 13. Inicio con la selección del área recortada sobre la imagen. 50 Figura 14. Inicio con recorte de la imagen. 51 Figura 15. Tratamiento de la imagen, FFT. 52 Figura 16. Tratamiento de la imagen, escala de grises. 53 Figura 17. Tratamiento de la imagen, filtrado gaussiano. 54 Figura 18. Tratamiento de la imagen, imagen ecualizada e histograma. 55 Figura 19. Tratamiento de la imagen, filtrado de mediana. 56 Figura 20. Tratamiento de la imagen, contornos. 57 Figura 21. Guardar imagen tratada seleccionada. 58 Figura 22. Contour y vista preliminar 3D de la FFT. 59 Figura 23. Visualización 3D de la FFT. 60 Figura 24. Inicio, limpieza de imagen. 61 1 1 INTRODUCCIÓN En nuestro día a día, tanto el ámbito laboral como en el cotidiano, estamos trabajando con imágenes, visualizando y analizándolas para intentar extraer información de ellas. Además, cualquier persona va recolectando a lo largo de la jornada imágenes con su cámara o su teléfono móvil, por ejemplo. También estas personas realizan de manera indirecta una serie de transformaciones a estas imágenes, como un simple cambio de escala, o un recorte sobre la imagen original. Los ingenieros de telecomunicaciones se encargan de que este tratamiento de las imágenes sea de la manera más sencilla posible para la persona que lo necesite. Así, a través de una interfaz gráfica se va a elaborar una herramienta en la que el cliente podrá realizar transformaciones indirectamente para extraer información de la imagen. Esta interfaz será sencilla para el público. Sobre ello trata la Herramienta de Reconocimiento de Imágenes, en la que se hacen una serie de transformaciones con la imagen que inserta el usuario para conseguir distintos objetivos: FFT, la imagen ecualizada, su visualización en 3D... 1.1 Objetivos El objetivo principal del trabajo es implementar un software GUI que pueda ser fácilmente adaptado para crear un software de procesado de imagen y su posible distribución para uso de terceros. Para desarrollar esta GUI, utilizaremos la librería Tkinter, y utilizaremos otras librerías como NumPy y Matplotlib para realizar todas las transformaciones necesarias. Las transformaciones realizadas han sido estudiadas en el grado, como la Transformada de Fourier, y plasmaremos los resultados en distintas pestañas de la interfaz gráfica. Otro de los objetivos será que el usuario pueda interactuar sin ningún tipo de problema con la interfaz, y que pueda hacerlo entendiendo todo lo que esta manejando sobre el software. Para que esto sea así, el diseño de la GUI debe estar muy cuidada y ordenada respecto a los widgets (elementos) que la conforman. La idea sobre un futuro uso o una posible mejora de la interfaz es aumentar el número operaciones sobre el procesamiento de imagen (nuevos filtrados, aumento de brillo…) , y que estas imágenes transformadas imprimirlas o bien en la misma pestaña o tab o crear una nueva pestaña, siempre que dentro de esta pestaña haya una relación estrecha en el tratamiento de la imagen. De esta manera el ingeniero que quiera adaptar este software tendrá varias posibilidades de hacerlo. 1.2 Reconocimiento de imágenes Una vez el usuario introduce la imagen elegida para el tratamiento, la herramienta realiza una serie de transformaciones a esta imagen, y se obtiene como resultado [10]: • Escala de grises. Es la misma imagen introducida, pero la gama de colores se reduce a una escala de grises. Introducción 2 • FFT. Transformada rápida de Fourier de la imagen que se describe a continuación. • Filtrado de Mediana. Suaviza la imagen, reduce la cantidad de variaciones de intensidad entre píxeles vecinos. Consigue que las intensidades de los objetos pequeños se mezclen con el fondo con el fin de detectar los objetos de mayor tamaño. Elimina ruido. • Filtrado gausiano. Similar al Filtrado de Mediana, aunque produce un suavizado más uniforme. • Imagen ecualizada. Eliminación total del ruido. • Contorno imagen. Líneas que permiten trazar los límites de la imagen. • Visualización 3D. Representación tridimensional de la figura correspondiente. En este caso, se realiza una visualización 3D de la FFT de la imagen. 1.2.1 Transformada de Fourier Es una transformación matemática que transforma señales entre los dominios del tiempo y de la frecuencia. Hace corresponder a una función f(t) con otra función F(w). F(w) = ∫ 𝑓(𝑡)𝑒−𝑖𝑤𝑡 +∞ −∞ 𝑑𝑡 , y su transformada inversa f(t) = 1 2𝜋 ∫ 𝐹(𝑤)𝑒−𝑖𝑤𝑡 +∞ −∞ 𝑑𝑤, Las imágenes que conocemos, suelen estar en el dominio del tiempo, y estas la transformaremos para obtener su resultado en el dominio de la frecuencia a través del uso de distintas librerías. 1.2.2 Filtrado de imágenes El resultado de cada pixel se obtiene como combinación lineal de sus vecinos. Multiplicamos el entorno de cada pixel por una mascara, la media ponderada será el nuevo valor del pixel. Estos filtros operan en el dominio del tiempo. Dependiendo del tipo de filtrado (mencionados en 1.2), se usarán unas funciones u otras para encontrar el objetivo. 1.2.3 Visualización 3D y Contorno Respecto al contorno, consiste en encontrar la silueta o siluetas de todas las figuras distintivas que haya o se aprecien dentro de la imagen insertada. Estas formas, estarán reproducidas con unas líneas de color fluorescente. A través de la visualización 3D, se simula una imagen de dos dimensiones en tres dimensiones. Así, visualizamos la imagen, que en nuestro caso será la FFT de la imagen insertada por el usuario, desde 3 3 Herramienta de Reconocimiento de Imágenes en Python diferentes perspectivas. 1.3 Estructura El resto del presente documento está dividido en 5 capítulos: • Segundo capítulo: se explicará el lenguaje y la IDE escogida,y todas las librerías usadas para implementar esta herramienta. Este capítulo tiene una gran importancia para entender la implementación del programa. • Tercer capítulo: se hace una introducción general a Python como toma de contacto con el lenguaje. Este capítulo incluye la explicación del significado de conceptos básicos, tipos de datos, funciones usadas ya implementadas en las librerías… • Cuarto capítulo: se combinarán diseño e implementación del funcionamiento del programa. Se tendrá en cuenta el aspecto de la GUI. • Quinto capítulo: Compilación en PyInstaller del programa implementado en Python 3.8. • Líneas futuras y Conclusiones: centradas sobre todo en implementaciones y mejoras que se pueden realizar a partir de la realización del trabajo. También recapitulará todas las conclusiones que se hayan podido extraer a lo largo del trabajo y en la implementación del sistema. Introducción 4 5 2 SOFTWARE Y DISEÑO 2.1 Software En la actualidad existen multitud de lenguajes de programación y entornos para desarrollar distintas aplicaciones y herramientas. Para esta herramienta, vamos a implementar una GUI y hay multiples lenguajes que nos permiten desarrollar una interfaz gráfica. Entre ellos están Matlab, Java, JavaScript, C, C++ … y Python. Este ultimo es el elegido para crear nuestra interfaz. El motivo principal sobre la elección de hacer el problema en Python es porque es un requisito de partida. Además, es un lenguaje que se focaliza en conseguir una sintaxis fácil de leer, y eso ayudará a la comprensión total del Código [1]. 2.2 Python Este lenguaje de programación está presente en muchas aplicaciones y sistemas operativos, como iOS, Windows, Linux, Mac o Android, debido a que tiene una curva de aprendizaje moderada, con un Código legible. Es un Código versátil multiplataforma que se destaca por su Código limpio. Su éxito principalmente se debe a que es un Código abierto, que permite su uso desde cualquier rincón del mundo. Es importante destacar que en este software se trata con la version Python 3.8 (32 bits), actualmente la más reciente. Tiene como características [2]: • Lenguaje multiplataforma. Se puede usar en distintos sistemas operativos • Ideal para trabajar con volúmenes de datos muy grandes, favorece el procesamiento, siendo también el lenguaje utilizado por las empresas de Big Data. Algunas de sus ventajas son: Sencillez y presteza, selecto, legible, fácil de aprender, organizado, portable y con un gran número de usuarios que participant activamente en su Desarrollo. Creo que este es el mejor consejo: piensa constantemente cómo podrías hacer mejor las cosas. -Elon Musk- Software y diseño 6 • Imperativo. Describen el estado del programa y permiten la modificación mediante instrucciones de Código. Se describe paso a paso un conjunto de instrucciones. • Funcional. Variación del programa mediante la mutación y el cambio de variables. Así, se opera con datos de entrada y salida. • Orientado a Objetos. Los objetos manipulan a otros, para ofrecer una salida específica. También permite juntar librerías. • Dinámico. Una variable puede tomar distintos valores de diferentes tipos en distintos momentos. Es este el Código que utilizaremos para desarrollar la Herramienta de Reconocimiento de Imágenes, para poder trabajar con mayor facilidad todos los datos que componen una imagen. En Python, a los archivos se le coloca la extension .py de forma que se pueden incorporar paquetes. Esta incorporación de paquetes y librerías se realiza de distintas maneras: • Mediante el comando import paquete. • Mediante el comando import paquete.elemento. • Mediente from paquete import elemento. • Mediante import elemento as name_elemento, acortando el namespace del element. 2.3 Entorno de Desarrollo Integrado (IDE) Para que el lenguaje tenga una finalidad final, debemos desarrollarlo en un entorno de Desarrollo interactivo, que es una aplicación que nos proporciona servicios para facilitar al programador el Desarrollo software [3]. En este caso, como se ha desarrollado la GUI en Python, se ha escogido el entorno PyCharm. PyCharm es uno de los entornos de Desarrollo más completos. Cuenta con entornos para construir Código en distintos lenguajes como PHP o Ruby. Usaremos la versión “Community” que es gratuita. También para posibles usos futuros, existe el modelo “Professional” de pago, ideal para el Desarrollo web. El objetivo principal de este IDE es proveer un entorno de programación en Python que se ejecuta de forma concurrente. La version utilizada de Pycharm es Pycharm 2019.3.3 (Community Edition). Pycharm ofrece distintos servicios [3]: • Asistencia inteligente a Python. Pycharm proporciona una finalización del Código inteligente, inspecciones del Código, indicación de errores sobre la marcha y arreglos rápidos, así como refactorización de Código automática y completes funcionalidades de navegación. • Marcos de trabajo de Desarrollo web. Pycharm ofrece una gran compatibilidad con marcos de trabajo de Desarrollo web modern como Django, Flask, Google App Engine… • Herramientas científicas. Cuenta con una consola de Python interactive y es compatible con Anacando y varios paquetes científicos como matplotlib y NumPy. • Desarrollo multitecnología. Además de Python, PyCharm es compatible con JavaScript, HTML/CSS… • Capacidades para Desarrollo remote. Ejecuta, depura, prueba y desarrolla aplicaciones en máquinas virtuales 7 7 Herramienta de Reconocimiento de Imágenes en Python • Herramientas de Desarrollo. Una gran colección de herramientas listas para usar, como un depurador integrado y ejecutor de pruebas. • IDE multiplataforma. PyCharm funciona en Windows, Mac OS o Linux. Es posible instalar y ejecutar PyCharm en tantas máquinas como se tenga, y usar el mismo entorno y la misma funcionalidad. • Cabe destacar que PyCharm está diseñado por programadores y para programadores, con el fin de proporcionarles herramientas que son necesarias para un desarrollo productivo de Python. En la siguiente figura, hay un ejemplo del aspecto de la IDE que vamos a usar para desarrollar la aplicación, Pycharm. También la ejecución desde Pycharm de un fichero como ejemplo de funcionamiento, y las distitas pestañas y opciones que se abren en la IDE para tener una vision global de la configuración del fichero, que librerías usa, plot, distintas excepciones lanzadas si procede… [17] 2.4 Herramientas de Python 2.4.1 NumPy Biblioteca de funciones matemáticas que opera con matrices. Numpy, que significa “Numerical Python”, es la librería principal para la informática científica, que proporciona grandes estructuras de datos [5]. La versión utilizada de NumPy es NumPy v1.18.0. Tiene como características [5]: Figura 1. Ejecución fichero .py desde Pycharm. Software y diseño 8 • Implementación de matrices N-dimensionales • Contiene: integrales, generadores de números totalmente aleatorios, transformadas de Fourier… • Interoperabilidad. Permite plataformas de hardware. • Fácil de usar. Alto nivel de sintaxis que facilita la accesibilidad a cualquier programador. • Código abierto. Mantenido por la comunidad diversa GitHub. • Muy utilizado en el desarrollo de algoritmos de Machine Learning Esta librería ha tenido gran éxito en el estudio de ciertos casos: gracias a NumPy, junto con el uso de otras librerías como SciPy y Matplotlib, permitió al Event Horizon Telescope producir la primera imagen de un agujero negro, también científicos confirmaron la existencia de ondas gravitacionales que predijo Albert Einstein en 1916 [5]. 2.4.2 SciPy Paquete científico científico. Incluye librerías científicas, basadas para matemáticas, ciencias e ingeniería.Es una colección con algoritmos numéricos y cajas de herramientas específicas de dominio, que incluye procesamiento de señales, optimización, estadísticas y mucho más [6]. Se basa en NumPy, y es parte del conjunto NumPy, con herramientas como SymPy o Pandas. Algunas características son [11]: • Está programado en Python, C, Fortran, C++, Cython. • La comunidad de personas pertenecientes a SciPy, son las mismas que desarrollan esta pila. • Contiene módulos para optimización, álgebra, integración, FFT, procesamiento de imágenes… • Multiplataforma. • Software abierto para computación científica. • Proporciona rutas numéricas. • Cómputo de datos y experimentación científica e informática. 2.4.3 Os Este módulo viene incorporado en el sistema Python, y nos permite acceder a funcionalidades dependientes del sistema operativo. Permite acciones como crear una carpeta, listar contenidos de una carpeta, conocer acerca de un proceso, finalizar un proceso, etc. Facilita métodos para conocer el directorio en el que nos encontramos, crear un directorio distinto o eliminar archivos [9]. Notas sobre la disponibilidad de estas funciones [23]: • El diseño de los módulos de Python dependientes del sistema operativo incorporado es tal que, siempre que esté disponible la funcionalidad, utilizará la misma interfaz. • Las extensiones propias de un sistema operativo en particular también están disponibles en el módulo Os. 9 9 Herramienta de Reconocimiento de Imágenes en Python • Todas las funciones que afirman “Disponibilidad: Unix” son también compatibles con Mac OS X, que se basa en un núcleo de Unix. 2.5 Herramientas de procesado de imagen 2.5.1 OpenCV Biblioteca de software de código abierto. Proporciona una infraestructura común para aplicaciones de visión por computadora [4]. La librería OpenCV tiene más de 2500 algoritmos optimizados, que incluyen aprendizaje automático. Estos algoritmos se suelen utilizar para reconocer caras, identificar objetos, estraer modelos 3D, nubes de puntos, unir imágenes, recortes con alta resolución, buscador de imágenes parecidas en bases de datos, eliminación de ojos rojos en fotografías o imágenes con flash, reconocimiento de paisajes, realidad aumentada…[4] Tiene interfaces C++, Python, Java y MATLAB y es compatible con Windows, Linux, Android y Mac OS [4]. Está escrita en C++ y tiene una interfaz con plantilla que funciona a la perfección con contenedores STL [4]. En este software, se usa la versión 4.2.0 de OpenCV. 2.5.2 Matplotlib Biblioteca completa para crear visualizaciones estáticas e interactivas en Python. Permite desarrollar gráficos de calidad de publicación con solo unas pocas líneas de código. Toma el control total de estilos de línea, propiedades de los ejes [8]… En este software, se usa la versión 3.2.0 de Matplotlib. Algunas de sus características son [8]: • Como función principal genera gráficos a partir de datos en listas. • Proporciona una pylab, una API implementada para parecerse a MATLAB. • Amplio juego de herramientas. Incluyen trazado 3d en el kit de herramientas mplot3d. • Paquetes de terceros. Proyecciones y mapeos de imágenes. • Código abierto. Mantenido por la comunidad diversa GitHub. 2.5.3 PIL “Python Imagin Library”, es una biblioteca de imágenes de Python que agrega capacidades de procesamiento de imágenes. Soporta una variedad de formatos, incluidos GIF, JPEG y PNG. Maneja imágenes como rectángulos de datos de píxeles [21]. Admite diversos y múltiples formatos y proporciona potentes habilidades de procesamiento de imágenes y gráficos. Software y diseño 10 Gracias a PIL, creamos imágenes, las manipulamos, creamos e insertamos textos en ellas, y esto lanzando un script [21]. En este software se usa la versión 7.0.0 PIL. Código fuente abierto y mantenido por la comunidad GitHub. 2.5.4 Imutils Serie de funciones que facilitan algoritmos y funciones de procesamiento de imágenes, como la traducción, rotación, recortes, ampliaciones de tamaños y visualización de imágenes Matplotlib con OpenCV. En este software se usa la versión 0.5.3 Imutils. 2.6 Herramientas GUI 2.6.1 Tkinter Python tiene multitud de marcos para elaborar una GUI, pero Tkinter es el único marco integrado en la biblioteca estándar de Python. Permite crear, colocar y mover una variedad de elementos gráficos para control, información de entrada y salida (widgets) [7]. En este software se usa la versión 8.6 de Tkinter. Algunas de sus características son [12]: • Multiplataforma. El mismo código funciona en Windows, macOS y Linux. Los elementos visuales pertenecen a la plataforma donde se ejecutan. • Instalada por defecto en una instalación Microsoft Windows. • Algunas de sus funciones son: posiciona texto/imagen en cualquier lugar de un widget, indica colores de fondo de widget, posibilidad de especificar el ancho del borde de un elemento, nos permite crear un botón y nos asocia un método o función para cuando se pulse ese botón… • Ideal cuando el principal objetivo es construir algo funcional y multiplataforma. 11 3 HERRAMIENTA Y DESARROLLO EN PYTHON 3.1 Objetivo GUI La interfaz gráfica de usuario (GUI), es un sistema informático que interviene como interfaz, usando un conjunto de objetos gráficos para representar información y permitir al usuario realizar una serie de acciones. Su principal uso consiste en ofrecer al usuario un entorno visual sencillo que permite la comunicación con el sistema operativo del ordenador [13]. Por citar ejemplos, algunos son [13]: • Windows • X-Windows de Linux • Aqua de Mac OS X En relación con la interacción con el usuario, es un artefacto tecnológico que posibilita una interacción con un sistema informático. En este caso, el sistema informático es la Herramienta que hemos creado para trabajar con imágenes que el usuario decide introducir en el sistema. A través de esta GUI, programada en Python y basada en la librería Tkinter, el usuario podrá realizar una serie de operaciones a la imagen, simplemente clickando un botón. De esta manera tan sencilla, el usuario podrá percibir visualmente los cambios generados en la imagen (Imagen original en escala de grises, FFT de la imagen…) [13]. Todo este procesado de imagen se comentará con detalles en los capítulos posteriores. Como se observa en la Figura 2, a través de la manipulación directa del usuario se interactúa con la interfaz gráfica de usuario. Este artefacto tecnológico posibilita a través de la representación del lenguaje visual, una interacción interesante con un sistema informático. Algunos ejemplos de interfaz gráfica son KDE Plasma, los entornos de escritorios de Windows… Esta interfaz gráfica está directamente relacionada con el servidor de pantalla, programa que coordina y gestiona las entradas y salidas de sus clientes (X.org, muy conocido en el mundo de Linux). Estos servidores de pantalla están directamente relacionados con los gestores de ventanas que crean los efectos gráficos y hacen que se muestren en las ventanas. Otra parte importante a la hora de crear una GUI es tener en cuenta el núcleo, que es la parte fundamental del sistema operativo, y es el responsable de facilitar a los distintos programas o interfaces de gestionar un acceso seguro al hardware de la computadora [24]. Sé que parece que el mundo se está desmonorando, pero en realidad es una gran época para volvernos locos, seguir nuestra curiosidad y ser ambiciosos. No abandonéis vuestros sueños. ¡El mundo os necesita! - Larry Page - Herramienta y Desarrollo en Python 12 Figura 2. Capas GUI. 3.2 Variables del programa 3.2.1 Definición En programación está la definición de “variable”, que es similar pero no idéntico al concepto de variable en lasmatemáticas. Las variables están asociadas a “algo” en concreto. Además, cada lenguaje tiene una manera distinta de implementar tal concepto, por lo que las definiciones siguientes [14]: • En algunos lenguajes, una variable se puede entender como una caja en la que se guarda un valor. Esta variable o caja, corresponde a un lugar en la memoria del ordenador. • Se pueden representar con letras o palabras: x, a, c, variable, edad, ciudad… • Para asignar un valor a estas variables, se suele hacer con el símbolo de igualdad (=), entendiéndose como una asignación. Cuando se realizar esta asignación, le pedimos al programa que calcule lo que existe a la derecha de esta igualdad, y que lo guarde en la variable lo que haya calculado previamente. La información que se guarda en estas variables puede ser de varios tipos como, por ejemplo [14]: • Números, que estos a su vez pueden ser enteros, decimales, imaginarios… • Cadenas de texto, como una sola letra o un conjunto de juego de caracteres ASCII. • Conjuntos de números o texto, como matrices. • Estructuras, como punteros. 3.2.2 Variables en Python En Python, el concepto de variable cambia. Estas variables, entendidas anteriormente como “cajas” de 13 13 Herramienta de Reconocimiento de Imágenes en Python información para otros lenguajes de programación, se convierten en “etiquetas” que hacen referencia a los datos en Python. Python es un lenguaje de programación orientado a objetos, y su modelo también lo es. Para cada dato de un programa, Python crea un objeto. Cada objeto tiene [14]: • Identificador único. Número entero distinto para cada objeto. • Tipo de datos. Entero, decimal, cadena de caracteres… • Un valor. Propio del dato. Volviendo un poco a las variables que existen en Python, distinguimos tres tipos [14]: • Variables locales. Las variables locales son las que pertenecen al ámbito de la subrutina. También son accesibles a niveles inferiores. No necesitan identificación. • Variables globales. Pertenecen al ámbito del programa principal. Necesitan la identificación global. • Variables no locales. Pertenecen a un ámbito superior a las locales, pero no son globales. Necesitan la identificación nonlocal. En la GUI desarrollada en la Herramienta de reconocimiento de imágenes, distinguiremos principalmente entre dos tipos de variables: las variables globales y las variables locales. 3.2.3 Variables globales Si a una variable no se le asigna valor, Python la considerará libre, y busca su valor en niveles por encima a esa función. Si a la variable se le asigna un valor en el programa principal, se le considerará global. Si queremos cambiar el valor de estas variables globales en alguna función o subrutina, debemos declararla dentro de la función o subrutina como global nombre_variable. Así, en la subrutina se utilizará la variable con el valor del programa principal, o igualmente se podrá modificar el valor de esta variable para utilizarla posteriormente en el programa principal [14]. 3.2.4 Variables locales Son las variables a las que se le asigna un valor dentro de una función, y solo existen en la propia función, incluso cuando en el programa exista otra variable con el mismo nombre. No son accesibles desde otros niveles superiores. 3.3 Funciones usadas de librerias 3.3.1 Definición Una función o subrutina es un bloque de código con un nombre, que recibe cero o X argumentos como entrada, sigue una secuencia de sentencias que tienen alguna finalidad y operación deseada, y devuelven y/o realizan una tarea en el programa en el que se encuentran [22]. Las funciones son llamadas siempre que se necesiten en el programa principal. Son esenciales para la programación estructurada, característica de Python. Algunas de las ventajas de las funciones son [22]: Herramienta y Desarrollo en Python 14 • Modularización. Segmenta un programa amplio y completo en distintos módulos más simples, facilitando la programación y el depurado. • Reutilización. Estos subbloques permiten realizar una función en distintos programas. Las funciones definidas por usuario son usadas habitualmente. Es una sentencia ejecutable, que enlaza el nombre de la función a un objeto función. Estas funciones se definen con la sentencia def, de la siguiente forma que se explica en la figura: La definición de una función no ejecuta el cuerpo de la función, lo que sucedería solamente con la llamada de la función en alguna parte del programa principal. 3.3.2 Implementación La implementación de las funciones definidas por el usuario, necesitan de funciones ya implementadas en librerías importadas en el programa. Estas funciones necesitan uno, ninguno o varios argumentos de entrada, para proporcionar una, ninguna o varias salidas. Estas funciones pertenecen a librerías, e iremos explicando cada una de las funciones utilizadas según la librería en la que se describan. A continuación, están todas las funciones integradas que utilizaremos en la realización de la Herramienta en Python. • Librería NumPy. Nos agrega funciones de apoyo para el cálculo de vectores y matrices de alto nivel. o Numpy.arange(0, A). Devuelve valores espaciados uniformemente dentro de un intervalo dado, en este caso de 0 a un número desconocido A. o Numpy.meshgrid(Y, X). Devuelve dos matrices de coordenadas de vectores de coordenadas X e Y. o Numpy.array([A, A, A]). Devuelve una matriz de valor A en cada posición. Si en vez de introducir como argumento un vector, introducimos una imagen, esta se recoge como un array y devuelve una matriz. o Numpy.fft.fft2(image, [256,256]). Calcula la FFT bidimensional. La matriz de entrada (image) puede ser compleja. Nos devuelve un ndarray. Figura 3. Definición de función en Python. 15 15 Herramienta de Reconocimiento de Imágenes en Python o Numpy.fft.fftshift(f). Siendo f la FFT de la anterior sentencia, esta función cambia el componente de frecuencia cero al centro del espectro. o Numpy.abs(fshift). Calculamos la amplitud del espectro fshift. o Numpy.log(k). Realiza el logaritmo a un conjunto de número o número introducido K. • Librería OpenCV o Array.shape. Devuelve el tamaño de la cadena de caractéres Array. o Cv2.imread(ruta,bandera). El método carga una imagen del archivo especificado en la ruta para llegar hacia él. Con las banderas especificadas, podemos cambiar la forma de leer la imagen. o Cv2.calcHist(imagen, rangos). Cálculo de histograma de la imagen insertada. Hay diferentes opciones a insertar, pero la más importante es el rango en el que se va a mover el histograma. o Cv2.cvtColor(). Convierte una imagen de un espacio de color a otro. Hay más de 100 métodos de conversión disponibles en OpenCV. Por ejemplo, están Cv2.COLOR_BGR2RGB y Cv2.COLOR_BGR2HSV [15]. o Cv2.inRange(imagen, lowB, upB). Donde se introduce una imagen, y nos encontramos con un umbral donde asignamos un valor particular a la región que se encuentre entre los dos umbrales “lowB” y “upB”. Al resto de la región se le asigna un valor distinto. o Cv2.GaussianBlur(Imagen). Se aplica un suavizado gaussiano en la imagen de origen de entrada. o Cv2.medianBlur(Imagen). El elemento central de la imagen se reemplaza por la mediana de todos los píxeles en el área del núcleo. o Cv2.findContours(). Ayuda a extraer los contornos de la imagen. • Librería Matplotlib o Plt.figure(). Clase personaliza en la interfaz de pyplot. Relacionada con la visualización de figuras. Devuelve “Fig”. o Fig.gca(projection=’3d’). La figura “Fig” pasa a representarse en 3 dimensiones. El resultado lo llamaremos “Ax”. o Ax.contourf(XX, YY, imagen_array). Dibuja líneas de contorno y rellenos. XX e YY Herramienta y Desarrollo en Python 16 son matrices que conforman el dimensionamiento de la matriz que compone laimagen. o Plt.close(). Cierra una ventana de figura. o Matplotlib.pyplot.imsave(ruta, matriz). Guarda una matriz como un archivo de imagen, en una ruta seleccionada previamente. • Librería PIL o Imagen.crop(). Metodo usado para recortar una porción rectangular de cualquier imagen. o Imagen.resize(A,B). Devuelve una imagen con cuyo alto y ancho se ha pasado como parámetros A y B. o Image.fromarray(matriz). Crea una memoria de imagen a partir de un objeto que exporta la interfaz de matriz. • Librería Tkinter. Trataremos elementos Canvas, que proporciona funciones de gráficos estructurados que se puede utilizar para dibujar diagramas. o Tkinter.Tk(). Instancia de la clase Tk, de Tkinter. Es el administrador de ventanas con botones de cerrar, maximizar y minimizar en la parte superior como una GUI habitual. A esta instancia la llamaremos “raíz” para entender las siguientes funciones. o Raíz.geometry(“AxB”). Establece unas dimensiones AxB a la ventana raíz. o Raíz.title(“Titulo de la Herramienta”). Titulo del administrador de ventanas. o Raíz.config(). Aquí se pueden configurar distintos parámetros relacionados con el administrador de ventanas. Por ejemplo, modificar los bordes, el tipo de cursor, el color de fondo… o Raiz.mainloop(). Se ejecuta cuando la aplicación esta lista para realizarse. Bucle infinito. o Tkinter.Label(Raíz, text=’ ‘). Etiqueta de texto, que suele ser estático. También puede ser una etiqueta de imagen. Al resultado devuelto por esta función lo llamaremos Label. o Label.place(x=”A”, y=”B”). Posición del label dentro de la ventana en la que está incluida. 17 17 Herramienta de Reconocimiento de Imágenes en Python o Tkinter.Frame(Raíz). Funciona como un contenedor, que se encargar de organizar la posición de los widgets. o Tkinter.Canvas(Raíz). Crea un widget de propósito general para mostrar gráficos en raíz. Al resultado de este widget lo llamaremos “Canvas”. o Canvas.create_rectangle(topx, topy, topx, topy, dash=(2,2), fill=’’, outline=’white’). Dibuja un rectángulo discontinuo de color blanco en el área seleccionada en este widget con el ratón. El rectángulo creado lo llamaremos “Rectángulo”. o Canvas.create_image(0,0,image=Imagen). En este widget se incluye la imagen que se quiere introducir. o Canvas.coords(Rectángulo, topx, topy, botx, boty). Si se dan las coordenadas, como en este caso, estas se reemplazarán por las coordenadas actuales del ítem seleccionado. o Canvas.bind(‘<Button-1>’, función). Esta función se ejecuta cada vez que se clickea el botón derecho del ratón. “Función” es a la subrutina que se llama cuando esto pasa. o Tkinter.Button(Raíz). Crea un Botón en el administrador de ventanas Raiz. o ImageTk.PhotoImage(Imagen). Produce una imagen compatible con Tkinter. Esto se puede usar en todas partes donde Tkinter espera un objeto imagen. o Widget.Destroy() . Destruye el widget selecionado. o Filedialog.askopenfilename(). Función para crear un objeto de diálogo de archivo. Puede ser bien para abrir el archivo, para guardar el archivo, o para abrir el directorio. Herramienta y Desarrollo en Python 18 3.4 Diagrama de flujo Este diagrama representa el funcionamiento de la interfaz gráfica de usuario. Figura 4. Diagrama de Flujos. 19 4 IMPLEMENTACIÓN EN PYTHON El software es como la entropía: dificil de atrapar, no pesa, y cumple la Segunda Ley de la Termodinámica, es decir, tiende a incrementarse. -Norman Augustine- Python es un lenguaje de programación diferencial, y por ello se escoge este lenguaje por delante de otros también muy usados como Java o Node.js [16]. Muchísimas ventajas mencionadas anteriormente como ser un lenguaje multiparadigma, orientado objetos e interpretado… hacen a Python ser el segundo lenguaje con mayor crecimiento en los últimos años, y ser el elegido por la mayoría de los ingenieros, desarrolladores y científicos a la hora de crear o desarrollar una Herramienta software [16]. A continuación, se explica el desarrollo del programa, sus partes, las variables utilizadas, tanto globales como locales, el módulo principal y las funciones creadas por el desarrollador para implementar la herramienta. Antes de comenzar, sería útil definir el concepto de widget, sencillo pero que vamos a nombrar a lo largo de este capítulo. Un widget es un componente que aparece dentro de nuestra ventana raíz. Se distinguen 21 tipos de widgets distintos: Canvas widget, Lavel widget, Button widget… con diferentes utilidades. 4.1 Fichero principal GUI_img.py El programa está compuesto exclusivamente por este fichero donde se encuentran todas las variables, tanto locales como globales, declaradas. De esta manera, también están las funciones definidas por el desarrollador, que explicaremos más adelante. Este fichero corresponde a un archivo de script de Python. Para crearlo, primeramente, se tiene que recurrir a la IDE elegida que en este caso es PyCharm. Se importan todas las librerías mencionadas en el capítulo anterior. Código: from tkinter import * from tkinter import filedialog from tkinter import ttk import tkinter as tk import cv2 from numpy import * import numpy as np import matplotlib as matplotlib from matplotlib import pyplot as plt from PIL import Image, ImageTk Implementación en Python 20 import scipy.misc as scimis import imutils from mpl_toolkits.mplot3d import axes3d from os import remove 4.2 Variables definidas por usuario Estas etiquetas son las utilizadas por el desarrollador para hacer referencia en el programa a diferentes valores o datos. Distinguimos entre las variables globales y locales, cuyas diferencias están explicadas anteriormente. 4.2.1 Variables globales Estas variables son usadas en todo el programa. Para la “Herramienta de reconocimiento de imágenes en Python”, vamos a usar las siguientes, con sus distintas funcionalidades que se describen a continuación: • Path_global: Contiene la ruta a la imagen original introducida por el usuario. A través de ella, se puede leer la imagen de la manera que mejor convenga. • Image_1_global: Contiene la imagen en forma de array introducida por el usuario. Si la imagen no ha conseguido leer sería una matriz vacía. Carga la imagen a color. • Hsv_global: Cambio de espacio de color de Image_1_global para representar mejor los colores. • Mask_global: Detecta los colores de Image_1_global en un rango definido [0,255]. • B_global: Etiqueta del botón que llama a la función correspondiente a recortar la imagen original, y hacer que sea la imagen activa para trabajar en la GUI. • B1_global: Etiqueta del botón que llama a la función correspondiente a devolver a la imagen original a la imagen activa para trabajar en la GUI. • B2_global: Etiqueta del botón que hace que el programa comience a realizar una serie de transformaciones sobre la imagen activa en la GUI. • MagnitudFFT_global: Representa a la FFT de la imagen activa en la GUI, es decir, la imagen original o una imagen recortada sobre la original. • MagnitudFFT2_global: Representa a un objeto ImageTk, que es una imagen adaptada a Tkinter. Esta imagen es la FFT de la imagen activa en la GUI. 21 21 Herramienta de Reconocimiento de Imágenes en Python • Imagen_ecualizada_global: Representa a un objeto ImageTk que es una imagen adaptada a Tkinter. Esta imagen es la imagen ecualizada de la imagen activa de la GUI. • Imagen_byn_global: Representa a un objeto ImageTk que es una imagen adaptada a Tkinter. Es la imagen en escala de grises de la imagen activa de la GUI. • Blur_img_tk_global: Representa un objeto ImageTk que es una imagen adaptada a Tkinter. Es la imagen activa tras ser filtrada por un filtro gaussiano.• Median_img_tk_global: Representa un objeto ImageTk que es una imagen adaptada a Tkinter. Es la imagen activa pasada por un filtro de mediana. • Res1_global: Representa la cadena de caracteres de la imagen en escala de grises, pero como conjunción de dos matrices. • Res2_global: Representa la cadena de caracteres de la imagen en escala de grises, pero como conjunción de dos matrices. • Blur_global. Representa la cadena de caracteres de la imagen activa tras ser filtrada por un filtro gausiano. • Median_global: Representa la cadena de caracteres de la imagen activa tras ser pasada por un filtro de mediana. • Image_1_global_contour. Representa la cadena de caracteres que conforma el contorno de la imagen activa dentro de la GUI. • Contours_cv2_tk_global. Representa un objeto ImageTk que es una imagen adaptada a Tkinter. Es la imagen que conforma el contorno de la imagen activa dentro de la GUI. • Cnts_global: Representa una matriz con los contornos de la imagen. • Alto_global: Representa al alto de la imagen recortada o de la imagen original en la GUI. • Ancho_global: Representa al ancho de la imagen recortada o de la imagen original en la GUI. • Alto_adaptado_global: Alto de la imagen activa adaptado a la altura de la interfaz gráfica. Esta variable hace que la imagen activa no sobrepase los límites de la GUI. Implementación en Python 22 • Ancho_adaptado_global:Ancho de la imagen activa adaptado a la anchura de la interfaz gráfica. Esta variable hace que la imagen activa no sobrepase los límites de la GUI. • Topx, topy, botx, boty: Coordenadas de la selección del rectángulo a la hora de recortar una imagen dentro de la GUI. • Rect_id_global: Representa al rectángulo que se imprime en la imagen activa que se quiere recortar dentro de la GUI. • Image_original_global: Representa a la imagen original adaptada al tamaño de la interfaz gráfica. • Canvas2_global: Representa al objeto Canvas perteneciente a Tkinter, donde se introduce la imagen recortada en la GUI. • Pix_global: Contiene la matriz de la imagen con la que vamos a realizar todas las transformaciones implantadas en la GUI. • HayRecorte: Variable con función de bandera. Cuando esta variable tiene valor ‘1’, existe un recorte sobre la imagen original, sin embargo, cuando vale ‘0’, la imagen original no ha sufrido más cambios mas allá de un cambio de escala para que esta quepa en la GUI. A todas estas variables le daremos un valor boolean inicial (Falso o verdadero), un entero (cero) o ninguno (None). Código: path_global = True; image_1_global = True; hsv_global = True; mask_global = True; B_global = True; B1_global = True; B2_global = True; magnitudFFT_global = True; magnitudFFT2_global = True; imagen_ecualizada_global = True; imagen_byn_global = True; blur_img_tk_global =True; median_img_tk_global=True; res1_global = True; res2_global = True; 23 23 Herramienta de Reconocimiento de Imágenes en Python blur_global = True; median_global = True; image_1_global_countour = True; contours_cv2_tk_global = True; cnts_global = True; alto_global = True; ancho_global = True; alto_adaptado_global= True; ancho_adaptado_global = True; topx, topy, botx, boty = 0, 0, 0, 0 rect_id_global = None image_original_global = None canvas2_global = None pix_global = True; HayRecorte = 0; 4.2.2 Variables locales Las variables locales se desclaran en su ámbito de uso (en el programa principal, y dentro de una función). Por lo tanto, debido a su definición se describirán las variables locales declaradas y definidas por el usuario en el programa principal o en las funciones en las que están declaradas, ya que son muchas y puede dar lugar a confusión. 4.3 Módulo principal El modulo principal del programa contiene los elementos más esenciales del programa, en los que están incluidos todos los widgets Tkinter, que conformarán nuestra interfaz gráfica. Uno de ellos, el más importante, es la raíz o base de la interfaz gráfica. A partir de este widget raíz, el resto de widgets se van almacenando dentro de este, y así se ira dando un aspecto a la interfaz gráfica haciendo el widget raiz de contenedor. Recibe el nombre de “ventana1”. Le ponemos un tamaño, un nombre, y no dejamos que su tamaño sea modificable por el usuario (todo en las siguientes figuras). También se configura el color de fondo, el tipo de cursor, el contorno y el borde de este en la GUI. A partir de la raíz, creamos el resto de widgets necesarios para la implementación de la interfaz gráfica. Se crean widgets como “labeltitulo” que son etiquetas de texto. Estos elementos de la interfaz gráfica también se configuran para darle forma a la GUI. Debido a las funcionalidades de la herramienta, se ha decidido dividir la interfaz en 3 pestañas distintas para diferenciar la funcionalidad de cada una dentro de la interfaz. Por lo tanto, se crea un panel de pestañas siendo esto un tipo de element gráfico que permite dividir una parte de la ventana en distintas solapas llamado “cuaderno1”. Se añaden las pestañas con nombre “pagina1”, “pagina2” y “pagina3”. Dentro de estas pestañas, se diseñan todos los elementos que se introducen cada una de ellas. Implementación en Python 24 En la primera pestaña, se incluye “boton1_abrir”. Este botón permite al usuario introducer una imagen para tratar con ella. En esta misma pestaña, se introduce un objeto Canvas para una vez introducida la imagen por el usuario, este objeto Canvas contenga la imagen introducida por el usuario. Asimismo, este objeto Canvas permite seleccionar un rectangulo sobre la imagen insertada. Este objeto Canvas recibe el nombre de “canvas”. Se configura también un segundo botón llamado “boton2_limpiar” que limpiará la GUI de la imagen introducida una vez el botón sea pulsado. En la segunda pestaña, se incluye una lista deplegable llamada “comboLabel”, en la que están las siguientes opciones: • FFT. • Escala de grises. • Filtrado gaussiano. • Histograma. • Filtrado de Mediana. • Contorno imagen Según la opción elegida, en el widget de imagen “label20” se imprimirá la imagen correspondiente a la opción elegida. También existen dos botones más que son importantes: “boton3_guardar” y “boton5_histograma”. El primero de ellos sirve para guardar la imagen según la opción elegida, y el segundo sirve para imprimir un histograma relacionado con la imagen ecualizada. Por ultimo, en la tercera pestaña se representará la imagen contour y la visualización en 3d de la FFT de la imagen activa. Estas imagenes respectivamente se insertarán en los widgets “label15” y “label17”. Código: ventana1 = tk.Tk() ventana1.geometry("1200x700") ventana1.title("Herramienta de reconocimiento de imágenes") ventana1.resizable(0,0) ventana1.config(bg="#0B121D") ventana1.config(cursor="hand2") ventana1.config(relief="sunken") ventana1.config(bd=8) labeltitulo= Label(ventana1, text="Herramienta de reconocimiento de imágenes", padx=15,pady=15) labeltitulo.config(fg="white", bg="#0B121D",font=("Arial",18)) labeltitulo.pack(anchor=N) cuaderno1 = ttk.Notebook(ventana1,width=800, height=600) cuaderno1.place(x=200,y=50) 25 25 Herramienta de Reconocimiento de Imágenes en Python pagina1 = ttk.Frame(cuaderno1) cuaderno1.add(pagina1, text="Inicio", padding=10) label1 = ttk.Label(pagina1, text="Bienvenido \nInserte una imagen para tratar con ella.\n") label1.grid(column=0, row=0) boton1_abrir = ttk.Button(pagina1, text="Abrir imagen",command=choose) boton1_abrir.grid(column=0, row=1) canvas = tk.Canvas(pagina1) canvas.config(cursor="arrow") canvas.bind('<Button-1>', get_mouse_posn) canvas.bind('<B1-Motion>', update_sel_rect) label19 = Label(pagina1, text=None) label19.grid(column=0,row=4)boton2_limpiar = ttk.Button(pagina1, text="Limpiar",command=limpiar) boton2_limpiar.grid(column=0,row=3) pagina2 = ttk.Frame(cuaderno1) cuaderno1.add(pagina2, text="Tratamiento de la imagen",padding=10,state="disabled") comboLabel = ttk.Combobox(pagina2, values=[ "FFT", "Escala de grises", "Filtrado gaussiano", "Histograma", "Filtrado de Mediana", "Contorno imagen"]) comboLabel.current(1) comboLabel.bind("<<ComboboxSelected>>", imprime_label) comboLabel.place(x="20",y="20") label2 = ttk.Label(pagina2, text=None) label2.place(x="20", y="0") label20 = Label(pagina2, image=None) label20.place(x="320",y="20") label21 = ttk.Label(pagina2,text=None) label21.place(x="20", y="100") boton3_guardar = ttk.Button(pagina2, text="Guardar",command=save_file,state="disabled" ) boton3_guardar.place(x="20", y="45") boton5_histograma = ttk.Button(pagina2, text="Imprimir Histograma", command=histograma) Implementación en Python 26 boton5_histograma.place(x="20", y="70") label_histograma_titulo = Label(pagina2, text=None) label_histograma_titulo.place(x="20", y="175") label_histograma = Label(pagina2, image=None) label_histograma.place(x="20", y="200") pagina3 = ttk.Frame(cuaderno1) cuaderno1.add(pagina3, text="Contour / 3D",padding=10,state="disabled" ) label14 = ttk.Label(pagina3, text=None) label14.place(x="175", y="0") label15= ttk.Label(pagina3,image=None) label15.place(x="0", y="60") label16 = ttk.Label(pagina3, text=None) label16.place(x="15", y="550") label17_titulo = ttk.Label(pagina3, text=None) label17_titulo.place(x="550", y="0") label17 = ttk.Label(pagina3, image=None) label17.place(x="370", y="20") boton4_contour = ttk.Button(pagina3, text="Visualizacion 3D", command = contour_y_3d) boton4_contour.place(x="650", y="400") ventana1.mainloop() 4.4 Funciones definidas por usuario 4.4.1 Get_mouse_posn(event) Se utilizan las variables globales indicadas en la figura. Devuelve la posición del ratón cada vez que pase el ratón por la imagen. Código: def get_mouse_posn(event): global topy, topx topx, topy = event.x, event.y 4.4.2 Update_sel_rect(event) Se utilizan las variables globales indicadas en la figura. 27 27 Herramienta de Reconocimiento de Imágenes en Python Selecciona las coordenadas del rectángulo a recortar sobre la imagen. Código: def update_sel_rect(event): global rect_id_global global topy, topx, botx, boty botx, boty = event.x, event.y canvas.coords(rect_id_global, topx, topy, botx, boty) 4.4.3 Contour_y_3d() Se utilizan las variables globales indicadas en la figura. En esta función, al comienzo cierras las posibles ventanas de pyplot creadas con anterioridad en otras funciones. Tras esto, se crea una ventana pyplot con la figura 3D de la FFT de la imagen. Código: def contour_y_3d(): global magnitudFFT_global plt.clf() plt.close() x, y = magnitudFFT_global.shape X = np.arange(0, x) Y = np.arange(0, y) xx, yy = np.meshgrid(Y, X) fig2 = plt.figure() ax = fig2.gca(projection='3d', facecolor="#F0F0F0") ax.plot_surface(xx, yy, magnitudFFT_global, facecolor="#F0F0F0") ax.contourf(xx, yy, magnitudFFT_global, zdir='x', offset=-5) plt.show() 4.4.4 Histograma() Se utilizan las variables globales indicadas en la figura. Se hace grafica un histograma sobre la imagen que procede. Esta imagen se asignará dependiendo de lo que valga “HayRecorte”. Si esta variable vale “0” el histograma se hará sobre la imagen original introducida al comienzo del programa por el usuario. Sin embargo, si esta variable vale “1” el histograma se realizará sobre la imagen recortada. Todo el procedimiento para graficar el histograma desencadena en la variable “img_histograma_label”, Implementación en Python 28 un objeto ImageTk que contiene la imagen en Tkinter del histograma a imprimir. Esta imagen se imprimirá en la variable “label_histograma”. Posteriormente, se borran las figuras creadas en pyplot. Código: def histograma(): global HayRecorte global pix_global if HayRecorte == 0: image_1_global = cv2.imread(path_global, 1) if HayRecorte == 1: image_1_global = pix_global fig = plt.figure() histr = cv2.calcHist([image_1_global], [0], None, [256], [0, 256]) plt.plot(histr) fig.savefig('plot.png', facecolor="#F0F0F0") image_1_global = cv2.imread('plot.png', 1) image_1_global.shape image = cv2.cvtColor(image_1_global, cv2.COLOR_BGR2RGB) hsv = cv2.cvtColor(image_1_global, cv2.COLOR_BGR2HSV) lw_range = np.array([0, 0, 0]) up_range = np.array([255, 255, 255]) mask= cv2.inRange(hsv, lw_range, up_range) res = cv2.bitwise_and(image, image, mask=mask) imagen_histograma = Image.fromarray(res).resize((280, 280), Image.ANTIALIAS) img_histograma_label = ImageTk.PhotoImage(imagen_histograma) label_histograma.configure(image=img_histograma_label) label_histograma.image = img_histograma_label remove("plot.png") label_histograma_titulo.configure(text="Nº total píxeles - Rango píxeles") plt.close(fig) 29 29 Herramienta de Reconocimiento de Imágenes en Python 4.4.5 NoRecorta() Se utilizan las variables globales indicadas en la figura. En esta función se retrocede para volver a obtener la imagen original insertada por el usuario. Por lo tanto, se borra el objeto “canvas2_global” donde se almacena la imagen recortada, y se procede a leer de nuevo la imagen. Esta imagen guardará en “canvas”, y sus características geométricas en “alto_global” y “ancho_global”. Código: def NoRecorta(): global image_original_global global canvas2_global global B1_global global HayRecorte global ancho_global ,alto_global global pix_global HayRecorte = 0 img = ImageTk.PhotoImage(image_original_global) canvas2_global.destroy() canvas.img = img canvas.create_image(0, 0, image=img, anchor=tk.NW) canvas.place(x="320",y="20") B1_global.configure(state="disabled") data_original = image_original_global.size alto_global = data_original[0] ancho_global = data_original[1] pix_global = np.array(image_original_global) 4.4.6 Recorta() Se utilizan las variables globales indicadas en la figura. En esta función se pretende recortar a través del rectángulo seleccionado la imagen introducida por el usuario. Se pretende, porque si esta función es llamada sin existir ningún área seleccionada para recortar, esta función no hace ninguna acción dentro de la GUI. En esta función, una vez recortada la imagen esta se guarda en “cropped_img”. Con el matiz de que ahora esta imagen recortarda “cropped_img” tenemos que aumentarla para que el usuario pueda observarla con mayor definición, manteniendo la relación de aspecto de la imagen y no desfigurarla. Implementación en Python 30 Código: def Recorta(): global image_1_global global topy, topx, botx, boty global cropped_img_label global canvas2_global global B1_global global pix_global global HayRecorte global alto_global, ancho_global global image_original_global if topx != 0 or topy != 0 or botx != 0 or boty != 0: canvas.delete("all") HayRecorte = 1 canvas2_global = tk.Canvas(pagina1) area = (topx, topy, botx, boty) cropped_img = image_original_global.crop(area) data1 = cropped_img.size alto_global = data1[0] ancho_global = data1[1] if alto_global < 550 and ancho_global < 550: while ancho_global < 450 and alto_global < 450: alto_global = alto_global* 1.02 ancho_global = ancho_global * 1.02 cropped_img1 = cropped_img.resize((int(alto_global),int(ancho_global)),Image.ANTIALIAS) cropped_img_label = ImageTk.PhotoImage(cropped_img1) canvas2_global.create_image(0, 0, image=cropped_img_label, anchor=tk.NW) canvas2_global.place(x="320",y="20") canvas2_global.config(width=int(alto_global), height=int(ancho_global)) pix_global = np.array(cropped_img) image_1_global = pix_global B1_global.configure(state="normal") 31 31 Herramienta de Reconocimiento de Imágenes en Python 4.4.7 Limpiar() Se utilizan las variables globales indicadas en la figura. En esta función, las dos pestañas últimas se deshabilitan para no poder acceder a ellas, ya que se va a eliminar la imagen insertada a la GUI. Si existe una imagen recortada, se eliminará el objeto canvas “canvas2_global” que es donde se encuentra. Igualmente, se vaciarán los widgets pertenecientes a la primera pestaña. Código: def limpiar(): global B_global, B1_global, B2_global global canvas2_global global HayRecorte try: cuaderno1.tab(1, state="disabled") cuaderno1.tab(2,state="disabled") canvas.delete("all") if HayRecorte == 1: canvas2_global.destroy() label19.configure(text=" ") label_histograma_titulo.configure(text=" ") label21.configure(text=" ") label_histograma.configure(image=None) label_histograma.image = None label20.configure(image=None, relief = None) label20.image=None boton3_guardar.configure(state="disabled") B_global.destroy() B1_global.destroy() B2_global.destroy() except AttributeError: print("No hay nada que borrar") 4.4.8 ReconocimientoImagen() Se utilizan las variables globales indicadas en la figura. Implementación en Python 32 Esta función es la función cerebro del programa y la que realiza todas las funciones relacionadas con el tratamiento de las imágenes. Se habilitan las 2 pestañas que estaban deshabilitadas al comienzo del programa. Dependiendo si ha habido recorte o no con anterioridad sobre la imagen insertada, se procede de una manera o de otra para que la variable local “image_2” y la variable global “image_1_global” hagan referencia al recorte o a la imagen original. Tras esto, se realiza a realizar transformaciones para conseguir los siguientes objetivos: • FFT de la imagen. • Imagen en escala de grises. • Filtrado gaussiano de la imagen. • Filtrado de Mediana sobre la imagen. • Imagen ecualizada • Contorno de los límites de la imagen • Contorno de la FFT (con 7 niveles) • Vista preliminar de la visualización 3d de la FFT. Tras realizar las transformaciones pertinentes, se borran las figuras creadas en pyplot y los archivos guardados. Código: def ReconocimientoImagen(): global path_global global cropped_img_global global imagen_global global image_1_global global hsv_global global mask_global global magnitudFFT2_global global imagen_ecualizada_global global imagen_byn_global global blur_img_tk_global global median_img_tk_global global magnitudFFT_global global res1_global global blur_global global median_global global res2_global global image_1_global_countour 33 33 Herramienta de Reconocimiento de Imágenes en Python global contours_cv2_tk_global global cnts_global global alto_adaptado_global , ancho_adaptado_global global pix_global global HayRecorte cuaderno1.tab(1, state="normal") cuaderno1.tab(2, state="normal") imagen_recortada= Image.fromarray(pix_global) imagen_recortada.save("Prueba.png") image_2 = cv2.imread("Prueba.png", 0) if HayRecorte == 1: data_nuevo = image_2.shape alto_nuevo = data_nuevo[0] ancho_nuevo = data_nuevo[1] while ancho_nuevo < 450 and alto_nuevo < 550 alto_nuevo = alto_nuevo * 1.02 ancho_nuevo = ancho_nuevo * 1.02 if HayRecorte == 0: image_2 = cv2.imread(path_global, 0) image_1_global = cv2.imread(path_global, 1) alto_nuevo = alto_adaptado_global ancho_nuevo = ancho_adaptado_global f = np.fft.fft2(image_2, [256,256]) fshift = np.fft.fftshift(f) magnitudFFT_global = 20 * np.log(np.abs(fshift)) magnitudFFT1_global = Image.fromarray(magnitudFFT_global).resize((350, 350), Image.ANTIALIAS) magnitudFFT2_global = ImageTk.PhotoImage(magnitudFFT1_global) gray_img = cv2.cvtColor(image_1_global, cv2.COLOR_BGR2GRAY) hsv_global = cv2.cvtColor(image_1_global, cv2.COLOR_BGR2HSV) lw_range = np.array([0, 0, 0]) up_range = np.array([255, 255, 255]) mask_global=cv2.inRange(hsv_global, lw_range, up_range) Implementación en Python 34 res1_global = cv2.bitwise_and(gray_img, gray_img, mask=mask_global) gray_img1 = Image.fromarray(res1_global).resize((int(ancho_nuevo), int(alto_nuevo)), Image.ANTIALIAS) imagen_byn_global = ImageTk.PhotoImage(gray_img1) blur_global = cv2.GaussianBlur(image_1_global, (5, 5), 0) blur_img = Image.fromarray(blur_global).resize((int(ancho_nuevo),int(alto_nuevo)),Image.ANTIALIAS) blur_img_tk_global= ImageTk.PhotoImage(blur_img) median_global = cv2.medianBlur(image_1_global, 5) median_img = Image.fromarray(median_global).resize((int(ancho_nuevo),int(alto_nuevo)),Image.ANTIALIAS) median_img_tk_global = ImageTk.PhotoImage(median_img) img_to_yuv = cv2.cvtColor(image_1_global, cv2.COLOR_BGR2YUV) img_to_yuv[:, :, 0] = cv2.equalizeHist(img_to_yuv[:, :, 0]) hist_equalization_result = cv2.cvtColor(img_to_yuv, cv2.COLOR_YUV2BGR) res2_global = cv2.bitwise_and(hist_equalization_result, hist_equalization_result, mask=mask_global) hist_equalization_result2 = Image.fromarray(res2_global).resize((int(ancho_nuevo), int(alto_nuevo)), Image.ANTIALIAS) imagen_ecualizada_global = ImageTk.PhotoImage(hist_equalization_result2) image_1_global_countour = image_1_global gray = cv2.cvtColor(image_1_global_countour, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1] cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts_global = imutils.grab_contours(cnts) for c in cnts_global: cv2.drawContours(image_1_global_countour, [c], -1, (0, 255, 0), 2) contours_cv2 = Image.fromarray(image_1_global_countour).resize((int(ancho_nuevo), int(alto_nuevo)), Image.ANTIALIAS) contours_cv2_tk_global = ImageTk.PhotoImage(contours_cv2) label14.configure(text="Contour") label17_titulo.configure(text="3D") 35 35 Herramienta de Reconocimiento de Imágenes en Python lw_range1 = np.array([0, 0, 0]) up_range1 = np.array([255, 255, 255]) x, y = magnitudFFT_global.shape X = np.arange(0, x) Y = np.arange(0, y) xx, yy = np.meshgrid(Y, X) fig = plt.figure() contorno1 = plt.contour(xx, yy, magnitudFFT_global, alpha=0.9, levels=7) plt.colorbar(contorno1) fig.savefig('plot.png', facecolor="#F0F0F0") imagen_contour = cv2.imread('plot.png', 1) hsv_global1 = cv2.cvtColor(imagen_contour, cv2.COLOR_BGR2HSV) imagen_cv2_contour = cv2.cvtColor(imagen_contour, cv2.COLOR_BGR2RGB) mask_global1 = cv2.inRange(hsv_global1, lw_range1, up_range1) res_global1 = cv2.bitwise_and(imagen_cv2_contour, imagen_cv2_contour, mask=mask_global1)image_1_1 = Image.fromarray(res_global1).resize((400, 400), Image.ANTIALIAS) img_1 = ImageTk.PhotoImage(image_1_1) label15.configure(image=img_1) label15.image = img_1 fig.clf() plt.clf() fig = plt.figure() ax = fig.gca(projection='3d', facecolor="#F0F0F0") ax.plot_surface(xx, yy, magnitudFFT_global, facecolor="#F0F0F0") ax.contourf(xx, yy, magnitudFFT_global, zdir='x', offset=-5) fig.savefig('plot.png', facecolor="#F0F0F0") surface1 = cv2.imread('plot.png', 1) hsv_global2 = cv2.cvtColor(surface1, cv2.COLOR_BGR2HSV) surface1_cv2 = cv2.cvtColor(surface1, cv2.COLOR_BGR2RGB mask_global2 = cv2.inRange(hsv_global2, lw_range1, up_range1) res_global2 = cv2.bitwise_and(surface1_cv2, surface1_cv2, mask=mask_global2) image_surface = Image.fromarray(res_global2).resize((350, 350), Image.ANTIALIAS) img_surface = ImageTk.PhotoImage(image_surface) label17.configure(image=img_surface) label17.image = img_surface remove('Prueba.png') Implementación en Python 36 remove('plot.png') fig.clf() plt.clf() plt.close(fig) 4.4.9 Choose() Se utilizan las variables globales indicadas en la figura. Esta función permite interactuar por primera vez al usuario con la GUI, permitiéndole al usuario introducir una imagen para realizarle todas las transformaciones mencionadas anteriorimente. Antes de guardar la imagen en un objeto canvas, tenemos que saber las dimensiones de este objeto, pero sin sobrepasar una geometría determinada (450 x 550 píxeles) manteniendo siempre su relación de aspecto. Una vez se encuentra el nuevo tamaño de la imagen, la metemos en el objeto canvas llamado “canvas”. Se extrae información sobre la imagen original y la imprimimos en el widget “label19”. Se crean nuevos botones: • “B_global”, que llama a la función Recorta() • “B1_global”, que llama a la función NoRecorta() • “B2_global”, que llama a la función ReconocimientoImagen() Código: def choose() global B_global, B1_global, B2_global global path_global global cropped_img_global global imagen_global global image_1_global global hsv_global global mask_global global funcion_global global res_global global ancho_global, alto_global global alto_adaptado_global, ancho_adaptado_global global image_original_global global pix_global global rect_id_global global HayRecorte 37 37 Herramienta de Reconocimiento de Imágenes en Python global canvas2_global try: if HayRecorte == 1: canvas2_global.destroy() path_global = filedialog.askopenfilename() image_1_global = cv2.imread(path_global, 1) data = image_1_global.shape image_original_global = cv2.cvtColor(image_1_global, cv2.COLOR_BGR2RGB) hsv_global = cv2.cvtColor(image_1_global, cv2.COLOR_BGR2HSV) lw_range = np.array([0, 0, 0]) up_range = np.array([255, 255, 255]) mask_global = cv2.inRange(hsv_global, lw_range, up_range) res_global= cv2.bitwise_and(image_original_global, image_original_global, mask=mask_global) alto_global = data[0] ancho_global = data[1] while ancho_global > 450 or alto_global > 550: alto_global = alto_global * 0.9 ancho_global = ancho_global * 0.9 alto_adaptado_global = alto_global ancho_adaptado_global = ancho_global image_original_global = Image.fromarray(res_global).resize((int(ancho_global),int(alto_global)), Image.ANTIALIAS) pix_global = np.array(image_original_global) imagen_global=image_original_global; img = ImageTk.PhotoImage(image_original_global) canvas.img = img canvas.create_image(0, 0, image=img, anchor=tk.NW) canvas.place(x="320",y="20") canvas.config(width=int(ancho_global), height=int(alto_global)) rect_id_global = canvas.create_rectangle(topx, topy, topx, topy,dash=(2, 2), fill='', outline='white') Implementación en Python 38 label19.configure(text="Informacion Imagen Original\nAlto: {} píxeles\nAncho: {} píxeles\nCanales: {} píxeles".format(data[0], data[1], data[2])) B_global = Button(pagina1, text="Recortar Imagen", command=Recorta) B_global.place(x=0,y=200) B1_global = Button(pagina1, text="Sin recortar", command= NoRecorta,state="disabled") B1_global.place(x=100,y=200) B2_global = Button(pagina1, text="Comenzar", bg = "#F0F0F0" , command=ReconocimientoImagen) B2_global.place(x=220, y=200) rect_id_global = canvas.create_rectangle(topx, topy, topx, topy, dash=(2, 2), fill='', outline='white') except AttributeError: print("Error tipo NoneType") 4.4.10 Save_file() Se utilizan las variables globales indicadas en la figura. Dependiendo de la opción escogida dentro de la segunda pestaña, se guardará una imagen u otra. Por ejemplo, si en la segunda pestaña está escogida la opción de la FFT de la imagen, y es llamada esta función con el botón correspondiente, se guardará la imagen FFT en extensión .png en el directorio que el usuario elija. Código: def save_file(): try: global magnitudFFT_global global res1_global global blur_global global median_global global res2_global global image_1_global_countour gray_img1 = Image.fromarray(res1_global) 39 39 Herramienta de Reconocimiento de Imágenes en Python file = filedialog.asksaveasfilename(filetypes=[("PNG",".png")],defaultextension=".png") if comboLabel.get() == "FFT": matplotlib.image.imsave(str(file), magnitudFFT_global) elif comboLabel.get() == "Escala de grises": gray_img1.save(str(file)) elif comboLabel.get() == "Filtrado gausiano": matplotlib.image.imsave(str(file), blur_global) elif comboLabel.get() == "Histograma": matplotlib.image.imsave(str(file), res2_global) elif comboLabel.get() == "Filtrado de Mediana": matplotlib.image.imsave(str(file), median_global) elif comboLabel.get() == "Contorno imagen": matplotlib.image.imsave(str(file), image_1_global_countour) except ValueError: print("No hay formato escogido") Implementación en Python 40 4.4.11 Imprime_label(event) Se utilizan las variables globales indicadas en la figura. Imprimirá en “label2” y en “label20” el título y la imagen correspondiente con la opción elegida dentro de la lista desplegable con las opciones . Por ejemplo, si la opción elegida es la FFT de la imagen, en “label2” aparecerá la etiqueta de texto correspondiente a la FFT, y en “label20” aparecerá la imagen correspondiente a la FFT. Código: def imprime_label(event): global magnitudFFT2_global global imagen_ecualizada_global global imagen_byn_global global median_img_tk_global global contours_cv2_tk_global global cnts_global label21.configure(text=" ") boton3_guardar.configure(command=save_file, state="normal") # state=disabled para deshabilitar if comboLabel.get() == "FFT": label20.configure(image=magnitudFFT2_global) label20.image = magnitudFFT2_global label2.configure(text="Imagen FFT") elif comboLabel.get() == "Escala de grises": label2.configure(text="Imagen original en escala de grises") label20.configure(image=imagen_byn_global) label20.image = imagen_byn_global elif comboLabel.get() == "Contorno
Compartir