Estaba pensando que hacer para el próximo concurso al que me voy a apuntar y pensé que siempre había querido aprender sobre tratamiento de imagen. Sin embargo, siempre había encontrado una excusa para no ponerme a ello, así qué decidí que para mi próximo proyecto no me podía negar. Y el resultado os lo voy a ir mostrando como lo he ido aprendiendo yo.

En este primer post, vamos a ver como empecé y que problemas encontré y así os puedo intentar explicar lo que más me lió al principio.

OpenCV

Lo primero para empezar con el tratamiento digital de imagen, es elegir qué y cómo queremos proceder. Podemos coger y picarnos todos los algoritmos de tratamiento de imagen, pero lo ideal es usar alguna librería para el tratamiento de imagen. La más famosa es openCV y es la que usaremos, aunque también existen otras alternativas, que no he probado, y la verdad, he tenido que buscar para escribir este post:

Una de las ventajas que veo de usar OpenCV frente a las otras alternativas, es que al ser muy conocido, existe muchísima información online y en los foros podéis encontrar casi todas las dudas que os surgirán, sobre todo al principio.

Además, OpenCV tiene wrappers para otros lenguajes de programación. Aunque esté programado en C++, ahora mismo se puede ejecutar, entre otos, en Java, Python, C#, Android, iOS… Como ya habréis adivinado, vamos a empezar a usarlo en Python. Qué, aun sin parecerme el mejor lenguaje de programación para programas grandes, si que lo considero idóneo para aprender y trastear.

Instalación

Lo primero que necesitamos es instalar el entorno. Vamos a usar Anaconda con Python 3 y para instalar todo tenéis un tutorial aquí. Aunque si sabes un poco y quieres pasar de leerte paso a paso la instalación, se puede resumir en:

  1. Instalar Anaconda y Python (Link descarga)
  2. Instalar opencv y utilidades necesarias:
    pip install numpy scipy matplotlib scikit-learn jupyter
    pip install opencv-contrib-python imutils
    
  3. Comprobar que todo ha ido bien:
    import cv2
    print(cv2.__version__)
    # '4.1.1' (o una versión mayor)

Empezamos con OpenCV de verdad

Ventanas y primeras imágenes

Para mi proyecto voy a trabajar con vídeo, ya que necesito actualización de datos en tiempo real. Sin embargo, ya que no todo el mundo tiene cámara, y además todo lo que se hace para una imagen se hace igual para un vídeo , vamos a ir con el ejemplo más sencillo en el vamos a abrir una imagen y la mostramos en una ventana.

import cv2

img = cv2.imread('LaboratorioGluon.jpg')

