scieee AI-readable full text Open interactive document viewer

MakeUP Stylist

Sahuquillo Falaguera, Silvia,Fortuny Elvira, Oscar,Sabic, Nedim

Abstract

Este proyecto, es una aplicación de escritorio que simula los efectos que se producen al aplicar un determinado maquillaje, o un complemento como podrían ser unas gafas, sobre un rostro. Por eso hemos llamado a este proyecto, MakeUP Stylist, algo así como estilista de maquillaje. Sobre las características técnicas , hemos de decir que está programado en C#, con un diseño de interfaces, basado en la tecnología WPF. Para detectar las caras a maquillar o sus elementos, ojos y boca, hemos empleado una potente librería de visión por computador llamada OpenCv, en su versión .Net: Emgu. Utiliza un estilo de programación basado en el modelo 3 capas y un patrón de diseño llamado Model-View-ViewModel. A groso modo, este programa funciona de la siguiente forma, realiza una captura de de imágenes empleando una cámara web o una imagen cargada del sistema, procesa la información obtenida buscando los elementos de la cara y muestra una serie de respuestas y/o soluciones basadas en el maquillaje.

Full text

INGENIERÍA TÉCNICA DE INFORMÁTICA DE GESTIÓN PROYECTO FINAL DE CARRERA Tutor ________________ Manuel AgustíMelchor Alumnos________________ Nedim Sabic Silvia Sahuquillo Falaguera Oscar Fortuny Elvira Valencia, 27 de Julio 2012 MakeUP Stylist 1 Proyecto final de carrera: MakeUP Stylist PROYECTO FINAL DE CARRERA: MAKEUP STYLIST 2 Proyecto final de carrera: MakeUP Stylist CONTENIDO MakeUP Stylist .......................................................................................................................... 1 1-. Introducción ........................................................................................................................ 5 2-. Estrategias y técnicas de detección y procesamiento de imagen ......................................... 7 2.1-. Introducción .................................................................................................................. 7 2.2-. Detección Y maquillado de la cara ................................................................................9 2.2.1-. Introducción ...........................................................................................................9 2.2.2-. Detección de la piel para aplicar una base de maquillaje. ..................................... 10 2.2.3-. aplicar una base de maquillaje .............................................................................. 15 2.3-. Detección y maquillado de los pómulos ...................................................................... 16 2.3.1-. Detección de la zona a maquillar .......................................................................... 16 2.3.2-. Maquillando: el colorete ....................................................................................... 17 2.4-. Detección y maquillado de los ojos ............................................................................. 20 2.4.1-. Detección de la zona a maquillar .......................................................................... 20 2.4.2-. Maquillando: Sombra de ojos .............................................................................. 21 2.5-. Accesorios: las gafas ................................................................................................... 23 2.6-. Detección y maquillado de la boca .............................................................................. 24 2.6.1-. Detección de la boca ............................................................................................ 24 2.6.2-. maquillado labial .................................................................................................. 25 3-. Interfaz .............................................................................................................................. 26 3.1-. Gestor de capas ........................................................................................................... 26 3.1.1-. Visualización del maquillaje. ................................................................................. 26 3.1.2-. Guardado de los efectos de maquillaje ................................................................ 27 3.1.3-. Estado del maquillaje ........................................................................................... 28 3.1.4-. Dibujado maquillaje ............................................................................................. 29 3.1.5-. Otras características ............................................................................................. 29 3.2-. Captura de imágenes con la cámara............................................................................ 30 3 Proyecto final de carrera: MakeUP Stylist 3.3-. Extensión .MKUP ........................................................................................................ 32 3.3.1-. Proceso de guardado del proyecto .mkup ............................................................. 32 3.3.2-. ProceSo de carga del proyecto .mkup .................................................................. 32 4-. Tecnologías utilizadas ....................................................................................................... 34 4.1-.Librería de visión: OpenCV .......................................................................................... 34 4.1.1-. ¿Qué es OpenCV? ................................................................................................. 34 4.1.2-. Wrappers: librerías envoltorio de OpenCV ........................................................... 35 4.2-. Interfaz de programación de aplicaciones ................................................................... 39 GDI+ (Graphics Device Interface) .............................................................. 39 4.3-. Lenguaje de programación: ........................................................................................ 41 C Sharp ..................................................................................................... 41 4.4-. Tecnología de la capa de presentación: ...................................................................... 42 Windows Presentation Foundation (WPF) ................................................ 42 4.4.1-. Los aspectos más destacados de WPF: ................................................................ 42 4.5-. Entorno de desarrollo: VS2008 ................................................................................... 44 4.6 Entorno de desarrollo para la compatibilidad con Linux: MONO ................................... 45 5-. Patrón de diseño: Model-View-ViewModel MVVM ............................................................ 47 5.1-. Introducción ................................................................................................................ 47 5.2-. Conceptos básicos ...................................................................................................... 48 5.3-. Arquitectura de Makeup Stylist ................................................................................... 53 5.3.1-. Vistas ................................................................................................................... 54 5.3.2-. ViewModels ......................................................................................................... 54 5.4-. Mediator. Comunicación entre ViewModels ............................................................... 59 6-. El modelo de datos y persistencia ...................................................................................... 62 6.1-. Modelo de datos ......................................................................................................... 62 6.2-. Persistencia de datos ..................................................................................................66 6.3-. Clases ......................................................................................................................... 70 4 Proyecto final de carrera: MakeUP Stylist 7-. Conclusiones ...................................................................................................................... 72 8-. Manual de usuario ............................................................................................................. 73 8.1-. Presentación de la Interfaz. ......................................................................................... 73 8.2-. Seleccionar una imágen .............................................................................................. 74 8.3-. Aplicar filtros de maquillaje ......................................................................................... 76 8.4 Gestión de catálogos. ................................................................................................... 79 9-. Futuros desarrollos ............................................................................................................ 82 10-. Bibliografía: ..................................................................................................................... 83 5 Proyecto final de carrera: MakeUP Stylist 1-. INTRODUCCIÓN Este proyecto, es una aplicación de escritorio que simula los efectos que se producen al aplicar un determinado maquillaje, o un complemento como podrían ser unas gafas, sobre un rostro. Por eso hemos llamado a este proyecto, MakeUP Stylist, algo así como estilista de maquillaje. Lo hemos desarrollado entre tres alumnos, que hemos estudiado tres intensificaciones diferentes: Ingeniería del software, Tecnologías y servicios para web y Multimedia, por lo que cada uno de nosotros, hemos tratado de poner en práctica todos estos conocimientos en nuestro proyecto. Aunque podemos decir que todos hemos colaborado en todas las partes del proyecto, Nedim ha participado en el diseño general de la interfaz y estructura del proyecto, pintado con GDI+ en los elementos: sombra de ojos, colorete y labios, además de desarrollar la parte del gestor de productos y almacenamiento XML. Oscar ha desarrollado la parte de la interfaz de la captura de imágenes con la cámara, galería de imágenes de accesorios, estrategia de pintado en capas y la parte de guardar el resultado bajo la extensión .mkup. Por último Silvia ha desarrollado la estrategia de detección en la carga de la imagen, pintado de accesorios, pintado de la piel, boca y raya de ojos. Sobre las características técnicas de este proyecto, hemos de decir que está programado en C#, con un diseño de interfaces, basado en la tecnología WPF. Para detectar las caras a maquillar o sus elementos, ojos y boca, hemos empleado una potente librería de visión por computador llamada OpenCv, en su versión .Net: Emgu. Utiliza un estilo de programación basado en el modelo 3 capas y un patrón de diseño llamado Model-View-ViewModel. Más adelante, explicaremos cómo hemos aplicado todos estos conceptos y tecnologías de programación a nuestro proyecto. A groso modo, este programa funciona de la siguiente forma, realiza una captura de de imágenes empleando una cámara web o una imagen cargada del sistema, procesa la información obtenida buscando los elementos de la cara y muestra una serie de respuestas y/o soluciones basadas en el maquillaje. En un principio, planteamos la posibilidad de que nuestra aplicación funcionase en modo video. Pero tras hacer algunas pruebas, vimos que era prácticamente imposible hacer todo el 6 Proyecto final de carrera: MakeUP Stylist proceso en un tiempo razonable para obtener el resultado del maquillaje y mostrarlo manteniendo la ilusión óptica del vídeo, al menos, con los medios de los que disponíamos. Para la localización de la cara y sus elementos hemos utilizado unos algoritmos, llamados algoritmos Haar. Son el producto de analizar múltiples imágenes que contienen un determinado objeto. Este producto, se almacena en unos fichemos XML que posteriormente lee nuestro programa a fin de obtener la detección de un objeto. También se les conoce como clasificadores, ya que obtienen “entrenando”, procesando esas imágenes, y cuantas más imágenes se les proporciona mejores resultados ofrecen en cuanto a detección de ese objeto, dentro de una imagen cualquiera que lo contenga. Este proceso, teniendo en cuenta su complejidad, es realmente ágil, pero sólo es una parte de la detección. Con detección de los algoritmos Haar, obtenemos una serie de rectángulos marcando el objeto a encontrar, el problema es que en muchas ocasiones marca varias veces un mismo objeto. Este ha sido uno de nuestros trabajos, elegir cual de las caras obtenidas era la elegida para maquillar, comprobando que esta cara contenía 2 ojos en la parte media superior y una boca en la inferior. De esta forma, no tendremos que analizar todos los píxeles de la imagen. Nos centraremos únicamente en los píxeles que componen la cara o los elementos que sean de nuestro interés y descartar el resto, para hacer esta localización de una forma eficiente. Una vez localizados todos los elementos de nuestro interés, ya podemos comenzar a proceder con nuestro objetivo de maquillar virtualmente a nuestro usuario empleando las técnicas más adecuadas al tipo de maquillaje que se seleccione. 7 Proyecto final de carrera: MakeUP Stylist 2-. ESTRATEGIAS Y TÉCNICAS DE DETECCIÓN Y PROCESAMIENTO DE IMAGEN 2.1-. INTRODUCCIÓN El proceso de detección comienza cada vez que se carga la foto seleccionada. En primer lugar buscamos la localización de la cara, para ello hacemos valer el algoritmo de detección Haar proporcionado por la plataforma de detección. En la clase FaceDetector, encontramos un algoritmo con el mismo nombre, facedetector al cual le pasamos por parámetros la imagen a maquillar cargada en un objeto de tipo Bitmap. El algoritmo Haar nos devolverá el conjunto de rectángulos que encuadran las caras detectadas en la imagen que proporcionamos, entre los cuales escogeremos el primero que contenga en su interior los dos ojos y la boca lo cual significará con mayor exactitud que se trata de una cara, ya que los algoritmos de detección son realmente buenos en su trabajo, pero no infalibles. Comentamos que nuestro algoritmo facedetector escoge el rectángulo que contiene los 2 ojos y la boca, pero... ¿cómo hace este proceso? Muy sencillo, llama a los respectivos algoritmos de detección de las clases detectoras de los ojos y la boca, y comprueba que devuelven el rectángulo que los contiene. Este mecanismo lo hace mediante hilos de ejecución para que resulte más eficiente. A la misma vez que llama a los algoritmos de detección llama al algoritmo que detecta la piel de la cara. Estos tres métodos son los más costosos de la parte de detección por eso los emparejamos para que se ejecuten simultáneamente lanzando tres hilos de ejecución. Cuando termina el hilo que detecta la boca devolviendo el rectángulo que la localiza invocamos al método que detecta el contorno de los labios. Aparte, ha sido muy útil realizar un estudio de las proporciones de un rostro, (Imagen 2.1) porque muchos problemas de detección y/o de eficiencia han sido resueltos empleando estos métodos. La ventaja de estos conocimientos, radica en que se puede obtener un resultado aproximado, con una probabilidad de éxito muy alta, en unos milisegundos. Esto es así, porque se realizan operaciones matemáticas muy sencillas, que apenas consumen recursos 8 Proyecto final de carrera: MakeUP Stylist del procesador y no hacen un estudio del píxel que es lo que de alguna manera encarece el coste de detección. Teniendo en cuenta este estudio, hemos enriquecido nuestro algoritmo de detección, ayudando a los algoritmos de detección Haar, a encontrar los ojos y la boca con mayor eficiencia. Fruto de esta proporcionalidad y simetría, les hemos indicado el rectángulo que describe la zona de la cara detectada dónde deberían encontrarse estos objetos. De esta forma, hemos reducido el área de búsqueda dónde los algoritmos tendrían que realizar el trabajo. Imagen 2.1 Proporciones de la cara Siendo F el ancho de la frente fuente: http://bitacoraricardomiranda.blogspot.com.es/2011/08/investig acion-2da-experiencia-de.html Cuando terminamos esta fase de detección comprobamos que todas las variables contienen los objetos que requeríamos. De ser así asignamos a la variable que almacena el área del rectángulo de la cara, el número entero máximo para que no tenga en cuenta el resto de rectángulos detectados por el Haar de la cara. Ya que otra de las condiciones que ponemos en este algoritmo es que siempre busque un rostro en un rectángulo mayor. Esto es así porque entendemos que la cara mayor será la más cercana a la cámara por lo que será la más 15 Proyecto final de carrera: MakeUP Stylist Puesto que nuestro objetivo era que este algoritmo fuese capaz de detectar tanto la piel blanca como la de color, presenta algunos errores. Muchas veces, como se puede apreciar en las imágenes anteriores, toma como parte de la piel, partes del pelo. 2.2.3-. APLICAR UNA BASE DE MAQUILLAJE Vamos a aplicar un maquillaje base, para ello, tomamos el bitmap que contiene los píxeles considerados piel, que quedó inicializado en la fase de detección. Este bitmap llamado skin se encuentra en la clase faceDetector. Como ya habíamos explicado, este bitmap tiene las dimensiones de la imagen original, con la peculiaridad de que sólo tiene los píxeles que hemos considerado piel, tras cumplir las condiciones explicadas en el apartado anterior, y discriminando aquellos que no cumpliesen alguna de éstas. Este bitmap es una especie de máscara y los píxeles que la componen serán los que se pintarán con el color seleccionado, y con opacidad 100. Hemos tomado este valor de opacidad tras comprobar que era el que imitaba el maquillaje de una forma más realista, respetando la sensación de volumen de la imagen original. Esta máscara ya está preparada para visualizarse, por lo que sólo tendremos que añadirla a nuestro gestor de capas (Véase apartado: 3.1-. Gestor de capas) para poder visualizarla sobre la imagen original. Y obtendremos un efecto un efecto similar como se muestra en: (Imagen 2.6) Imagen 2.6: Efectos obtenidos al aplicar el maquillado base de la cara 16 Proyecto final de carrera: MakeUP Stylist 2.3-. DETECCIÓN Y MAQUILLADO DE LOS PÓMULOS 2.3.1-. DETECCIÓN DE LA ZONA A MAQUILLAR En este caso, detectar la zona a maquillar es relativamente fácil. No se requieren algoritmos complejos como los Haar, sino que simplemente, nos vamos a basar en las proporciones de la cara, a partir del rectángulo de la cara detectada. Este proceso se lleva a cabo en la fase de detección inicial que hemos explicado en el apartado de introducción 2.1 y se realiza únicamente durante la carga inicial de la imagen. Para obtener los 2 rectángulos que definirán la zona donde posterior mente se pintará el colorete, hay que llamar a los métodos definidos en la clase FaceProportions. Estos rectángulos se basarán en el rectángulo de la cara en la siguiente proporcionalidad: Ancho = 1 * Ancho cara / 4 Alto = 1 * Altura cara / 6 Según las siguientes fórmulas para definir el punto (0,0) correspondiente a la esquina superior izquierda de cada rectángulo: Mejilla derecha: X = P(x,0) cara + 8 * Ancho cara / 12; Mejilla izquierda: X = P(x,0) cara + 1 * Ancho cara / 12; En ambos casos, la altura de los rectángulos será la misma. Ya que por la simetría en una cara frontal, las mejillas están más o menos a la misma altura. Y = P(0,y) cara + 8 * Altura cara / 15; *Siendo P(x,y) el punto a la esquina superior izquierda de cada rectángulo que define la cara. 17 Proyecto final de carrera: MakeUP Stylist 2.3.2-. MAQUILLANDO: EL COLORETE Para maquillar el colorete, emplearemos la interfaz de usuario GDI+ (Véase capítulo 5), por su amplia gama de efectos gráficos. En este caso, nos interesa un degradado, además, que tenga la forma esférica, por lo tanto, usaremos una brocha definida en GDI que defina la forma del objeto a colorear, en este caso una elipse, y otra, que rellene dicha elipse, empleando el efecto de degradado. Para ello, creamos la brocha e invocamos el método para rellenar el interior de la forma. La clase en GDI+ que nos permite crear formas personalizadas es GraphicsPath. Nuestra forma va a tener aspecto de una elipse, con el origen en 0, 0 del mapa de bits donde se dibujará esta forma, con las dimensiones de la mejilla. Ahora creamos la brocha que recibirá como parámetro la forma gráfica que acabamos de crear. Como vemos, antes de realizar el pintado, llamando a la función FillPath, es necesario inicializar algunas de las propiedades de la brocha. La primera de ellas, CenterColor, indica el color central que tendrá el colorete. Como no queremos un color totalmente sólido usamos el método FromArgb para especificar la opacidad / transparencia (en nuestro caso el valor 100). La segunda propiedad recibe un array de colores, empezando en la frontera de la esfera y degradándose hacia el centro. using (PathGradientBrush pgb = new PathGradientBrush ()) { pgb.CenterColor = Color.FromArgb(100, clr)); pgb.SurroundColors = new Color[] { Color.Transparent }; g.FillPath(pgb, cheek); } using (GraphicsPath cheek = new GraphicsPath()) { cheek.AddEllipse(0, 0, getBlushWidth(), getBlushHeight()); } 18 Proyecto final de carrera: MakeUP Stylist Por lo tanto en nuestro caso, la esfera se pintará desde el color transparente hacía el color del colorete. Finalmente invocamos al método para pintar nuestra esfera pasándole la brocha y la forma a pintar. Es posible especificar la posición donde empieza y acaba cada color de degradado mediante la propiedad InterpolationColors. El código completo queda como sigue: El último paso consiste en dibujar nuestro colorete sobre la imagen original, en la posición calculada por el detector de cara. Primero tenemos que obtener el contexto gráfico desde la imagen original. La clase CvImage de OpenCV dispone del método ToBitmap que nos permite convertir la imagen OpenCV al mapa de bits de GDI+. using (Bitmap cheekBitmap = new Bitmap(getBlushWidth(), getBlushHeight())) { using (Graphics g = Graphics.FromImage(cheekBitmap)) { using (GraphicsPath cheek = new GraphicsPath()) { cheek.AddEllipse(0, 0, getBlushWidth(), getBlushHeight()); using (PathGradientBrush pgb = new PathGradientBrush ()) { pgb.CenterColor = Color.FromArgb(100, clr)); pgb.SurroundColors = new Color[]{Color.Transparent}; g.FillPath(pgb, cheek); } } } } 19 Proyecto final de carrera: MakeUP Stylist Finalmente dibujamos nuestro colorete sobre la imagen. Para ello usamos el método DrawImage, pasándole el mapa de bits, y la posición X e Y donde se dibujará en la imagen original. El código final queda así: Imagen 2.7: Efecto obtenido al aplicar el maquillado del colorete, con 5 colores diferentes using (Bitmap canvas = faceDetector.getDetectableImage().ToBitmap()) { using (Graphics gfx = Graphics.FromImage(canvas)) { gfx.DrawImage(cheekBitmap, (m_leftCheekRect.Left + m_leftCheekRect.Width / 2) - getBlushWidth() / 2, m_leftCheekRect.Top + getAdjustFactor()); } } gfx.DrawImage(cheekBitmap, (m_leftCheekRect.Left + m_leftCheekRect.Width / 2) - getBlushWidth() / 2, m_leftCheekRect.Top + getAdjustFactor()); using (Bitmap canvas = faceDetector.getDetectableImage().ToBitmap()) { using (Graphics gfx = Graphics.FromImage(canvas)) { } } 20 Proyecto final de carrera: MakeUP Stylist 2.4-. DETECCIÓN Y MAQUILLADO DE LOS OJOS 2.4.1-. DETECCIÓN DE LA ZONA A MAQUILLAR Para detectar la zona de los párpados, se lanza un hilo de ejecución desde el proceso inicial de detección. Este hilo llama al método de detección de la clase EyeDetector, y empieza todo el proceso. Primero se consulta la zona de la cara donde se encuentran los ojos, para ello hacemos uso de las proporciones. Situamos la zona a detectar en base a la siguiente fórmula: A 1/5 de la altura total de la cara y con 1/3 de altura: Y = Alto cara / 5; X=P(x,0) cara; Width = Ancho cara ; Height = * Ancho cara / 3; Imagen 2.8: Muestra la zona donde por simetría y por la proporcionalidad definida en la izquierda, estarán situados los ojos. Tomaremos como zona donde se encontrará el ojo izuierdo la mitad izquierda del rectángulo anterior, y como derecho la zona derecha. 21 Proyecto final de carrera: MakeUP Stylist A continuación, utilizamos el algoritmo Haar sobre la zona definida anteriormente, para detectar la zona exacta donde se encontrarán ambos ojos. Todo el proceso anterior supone ganar en eficiencia y reducir la probabilidad de error, ya que el algoritmo Haar recibe un recorte de la imagen en que se encuentran los ojos de un tamaño muy inferior a la que contiene la cara. De esta forma el algoritmo nos devuelve una cantidad de rectángulos a analizar muy inferior y con resultados válidos, que si evaluaramos el algoritmo sobre la totalidad de la imagen o de la cara. A lo largo del estudio, antes de llegar a la solución escogida, hicimos pruebas localizando los ojos sobre la totalidad del rectángulo que contiene la cara y sobre la mitad superior de éste, obteniendo en ocasiones, resultados poco apropiados o poco eficientes. Aparte teníamos que comprobar sobre los resultados obtenidos cuales eran ojos derechos o izquierdos y comprobar que estubiesen a la misma altura, teniendo en cuenta alguna desviación, lo cual, encarecía considerablemente el coste del algoritmo de detección, por lo que finalmente optamos por la solución anteriormente descrita. 2.4.2-. MAQUILLANDO: SOMBRA DE OJOS Una vez obtenidas las posiciones, de los rectángulos obtenidos en la fase detección de los ojos, se procede al dibujo de la sombra. Para ello, creamos una forma geométrica en forma de elipse, definida dentro de un rectángulo de anchura 5/4 y altura 3/5 sobre la anchura y altura de uno de los dos rectángulos obtenidos en la fase de detección. Rectangle eyeRect = new Rectangle(0, 0, leftEyeRect.Width + leftEyeRect.Width/4, 3 * leftEyeRect.Height / 5); 22 Proyecto final de carrera: MakeUP Stylist Una vez establecida la región de recorte con el rectángulo del ojo, mediante la técnica de degradado múltiple creamos la mezcla de colores y dibujamos la forma. Puntualizar que en la mezcla se puede incluir número ilimitado de colores, con lo que se podrán crear efectos bastante atractivos. Siendo esto la primera versión del método, hace falta realizar más experimentos hasta conseguir que la aplicación del maquillaje sea los más realista posible. Imagen 2.9: Efecto obtenido al aplicar la sombra de ojos public static void createColorBlendPath(Graphics g, GraphicsPath path, List<BlendMakeupColor> lBlends) { using (PathGradientBrush pgb = new PathGradientBrush(path)) { pgb.InterpolationColors = doColorBlend(lBlends); g.SmoothingMode = SmoothingMode.AntiAlias; g.FillPath(pgb, path); } } using (GraphicsPath shadowPath = new GraphicsPath()) { shadowPath.AddEllipse(eyeRect); MakeUpUtils.createColorBlendPath(gs, shadowPath, lColors); } 23 Proyecto final de carrera: MakeUP Stylist 2.5-. ACCESORIOS: LAS GAFAS Para crear el efecto de la prueba de accesorios, en este caso prueba de gafas, hemos empleado la siguiente estrategia. Cuando se selecciona la gafa deseada, esta pasa a ajustarse a la cara para que tenga una apariencia real. Para realizar esto al seleccionar las gafas primero se obtiene la posición que deberían tomar en relación a la cara. Esta posición se obtiene a través de la clase FaceDetector, obtenemos la posición de los dos ojos. Para que las gafas encajen perfectamente, reducimos el tamaño de éstas por los 2 lados, ya que como hemos visto en el apartado anterior, FaceDetectors nos devuelve un rectángulo más ancho que el tamaño de la cara. A su vez, lo posicionamos unos pixeles por encima de la posición de la altura del rectángulo de los ojos. Una vez tenemos la posición de las gafas, solo queda el redimensionado del fichero que contiene la imagen con las gafas. Primero obtenemos las proporciones de la fotografía con una simple formula: Proporción = ancho Imagen / ancho Gafas. Con esa proporción simplemente la aplicamos a la imagen de las gafas para calcular la altura de la imagen que tendrán las gafas y la situamos en la posición calculada anteriormente. Y ya tenemos las gafas situadas y proporcionales a la cara. Imagen 2.10: Efecto obtenido en la prueba de las gafas 24 Proyecto final de carrera: MakeUP Stylist 2.6-. DETECCIÓN Y MAQUILLADO DE LA BOCA 2.6.1-. DETECCIÓN DE LA BOCA Para realizar la detección de la boca, empleamos una estrategia semejante a la expuesta en el anterior apartado. Nos apoyamos en un algoritmo Haar esta vez dirigido a la detección de la boca. Retomando el apartado de la detección de la cara, dijimos que para decir que el rectángulo obtenido por el algoritmo Haar contiene realmente una cara, comprobamos que tiene 2 ojos y una boca en su interior. Pues bien, empezamos a analizar el conjunto de rectángulos que nos proporciona el algoritmo Haar, esta vez correspondiente a la boca. Y nos quedamos con el primero que se encuentre en el tercio inferior y en los 2 cuartos centrales del rectángulo que supuestamente contendrá una cara. (Ver Imagen 2.11) X = P(x,0) cara + Ancho cara / 4; Y = P(0,y) cara + 2 * + Ancho cara / 4 Width = Ancho cara / 2; Height = + Ancho cara / 4; Imagen 2.11: Proporciones de la cara que definen la boca 31 Proyecto final de carrera: MakeUP Stylist la aplicación. Y este comando se desencadenará haciendo clic sobre la imagen que deseemos cargar. 32 Proyecto final de carrera: MakeUP Stylist 3.3-. EXTENSIÓN .MKUP Para almacenar el resultado del maquillado, sin perder el contenido de las capas, hemos creado ficheros de extensión .mkup . Estos ficheros contienen la foto original y todas las capas, con su correspondiente contenido, que el usuario haya realizado en la aplicación. De esta forma, el usuario podrá guardar el proyecto y seguir maquillando cuando lo desee. 3.3.1-. PROCESO DE GUARDADO DEL PROYECTO .MKUP Al terminar de maquillar la foto elegida, podemos pulsar en el proyecto el botón de Guardar proyecto. Primero seleccionaremos el lugar donde deseamos guardar la fotografía, una vez seleccionado, el programa comenzara el proceso de guardado. Primero se transformará la imagen original en una array de bytes. Tras la transformación escribirá 2 líneas, la primera indicará que lo que se va a volcar es una foto, escribiendo la frase Foto. En la segunda línea se volcara el array de bytes originado tras la conversión de la imagen. Cada elemento del array estará separado por un punto y coma: “;” . Después volcaremos al fichero, las capas que el usuario haya creado. Para ello, primero se escribe en el fichero el nombre de la capa que se haya creado, en la siguiente línea se inserta un booleano que indica si la capa se encontraba escondida o visible. Las siguientes cuatro líneas indicaran el color de la capa, escribiendo en el fichero el color formado por el R G B y la variable Alpha que contiene la opacidad o transparencia con la que se aplica el color de la capa de maquillaje. 3.3.2-. PROCESO DE CARGA DEL PROYECTO .MKUP Para hacer el proceso de carga se comenzara primero leyendo la array de bytes de la imagen original guardada en el fichero. Una vez se ha leído se transformara en la foto y se indica al programa que tenemos una foto original, estableciéndola en la clase LayerManager, al realizar esto, también se ejecutara el proceso de detección de las diferentes partes de la cara. 33 Proyecto final de carrera: MakeUP Stylist Posteriormente se comienzan a generar las capas que se leen del fichero. Este proceso es muy sencillo, ya que simplemente se lee que tipo de capa se va a insertar y el color. Una vez obtenidos estos dos parámetros, se realiza el mismo proceso que se ejecuta cuando pintamos una foto (jpg , gif , . . . ) cargada desde archivo utilizando los colores de los menús . Por ejemplo, como anteriormente ya hemos detectado los ojos de la cara, si cargamos desde el fichero la sombra de ojos izquierda y su color correspondiente, se llamara al proceso de pintado de sombra de ojos, con el color. Este se encargará de pintar el ojo correspondiente y de añadir la capa de sombra de ojos al LayerManager. Para finalizar el proceso tan solo estableceremos en la clase LayerManager si la capa estaba visible o invisible cuando fue guardada. 34 Proyecto final de carrera: MakeUP Stylist 4-. TECNOLOGÍAS UTILIZADAS 4.1-.LIBRERÍA DE VISIÓN: OPENCV 4.1.1-. ¿QUÉ ES OPENCV? OpenCV es una potente librería útil para la visión por computador, es decir, para la captura y procesamiento de imágenes, como sus siglas en términos anglosajones indican: Open Source Computer Vision. Fue desarrollada por Intel en 1999 y se emplea en multitud de aplicaciones que requieran de la visión artificial para su funcionamiento. Está programada en c y c++, compuesta por más de 500 algoritmos optimizados que aprovechan el rendimiento de los procesadores multinúcleo. OpenCV tiene una licencia BSD, lo que la convierte en una librería de libre disposición, tanto para el empleo académico como comercial. Este tipo de licencia, es si cabe menos restrictiva que la GPL, ya que permite el uso del código fuente para software no libre. OpenCV es una librería multiplataforma, está preparada para su uso en diferentes sistemas operativos, tanto Linux como Windows y Mac OS X. Como hemos podido comprobar, la potencia de esta librería radica en su capacidad para el reconocimiento de objetos, detección de movimiento y tratamiento de imágenes. Nosotros, la hemos empleado para localización de la cara y sus partes: ojos y boca. Ésta librería nos ha sido muy útil, ya que soporta toda la parte de la detección y localización de la cara y sus elementos dentro de una imagen dada. Puesto que propiamente esta librería está enfocada como tal para la programación con c, c++ y python, hemos utilizado para el desarrollo de nuestra aplicación, una librería envoltorio de OpenCV, Emgu, que nos permite utilizar los algoritmos implementados por OpenCV en el lenguaje de programación c#. 35 Proyecto final de carrera: MakeUP Stylist 4.1.2-. WRAPPERS: LIBRERÍAS ENVOLTORIO DE OPENCV A lo largo del proceso de desarrollo del proyecto, hemos probado distintos “wrappers”, adaptándonos a las novedades del momento. Los “wrappers” son librerías envoltorio, reciben este nombre, porque su propósito principal es llamar a otras funciones pudiendo añadirles funcionalidad y sin tener en cuenta el lenguaje de programación en que han sido desarrolladas. En nuestro caso, estos “wrappers” nos han servido de nexo para interactuar con las funciones programadas en c y c++ de OpenCV. 4.1.2.1-. OPENCVDOTNET En un principio empleamos OpenCVDotNet. OpenCVDotNet es un proyecto desarrollado para la Universidad de IDC Herzliya (Israel) por el Dr. Yael Moses. Para nosotros fue un descubrimiento, ya que esta librería envoltorio nos permitía combinar la calidad de OpenCV en cuanto a detección y reconocimiento de formas se trata y la potencia de la plataforma .NET en el framework 3.5 con C#. Hicimos varios proyectos ejemplo empleando esta librería, enfrentándonos a los problemas que planteaba nuestro proyecto. Los resultados en cuanto a detección y reconocimiento de formas eran bastante satisfactorios, aunque había algunas funciones de OpenCv que no estaban implementadas o mejor dicho “envueltas”. Seguimos investigando y nos encontramos con Emgu. 4.1.2.2-. COMPARATIVA ENTRE EMGU CV Y OPENCVDOTNET Emgu CV aporta muchas más funciones que OpenCVDotNet y es más operativo. 36 Proyecto final de carrera: MakeUP Stylist Además a diferencia de otras envolturas como OpenCVDotNet o SharperCV que utilizan para su desarrollo código no seguro, Emgu CV está escrito enteramente en C #. La parte más interesante es que puede ser compilado en mono y por lo tanto es capaz de funcionar en cualquier plataforma compatible con Mono, incluyendo Linux, Solaris y Mac OS X, por lo que es multi-plataforma. Emgu trabaja con la última versión de OpenCV la 2.1. Además, nos proporciona las funciones de OpenCV que se utilizan para entrenar los clasificadores para la detección. Llamado HaarTraining, de manera que podemos crear nuestros propios clasificadores: Haars. Este proceso, explicado de una forma sencilla se realiza cargando en un programa una gran cantidad de imágenes que contienen un determinado objeto, a mayor cantidad de imágenes procesadas la calidad de la detección será mayor. Tras procesarlas, este algoritmo nos proporciona un documento .xml que contiene los parámetros necesarios para la detección de dicho objeto en una determinada imagen. De una forma más sencilla también se pueden implementar a través de matrices que contienen determinadas características de los objetos a tratar Emgu nos proporciona un manejador de excepciones y depurador de código fuente. Los creadores de Emgu nos garantizan la actualización de la librería en un periodo máximo de un año. Tanto es así que nuestro proyecto ha ido amoldándose a varias de estas actualizaciones, empezamos programando en la versión 1.5 y hemos actualizado a la versión 2.3.0, la más reciente. Desde la versión 1.5 a la actual, esta librería ha experimentado cambios extraordinarios. Ha solucionado los bugs de sus principios como por ejemplo, uno que nos afectaba directamente, el que definía la región de interés de una imagen (ROI), en la primera versión de nuestro proyecto hacíamos algunas operaciones bastante engorrosas para esquivar este bug que desaparecieron en la versión 2.1. La versión 2.3.0 aporta una mayor eficiencia en los cálculos, ya que el esfuerzo computacional se distribuye entre la CPU y la GPU, siendo especialmente eficiente en las gráficas más modernas de nVidia de la serie G8X que implementan algoritmos CUDA (Arquitectura de Dispositivos de Cómputo Unificado) Emgu viene con una licencia GPL, Licencia Pública General, esto nos permite utilizar esta librería gratuitamente con fines académicos y no comerciales, aunque parece ser que para los fines comerciales han puesto un precio bastante económico . 37 Proyecto final de carrera: MakeUP Stylist Imagen 4.1 : Tabla comparativa de las librerías envoltorio EmguCV y OpenCVDotNet Podemos decir que Emgu CV está mucho más desarrollado que OpenCVDotNet, posiblemente esto se debe a que Emgu es una librería en constante desarrollo mientras que OpenCVDotNet fue ideada como un proyecto “cerrado” por decirlo de alguna manera, aunque quien sabe si sus desarrolladores siguen trabajando en ella y pronto nos sorprenden con nuevas e interesantes características. Por todo ello, nosotros hemos apostado por Emgu para el desarrollo de nuestro proyecto. 4.1.2.3-. ARQUITECTURA DE EMGU EmguCV tiene dos capas de envoltorio. 1La capa de base (Layer 1) contiene la función, estructura y asignaciones de enumeración que reflejan directamente los de OpenCV. 2La segunda capa (Layer 2) contiene las clases de . NET. 38 Proyecto final de carrera: MakeUP Stylist Imagen 4.2 : Imagen que representa la arquitectura de la librería EmguCV 39 Proyecto final de carrera: MakeUP Stylist 4.2-. INTERFAZ DE PROGRAMACIÓN DE APLICACIONES GDI+ (GRAPHICS DEVICE INTERFACE) La interfaz GDI, es uno de los tres componentes o subsistemas de la interfaz de usuario de Microsoft Windows. En GDI+ la clase principal para obtener acceso a las operaciones de dibujo es la clase Graphics. Como es abstracta, no se puede instanciar, sino que dispone de una serie de métodos estáticos que permiten obtener el contexto gráfico. Entre ellas esta el método FromImage() que permite obtener el contexto desde una imagen o un mapa de bits. Por lo tanto, en nuestro caso para realizar las operaciones de maquillaje, la estrategia, ha sido dibujar en un mapa de bits que representará una capa, y una vez finalizado el dibujo volcar / dibujar el contenido de la capa sobre la imagen original, en este caso la imagen del rostro humano. GDI+ nos proporciona una cantidad bastante amplia de métodos, propiedades, etc. , para el dibujo 2D, dibujo de líneas simple, líneas Béizer, degradados, formas personalizadas, transformaciones, etc. Empecemos creando un nuevo mapa de bits donde vamos a realizar el pintado: using (Bitmap cheekBitmap = new Bitmap(getBlushWidth(), getBlushHeight())) { using (Graphics g = Graphics.FromImage(cheekBitmap)) { } } Como se observa en este fragmento de código, en la primera línea creamos el mapa de bits con las dimensiones de la mejilla, que se calculan previamente mediante los métodos 40 Proyecto final de carrera: MakeUP Stylist getBlushWidth() y getBlushHeight(). Una vez creado el mapa de bits, obtenemos el contexto de dibujo desde el mismo. Notar el uso de los bloques using – permiten liberar automáticamente los recursos usados por los objetos, invocando al método Dispose() de los mismos. De esta manera evitaremos leaks de memoria. Teniendo el contexto gráfico correctamente obtenido podemos empezar a “dibujar” sobre él. En GDI+ muchos métodos que empiezan por draw, tienen su correspondiente método fill. El primero de ellos dibujará el borde del objeto, séase, un rectángulo, circulo, forma personalizada etc., mientras que su correspondiente pintará el interior del objeto. Existen diferentes tipos de brochas con las cuales rellenar el interior del objeto, entre los cuales están SolidBrush, LinearGradientBrush y PathGradientBrush. La primera de ellas rellena el interior del objeto con un color sólido, mientras que las otras dos realizan un relleno con 2 o varios colores de manera degradada – la primera de ellas rellena el interior de un rectángulo, mientras que la segunda el interior de cualquier forma personalizada que podemos imaginar / crear. 47 Proyecto final de carrera: MakeUP Stylist 5-. PATRÓN DE DISEÑO: MODEL-VIEW-VIEWMODEL MVVM 5.1-. INTRODUCCIÓN Con la llegada del marco de trabajo WPF, la puesta en práctica del patrón MVVM es cada vez más popular. Por lo tanto hemos optado, además de la arquitectura en capas, usar dicho patrón para llevar a cabo la implementación del proyecto. Unas de las principales ventajas de este patrón es la separación de los distintos artefactos. Las vistas nunca interactúan directamente con el modelo de datos, sino que lo hacen a través del controlador llamado ViewModel, que representa la abstracción de la vista, además de encargarse de enlazar los datos entre el modelo y la vista. El lenguaje declarativo XAML nos ofrece la posibilidad de establecer esos enlaces (data bindings), que pueden ser unidireccionales o bidireccionales, evitando que la vista contenga código correspondiente a la lógica de negocio y además consiguiendo el nivel de acoplamiento muy bajo. Las acciones del usuario que interactúa con los elementos de la vista (View ) se abstraen en forma de comandos que se enlazan con los componentes de la interfaz (botones, listas, cajas de verificación, etc. ). Al ejecutarse los comandos, invocarán a los métodos definidos en nuestros modelos (Model ). Otras de las ventajas de este estilo arquitectónico es la división del trabajo por roles cuando se trabaja en equipo. Como los requisitos funcionales del diseño de la interfaz de usuario son más propios del diseñador gráfico que del programador, la GUI se podrá crear en otras herramientas, simultáneamente e independientemente de la parte de lógica de negocios o datos. Por último, cuando nuestra aplicación está ensamblada usando MVVM las pruebas unitarias son mucho más llevaderas y comprensivas, ya que hay poca dependencia entre las diferentes partes que la forman. 48 Proyecto final de carrera: MakeUP Stylist 5.2-. CONCEPTOS BÁSICOS Antes de empezar a explicar con detalle como está implementado el patrón MVVM en Makeup Stylist, se explicarán las nociones básicas a nivel de código en el lenguaje de programación C#. Todo manejador de vista (ViewModel) debe extender de la clase abstracta ViewModelBase que tiene el siguiente aspecto: Como vemos, debe implementar la interfaz INotifyPropertyChanged, y concretamente el método NotifyPropertyChanged que notificará al sistema WPF de datos enlazados que la propiedad dispone de nuevo valor y que en consecuencia tiene que actualizarse. El primer argumento del método NotifyPropertyChanged tiene que ser el nombre de la propiedad que ha cambiado, que al establecerse un nuevo valor disparará el evento de tipo PropertyChangedEventHandler. Una vez definida la clase base, podemos empezar a crear nuevos manejadores de vistas. Para el ejemplo vamos definir un ViewModel encargado de manejar la información de los contactos de usuarios. public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(String sData) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(sData)); } } } 49 Proyecto final de carrera: MakeUP Stylist Para mantener la implementación los más sencilla posible no se ha creado ningún servicio empresarial , sino que el propio ViewModel ya define los métodos que se encargarán de añadir nuevo contacto, eliminar un contacto dado o obtener todos los contactos de la lista. El modelo es el siguiente. public class ContactViewModel : ViewModelBase { private ObservableCollection<Contact> contacts = new ObservableCollection<Contact> (); private void addContact(Contact contact) { contacts.Add(contact); } private void delContact(Contact contact) { contacts.Remove(contact); } private ObservableCollection<Contact> getContacts() { return this.contacts; } } 50 Proyecto final de carrera: MakeUP Stylist Definimos dos atributos que representan el nombre del contacto y su número de teléfono. Ahora vamos a ampliar el ViewModel declarando el comando que se ejecutará al añadir nuevo contacto desde la interfaz de usuario. El procedimiento para la eliminación del contacto es el mismo así que para más claridad del código se omitirá. public class Contact { private String name; private String phone; public String Name { get { return this.name; } set { this.name = value; } } public String Phone { get { return this.phone; } set { this.name = phone; } } } } 51 Proyecto final de carrera: MakeUP Stylist public class ContactViewModel : ViewModelBase { private ObservableCollection<Contact> contacts = new ObservableCollection<Contact> (); private ICommand addContact; public ICommand AddContact { get { return this.addContact; } set { this.name = addContact; } } public ContactViewModel() { doInitCommand(); } private void doInitCommand() { this.addContact = new GenericCommand(){ CanExecuteDelegate = c => c != null, ExecuteDelegate = c => addContact ((String) c )}; } private void addContact(String name) { Contact contact = new Contact(); contact.Name = name; contacts.Add(contact); } private void delContact(Contact contact) { contacts.Remove(contact); } private ObservableCollection<Contact> getContacts() { return this.contacts; } } 52 Proyecto final de carrera: MakeUP Stylist El método doInitCommand se encarga de inicializar los delegados del comando pudiendo solamente ejecutarse la adición del nuevo contacto cuando este sea distinto de null. Finalmente nos queda enlazar el comando con la vista. Para ello vamos a usar el lenguaje declarativo XAML. Como se ve en el anterior código estamos inicializando la propiedad Command del botón con el comando que está definido en nuestro ContactViewModel, y como parámetro le pasamos el valor que se proporciona en la caja de texto. Para tener acceso a las propiedades de nuestro ContactViewModel tenemos que inicializar el contexto de datos en nuestra vista. Una vez vistos los conceptos básicos veamos como se ha llevado a cabo la implementación del patrón MVVM en MakeupStylist. public partial class MainWindowView { private ContactViewModel contactViewModel = new ContactViewModel(); public MainWindowView (){ InitializeComponent(); this.DataContext = contactViewModel; } } <Window x:Class="ContactManager.MainWindowView" xmlns="http://schemas.microsoft.co m/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winf x/2006/xaml" Title="Contactos" Height="300" Width="300"> <Grid> <TextBox Margin="ContactName" Margin="50,34,50,160"></TextBox> <Button Margin="70,122,70,120" Content="Insertar contacto" Command= "{Path= AddContact }" CommandParameter ="{Binding ElementName= ContactName, Path=Text}"></Button> </Grid> </Window> 53 Proyecto final de carrera: MakeUP Stylist 5.3-. ARQUITECTURA DE MAKEUP STYLIST A continuación mostramos la arquitectura de Makeup Stylist desde el punto de vista del patrón MVVM. (Véase Imagen 5.1) Imagen 5.1: Arquitectura del proyecto A continuación vamos a explicar brevemente cada uno de los artefactos presentes en el diagrama. 54 Proyecto final de carrera: MakeUP Stylist 5.3.1-. VISTAS MainWindowView es la vista principal donde se alojan los elementos que componen la interfaz del usuario. EyeMakeupView es el control de usuario que permite realizar el maquillaje de los ojos (sombra y lápiz). FaceMakeupView vista encargada de proporcionar elementos para realizar el maquillaje de la cara. MouthMakeupView es la vista que permite realizar el pintado de los labios. CatalogView visualiza listas de catálogos de productos que se obtienen desde el archivo XML. Al seleccionar un catálogo se muestran todos los productos con información detallada de cada uno. Asimismo es posible realizar las acciones de inserción, borrado, edición tanto de catálogos como de productos. ThumbnailView proporciona acceso a imágenes en miniatura seleccionables, para representar accesorios como las gafas. 5.3.2-. VIEWMODELS MainViewModel inicializa los comandos de carga / captura de la imagen y las opciones de la webcam. También se encarga de notificar a los demás controladores de vista cuando se carga la imagen desde el cuadro de diálogo, usando el patrón Mediator que se explicará más adelante. EyeViewModel invoca los métodos del modelo para realizar el pintado y eliminación de la sombra y el lápiz de ojos, definiendo los comandos necesarios. También añade las capas correspondientes a la sombra / lápiz en la lista usando el manejador de capas. 55 Proyecto final de carrera: MakeUP Stylist FaceViewModel inicializa los comandos usados para dibujar el colorete y el maquillaje sólido y registra las capas correspondientes. MouthViewModel registra los comandos que se ejecutaran en respuesta al pintado de labios. También notifica cuando la imagen está a punto de repintarse. CatalogViewModel invoca los métodos de la capa de datos que se ha construido usando la tecnología XLINQ. Entre los métodos están los encargados de persistir la información relacionada con los catálogos de productos, así como recuperar dicha información en el arranque de la aplicación. LayerViewModel lleva a cabo la gestión de las capas renderizadas en MakeupStylist. Ofrece métodos de eliminación y ocultación de las capas. ThumbnailViewModel contiene métodos para llevar a cabo la carga de imágenes que representan los accesorios de maquillaje. Para ver como se ha implementado el patrón MVVM en MakeupStylist, vamos a tomar como ejemplo de referencia el pintado de mejillas, operación perteneciente a FaceViewModel. Este artefacto contiene la referencia al modelo FaceMakeup que define e implementa las operaciones de dibujo sobre el rostro. 56 Proyecto final de carrera: MakeUP Stylist En el constructor se inicializan los comandos invocando al método doInitCommands(). Mediante las expresiones lambda se inicializa la acción y el predicado del comando. Con CanExecuteDelegate indicamos que la acción solamente se podrá ejecutar si la imagen ha sido cargada. Cuando se cumpla la condición del primer delegado, entonces el comando se protected override void doInitCommands() { this.drawBlushCommand = new GenericCommand() { CanExecuteDelegate = c => LayerManager.Instance.hasImageLoaded(), ExecuteDelegate = c => doBlushMakeUp((Color) c ) }; } public class FaceViewModel : ViewModelBase { private FaceMakeup faceMakeup = new FaceMakeup(); private ICommand drawBlushCommand; public FaceViewModel() { doInitCommands(); doRegisterImageLoad(); doRegisterWebCamImage(); } public ICommand DrawBlushCommand { get { return this.drawBlushCommand; } set { this.drawBlushCommand = value; NotifyPropertyChanged("DrawBlushCommand"); } } } 63 Proyecto final de carrera: MakeUP Stylist En la clase FaceDetector se definen varios métodos para la detección del rostro usando para ellos diferentes técnicas y algoritmos. Una vez detectadas todas las partes de interés, se procede al pintado sobre la imagen original. Por ejemplo la clase Cheek representa la mejilla a pintar, basándose en los rectángulos obtenidos previamente por el detector. public abstract class BaseDetector<T> : IBaseDetector { /// <summary> /// El archivo haar de entrenamiento. /// </summary> protected HaarCascade haarFace = new HaarCascade(Constants.FACE_HAAR_XML); /// <summary> /// El archivo haar de entrenamiento. /// </summary> protected HaarCascade haarEye = new HaarCascade(Constants.EYE_HAAR_XML); /// <summary> /// El archivo haar de entrenamiento. /// </summary> protected HaarCascade haarMouth = new HaarCascade(Constants.MOUTH_HAAR_XML); /// <summary> /// Imagen sobre cual se realizará la detección / pintado: /// </summary> private Image<Bgra, Byte> cvImage; /// <summary> /// Imagen original pasada a escala de grises. /// </summary> protected Image<Gray, Byte> cvImageGray; } 64 Proyecto final de carrera: MakeUP Stylist En el método drawCheekFill se crea un mapa de bits donde inicialmente se pintará la capa correspondiente a la mejilla. Para ellos se usan las funciones del API de GDI+. Una vez private class Cheek { /// <summary> /// El rectángulo delimitador de la cara. /// </summary> private Rectangle m_rightCheekRect = new Rectangle(); private Rectangle m_leftCheekRect = new Rectangle(); /// <summary> /// Indica que mejilla se pintará. /// </summary> public enum CheekEnum { /// <summary> /// Se pinta la mejilla izquierda. /// </summary> Left, /// <summary> /// Se pinta la mejilla derecha. /// </summary> Right } private CheekEnum m_eCheek = CheekEnum.Left; public Cheek(CheekEnum cheekEnum) { this.m_eCheek = cheekEnum; } private void doInitRects(Rectangle faceRect) { m_rightCheekRect = FaceDetector.cheekR; m_leftCheekRect = FaceDetector.cheekL; } public void drawCheekFill(Rectangle faceRect, List<BlendMakeupColor> colors ) { Bitmap cheekBitmap = new Bitmap(getBlushWidth(faceRect), getBlushHeight(faceRect)); doInitRects(faceRect); using (Graphics g = Graphics.FromImage(cheekBitmap)) { createCheekFill(g, colors, faceRect); } if (m_eCheek == CheekEnum.Left) { LayerManager.Instance.addLayer(LayerManager.LayerType.LeftCheek, new Layer(cheekBitmap, new Point((m_leftCheekRect.Left + m_leftCheekRect.Width / 2) - getBlushWidth(faceRect) / 2, m_leftCheekRect.Top + getAdjustFactor(faceRect)), "Blush",LayerManager.LayerType.LeftCheek)); } else { LayerManager.Instance.addLayer(LayerManager.LayerType.RightCheek, new Layer(cheekBitmap, new Point((m_rightCheekRect.Left + m_rightCheekRect.Width / 2) - getBlushWidth(faceRect) / 2, m_rightCheekRect.Top + getAdjustFactor(faceRect)), "Blush",LayerManager.LayerType.RightCheek)); } } } 65 Proyecto final de carrera: MakeUP Stylist dibujada la capa, se invoca al método addLayer de la clase LayerManager para añadir la capa en la lista. Las clases Shadow que representa la sombra, Eyeliner el pincel de ojos, usan técnicas similares para realizar el pintado de las capas. Queda por mencionar la clase StylingAccessory encargada de dibujar elementos accesorios. Antes de dibujar el elemento, es necesario calcular las proporciones de la imagen fuente, y en función de ello escalar el mapa de bits destino antes de que se efectué el pintado. También mencionar la clase MouthMakeup que dispone de métodos para dibujar el contorno del labio usando para ellos las curvas Bézier. public class StylingAccesory : BaseMakup { public StylingAccesory() { } public void doWearGlasses(string path) { Bitmap btm = new Bitmap(path); Bitmap glasses = getProportionalGlasses(btm); LayerManager.Instance.addLayer(LayerManager.LayerType.Glasses, new Layer(glasses, new Point(EyeDetector.getGlasses().X, EyeDetector.getGlasses().Y), "Glasses", LayerManager.LayerType.Glasses)); } private Bitmap getProportionalGlasses(Bitmap btmGlasses) { double prop = 0.0; double anchoC = EyeDetector.getGlasses().Width; double anchoG = btmGlasses.Width; prop = anchoC/anchoG; int anchoN = int.Parse((Math.Truncate((btmGlasses.Width) * prop)).ToString()); int altoN = int.Parse((Math.Truncate(btmGlasses.Height * prop)).ToString()); btmGlasses = new Bitmap(btmGlasses, new Size(anchoN, altoN)); return btmGlasses; } } 66 Proyecto final de carrera: MakeUP Stylist 6.2-. PERSISTENCIA DE DATOS Para persistir la información de catálogos y productos de maquillaje asociados, MakeupStylist usa como soporte un archivo XML con la siguiente estructura: <catalogs> <catalog type="Pintalabios" name="Loreal"> <products> <product> <name>Natural Shimmer (N)</name> <color>#950000</color> </product> </catalog> </catalogs> Como podemos apreciar, todos los catálogos se encuentran dentro del elemento raíz <catalogs> y tienen dos atributos – type que determina el tipo de catálogo y name que se corresponde con el nombre del mismo. Cada catálogo puede tener indeterminado número de elementos <product> que representan el producto de maquillaje. A su vez estos contienen dos elementos, name que determina el nombre del producto y color que es la cadena en formato hexadecimal para representar los niveles del color (ARGB). La escritura y obtención de elementos desde el fichero XML se lleva a cabo mediante la tecnología XLINQ(XML Language Integrated Query) disponible a partir de la versión 3.5 del .NET framework. Esta tecnología dota al lenguaje de programación C# de operadores que permiten realizar consultas estilo lenguaje SQL. Las ventajas de XLINQ se reflejan en la facilidad del uso de su API, su robustez, y rendimiento, combinados con las características del lenguaje como la genericidad. Veamos, como se ha implementado el mecanismo de persistencia en MakeupStylist. En primer lugar, se ha definido la clase base abstracta de la que van a heredar todos los objetos DAO (Data Access Object) encargados de consultar o escribir información en el almacén de catálogos (fichero XML). 67 Proyecto final de carrera: MakeUP Stylist Esta clase abstracta declara un atributo protegido que representa el documento XML. En el constructor de la misma se realiza la carga del documento desde un el flujo de datos obtenido a partir del recurso embebido. El método save realiza la escritura sobre el flujo pasado como parámetro. Una vez definida la clase base, creamos la interfaz con todas las operaciones posibles que se pueden realizar sobre el almacén de catálogos. public interface ICatalogXLinqDao { List<CatalogDto> getCatalogs(); CatalogDto getCatalog(String catalogName); void addNewCatalog(String catalogName, String catalogType); void delCatalog(String catalogName); void updateCatalog(String catalogName, String newCatalogName); void addNewProduct(String productName, String catalogName, String color ); void delProduct(String catalogName, String productName); void updateProduct(String catalogName, String productName, String newProductName, String newProductColor); } public abstract class AbstractXLinqDao { protected XDocument xml; public AbstractXLinqDao(Stream catalogXmlStream) { this.xml = XDocument.Load(new XmlTextReader(catalogXmlStream)); } public void save(Stream catalogXmlStream) { this.xml.Save(new XmlTextWriter(catalogXmlStream, Encoding.UTF8)); } } 68 Proyecto final de carrera: MakeUP Stylist Finalmente creamos la clase que heredará la clase base e implementará la interfaz anterior. El método getCatalogs() devolverá la lista de objetos de tipo CatalogDto con la información de todos los catálogos recuperados desde el fichero XML. Obsérvese la facilidad de uso del API XLINQ y la similitud de las construcciones con las consultas SQL. public class CatalogXLinqDao : AbstractXLinqDao, ICatalogXLinqDao { private Stream catalogXmlStream; public CatalogXLinqDao(Stream catalogXmlStream) : base(catalogXmlStream) { this.catalogXmlStream = catalogXmlStream; } public List<CatalogDto> getCatalogs() { List<CatalogDto> lCatalogs = new List<CatalogDto>(); List<ProductDto> lProducts = new List<ProductDto>(); IEnumerable<XElement> catalogs = ( from c in xml.Root.Elements("catalog") select c ); foreach (XElement catalog in catalogs ) { CatalogDto cat = new CatalogDto(); cat.Name = catalog.Attribute("name").Value; cat.Type = catalog.Attribute("type").Value; if (catalog.Element("products") != null) { IEnumerable<XElement> products = ( from prod in catalog.Element("products").Elements("product") select prod ); foreach( XElement prod in products ) { ProductDto p = new ProductDto(); p.Name = prod.Element("name").Value; p.Color = prod.Element("color").Value; p.CatalogName = cat.Name; lProducts.Add( p ); } cat.Products = lProducts; } lCatalogs.Add( cat ); } return lCatalogs; } 69 Proyecto final de carrera: MakeUP Stylist Esta instrucción devuelve la lista de elementos de tipo XElement de todos los nodos catalog encontrados en la raíz del documento. A partir de cada catálogo encontrado, se obtienen todos los productos pertenecientes al catálogo. En la escritura de información se invoca al método save definido en la clase base. public void addNewCatalog(String catalogName, String catalogType) { xml.Root.Add(new XElement("catalog", new XAttribute("name", catalogName), new XAttribute("type", catalogType), new XElement("products"))); save(catalogXmlStream); } IEnumerable<XElement> catalogs = ( from c in xml.Root.Elements("catalog") select c ); 70 Proyecto final de carrera: MakeUP Stylist 6.3-. CLASES Además del modelo de datos, MakeupStylist también usa una serie de clases para llevar a cabo operaciones de distinta índole. En la tabla adjunta se listan dichas clases junto con su descripción. Nombre clase Descripción LayerManager Dispone de métodos que permiten realizar el pintado de capas, dependiendo del tipo. FaceProportions Permite obtener el rectángulo delimitador de las mejillas y ojos basándose en las proporciones faciales. GlobalContext Contiene referencia a la imagen actualmente cargada en la superficie de dibujo de MakeupStylist. Helper Clase utilitaria con los métodos para persistir en disco el resultado de las operaciones de pintado sobre la imagen. ImageUtils Operaciones de conversión entre el Bitmap y el tipo de dato compatible con el framework WPF. IOUtils Dispone de métodos para obtener la ruta relativa de los archivos Haar. Layer Representa la capa en MakepStylist, que 71 Proyecto final de carrera: MakeUP Stylist entre otros atributos contiene el mapa de bits, rectángulo delimitador de la capa, nombre, etc. LayerObservableCollection Tipo de colección que hereda la clase ObservableCollection<Layer>. MakeUpUtils Usa los métodos de GDI+ para realizar pintados de varios colores. MakeUpColors Contiene las constantes que identifican los colores de los productos de maquillaje. NativeMethods Contiene declaraciones de los métodos nativos del API de Windows. Pixel Clase para representar el pixel. WebCam Inicializa la webcam y ofrece métodos para obtener la captura de imagen desde la misma. NonFaceDetectableException Excepción que se lanza cuando no se detecta el rostro en la imagen proporcionada. 72 Proyecto final de carrera: MakeUP Stylist 7-. CONCLUSIONES Con este proyecto, hemos tenido la posibilidad de familiarizarnos con el apasionante mundo de la Inteligencia Artificial y la Visión por Computador. Hemos utilizado varias librerías que apuestan por este mundo como son OpenCv, OpenCVDotnet y por último Emgu que ha sido la elegida para el desarrollo por ser las que más posibilidades nos ofrecía. Los algoritmos Haar, han sido todo un descubrimiento, hemos comprobado su potencia y eficiencia. Nos hemos informado sobre la forma de crearlos mediante el entrenamiento con imágenes. Hemos descubierto fórmulas que definen la cromática de la piel humana y las hemos sintetizado en algoritmos que analizan los píxeles de una imagen y determinan aquellos susceptibles de contener piel. Con este proyecto nos hemos sumergido en el mundo de WPF. Hemos podido trabajar conjuntamente bajo un patrón de diseño Model View Viewmodel (MV-VM) que nos ha facilitado la comprensión del código. A su vez también nos ha ahorrado mucho trabajo, ya que gracias a las nuevas clases que proporciona WPF junto con MV-VM no hemos tenido que realizar el refresco de las interfaces, porque se ejecuta automáticamente. Con WPF, hemos podido observar la evolución en las interfaces. Se han adaptado a los nuevos tiempos, pudiendo tener el trabajo de un diseñador y un programador totalmente independientes, lo que ahorra molestias y simplifica el trabajo. 79 Proyecto final de carrera: MakeUP Stylist 8.4 GESTIÓN DE CATÁLOGOS. En la tercera pestaña (5) podemos configurar los colores que se mostraran en los filtros. (2). En esta parte se nos muestra una lista de catálogos de colores y para qué tipo de filtro está relacionado. Todo esto se carga/guarda en un archivo XML. 1-Catálogos 2-Productos 3-Gestión de productos Imagen 8.8: Interfaz de usuario. Gestión de productos Si quisiéramos modificar los colores del catálogo. Seleccionamos el catálogo(1) a cargar. Se nos cargara en la lista inferior, los colores que componen el catálogo. 80 Proyecto final de carrera: MakeUP Stylist Para borrar un color (producto). Lo seleccionamos y pulsamos el botón “Borrar Producto”. Imagen 8.9: Panel de gestión de productos. Borrar un producto Para crear un producto, desplegamos el menú Gestión Productos (3) Seleccionamos el color del producto, insertamos el nombre y pulsamos el botón “Agregar producto” para agregar el producto al catálogo. 81 Proyecto final de carrera: MakeUP Stylist . Imagen 8.10: Panel de gestión de productos. Dar de alta un producto. 82 Proyecto final de carrera: MakeUP Stylist 9-. FUTUROS DESARROLLOS Este proyecto deja muchas posibilidades para hacer futuros desarrollos, a continuación exponemos algunas ideas: Enriquecer la funcionalidad del programa introduciendo otros complementos como pelucas, piercings, gorras, sombreros, diademas... Enriquecer el programa ofreciendo también la detección de caras de perfil. Enfocar el programa en vez de al maquillaje realista a la creación de personajes ficticios, añadiendo para ello otras herramientas de maquillado y accesorios, por Ej: tipos de orejas, pelucas, maquillaje con varios colores incluso con texturas... Conseguir bases de datos con imágenes que contengan caras u ojos o boca y hacer algoritmos de detección propios que mejoren las prestaciones de los proporcionados por OpenCV. Como hemos comentado anteriormente, la última versión de Emgu, permite optimizar el tiempo de detección empleando unas gráficas que acepten CUDA. Un posible futuro desarrollo es la implementación de la detección de la boca y ojos para que acepte este algoritmo, la parte de la detección de una cara está implementada, pero habría que probar con dicha gráfica que el algoritmo es correcto. Esto abriría la posibilidad de hacer un estudio de eficiencia comparando los tiempos obtenidos empleando o no el CUDA. Mejorar y enriquecer el algoritmo detector de la piel para que sea lo más correcto posible, evitando confundir zonas de pelo con las de la piel. Otra de las bazas a explotar en este proyecto, seria la programación multiprocesador. Se podría gestionar la carga de los diferentes procesos repartiendo el trabajo en diferentes núcleos. Si se desarrollara la idea de programación multiprocesador, se podrían superar las limitaciones para mostrar el resultado en forma de vídeo. Así, además de desarrollar el proceso de maquillaje en foto, se podría realizar en video. Actualmente, el coste con un procesador para realizar todo esto, es tan elevado que hemos preferido no continuar el desarrollo en esa dirección. 83 Proyecto final de carrera: MakeUP Stylist 10-. BIBLIOGRAFÍA: [Ahlberg 1999] AHLBERG, J. 1999. A system for face localization and facial feature extraction. Tech. Rep. LiTH-ISY-R-2172, Linkoping University. [Brand and Mason 2000] BRAND, J., AND MASON, J. 2000. A comparative assessment of three approaches to pixellevel human skin-detection. In Proc. of de International Conference on Pattern Recognition. [Vezhnevets et al. 2003] VEZHNEVETS, V., SAZONOV, V., AND ANDREEVA, A. 2003. A Survey on Pixel-Based Skin Color Detection Techniques. In Proc. Graphicon-2003, pp. 85 – 92. Moscow, Rusia. [Chen et al. 1995]CHEN, Q., WU, H., AND YAHCIDA, M. 1995. Face detection by fuzzy pattern machining. In Proc. of the Fifth International Conference on Computer Vision. [Esqueda 2002] ESQUEDA, J.J. 2002. Fundamentos de procesamientos de imágenes. CONACTEC 2002. Instituto Tecnológico de Ciudad Madero. México. [Gomez 2000] GOMEZ, G. 2000. On selecting colour components for skin detection. In Proc. of the ICPR, vol. 2. [Gomez and Morales 2002] GOMEZ, G., AND MORALES, E. 2002. Automatic feature construction and a simple rule induction algorithm for skin detection. In Proc. of the ICML Workshop on Machine Learning in Computer Vision. [Hsu et al. 2002] HSU, R. L., ABDEL-MOTTALEB, M., AND JAIN, A. K. 2002. Face detection in color images. IEEE Trans. Pattern Analysis and Machine Intelligence 24, 5. [Jordao et al. 1999] JORDAO, L., PERRONE, M., COSTEIRA, J., AND SANTOS-VICTOR, J. 1999. Active face and feature tracking. In Proceeding of the 10th International Conference on Image Analysis and Processing. 84 Proyecto final de carrera: MakeUP Stylist [Menser and Wien 2000] MENSER, B., AND WIEN, M. 2000. Segmentation and tracking of facial regions in color image sequences. In Proc. SPIE Visual Communications and Image Processing 2000. [Oliver et al. 1997] OLIVER, N., PENTLAND, A., AND BERARD, F. 1997. Lafter: Lips and face real time tracker. In Proc. Computer Vision and Pattern Recognition. [Peer et al. 2003] PEER, P., KOVAC, J., AND SOLINA, F. 2003. Human skin colour clustering for face detection. In submitted to EUROCON 2003 – International Conference on Computer as a Tool.. [Saber and Tekalp 1998] SABER, E., AND TEKALP, A. 1998. Frontal-view face detection and facial feature extraction using color, shape and symmetry based cost functions. In Pattern Recognition Letters, vol.9, 669 – 680. [Schied 1997] SCHIED, A., 1997. Geometric aspects of Fleming-Viot and Dawson-Watanebe processes. Humboldt-Universität zu Berlin. In The annals of Probability, 1997, vol. 25, Nº 3. [Schumeyer and Barner 1998] SCHUMEYER, R., AND BARNER, K. 1998. A color-based classifier for region identification in video. In Visual Communications and Image Processing 1998, SPIE. [Terrillon et al. 2000] TERRILLON, J. C., SHIRAZI, M. N., FUKAMACHI, H., AND AKAMATSU, S. 2000. Comparative performance of different skin chrominance models and chrominance spaces for the automatic detection of human faces in color images. In Proc. of the International Conference on Face and Gesture Recognition. [Vélez et al. 2003] VÉLEZ, J., SANCHEZ, A., MORENO, A. B., ESTEBAN, J. L. 2003. Visión por computador. Dykinson S. L. [web1] http://www.cs.bu.edu/techreports/pdf/2005-012-blink-detection.pdf [web2] http://miron.disca.upv.es/VxC/Trabajos/Detecci%F3n%20de%20caras%20y%20an%E1lisis%2 0de%20expresiones%20faciales.pdf 85 Proyecto final de carrera: MakeUP Stylist [web3] http://www.google.es/url?sa=t&source=web&cd=1&sqi=2&ved=0CCAQFjAA&url=http%3A%2 F%2Frepository.tudelft.nl%2Fassets%2Fuuid%3A13074be2-bf06-4c9b-82609f84c2db8fd4%2Fewi_Choy!Chung!Harahap!Natadarma!Wu.pdf&rct=j&q=ewi_Choy!Chung! Harahap!Natadarma!Wu.pdf&ei=CrZTomzCsfoOdrZsIkK&usg=AFQjCNEwZ_6LpVOtNRfISbOMXt9SuKTWcQ&sig2=1Zdpgfy8ab 7Hs36kyUPsaw&cad=rja [web4] http://cpdsi-fich.wdfiles.com/local--files/tpsaplicacion/2005_Ramello-Piel.pdf [web5] http://www.codeproject.com/KB/cs/Intel_OpenCV.aspx?fid=1525118&df=90&mpp=25&noise =3&sort=Position&view=Quick&select=2699733 [web6] http://www.codeproject.com/KB/cpp/TrackEye.aspx?fid=1403145&df=90&mpp=25&noise=3& sort=Position&view=Quick&select=2726159 [web7] http://www.codeproject.com/KB/cpp/TrackEye.aspx [web8] http://iie.fing.edu.uy/investigacion/grupos/gti/timag/trabajos/2005/caras/Features/deteye.ht m [web9] http://www.ubuntu-es.org/index.php?q=node/51097 [web10] http://www.bernardotti.it/portal/showthread.php?p=78640 [web11] http://www710.univ-lyon1.fr/~bouakaz/OpenCV0.9.5/docs/ref/OpenCVRef_Experimental.htm#decl_cvHaarDetectObjects [web12] http://www.cs.indiana.edu/cgi-pub/oleykin/website/OpenCVHelp/ [web13] http://es.scribd.com/doc/4547752/opencv-objectdetection-2007june10 [web14] http://www.openframeworks.cc/addons/opencv [web15] http://forum.openframeworks.cc/index.php?topic=432.0 [web16] http://seniordesign.deegrayve.net/Paper.pdf 86 Proyecto final de carrera: MakeUP Stylist [web17] http://madcomputerscientist.net/projects/face_extractor/report.pdf [web18] http://www.google.com/notebook/public/13533147217413439515/BDRwQSwoQ0664msYh [web19] http://www.ralfwilke.com/download/puc/tarea1_ralf_wilke_imagenes.pdf [web20] http://alereimondo.no-ip.org/OpenCV/uploads/41/tema2.pdf [web21] http://note.sonots.com/SciSoftware/haartraining.html#f3d39ccf [web22] http://www.gts.tsc.uvigo.es/PRESA/papers/URSI07.pdf [web23] http://groups.google.com/group/opencvdotnetdiscuss/unlock?_done=/group/opencvdotnetdiscuss/browse_thread/thread/f75243b9c1527d6d/5b65e1bca15fa825%3Flnk%3Dgst%26q%3 Dface [web24] http://www.youtube.com/watch?v=5sPh6-J9wOA [web25] http://www.emgu.com/wiki/index.php/Main_Page [web26] http://code.google.com/p/opencvdotnet/