{ "cells": [ { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "72a7c411-cf21-4d47-a9b9-f5db1f8c9c60" } }, "source": [ "# CURSO DE INTRODUCCIÓN A OPENCV Y PYTHON" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "7cd741bd-a4ab-4824-9dcd-3708cfbad4e3" } }, "source": [ "# 0. ¿Quién soy?\n", "\n", "**Rubén Crespo Cano**\n", "\n", "Estudios:\n", "* Ingeniería en Informática [Universidad de Alicante] (2006 - 2012)\n", "* Máster en Ingeniería de Telecomunicación [Universidad de Alicante] (2012 - 2015)\n", "* Doctorado en Informática [Universidad de Alicante] (2016 - Presente)\n", "\n", "\n", "\n", "Trabajo:\n", "* Ingeniero de software en Everilion [http://www.everilion.com] (2012 - Presente)\n", "\n", "\n", "\n", "¿Dónde podéis encontrarme?\n", "* Email: rcrespocano@gmail.com\n", "* Researchgate: https://www.researchgate.net/profile/Ruben_Crespo-Cano\n", "* Twitter: @rcrespocano\n" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "a32ecb3d-7126-45aa-b39c-f245fd0245a4" } }, "source": [ "# 1. Introducción\n", "\n", "\n", "## OpenCV\n", "OpenCV es una biblioteca de visión artificial de código libre, escrita en C++, originalmente desarrollada por Gary Bradsky en Intel. Fue construida para proporcionar una infrastructura común para aplicaciones de visión por computador. \n", "\n", "La librería contiene más de 2500 algoritmos optimizados, entre los que se incluyen algoritmos clásicos y algoritmos del estado del arte de los campos de visión por computador y aprendizaje automático.\n", "\n", "Características principales:\n", "\n", "* Licencia BSD.\n", "* Interfaces: C++, C, Python, Java y MATLAB.\n", "* Sistemas Operativos: Windows, GNU/Linux, Android, iOS y Mac OS.\n", "* Soporte CUDA, OpenCL y FPGA.\n", "\n", "URL: http://www.opencv.org\n", "\n", "\n", "\n", "\n", "## Python\n", "Python es un lenguaje de programación creado por Guido van Rossum a principios de los años 90 cuyo nombre está inspirado en el grupo de cómicos ingleses *Monty Python*. Es un lenguaje similar a Perl, pero con una sintaxis muy limpia que favorece un código legible. Python es un lenguaje de propósito general que ha llegado a ser muy popular en muy poco tiempo debido a su simplicidad y legibilidad, ya que permite a el/la programador/a expresar ideas en muy pocas líneas de código sin reducir la legibilidad.\n", "\n", "Se trata de un lenguaje interpretado o de script, con tipado dinámico, fuertemente tipado, multiplataforma y orientado a objetos.\n", "\n", "Si se compara con lenguajes como C/C++, Python es generalmente más lento debido en gran parte a que es un lenguaje de tipado dinámico. Por ejemplo, si se hace uso de librerías como [Numba](http://numba.pydata.org/) o [Cython](http://cython.org/), donde las variables se pueden tipar, la velocidad de ejecución aumenta considerablemente sin apenas esfuerzo. Además, Python puede ser fácimente extendido mediante código C/C++, lo que permite escribir código fuente computacionalmente intensivo en C/C++ y crear envolturas/wrappers en Python que puedan ser usadas por módulos de Python. Esto proporciona dos ventajas: primero, el código es tan rápido como el código original C/C++ y segundo, es más fácil desarrollar en Python que en C/C++.\n", "\n", "URL: https://www.python.org/\n", "\n", "\n", "\n", "\n", "## OpenCV-Python\n", "\n", "OpenCV-Python es el API de OpenCV para Python, donde se combinan las mejores cualidades del API C++ de OpenCV con el lenguaje de programación Python, diseñada para resolver problemas de visión por computador. \n", "\n", "OpenCV-Python hace uso de Numpy, que es una librería altamente optimizada para operaciones de cálculo numérico con una sintaxis similar a la de MATLAB. Todas las estructuras array son convertidas a Numpy arrays. Esto permite la integración con otras librerías que también hacen uso de Numpy, tales como SciPy o Matplotlib.\n", "\n", "\n", "## Plataformas\n", "\n", "OpenCV se ha diseñado para ser multiplataforma. En la actualidad es soportado en CUDA/OpenCL e incluso en plataformas móviles iOS/Android.\n", "\n", "URL: http://opencv.org/platforms.html" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "c46893e7-63cc-4cd0-a5fe-85a94f724611" } }, "source": [ "# 2. Instalación\n", "\n", "## Instalación en Windows\n", "* http://docs.opencv.org/3.1.0/d5/de5/tutorial_py_setup_in_windows.html\n", "* Enlace con las instrucciones de instalación del grupo CAChemE:\n", " * https://github.com/CAChemE/curso-opencv-python/blob/master/instalacion-opencv.md\n", "\n", "\n", "\n", "## Instalación en GNU/Linux\n", "* http://docs.opencv.org/3.1.0/dd/dd5/tutorial_py_setup_in_fedora.html\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "ce9a3de4-cd6b-4df8-bfb2-9e62e1243c7e" } }, "source": [ "# 3. Manejo de ficheros, cámaras e interfaces gráficas de usuario\n", "\n", "La gran mayoría de aplicaciones que se desarrollan con OpenCV necesitan como entrada una o varias imágenes, pero también puede ser que esas imágenes se presenten en forma de vídeo. Además, también es bastante probable que las aplicaciones necesiten generar como salida del programa una nueva imágen. \n", "\n", "\n", "## Lectura y escritura de imágenes\n", "Hay que utilizar la función **cv2.imread()** para leer una imágen. La imágen debe estar en el directorio de trabajo o debe proporcionarse la ruta absoluta de la imagen (o relativa).\n", "\n", "El segundo argumento de la función es un *flag* que especifica la forma en la que la imagen debe ser leída.\n", "\n", "* cv2.IMREAD_COLOR: Carga la imagen a color. Si la imagen posee transparencias serán desechadas. Es el *flag* por defecto.\n", "* cv2.IMREAD_GRAYSCALE: Carga la imagen en modo escala de grises.\n", "* cv2.IMREAD_UNCHANGED: Carga la imagen incluyendo el canal *alpha*.\n", "\n", "Para poder escribir/guardar una nueva imagen, hay que utilizar la función **cv2.imwrite()**.\n", "\n", "\n", "**Ejemplo 1**. Cargar una imagen y guardarla con otro nombre." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "nbpresent": { "id": "8751a1a2-8b04-4589-a932-4225e954220f" } }, "outputs": [], "source": [ "import cv2\n", "\n", "# Load\n", "image_path = 'examples/images/rabbit.jpg'\n", "image = cv2.imread(image_path)\n", "\n", "# Save copy as png\n", "image_copy_path = 'examples/images/rabbit-copy.png'\n", "cv2.imwrite(image_copy_path, image)\n", "\n", "# Load copy\n", "image_copy = cv2.imread(image_copy_path)\n", "\n", "# Show\n", "cv2.imshow('Original', image)\n", "cv2.imshow('Copy', image_copy)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "5a74133a-56c5-4d91-b13b-33ab05d996ce" } }, "source": [ "\n", "**Ejercicio 1**. Descargar una imagen de internet, cargarla y guardar una copia en blanco y negro." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "nbpresent": { "id": "53036ea1-9525-4153-ba9b-11f2494d8696" } }, "outputs": [], "source": [ "# Ejercicio 1" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "6fcb7aee-2b44-4099-bb62-88ed8d1a557c" } }, "source": [ "## Conversión entre imágenes y *raw bytes*\n", "Conceptualmente, un byte es un entero que se encuentra en el rango [0, 255]. En las aplicaciones actuales, un píxel es representado normalmente por un byte por canal, aunque también pueden haber otras representaciones.\n", "\n", "Una imagen OpenCV es un array 2D o 3D de tipo **numpy.array**. Una imagen en escala de grises de 8 bits es un array 2D que contiene valores para cada byte. Una imagen a color RGB es un array 3D, que también contiene valores para cada byte. Es posible acceder a esos valores utilizando una expresión como la siguiente:\n", "* image[0, 0] o image[0, 0, 0]\n", "\n", "El primer índice representa la coordenada *y* (fila), siendo el 0 el valor que está más arriba. El segundo índice representa la coordenada *x* (columna), siendo el valor 0 el que está más a la izquierda. El tercer índice (en imágenes RGB) representa el canal de color.\n", "\n", "Por ejemplo, una imagen en escala de grises con un píxel blanco en la esquina superior izquierda, image[0, 0] sería 255. Para una imagen RGB con un píxel de color azul en la esquina superior izquierda, image[0, 0] sería [255, 0, 0]. También se puede utilizar el método **setitem** para asignar valores a cada píxel, ya que esta función es más eficiente. Pero, como veremos a continuación, normalmente realizaremos operaciones sobre conjuntos de píxeles más grandes y no trabajaremos píxel a píxel de forma manual.\n", "\n", "Sabiendo que una imagen tiene 8 bits por canal, podemos realizar la conversión a un bytearray de python, que es unidimensional. De igual forma, dado un bytearray que contenga bytes en el orden adecuado, podemos realizar la conversión a un objeto de tipo numpy.array, que en efecto es una imagen.\n", "* gray_image = numpy.array(gray_byte_aray).reshape(height, width)\n", "* bgr_image = numpy.array(bgr_byte_array).reshape(height, width, 3)\n", "\n", "**Ejemplo2**. Conversión de un bytearray que contiene bytes aleatorios en una imagen en escala de grises y en una imagen RGB." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy\n", "import os\n", "\n", "# Make an array of 120,000 random bytes.\n", "random_byte_array = bytearray(os.urandom(120000))\n", "flat_numpy_array = numpy.array(random_byte_array)\n", "\n", "# Convert the array to make a 400x300 grayscale image.\n", "gray_image = flat_numpy_array.reshape(300, 400)\n", "cv2.imshow('Random gray', gray_image)\n", "\n", "# Convert the array to make a 400x100 color image.\n", "bgr_image = flat_numpy_array.reshape(100, 400, 3)\n", "cv2.imshow('Random color', bgr_image)\n", "\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Acceso a las propiedades de la imagen\n", "\n", "Las propiedades de una imagen incluyen numero de filas, de columnas y canales, tipos de datos, número de píxeles, etc.\n", "* *Forma*: img.shape\n", "* *Número de píxeles*: img.size\n", "* *Tipo de datos*: img.dtype" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introducción al manejo de vídeos\n", "\n", "Para capturar un vídeo, es necesario crear un objeto de tipo **VideoCapture**. Su argumento puede ser tanto el índice del dispositivo como el nombre del fichero.\n", "\n", "El índice del dispositivo es el número que identifica qué camara capturar. Como normalmente sólo suele haber una cámara conectada, se suele utilizar el identificador \"0\" para capturar de ella.\n", "\n", "El método **cap.read()** devuelve un valor booleano (True/False). Si el *frame* se leyó correctamente, devolverá True. De esta forma, se puede comprobar cuándo se ha llegado al final de la lectura del vídeo comprobando este parámetro. \n", "\n", "A veces, el objeto **VideoCapture** puede no haber logrado la inicialización de la captura correctamente. Por ello, es mejor comprobar si se ha inicializado o no a través del método **cap.isOpened()**. Si el resultado es True es que sí se ha podido abrir la captura.\n", "\n", "**Ejemplo 3**. Cargar y reproducir un vídeo en escala de grises." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", "\n", "video_file = 'examples/videos/roller-coaster.mp4'\n", "cap = cv2.VideoCapture(video_file)\n", "\n", "while (cap.isOpened()):\n", " # Capture frame-by-frame\n", " ret, frame = cap.read()\n", "\n", " if ret == True:\n", " # Operations on the frame\n", " gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n", "\n", " # Display the resulting frame\n", " cv2.imshow('frame', gray)\n", " \n", " # Exit?\n", " if cv2.waitKey(1) & 0xFF == ord('q'):\n", " break\n", " else:\n", " break\n", "\n", "cap.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Además, también es posible acceder a algunas de las propiedades del vídeo utilizando el método **cap.get(prop_id)** donde **prop_id** es un número [0, 18] que denota una propiedad del vídeo. Por último, hay que destacar que algunos de esos valores pueden ser modificados mediante el método **cap.set(prop_id, value)**. En el siguiente enlace están descritas todas las propiedades:\n", "* http://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html#videocapture-get\n", "* http://docs.opencv.org/3.1.0/d8/dfe/classcv_1_1VideoCapture.html#aeb1644641842e6b104f244f049648f94\n", "\n", "**Ejemplo 4**. Mostrar el número de FPS del vídeo." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", "\n", "video_file = 'examples/videos/roller-coaster.mp4'\n", "cap = cv2.VideoCapture(video_file)\n", "\n", "# Properties\n", "font = cv2.FONT_HERSHEY_SIMPLEX\n", "font_scale = 1\n", "color = (255,255,255)\n", "thickness = 1\n", "\n", "if cv2.__version__.startswith('2.4'):\n", " height_prop = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT \n", "else:\n", " height_prop = cv2.CAP_PROP_FRAME_HEIGHT\n", "\n", "if cv2.__version__.startswith('2.4'):\n", " fps_prop = cv2.cv.CV_CAP_PROP_FPS\n", "else:\n", " fps_prop = cv2.CAP_PROP_FPS\n", "\n", "while(cap.isOpened()):\n", " ret, frame = cap.read()\n", " if ret == True:\n", " # Text position\n", " height = int(cap.get(height_prop))\n", " position = (50, height - 50)\n", " \n", " # Frames per second\n", " fps = \"{0:.2f}\".format(cap.get(fps_prop))\n", " text = \"FPS: \" + fps\n", " \n", " # Put text\n", " cv2.putText(frame, text, position, font, font_scale, color, thickness)\n", "\n", " # Display\n", " cv2.imshow(\"Video\", frame)\n", " \n", " # Exit?\n", " if cv2.waitKey(1) & 0xFF == ord('q'):\n", " break\n", " else:\n", " break\n", "\n", "# Release everything if job is finished\n", "cap.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "**Ejercicio 2**. Descargar un vídeo de https://videos.pexels.com/video-license y mostrar las propiedades más relevantes sobre el propio vídeo mientras se reproduce." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Captura de vídeo desde la *webcam* \n", "\n", "El índice del dispositivo es el número que identifica qué camara capturar. Como normalmente sólo suele haber una cámara conectada, se suele utilizar el identificador \"0\" para capturar de ella.\n", "\n", "**Ejemplo 5**. Captura de vídeo desde la *webcam* y realización de operaciones de inversión de la imagen." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", "\n", "webcam_id = 0\n", "cap = cv2.VideoCapture(webcam_id)\n", "\n", "while(cap.isOpened()):\n", " # Capture frame-by-frame\n", " ret, frame = cap.read()\n", " if ret == True:\n", " # Operations on the frame\n", " v_frame = cv2.flip(frame, 1)\n", " h_frame = cv2.flip(frame, 0)\n", " \n", " # Display\n", " cv2.imshow(\"Original\", frame)\n", " cv2.imshow(\"Vertical flip\", v_frame)\n", " cv2.imshow(\"Horizontal flip\", h_frame)\n", "\n", " # Exit?\n", " if cv2.waitKey(1) & 0xFF == ord('q'):\n", " break\n", " else:\n", " break\n", "\n", "# Release everything if job is finished\n", "cap.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "4ab4ce54-49ec-41a4-ab3f-33ee8d8cfbcb" } }, "source": [ "# 4. Filtrado y suavizado de imágenes\n", "\n", "## 4.1. Filtrado de imágenes. Convolución 2D\n", "\n", "Las imágenes pueden ser filtradas por varios tipos de filtros, tales como filtros *paso-bajo*, filtros *paso-alto*, etc.\n", "\n", "* Un filtro *paso-bajo* atenúa las frecuencias altas y mantiene sin variaciones las frecuencias bajas. El resultado en el dominio espacial es equivalente al de un filtro de suavizado, donde las altas frecuencias que son filtradas se corresponden con los cambios fuertes de intensidad. Consigue reducir el ruido suavizando las transiciones existentes.\n", "\n", "* Un filtro *paso-alto* atenúa las frecuencias bajas manteniendo invariables las frecuencias altas. Puesto que las altas frecuencias corresponden en las imágenes a cambios bruscos de densidad, este tipo de filtros es usado en la detección de bordes en el dominio espacial, ya que estos contienen gran cantidad de dichas frecuencias. Refuerza los contrastes que se encuentran en la imagen.\n", "\n", "El proceso de filtrado puede llevarse a cabo sobre los dominios de frecuencia y/o espacio. De aquí en adelante nos centraremos en filtros en el dominio del espacio.\n", "\n", "Las operaciones espaciales de filtrado se definen en un entorno de vecindad del punto a transformar (x,y). Para realizar un filtrado en el dominio del espacio se realiza una convolución (barrido) del kernel sobre la imagen. Para ello se sigue el Teorema de Convolución en el espacio: $g(x,y) = h(x,y) * f(x,y)$\n", "\n", "\n", "\n", "La máscara o *kernel* es una matriz de coeficientes donde:\n", "* El entorno del punto que se considera en la imagen para obtener el resultado final $g(x,y)$ está determinado por el tamaño y forma de la máscara.\n", "* El tipo de filtrado está determinado por el contenido de la máscara.\n", "\n", "\n", "\n", "OpenCV proporciona la función **cv2.filter2D()** para realizar la convolución de una imagen con un *kernel* determinado. Por ejemplo, un *kernel* de tamaño 3x3 para un filtro de promedio se puede definir de la siguiente forma:\n", "\n", "

\n", "$K = \\frac{1}{9} \\left[ \\begin{array}{ccc}\n", "1 & 1 & 1 \\\\\n", "1 & 1 & 1 \\\\\n", "1 & 1 & 1 \\\\ \\end{array} \\right]$\n", "

\n", "\n", "El procedimiento de filtrado es el siguiente: para cada píxel de la imagen, una ventana de tamaño 3x3 es centrada en él, realizándose el sumatorio de la multiplicación del *kernel* por los píxeles contiguos, dividiéndose entre 9. Esto equivale a realizar la media de los valores de los píxeles de dentro de la ventana. Esta operación se realiza para cada uno de los píxeles de la imagen.\n", "\n", "En cuanto a la función **cv2.filter2D()**, los parámetros son los siguientes:\n", "* src – input image.\n", "* ddepth – desired depth of the destination image; if it is negative, it will be the same as src.depth().\n", "* kernel - *kernel* convolutivo.\n", "\n", "**Ejemplo 6**. Filtrado de imagen con un kernel 3x3." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "\n", "image_file = 'examples/images/rabbit.jpg'\n", "img = cv2.imread(image_file)\n", "\n", "kernel = np.ones((3, 3), np.float32) / 9\n", "dst = cv2.filter2D(src=img, ddepth=-1, kernel=kernel)\n", "\n", "# Show\n", "cv2.imshow('Original', img)\n", "cv2.imshow('Filtered', dst)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 3**. Descargar una imagen y aplicar un filtrado 2D con un *kernel* de tamaño 5x5 ponderado. Probar a cambiar los distintos valores del *kernel*." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4.2. Suavizado de imágenes\n", "\n", "El suavizado de imágenes se logra realizando la convolución de la imagen con un filtro *paso-bajo*, y suele utilizarse para la reducción y/o eliminación del ruido, ya que se elimina el contenido de altas frecuencias. OpenCV proporciona varias técnicas para el suavizado de imágenes. A continuación veremos varias de ellas.\n", "\n", "### 4.2.1. Promedio\n", "El filtro de la media es el más simple, intuitivo y fácil de implementar \n", "para suavizar imágenes. Esta operación se realiza mediante la convolución de la imagen con un filtro normalizado. Simplemente calcula la media de los píxeles que están bajo el área del *kernel* y reemplaza el valor del elemento central. \n", "\n", "En OpenCV, esta operación se realiza mediante la función **cv2.blur()**. En la llamada, debe especificarse el tamaño del *kernel*, tanto el ancho como el alto.\n", "\n", "**Ejemplo 7**. Filtrado de imagen: filtro promedio." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "\n", "# Load image\n", "image_path = 'examples/images/sunset.jpg'\n", "image = cv2.imread(image_path)\n", "\n", "# Blur\n", "k = 5\n", "blur = cv2.blur(image, (k, k))\n", "\n", "# Show\n", "cv2.imshow('Original', image)\n", "cv2.imshow('Filtered', blur)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 4**. Descargar una imagen con muchos colores. Cambiar el tamaño del *kernel* y comprobar qué ocurre." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En cuanto a las desventajas del filtro promedio o de la media, encontramos que es bastante sensible a cambios locales. Además, puede crear nuevas intensidades de color que no aparecían en la imagen original.\n", "\n", "### 4.2.2. Filtro Gaussiano\n", "El filtro gaussiano se usa para emborronar imágenes y eliminar ruido. Es similar al filtro de media pero se usa una máscara diferente, modelizando la función gaussiana.\n", "\n", "

\n", "$G(x) = \\frac{1}{\\sqrt{2\\pi\\sigma^{2}}}e^{-\\frac{x^{2}}{2\\sigma^{2}}}$\n", "

\n", "\n", "\n", "\n", "Si queremos utilizar un kernel Gaussiano, deberemos utilizar la función **cv2.GaussianBlur()**. Al igual que en el caso anterior, debemos especificar el ancho y alto del kernel, que debe ser un número impar positivo. Además, también debe especificarse la desviación estándar en las direcciones *X* e *Y*, a través de los parámetros *sigmaX* y *sigmaY* respectivamente. Si sólo se especifica el valor para el parámetro *sigmaX*, la función asigna el mismo valor para el parámetro *sigmaY*.\n", "\n", "**Ejemplo 8**. Filtrado de imagen: filtro Gaussiano." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "\n", "# Load image\n", "image_path = 'examples/images/sunset.jpg'\n", "image = cv2.imread(image_path)\n", "\n", "# Gaussian blur\n", "k = 5\n", "sigma = 0\n", "blur = cv2.GaussianBlur(image, (k, k), sigma)\n", "\n", "# Show\n", "cv2.imshow('Original', image)\n", "cv2.imshow('Filtered', blur)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 5**. Cambiar el tamaño del *kernel* y el valor del parámetro *sigma* y comprobar qué ocurre." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Una de las razones por las que los filtros de tipo Gaussiano son tan importantes es que son muy efectivos para eliminar ruido Gaussiano de la imagen. Este filtro produce un suavizado más uniforme que el filtro promedio.\n", "\n", "**Ejemplo 9**. Filtrado de imagen: filtro Gaussiano sobre imagen con mucho ruido." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "\n", "# Load image\n", "image_path = 'images/taj-noise.jpg'\n", "image = cv2.imread(image_path)\n", "\n", "# Gaussian blur\n", "k = 5\n", "sigma = 1\n", "blur = cv2.GaussianBlur(image, (k, k), sigma)\n", "\n", "# Show\n", "cv2.imshow('Original with noise', image)\n", "cv2.imshow('Filtered', blur)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 6**. Buscar una imagen con ruido y aplicar filtro Gaussiano para reducir el nivel de ruido." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 6" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.2.3. Mediana\n", "El filtro mediana se aplica mediante la función **cv2.medianBlur()**. En él, se calcula la mediana de todos los píxeles que están bajo el área del *kernel*, y el elemento central se sustituye por el valor de la mediana. El valor para el tamaño del *kernel* debe ser un número impar positivo.\n", "\n", "Este filtro es muy efectivo para eliminar el ruido impulsional, llamado \"sal y pimienta\". \n", "* https://en.wikipedia.org/wiki/Salt-and-pepper_noise\n", "\n", "\n", "**Ejemplo 10**. Filtrado de imagen: filtro mediana sobre imagen con ruido de tipo \"sal y pimienta\"." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import cv2\n", "\n", "# Load image\n", "image_path = 'examples/images/salt_pepper_noise.png'\n", "image = cv2.imread(image_path)\n", "\n", "# Gaussian blur\n", "k = 5\n", "blur = cv2.medianBlur(image, k)\n", "\n", "# Show\n", "cv2.imshow('Original', image)\n", "cv2.imshow('Filtered', blur)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 7**. Buscar una imagen con ruido impulsional (\"sal y pimienta\") y aplicar el filtro mediana para reducir el nivel de ruido." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 7" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# 5. Gradientes\n", "\n", "En este apartado vamos a mostrar varios filtros *paso-alto* que nos permitirán filtrar las imágenes para extraer bordes y gradientes.\n", "\n", "## 5.1. Sobel\n", "El operador Sobel, también conocido como el operador Sobel–Feldman, realiza un gradiente espacial 2D sobre una imagen y de esta forma enfatiza las regiones con altras frecuencias espaciales, que se corresponden con bordes. \n", "\n", "\n", "\n", "\n", "\n", "El operador Sobel calcula el gradiente de la intensidad de una imagen en cada punto (píxel). Así, para cada punto, este operador da la magnitud del mayor cambio posible, la dirección de éste y el sentido desde oscuro a claro. El resultado muestra cómo de abruptamente o suavemente cambia una imagen en cada punto analizado y, en consecuencia, cuán probable es que éste represente un borde en la imagen y, también, la orientación a la que tiende ese borde. En la práctica, el cálculo de la magnitud -probabilidad de un borde- es más fiable y sencillo de interpretar que el cálculo de la dirección y sentido.\n", "\n", "El operador utiliza dos máscaras o *kernels* de tamaño 3x3 los cuales son convolucionados con la imagen original para calcular aproximaciones de las derivativas, una para cambios en el eje horizontal y otra para cambios en el eje vertical.\n", "\n", "*Kernels* de convolución Sobel:\n", "\n", "

\n", "$Gx = \\left[ \\begin{array}{ccc}\n", "-1 & 0 & +1 \\\\\n", "-2 & 0 & +2 \\\\\n", "-1 & 0 & +1 \\\\ \\end{array} \\right]$\n", "\n", "$Gy = \\left[ \\begin{array}{ccc}\n", "+1 & +2 & +1 \\\\\n", "0 & 0 & 0 \\\\\n", "-1 & -2 & -1 \\\\ \\end{array} \\right]$\n", "

\n", "\n", "Estos *kernels* están diseñados para responder ante ejes verticales y horizontales. Pueden ser aplicados de forma separada a la imagen de entrada para producir mediciones separadas, o por el contrario, pueden ser combinados para encontrar y delimitar ejes en ambas direcciones.\n", "\n", "La función disponible en OpenCV que realiza este filtrado es **cv2.Sobel()**. Se puede especificar la dirección de los gradientes, vertical u horizontal, mediante los argumentos *xorder/dx* e *yorder/dy* respectivamente. Además, también se puede especificar el tamaño del *kernel* mediante el argumento *ksize*.\n", "\n", "**Ejemplo 11**. Filtros de Sobel." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "\n", "# Load image\n", "image = cv2.imread('examples/images/sudoku.jpg', cv2.IMREAD_GRAYSCALE)\n", "\n", "# Sobel\n", "k = 3\n", "sobelx = cv2.Sobel(image, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=k)\n", "sobely = cv2.Sobel(image, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=k)\n", "\n", "# Show\n", "cv2.imshow('Original', image)\n", "cv2.imshow('Sobel X', sobelx)\n", "cv2.imshow('Sobel Y', sobely)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5.2. Laplacian\n", "\n", "El operador *Laplacian* es una medida isotrópica 2D de la segunda derivada espacial de una imagen. Aplicar un filtro *Laplacian* sobre una imagen consigue resaltar las regiones en las que se producen cambios bruscos de intensidad, y es por tanto utilizada para detección de bordes. Normalmente, el filtro *Laplacian* es aplicado sobre una imagen que ha sido previamente filtrada con un filtro Gaussiano para poder reducir la sensibilidad ante ruido.\n", "\n", "La función que proporciona OpenCV para aplicar un filtro Laplacian es **cv2.Laplacian()**. Los argumentos obligatorios son la imagen de entrada y la profundidad de la imagen de salida.\n", "\n", "**Ejercicio 8**. Buscar una imagen (con ruido) de edificios y aplicar el filtro Laplacian sobre la imagen sin filtrar y sobre la imagen filtrada (http://docs.opencv.org/3.1.0/d5/d0f/tutorial_py_gradients.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 8" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 6. Canny edge detector\n", "\n", "El algoritmo de Canny es un método desarrollado por John F. Canny en 1986 que utiliza un algoritmo de múltiples etapas para detectar una amplia gama de bordes en imágenes.\n", "\n", "Las etapas del algoritmo son las siguientes:\n", "\n", "1. **Reducción de ruido**. Debido a que la detección de bordes puede verse afectada por el ruido que contenga la imagen, el primer paso es la eliminación del ruido en la imagen mediante un filtro Gaussiano con un *kernel* de tamaño 5x5.\n", "\n", "2. **Búsqueda de gradientes de intensidad**. El borde de una imagen puede apuntar a diferentes direcciones, por lo que el algoritmo de Canny utiliza cuatro filtros para detectar los bordes en las direcciones horizontal, vertical y diagonales.\n", "\n", "3. **Supresión de no máximos**. Después de obtener las magnitudes de gradiente y dirección, se realiza un análisis de toda la imagen para eliminar los píxeles no deseados que no constituyan ningún eje. Para ello, cada píxel es examinado comprobando si es un máximo local en su vecindario en la dirección del gradiente. Si el píxel no es un máximo local, se establece a cero. En resumen, el resultado que se obtiene es una imagen binaria con \"ejes estrechos\".\n", "\n", "\n", "4. **Umbrales**. En esta última etapa del algoritmo, se decide sobre todos los ejes obtenidos cuáles de ellos son realmente ejes y cuáles de ellos no. Para ello, se establecen dos valores umbral, *minVal* y *maxVal*. Todos los ejes con una intensidad de gradiente mayor que el valor umbral *maxVal* se consideran de forma segura como ejes. Todos los ejes con una intensidad de gradiente menor que el valor umbral *minVal* se consideran de forma segura como no ejes, y por tanto, son descartados. Aquellos que se encuentran entre los valores *minVal* y *maxVal* son clasificados dependiendo de su conexión. Si están conetados a los píxeles con valor mayor a *maxVal* son considerados parte de los ejes. En cualquier otro caso son descartados.\n", "\n", "\n", "\n", "OpenCV proporciona la implementación del detector de bordes Canny mediante la función **cv2.Canny()**. El primer argumento de la función es la imagen. El segundo y el tercer argumento son los valores de los parámetros *minVal* y *maxVal* respectivamente. El cuarto argumento es el valor para establecer el tamaño del *kernel* del filtro Sobel (por defecto tiene asignado el valor 3).\n", "\n", "**Ejemplo 12**. Detector de bordes Canny a través de la *webcam*." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "\n", "webcam_id = 0\n", "cap = cv2.VideoCapture(webcam_id)\n", "\n", "# Cany edge detector thresholds\n", "threshold_one = 50\n", "threshold_two = 150\n", "aperture_size = 3\n", "\n", "while(cap.isOpened()):\n", " # Capture frame-by-frame\n", " ret, frame = cap.read()\n", " if ret == True:\n", " # Operations on the frame\n", " edges = cv2.Canny(frame, threshold_one, threshold_two, aperture_size)\n", " \n", " # Display\n", " cv2.imshow(\"Original\", frame)\n", " cv2.imshow(\"Canny edge detection\", edges)\n", "\n", " # Exit?\n", " if cv2.waitKey(1) & 0xFF == ord('q'):\n", " break\n", " else:\n", " break\n", "\n", "# Release everything if job is finished\n", "cap.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 9**. Buscar una imagen aérea de una ciudad y aplicar el filtro Canny." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 9" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 7. Histogramas\n", "\n", "Un histograma es un gráfico que nos da una idea general sobre la distribución de la intensidad de una imagen. Generalmente, es un gráfico en el que en el eje X se establece el rango [0, 255] y en el eje Y se muestra la cantidad de píxeles con dicha intensidad.\n", "\n", "Es otra forma de entender una imagen. Mirando el histograma de una imagen, podemos intuir el contraste, el brillo y la distribución de la intensidad de una imagen.\n", "\n", "\n", "\n", "Dentro de los parámetros básicos de un histograma, destacan los siguientes:\n", "\n", "* **BINS**. Establece el rango de valores que son mostrados en el eje X. Normalmente mostraremos 256 valores (de 0 a 255), pero podría darse el caso que quisiéramos agrupar las intensidades. Por ejemplo, podríamos mostrar los valores de 0 a 15, de 16 a 31, ..., de 240 a 255. De esta forma sólo tendríamos 16 valores que representar en el histograma.\n", "\n", "* **DIMS**. Número de dimensiones. Es el número de parámetros para los cuales se hace el cálculo. En este caso la dimensión será 1 ya que únicamente estamos calculando la intensidad.\n", "\n", "* **RANGO**. Es el rango de intensidades que se desea medir. Normalmente será [0, 255], es decir, todo el rango de valores.\n", "\n", "OpenCV proporciona la función **cv2.calcHist** para calcular el histograma de una imagen. Esta función tiene los siguientes parámetros:\n", "\n", "* *images*. Imagen fuente. El parámetro es una lista de imágenes.\n", "* *channels*. Índice del canal sobre el cual se desea calcular el histograma.\n", "* *mask*. Máscara de imagen. Si se desea calcular el histograma de la imagen completa, deberá establecerse a *None*.\n", "* *histSize*. Representa el contador BIN. También es una lista. En nuestro caso, utilizaremos [256].\n", "* *ranges*. Este es el RANGO. Normalmente, utilizaremos [0, 255].\n", "\n", "** Ejemplo 13**. Calcular el histograma sobre una imagen en escala de grises y mostrar el gráfico." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "\n", "# Load\n", "image_path = 'examples/images/rabbit.jpg'\n", "image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)\n", "\n", "# Show image\n", "cv2.imshow('Image', image)\n", "cv2.waitKey(0)\n", "\n", "# Histogram\n", "hist = cv2.calcHist([image], channels=[0], mask=None, histSize=[256], ranges=[0,255])\n", "\n", "# Plot\n", "plt.plot(hist)\n", "plt.xlim([0, 255])\n", "plt.show()\n", "\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 10**. Mostrar en un único gráfico los 3 histogramas de la imagen anterior para cada uno de los canáles de color RGB." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7.1. Gráfico con Matplotlib\n", "\n", "La librería **Matplotlib** también posee una función para mostrar histogramas: **matplotlib.pyplot.hist()**. La propia función directamente calcula el histograma y lo genera. Por tanto, no es necesario hacer uso de la función **cv2.calcHist()** de OpenCV.\n", "\n", "Nota: el algoritmo de OpenCV está más optimizado y es más rápido.\n", "\n", "** Ejemplo 14**. Cálculo y generación de histograma mediante la función matplotlib.pyplot.hist(). " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "from matplotlib import pyplot as plt\n", "\n", "# Load\n", "image_path = 'examples/images/rabbit.jpg'\n", "image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)\n", "\n", "# Histogram & plot\n", "plt.hist(image.ravel(), 256, [0, 255]);\n", "plt.xlim([0, 255])\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 8. Template matching\n", "\n", "Template matching es un método de procesamiento digital de imágenes para buscar y encontrar la localización/posición de una imagen *template* dentro de otra imagen. Es una técnica para encontrar pequeñas partes de una imagen que se ajusten a una imagen *template*.\n", "\n", "\n", "\n", "OpenCV viene con una función para este propósito: **cv2.matchTemplate**. De forma resumida, el algoritmo símplemente desliza la imagen *template* sobre la imagen general, como si se tratara de una convolución 2D, y compara la plantilla con esa zona de la imagen. El resultado es una imagen en escala de grises donde cada píxel denota cuánto se aproxima a la imagen *template* atendiendo a su vecindario. Mediante la función **cv2.minMaxLoc()** se puede buscar posteriormente dónde está el máximo/mínimo para poder localizar la imagen.\n", "\n", "En OpenCV existen varios métodos de comparación:\n", "* http://docs.opencv.org/3.1.0/df/dfb/group__imgproc__object.html#ga3a7850640f1fe1f58fe91a2d7583695d\n", "\n", "\n", "**Ejemplo 15**. Detección y localización de la señal de la imagen mediante template matching." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "\n", "img = cv2.imread('examples/images/hobbiton.jpg', 0)\n", "img2 = img.copy()\n", "template = cv2.imread('examples/images/template.png', 0)\n", "w, h = template.shape[::-1]\n", "\n", "# All the 6 methods for comparison in a list\n", "methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',\n", " 'cv2.TM_CCORR_NORMED','cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']\n", "\n", "for meth in methods:\n", " img = img2.copy()\n", " method = eval(meth)\n", "\n", " # Apply template Matching\n", " res = cv2.matchTemplate(img,template,method)\n", " min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)\n", "\n", " # If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum\n", " if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:\n", " top_left = min_loc\n", " else:\n", " top_left = max_loc\n", " bottom_right = (top_left[0] + w, top_left[1] + h)\n", "\n", " cv2.rectangle(img, top_left, bottom_right, 255, 2)\n", "\n", " plt.subplot(121),plt.imshow(res,cmap = 'gray')\n", " plt.title('Matching Result'), plt.xticks([]), plt.yticks([])\n", " plt.subplot(122),plt.imshow(img,cmap = 'gray')\n", " plt.title('Detected Point'), plt.xticks([]), plt.yticks([])\n", " plt.suptitle(meth)\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 11**. Descargar una imagen en la que se esté practicando algún deporte, recortar la cara de la jugadora o del jugador, y localizar esa cara en la imagen." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 11" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Caso de uso\n", "* **Artículo**: Microsaccades enable efficient synchrony-based coding in the retina: a simulation study\n", "* **DOI**: 10.1038/srep24086 (https://dx.doi.org/10.1038/srep24086)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9. Detección de caras\n", "\n", "La detección de objetos mediante el uso de clasificadores en cascada basados en la extracción de características con filtros de base Haar, es un método efectivo de detección de objetos propuesto por Paul Viola y Michael Jones en 2001. Es un método basado en las técnicas de aprendizaje automático donde una función cascada es entrenada mediante una gran cantidad de imágenes positivas y negativas, posibilitando la detección de objetos en otras imágenes. Las imágenes deben tener un tamaño prefijado.\n", "\n", "\n", "\n", "Primero, un clasificador es entrenado con unos pocos cientos de ejemplos de un objeto en particular, en este caso caras, llamados ejemplos positivos. Además, también es entrenado con imágenes arbitrarias del mismo tamaño, llamadas ejemplos negativos.\n", "\n", "Tras ser entrenado el clasificador, puede ser aplicado a una región de interés en una imagen de entrada. El clasificador generará como salida \"1\" si la región es como el objeto buscado o \"0\" en otro caso. Para buscar el objeto en la imagen completa, la ventana de búsqueda se moverá a traves de la imagen. Además, el clasificador está diseñado para poder cambiar su tamaño, para buscar objetos de diferentes tamaños.\n", "\n", "Se denomina \"en cascada\" porque el clasificador está formado por varios clasificadores simples en diferentes etapas, los cuales son aplicados de forma sucesiva hasta que en una etapa se rechaza o por el contrario se avanza hasta el final.\n", "\n", "OpenCV viene tanto con un entrenador como con un detector. Si se desea entrenar un clasificador propio para cualquier tipo de objeto (trenes, coches, perros, etc.), se puede utilizar OpenCV para crearlo. Además, OpenCV también viene muchos clasificadores previamente entrenados para caras, ojos, sonrisas, etc. Esos ficheros se encuentran almacenados en la siguiente ruta:\n", "* **opencv/data/haarcascades/**\n", "\n", "Las funciones que proporciona OpenCV son las siguientes:\n", "* **cv2.CascadeClassifier()**\n", "* **cv2.detectMultiScale()**\n", "\n", "**Ejemplo 16**. Detección de caras Haar-cascade a través de la *webcam*." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import sys\n", "\n", "# Load XML classifieres\n", "cas_path = 'examples/haarcascade/haarcascade_frontalface_default.xml'\n", "face_cascade = cv2.CascadeClassifier(cas_path)\n", "\n", "# Webcam\n", "webcam_id = 0\n", "video_capture = cv2.VideoCapture(webcam_id)\n", "\n", "# Detect multi-scale flags\n", "if cv2.__version__.startswith('2.4'):\n", " dmf_flag = cv2.cv.CV_HAAR_SCALE_IMAGE\n", "else:\n", " dmf_flag = cv2.CASCADE_SCALE_IMAGE\n", "\n", "while True:\n", " # Capture frame-by-frame\n", " ret, frame = video_capture.read()\n", "\n", " # Gray image\n", " gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n", "\n", " # Detect faces\n", " faces = face_cascade.detectMultiScale(\n", " gray_image,\n", " scaleFactor=1.1,\n", " minNeighbors=5,\n", " minSize=(30, 30),\n", " flags=dmf_flag\n", " )\n", "\n", " # Draw a rectangle around the faces\n", " for (x, y, w, h) in faces:\n", " cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)\n", "\n", " # Display the resulting frame\n", " cv2.imshow('Video', frame)\n", "\n", " if cv2.waitKey(1) & 0xFF == ord('q'):\n", " break\n", "\n", "# When everything is done, release the capture\n", "video_capture.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejemplo 17**. Detección de caras y ojos a través de la *webcam*." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import sys\n", "\n", "# Load XML classifieres\n", "cas_path = 'examples/haarcascade/haarcascade_frontalface_default.xml'\n", "eye_path = 'examples/haarcascade/haarcascade_eye.xml'\n", "face_cascade = cv2.CascadeClassifier(cas_path)\n", "eye_cascade = cv2.CascadeClassifier(eye_path)\n", "\n", "# Webcam\n", "webcam_id = 0\n", "video_capture = cv2.VideoCapture(webcam_id)\n", "\n", "# Detect multi-scale flags\n", "if cv2.__version__.startswith('2.4'):\n", " dmf_flag = cv2.cv.CV_HAAR_SCALE_IMAGE\n", "else:\n", " dmf_flag = cv2.CASCADE_SCALE_IMAGE\n", "\n", "while True:\n", " # Capture frame-by-frame\n", " ret, frame = video_capture.read()\n", "\n", " # Gray image\n", " gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n", "\n", " # Detect faces\n", " faces = face_cascade.detectMultiScale(\n", " gray_image,\n", " scaleFactor=1.1,\n", " minNeighbors=5,\n", " minSize=(30, 30),\n", " flags=dmf_flag\n", " )\n", "\n", " # Draw a rectangle around the faces\n", " for (x, y, w, h) in faces:\n", " cv2.rectangle(frame, (x,y), (x+w,y+h), (255,0,0), 2)\n", " \n", " # Detect eyes\n", " roi_gray = frame[y:y+h, x:x+w]\n", " roi_color = frame[y:y+h, x:x+w]\n", " eyes = eye_cascade.detectMultiScale(roi_gray)\n", " \n", " # Draw a rectangle around the eyes\n", " for (ex,ey,ew,eh) in eyes:\n", " cv2.rectangle(roi_color, (ex,ey), (ex+ew,ey+eh), (0,255,0), 2)\n", "\n", " # Display the resulting frame\n", " cv2.imshow('Video', frame)\n", "\n", " if cv2.waitKey(1) & 0xFF == ord('q'):\n", " break\n", "\n", "# When everything is done, release the capture\n", "video_capture.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 10. Colores y espacios de color\n", "\n", "En OpenCV hay más de 150 métodos de conversión distintos entre espacios de colores. Hasta ahora, ya hemos aprendido a convertir imágenes RGB en escala de grises. Ahora vamos a aprender a convertir imágenes al espacio de color HSV (del inglés Hue, Saturation, Value – Matiz, Saturación, Valor).\n", "\n", "**Espacio de color RGB**\n", "\n", "\n", "**Espacio de color HSV**\n", "\n", "\n", "Para la conversión del espacio de color, debemos utilizar la función **cv2.cvtColor(input_image, flag)**, donde el *flag* indica el tipo de conversión a realizar. El *flag* para convertir de RGB a HSV es ***cv2.COLOR_BGR2GRAY**.\n", "\n", "\n", "### Seguimiento de objetos\n", "\n", "Ahora que conocemos cómo convertir una imagen RGB a HSV, ya estamos en disposición de extraer objetos de un determinado color. En HSV, es más fácil representar un color que en el espacio de color RGB. El método para la extracción de objetos de un cierto color es el siguiente:\n", "\n", "* Obtención de cada *frame* del vídeo\n", "* Conversión del espacio de color: de RGB a HSV\n", "* Creación de una máscara de color\n", "* Extracción de la imagen aquellos objetos del color definido\n", "\n", "**Ejemplo 18**. Filtrado de colores." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "\n", "# Webcam\n", "webcam_id = 0\n", "cap = cv2.VideoCapture(webcam_id)\n", "\n", "while True:\n", " _, frame = cap.read()\n", "\n", " # Convert BGR to HSV\n", " hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)\n", "\n", " # Define range in HSV\n", " lower_color = np.array([85, 50, 50])\n", " upper_color = np.array([145, 255, 255])\n", "\n", " # Mask: threshold the HSV image to get only defined colors\n", " mask = cv2.inRange(hsv, lower_color, upper_color)\n", "\n", " # Bitwise-AND mask and original image\n", " res = cv2.bitwise_and(frame, frame, mask=mask)\n", "\n", " # Show\n", " cv2.imshow('Frame', frame)\n", " cv2.imshow('Mask', mask)\n", " cv2.imshow('Color', res)\n", "\n", " # Exit?\n", " if cv2.waitKey(1) & 0xFF == ord('q'):\n", " break\n", "\n", "cv2.destroyAllWindows()\n", "cap.release()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio12**. Descargar un vídeo y filtrar los objetos de un color determinado (https://videos.pexels.com/video-license). " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 12" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 11. Detección y extracción de características (feature detection)\n", "\n", "## 11.1. Features\n", "\n", "La mayoría de vosotros/as alguna vez ha jugado a montar puzles. En ellos, partiendo de un gran número de pequeñas piezas de una imagen, se debe reconstruir la imagen original la cual tiene un tamaño mayor. La pregunta es ¿cómo eres capaz de resolverlo?\n", "\n", "\n", "\n", "La respuesta está en la búsqueda de patrones específicos o características específicas que sean únicas, que puedan ser fácilmente rastreadas y comparadas. Si buscamos una definición para definir qué es una característica, encontraremos que es una tarea difícil de expresar en palabras, pero en el fondo sabemos qué son. Si alguien nos pregunta que le señalemos una buena característica para que pueda ser comparada entre varias imágenes, seguramente seremos capaces de señalarla. Esta es la razón por la cual los niños pequeños pueden simplemente jugar a montar puzles.\n", "\n", "No existe una definición universal o exacta de qué constituye una característica, y la definición concreta a menudo depende del problema o del tipo de aplicación que se esté analizando. Dicho esto, una característica es definida como una parte interesante de una imagen, y suelen utilizarse como punto de partida para muchos algoritmos de visión por computador. Por tanto, al ser el punto de partida, la calidad global del algoritmo dependerá de lo bueno que sea su detector de características. Consecuentemente, la propiedad deseable para cualquier detector de características es la repetibilidad/reproducibilidad, es decir, si es capaz o no de detectar la misma características en dos o más imágenes diferentes de la misma escena.\n", "\n", "El proceso de detección y extracción de características es una operación a bajo nivel y ocasionalmente puede ser computacionalmente costosa.\n", "\n", "\n", "\n", "En la parte superior de la imagen anterior se muestran 6 pequeñas imágenes. Intenta buscar y localizar la posición de esas imágenes.\n", "\n", "* Como se puede observar, las imágenes A y B están repartidas por gran parte de la imagen. Es difícil encontrar la localización exacta de esas imágenes.\n", "* Las imágenes C y D son mucho más simples, ya que son bordes del edificio y es más fácil encontrar una posición aproximada, pero la posición exacta sigue siendo difícil de determinar. Esto es porque el patrón es el mismo a lo largo de todo el borde. \n", "* Finalmente, las imágenes E y F son diferentes esquinas del edificio y como se observa, es fácil determinar su posición. Por tanto, pueden ser consideradas como buenas *features*.\n", "\n", "\n", "## 11.2. Harris corner detection\n", "\n", "Como ya sabemos, las esquinas son regiones en la imagen con una gran variación en la intensidad en todas las direcciones. Una de las primeras aproximaciones para encontrar *corners* fue propuesta por Chris Harris y Mike Stephens en su publicación \"A Combined Corner and Edge Detector\" en 1988. Por tanto, hoy en día se le conoce como Harris Corner Detector.\n", "\n", "\n", "\n", "OpenCV proporciona la función **cv2.cornerHarris()** para la localización de esquinas mediante el algoritmo propuesto por Chris Harris y Mike Stephens. Los argumentos de la función son los siguientes:\n", "\n", "* *img* - Imagen de entrada. Debe ser una imagen en escala de grises y de tipo float32.\n", "* *blockSize* - Es el tamaño de la vecindad considerada para la detección de esquinas.\n", "* *ksize* - Parámetro de apertura del filtro de Sobel.\n", "* *k* - Parámetro libre de la ecuación de Harris.\n", "\n", "\n", "**Ejemplo 19**. Detección de esquinas mediante el método de Harris." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "\n", "filename = 'images/taj-noise.jpg'\n", "img = cv2.imread(filename)\n", "\n", "# RGB -> Grayscale\n", "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n", "\n", "# Input image should be grayscale and float32 type\n", "gray = np.float32(gray)\n", "\n", "# Corner detection\n", "dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)\n", "\n", "# Threshold for an optimal value, it may vary depending on the image.\n", "img[dst>0.01*dst.max()]=[0,0,255]\n", "\n", "# Show\n", "cv2.imshow('dst', img)\n", "\n", "# Exit?\n", "if cv2.waitKey(0) & 0xFF == ord('q'):\n", " cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 13**. Descargar una imagen con un abundante número de *corners* y aplicar el algoritmo de Harris." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 13" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 11.3. ORB (Oriented FAST and Rotated BRIEF) \n", "\n", "ORB es un algoritmo creado en los \"laboratorios de OpenCV\", publicado en 2011 por Ethan Rublee, Vincent Rabaud, Kurt Konolige y Gary R. Bradski en el artículo \"*An efficient alternative to SIFT or SURF*\". Como indica el título del artículo, es una alternativa a los algoritmos *SIFT* y *SURF* de uso libre, ya que al estar patentados esos algoritmos, para utilizarlos hay que pagar un canon.\n", "\n", "* *SIFT*: http://docs.opencv.org/3.2.0/da/df5/tutorial_py_sift_intro.html\n", "* *SURF*: http://docs.opencv.org/3.2.0/df/dd2/tutorial_py_surf_intro.html\n", "\n", "De forma resumida, podemos decir que ORB es una fusión del detector de esquinas *FAST* y el algoritmo *BRIEF*, con ciertas modificaciones, que también hace uso de detector de esquinas de Harris.\n", "\n", "* *FAST*: http://docs.opencv.org/3.2.0/df/d0c/tutorial_py_fast.html\n", "* *BRIEF*: http://docs.opencv.org/3.2.0/dc/d7d/tutorial_py_brief.html\n", "\n", "\n", "\n", "Para crear un objeto de tipo ORB, OpenCV proporciona la función **cv2.ORB()**. Una vez creado, para detectar los *keypoints* y *descriptors* tendremos que utilizar el método **orb.detectAndCompute()**. Toda la información viene descrita en el siguiente enlace:\n", "* http://docs.opencv.org/3.1.0/d1/d89/tutorial_py_orb.html\n", "\n", "**Ejemplo 20**. Uso del algoritmo ORB para la detección de esquinas sobre una fotografía del castillo de Bratislava." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", "from matplotlib import pyplot as plt\n", "\n", "# Load image\n", "path = 'examples/images/bratislava_castle.jpg'\n", "img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)\n", "\n", "# Initiate ORB detector\n", "if cv2.__version__.startswith('2.4'):\n", " orb = cv2.ORB()\n", "else:\n", " orb = cv2.ORB_create()\n", "\n", "# Find the keypoints and descriptors with ORB\n", "kp, des = orb.detectAndCompute(img, None)\n", "\n", "# Draw only keypoints location, not size and orientation\n", "img2 = cv2.drawKeypoints(img, kp, None, color=(0, 255, 0), flags=0)\n", "cv2.imshow('Keypoints', img2)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 14**. Aplicar el algoritmo ORB sobre la imagen descargada para el ejercicio anterior." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 14" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 15**. Aplicar los algoritmos ORB, SIFT y FAST y comparar visualmente los resultados que se obtienen." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 15" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 11.4. Correspondencia entre características (Feature Matching)\n", "\n", "A continuación describiremos la técnica de búsqueda de correspondencia entre características de diferentes imágenes *Brute-Force matcher*. El proceso es simple: se selecciona el descriptor de una característica en el primer conjunto y se compara con todas las características del segundo conjunto mediante un cálculo de distancias, devolviendo la característica que más se le aproxime.\n", "\n", "\n", "\n", "En OpenCV, primero debemos crear el objeto BFMatcher mediante la función **cv2.BFMatcher()**. El método tiene dos parámetros: *normType* (método de cálculo de distancias) y *crossCheck* (devuelve los descriptores de ambos conjuntos). Una vez creado, el método **BFMatcher.match()** devolverá la mejor correspondencia. Por último, el método **cv2.drawMatches()** nos ayudará a dibujar las correspondencias, poniendo las dos imágenes juntas de forma horizontal y dibujando líneas entre las dos imágenes con las mejores correspondencias. \n", "\n", "El resultado del método **match** es una lista de objetos de tipo **DMatch**. Este objeto tiene los siguientes atributos:\n", "* DMatch.distance - Distancia entre descriptores. Mejor cuanto menor sea.\n", "* DMatch.trainIdx - Índice del descriptor en la imagen *train*.\n", "* DMatch.queryIdx - Índice del descriptor en la imagen *query*\n", "* DMatch.imgIdx - Índice de la imágen *train*.\n", "\n", "**Ejemplo 21**. Correspondencia de características entre dos imágenes, mediante el método Brute-Force matcher." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", "import matplotlib.pyplot as plt\n", "\n", "def drawMatches(img1, kp1, img2, kp2, matches):\n", " \"\"\"\n", " My own implementation of cv2.drawMatches as OpenCV 2.4.9\n", " does not have this function available but it's supported in\n", " OpenCV 3.0.0\n", "\n", " This function takes in two images with their associated \n", " keypoints, as well as a list of DMatch data structure (matches) \n", " that contains which keypoints matched in which images.\n", "\n", " An image will be produced where a montage is shown with\n", " the first image followed by the second image beside it.\n", "\n", " Keypoints are delineated with circles, while lines are connected\n", " between matching keypoints.\n", "\n", " img1,img2 - Grayscale images\n", " kp1,kp2 - Detected list of keypoints through any of the OpenCV keypoint \n", " detection algorithms\n", " matches - A list of matches of corresponding keypoints through any\n", " OpenCV keypoint matching algorithm\n", " \"\"\"\n", "\n", " # Create a new output image that concatenates the two images together\n", " # (a.k.a) a montage\n", " rows1 = img1.shape[0]\n", " cols1 = img1.shape[1]\n", " rows2 = img2.shape[0]\n", " cols2 = img2.shape[1]\n", "\n", " out = np.zeros((max([rows1,rows2]),cols1+cols2,3), dtype='uint8')\n", "\n", " # Place the first image to the left\n", " out[:rows1,:cols1] = np.dstack([img1, img1, img1])\n", "\n", " # Place the next image to the right of it\n", " out[:rows2,cols1:] = np.dstack([img2, img2, img2])\n", "\n", " # For each pair of points we have between both images\n", " # draw circles, then connect a line between them\n", " for mat in matches:\n", "\n", " # Get the matching keypoints for each of the images\n", " img1_idx = mat.queryIdx\n", " img2_idx = mat.trainIdx\n", "\n", " # x - columns\n", " # y - rows\n", " (x1,y1) = kp1[img1_idx].pt\n", " (x2,y2) = kp2[img2_idx].pt\n", "\n", " # Draw a small circle at both co-ordinates\n", " # radius 4\n", " # colour blue\n", " # thickness = 1\n", " cv2.circle(out, (int(x1),int(y1)), 4, (255, 0, 0), 1) \n", " cv2.circle(out, (int(x2)+cols1,int(y2)), 4, (255, 0, 0), 1)\n", "\n", " # Draw a line in between the two points\n", " # thickness = 1\n", " # colour blue\n", " cv2.line(out, (int(x1),int(y1)), (int(x2)+cols1,int(y2)), (255, 0, 0), 1)\n", "\n", "\n", " # Show the image\n", " cv2.imshow('Matched Features', out)\n", " cv2.waitKey(0)\n", " cv2.destroyWindow('Matched Features')\n", "\n", " # Also return the image if you'd like a copy\n", " return out\n", "\n", "# Query image\n", "img1 = cv2.imread('examples/images/r2d2.jpg', 0)\n", "# Train image\n", "img2 = cv2.imread('examples/images/starwars.jpg', 0)\n", "\n", "# Initiate ORB detector\n", "if cv2.__version__.startswith('2.4'):\n", " orb = cv2.ORB()\n", "else:\n", " orb = cv2.ORB_create()\n", "\n", "# Find the keypoints and descriptors with ORB\n", "kp1, des1 = orb.detectAndCompute(img1,None)\n", "kp2, des2 = orb.detectAndCompute(img2,None)\n", "\n", "# create BFMatcher object\n", "bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)\n", "\n", "# Match descriptors.\n", "matches = bf.match(des1, des2)\n", "\n", "# Sort them in ascending order of their distances so that best \n", "# matches (with low distance) come to front\n", "matches = sorted(matches, key = lambda x:x.distance)\n", "\n", "# Then we draw only first 10 matches (Just for sake of visibility\n", "drawMatches(img1, kp1, img2, kp2, matches[:10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejercicio 16**. Descargar un par de fotos de internet en la que la imagen *query* aparezca dentro de la imagen *train*. Aplicar el algoritmo Brute-Force matcher." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Ejercicio 16" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 12. Aprendizaje automático\n", "\n", "## 12.1. k-Nearest Neighbour\n", "\n", "El método kNN (k-nearest neighbour o k-vecinos más cercanos) es uno de los algoritmos de clasificación más simples dentro de los métodos de clasificación supervisada. Este es un método de clasificación no paramétrico, que estima el valor de la función de densidad de probabilidad o directamente la probabilidad a posteriori de que un elemento $x$ pertenezca a la clase $C_{j}$ a partir de la información proporcionada por el conjunto de prototipos. \n", "\n", "\n", "\n", "En la imagen, vemos dos tipos de objetos, los cuadrados azules y los triángulos rojos. A cada tipo de objeto distinto lo llamamos *clase*. Los objetos son mostrados en un plano al que llamaremos *feature space*.\n", "\n", "Ahora imaginemos que queremos clasificar un nuevo objeto (círculo verde), teniendo que elegir si pertenece al grupo de objetos azules o rojos. A este proceso lo llamamos *clasificación*. El algoritmo va a intentar clasificar al objeto nuevo dependiendo de cuáles sean los vecinos más cercanos, que dependerá del parámetro *k*. En cuanto a la elección de este parámetro, dependerá fundamentalmente de los datos; generalmente, valores grandes de *k* reducen el efecto de ruido en la clasificación, pero crean límites entre clases parecidas.\n", "\n", "El método kNN está disponible en OpenCV. Lo primero que tenemos que hacer es obtener una instancia mediante el método **knn = cv2.KNearest()**. Tras esto, deberemos realizar el proceso de entrenamiento mediante el método **knn.train()**. Por último, para realizar el proceso de clasificación haremos uso del método **knn.find_nearest()**.\n", "\n", "**Ejemplo 22**. Ejemplo de clasificación mediante el algoritmo k-nearest neighbour (kNN)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import cv2\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", " \n", "# Feature set containing (x,y) values of 25 known/training data\n", "trainData = np.random.randint(0, 100, (25, 2)).astype(np.float32)\n", "\n", "# Labels each one either Red or Blue with numbers 0 and 1\n", "responses = np.random.randint(0, 2, (25, 1)).astype(np.float32)\n", " \n", "# Take Red families and plot them\n", "red = trainData[responses.ravel()==0]\n", "plt.scatter(red[:,0], red[:,1], 80, 'r', '^')\n", "\n", "# Take Blue families and plot them\n", "blue = trainData[responses.ravel()==1]\n", "plt.scatter(blue[:,0], blue[:,1], 80, 'b', 's')\n", "\n", "# New object\n", "newcomer = np.random.randint(0, 100, (1, 2)).astype(np.float32)\n", "plt.scatter(newcomer[:,0], newcomer[:,1], 80, 'g', 'o')\n", "\n", "# Classification\n", "k = 5\n", "if cv2.__version__.startswith('2.4'):\n", " knn = cv2.KNearest()\n", " knn.train(trainData, responses)\n", " ret, results, neighbours, dist = knn.find_nearest(newcomer, k)\n", "else:\n", " knn = cv2.ml.KNearest_create()\n", " knn.train(trainData, cv2.ml.ROW_SAMPLE, responses)\n", " ret, results, neighbours, dist = knn.findNearest(newcomer, k)\n", "\n", "print(\"result: \" + str(results))\n", "print(\"neighbours: \" + str(neighbours))\n", "print(\"distance: \" + str(neighbours))\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 12.2. K-means\n", "\n", "El algoritmo de las K-medias (*K-means* en inglés), presentado por MacQueen en 1967, es uno de los algoritmos de aprendizaje no supervisado más simples para resolver el problema de la clusterización. El procedimiento aproxima por etapas sucesivas un cierto número (prefijado) de clusters haciendo uso de los centroides de los puntos que deben representar.\n", "\n", "\n", "**¿Cómo funciona?**\n", "\n", "Considera el conjunto de datos de la siguiente imagen. Se requiere clasificar estos datos en dos grupos diferentes. \n", "\n", "\n", "\n", "En el **primer paso**, el algoritmo escoge dos centroides, $C1$ y $C2$. Tras esto, en el **segundo paso**, el algoritmo calculará la distancia desde cada punto a cada uno de los centroides. Si uno de los datos está más cerca de $C1$, entonces ese dato se le asignará la etiqueta del centroide $C1$. Si por el contrario está más cerca del centroide $C2$, en ese caso se le asignará la etiqueta del centroide $C2$.\n", "\n", "\n", "\n", "En el **tercer paso** del algoritmo, para cada uno de los centroides, se realiza el cálculo de la media de todos los puntos asignados a ese centroide, siendo ésta la nueva posición a la que se desplazará el centroide (centro de masas).\n", "\n", "\n", "\n", "Por último, los **pasos dos y tres** serán iterados hasta que todos los centroides converjan a puntos fijos. Además, también pueden aplicarse otros criterios de parada, como son el número máximo de iteraciones o la obtención de una precisión determinada.\n", "\n", "El resultado final sería el siguiente:\n", "\n", "\n", "\n", "OpenCV proporciona la función **cv2.kmeans()** para la clusterización de datos mediante el algoritmo de las K-medias.\n", "\n", "Los parámetros de entrada del algoritmo son los siguientes:\n", "* *samples*: datos de entrada (tipo de dato np.float32).\n", "* *nclusters*: número de clusters requeridos al final de la ejecución.\n", "* *criteria*: criterio de parada.\n", "* *attempts*: número de veces que el algoritmo es ejecutado utilizando diferentes inicializaciones.\n", "* *flags*: indica cómo son los centroides elegidos al inicio.\n", "\n", "Los parámetros de salida del algoritmo son los siguientes:\n", "* *compactness*: suma de la distancia cuadrática desde cada punto hasta su correspondiente centroide.\n", "* *labels*: array con las etiquetas.\n", "* *centers*: array de centroides de los clusters.\n", "\n", "**Ejemplo 23**. El problema de la talla de camiseta (http://docs.opencv.org/3.1.0/de/d4d/tutorial_py_kmeans_understanding.html y http://docs.opencv.org/3.1.0/d1/d5c/tutorial_py_kmeans_opencv.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", "from matplotlib import pyplot as plt\n", "\n", "X = np.random.randint(25, 53, (25, 2))\n", "Y = np.random.randint(57, 85, (25, 2))\n", "Z = np.vstack((X,Y))\n", "\n", "# Convert to np.float32\n", "Z = np.float32(Z)\n", "\n", "# Define criteria\n", "criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)\n", "\n", "# Define number of cluters\n", "K = 2\n", "\n", "# Attempts\n", "attempts = 10\n", "\n", "# Apply kmeans\n", "if cv2.__version__.startswith('2.4'):\n", " ret, label, center = cv2.kmeans(Z, K, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)\n", "else:\n", " ret, label, center = cv2.kmeans(Z, K, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)\n", "\n", "# Now separate the data, Note the flatten()\n", "A = Z[label.ravel() == 0]\n", "B = Z[label.ravel() == 1]\n", "\n", "# Plot the data\n", "plt.figure(1)\n", "\n", "plt.subplot(211)\n", "plt.scatter(A[:,0], A[:,1], c = 'gray')\n", "plt.scatter(B[:,0], B[:,1], c = 'gray')\n", "plt.xlabel('Height')\n", "plt.ylabel('Weight')\n", "\n", "plt.subplot(212)\n", "plt.scatter(A[:,0], A[:,1])\n", "plt.scatter(B[:,0], B[:,1], c = 'r')\n", "plt.scatter(center[:,0], center[:,1], s = 80, c = 'y', marker = 's')\n", "plt.xlabel('Height')\n", "plt.ylabel('Weight')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 12.2.1. Color Quantization\n", "\n", "En el campo de gráficos por computador o visión artificial, se denomina *color quantization* al proceso de reducción del número de colores distintos presentes en una imagen, utilizado usualmente con la intención de obtener una nueva imagen que sea visualmente similar a la imagen original pero con un menor número de colores, consiguiendo de esta forma reducir el tamaño de la imagen. Muchas veces, algunos dispositivos tienen la limitación de producir únicamente un conjunto reducido de colores. En esos casos, se puede realizar el proceso de cuantización para reducir los colores, haciendo uso del algoritmo k-means.\n", "\n", "A continuación se presenta un ejemplo de cuantización. Aquí tendremos tres características, es decir, los tres canales de color R,G,B. Por tanto, necesitamos hacer un *reshape* de la imagen a un array de tamaño Mx3 (siendo M el número de píxeles de la imagen). Después de realizar la clusterización, se aplicarán los valores de los centroides a todos los píxeles, obteniendo una imagen con el número de colores especificado (K).\n", "\n", "**Ejemplo 23**. Cuantización de colores de una imagen." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", " \n", "img = cv2.imread('images/machu_picchu.jpg')\n", "Z = img.reshape((-1,3))\n", " \n", "# Convert to np.float32\n", "Z = np.float32(Z)\n", "\n", "# Define criteria\n", "criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)\n", "\n", "# Define number of clusters(K) \n", "K = 8\n", "\n", "# Attempts\n", "attempts = 10\n", "\n", "# Apply kmeans\n", "if cv2.__version__.startswith('2.4'):\n", " ret, label, center = cv2.kmeans(Z, K, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)\n", "else:\n", " ret, label, center = cv2.kmeans(Z, K, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)\n", "\n", "# Now convert back into uint8, and make original image\n", "center = np.uint8(center)\n", "res = center[label.flatten()]\n", "final_image = res.reshape((img.shape))\n", "\n", "cv2.imshow('Original image', img)\n", "cv2.imshow('Color Quantization', final_image)\n", "cv2.waitKey(0)\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "61f02149-01e2-4d54-9bd1-d7dd846badcc" } }, "source": [ "# Referencias\n", "* http://www.wikipedia.com\n", "* https://commons.wikimedia.org\n", "* http://www.opencv.org\n", "* http://www.python.org\n", "* https://realpython.com\n", "* http://homepages.inf.ed.ac.uk/rbf/HIPR2/hipr_top.htm\n", "* http://www.cs.us.es/~fsancho/?e=43\n", "* Python para todos. Raúl González Duque.\n", "* OpenCV Computer Vision with Python. Joseph Howse.\n" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" }, "nbpresent": { "slides": { "22e9b66f-fd2e-42df-99cd-e4e0f21ad779": { "id": "22e9b66f-fd2e-42df-99cd-e4e0f21ad779", "prev": "477fec80-ffd1-42e0-8484-27ff3f4898c8", "regions": { "03918df8-3e2c-4ae3-9a27-26c1a2cbf0c5": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "4ab4ce54-49ec-41a4-ab3f-33ee8d8cfbcb", "part": "whole" }, "id": "03918df8-3e2c-4ae3-9a27-26c1a2cbf0c5" } } }, "24557e4b-ec5d-43f2-bf81-bab6bc0cef90": { "id": "24557e4b-ec5d-43f2-bf81-bab6bc0cef90", "prev": null, "regions": { "0366134a-f9e8-4ee3-b881-81f69d10a306": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "72a7c411-cf21-4d47-a9b9-f5db1f8c9c60", "part": "whole" }, "id": "0366134a-f9e8-4ee3-b881-81f69d10a306" } } }, "3c3beb2d-9d61-4d5a-8994-02de7df212c0": { "id": "3c3beb2d-9d61-4d5a-8994-02de7df212c0", "prev": "96a74b97-5cea-4633-9068-6187a1db86f2", "regions": { "9d1e269f-6790-477c-b410-b24d3bd25260": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "c46893e7-63cc-4cd0-a5fe-85a94f724611", "part": "whole" }, "id": "9d1e269f-6790-477c-b410-b24d3bd25260" } } }, "477fec80-ffd1-42e0-8484-27ff3f4898c8": { "id": "477fec80-ffd1-42e0-8484-27ff3f4898c8", "prev": "b0c0a833-6909-4699-9d26-99772e73cf0f", "regions": { "90449ea6-5afc-49c7-a0c0-f96385d5f3b1": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "6fcb7aee-2b44-4099-bb62-88ed8d1a557c", "part": "whole" }, "id": "90449ea6-5afc-49c7-a0c0-f96385d5f3b1" } } }, "6519229c-aad1-404e-bb0e-5f5e24e9166d": { "id": "6519229c-aad1-404e-bb0e-5f5e24e9166d", "prev": "22e9b66f-fd2e-42df-99cd-e4e0f21ad779", "regions": { "06daaf59-4da8-42e6-9c06-ee62a11f130d": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "61f02149-01e2-4d54-9bd1-d7dd846badcc", "part": "whole" }, "id": "06daaf59-4da8-42e6-9c06-ee62a11f130d" } } }, "651edad8-ab58-4682-a530-d5d52db42903": { "id": "651edad8-ab58-4682-a530-d5d52db42903", "prev": "24557e4b-ec5d-43f2-bf81-bab6bc0cef90", "regions": { "6b41e10a-85cb-4b62-9cb9-d945af1bfae9": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "7cd741bd-a4ab-4824-9dcd-3708cfbad4e3", "part": "whole" }, "id": "6b41e10a-85cb-4b62-9cb9-d945af1bfae9" } } }, "6e92a7c5-48a3-47c4-83a7-2f78b56827d5": { "id": "6e92a7c5-48a3-47c4-83a7-2f78b56827d5", "prev": "c7c0225d-9d5f-4edf-a823-f2285eee72cc", "regions": { "7629b6b1-7cee-4ec2-9d6d-52d8ea7c0253": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "5a74133a-56c5-4d91-b13b-33ab05d996ce", "part": "whole" }, "id": "7629b6b1-7cee-4ec2-9d6d-52d8ea7c0253" } } }, "96a74b97-5cea-4633-9068-6187a1db86f2": { "id": "96a74b97-5cea-4633-9068-6187a1db86f2", "prev": "651edad8-ab58-4682-a530-d5d52db42903", "regions": { "ffda6832-e8b3-4b95-b1cd-c5261967aed8": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "a32ecb3d-7126-45aa-b39c-f245fd0245a4", "part": "whole" }, "id": "ffda6832-e8b3-4b95-b1cd-c5261967aed8" } } }, "98714c80-02b2-40f2-a770-b91ce7c1f3fa": { "id": "98714c80-02b2-40f2-a770-b91ce7c1f3fa", "prev": "3c3beb2d-9d61-4d5a-8994-02de7df212c0", "regions": { "502d0bc7-0b3d-4eba-bce3-89606d3733c5": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "ce9a3de4-cd6b-4df8-bfb2-9e62e1243c7e", "part": "whole" }, "id": "502d0bc7-0b3d-4eba-bce3-89606d3733c5" } } }, "b0c0a833-6909-4699-9d26-99772e73cf0f": { "id": "b0c0a833-6909-4699-9d26-99772e73cf0f", "prev": "6e92a7c5-48a3-47c4-83a7-2f78b56827d5", "regions": { "0b493bb1-915b-485c-9ca4-d4c1254dd071": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "53036ea1-9525-4153-ba9b-11f2494d8696", "part": "whole" }, "id": "0b493bb1-915b-485c-9ca4-d4c1254dd071" } } }, "c7c0225d-9d5f-4edf-a823-f2285eee72cc": { "id": "c7c0225d-9d5f-4edf-a823-f2285eee72cc", "prev": "98714c80-02b2-40f2-a770-b91ce7c1f3fa", "regions": { "3dd8f6f9-e07c-45ed-80ed-3f63303ca108": { "attrs": { "height": 0.8, "width": 0.8, "x": 0.1, "y": 0.1 }, "content": { "cell": "8751a1a2-8b04-4589-a932-4225e954220f", "part": "whole" }, "id": "3dd8f6f9-e07c-45ed-80ed-3f63303ca108" } } } }, "themes": {} } }, "nbformat": 4, "nbformat_minor": 1 }