cv2.imshow("Imagen", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Ventana básica con la imagen

Ventana básica con la imagen

El código es bastante auto-explicativo, con cv2.imread(…) leemos una imagen, luego mostramos la imagen en una ventana titulada “Imagen”, esperamos a que se pulse una tecla, y cerramos todas la ventanas.

El 99% de los tutoriales que he visto, a partir de este punto se ponen a hablar de mil y una funciones que tiene openCV, pero no de cual es el flujo estándar de trabajo. Generalmente, cuando se hace tratamiento de imágenes es para obtener información útil sobre lo que hay en la imagen. Es decir, extraer características(features) de la imagen.

El enmascarado

Una de las primeras herramientas que vamos a usar siempre son las máscaras. Estas máscaras se usan para elegir que parte de la imagen nos queremos quedar, o a qué parte queremos aplicar cierta modificación. Siguiendo con nuestro ejemplo vamos a separar el fondo de la parte de las letras. Antes de empezar a trabajar con la imagen, hay que saber que generalmente se pasa a formato HSV (Hue, Saturation, Value), y que los rangos que maneja openCV son:

  • Hue: 0 – 179
  • Saturación: 0 – 255
  • Valor/Brillo: 0 – 255

Por defecto openCV abre los ficheros en BGR (que no RGB que es a lo que estamos acostumbrados), así que primero hay que obtener la imagen en formato HSV con la función cv2.cvtColor. Luego, con algún colorpicker de internet podemos ver que el color de las letras está aproximadamente en torno al valor de Hue 25, así que podríamos intentar separar la imagen por: Todos los pixeles que tengan un color parecido al Hue 35, vamos a elegirlos como “buenos”.

Para esto tenemos la función cv2.inRange(…) que nos deja elegir los pixeles que estén entre dos rangos de HSV. Para nuestro ejemplo: Queremos algo que esté alrededor de Hue 35 y como no sabemos muy bien que valores de Saturación y Brillo elegir podemos decirle a la función que no filtre por estos dos últimos valores. Con esto, nuestros límites podrían quedar, por ejemplo:

  • Limite inferior: (15, 0, 0)
  • Limite superior: (30, 255, 255)
import cv2

img = cv2.imread('LaboratorioGluon.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (15,0,0), (30,255,255))

cv2.imshow("Mascara", mask)
cv2.imshow("Imagen", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Imagen con filtro de color aplicado

Imagen con filtro de color aplicado

Como podemos ver en la ventana anterior “Mascara” ya tenemos separados el fondo de las letras. Las máscaras son mapas de pixeles donde el color blanco significa “seleccionado” y el negro significa “eliminado”. Es decir, si aplicamos cualquier función usando la máscara, solo se aplicará donde la máscara tenga un 1.

El detector

Muy bien, ya tenemos manchas blancas en una ventana, y ahora qué? A partir de aquí hay varias formas de proceder, yo estoy orientando mis pasos a extraer las características de una imagen y detectar puntos de ciertos colores, o marcas. Así que lo lógico sería poder separar y obtener los datos de cada una de estas marcas. Para ellos, openCV nos proporciona la función cv2.findContours, que nos separa cada uno de los contornos de la imagen.

Por ejemplo, podemos extraer los contornos de cada una de las letras de la siguiente manera:

import cv2

img = cv2.imread('LaboratorioGluon.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 
mask = cv2.inRange(hsv, (15,0,0), (30,255,255))

cnts, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]

for contour in cnts:
    cv2.drawContours(img, contour, -1, (0, 255, 0), 2)

cv2.imshow("Mascara", mask)
cv2.imshow("Imagen", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Imagen con los bordes remarcados

Imagen con los bordes remarcados

Y lo más importante… el Tester

Mientras estás empezando en esto, y haciendo pruebas, muchas veces vienen bien que haya algún tipo de input que haga que puedas modificar el algoritmo que se está aplicando a la imagen, en tiempo real. OpenCV ha pensado en todo y nos da la opción de tener unos manejadores muy majos, que podemos conectarlos con los límites del Hue Saturación y “Valor”, y ver el resultado en tiempo real.

Este código es un poco más denso, pero básicamente lo que hacemos es mostrar una nueva ventana, llamada ‘tester’, donde incluiremos unos cuantos Trackbars que luego conectaremos a nuestras variables para los rangos. Iremos actualizando la máscara en cada ciclo, y podremos ajustar los valores hasta obtener la máscara deseada.

import cv2
import numpy as np
def nothing(a):
    pass

cv2.namedWindow('Tester')
cv2.createTrackbar('HH','Tester',0,179,nothing)
cv2.createTrackbar('HL','Tester',0,179,nothing)
cv2.createTrackbar('SH','Tester',0,255,nothing)
cv2.createTrackbar('SL','Tester',0,255,nothing)
cv2.createTrackbar('VH','Tester',0,255,nothing)
cv2.createTrackbar('VL','Tester',0,255,nothing)

img = cv2.imread('LaboratorioGluon.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 

key = cv2.waitKey(1)

while not key == ord('q'):

    hh = cv2.getTrackbarPos('HH','Tester')
    hl = cv2.getTrackbarPos('HL','Tester')
    sh = cv2.getTrackbarPos('SH','Tester')
    sl = cv2.getTrackbarPos('SL','Tester')
    vh = cv2.getTrackbarPos('VH','Tester')
    vl = cv2.getTrackbarPos('VL','Tester')

    lower_white = np.array([hl,sl,vl], dtype=np.uint8)
    upper_white = np.array([hh,sh,vh], dtype=np.uint8)
    
    mask = cv2.inRange(hsv, lower_white, upper_white)

    cv2.imshow("Mascara", mask)
    cv2.imshow("Imagen", img)

    key = cv2.waitKey(1)
    
cv2.destroyAllWindows()
Slides con la opción para solo coger las sombras

Slides con la opción para solo coger las sombras

Y esto es todo… Para ir empezando a segmentar y a probar cosas es suficiente. Aunque también sería interesante mirar las siguientes funciones de cara a mejorar la segmentación:

  • cv2.erode y cv2.dilate
  • cv2.GaussianBlur y cv2.medianBlur

En el próximo post hablaré de como podemos usar la imagen de la cámara para obtener la posición de la cámara y los objetos que estamos viendo.


Gluón

Teleco con ganas de aprender más y compartirlo. Viajero empedernido y amante de la fotografía y la tecnología. Espero dejar mi granito de arena y que este pueda servir de ayuda.