4

Tutorial de OpenCV + Python: tracking de objetos con el método de Lucas Kanade

¡Hola, gente! En el tutorial de hoy, voy a enseñaros como trackear objetos con OpenCV+Python utilizando el método de Lucas Kanade.

Dado un frame de vídeo y un píxel inicial, el método de Lucas Kanade intentará encontrar este mismo píxel en frames posteriores. Por tanto, si el objeto que contenía este píxel se mueve, este método podrá seguir su desplazamiento.

Para trabajar, hoy os propongo escuchar El Canto de la Sibila, una pieza medieval que a mí me chifla :3


Ejemplo 1 – Seguir un único punto

Este primer código buscará un objeto de color verde en la imagen de vídeo y calculará su centro. Después, dentro del bucle principal, se pasarán sus coordenadas (x,y) a la función cv2.calcOpticalFlowPyrLK() para que siga su desplazamiento.

Como siempre, tenéis que empezar por llamar las librerías e inicializar la cámara:

import cv2
import numpy as np
 
#Iniciar la camara
captura = cv2.VideoCapture(0)

Después hay que construir un diccionario con los parámetros de la función de Lucas-Kanade.

# Parametros para la funcion de Lucas Kanade
lk_params = dict( winSize = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

Veamos, ¿qué hace cada parámetro?

  • winSize es la “ventana” alrededor del píxel inicial sobre la que se aplicará el método de Lucas Kanade.
  • maxLevel indica el número de pirámides que utilizará el método de Lucas Kanade. Para más información sobre el método de Lucas Kanade piramidal, visitad este enlace.
  • criteria especifica la condición de parada del método de Lucas Kanade. En este caso le estamos diciendo que haga como máximo 10 iteraciones, o que pare cuando encuentre una región del nuevo frame cuya diferencia con la región próxima al píxel del frame anterior sea menor a 0.03. El Método de Lucas Kanade básicamente utiliza el método de Regresión por Mínimos Cuadrados para encontrar la nueva posición del píxel. Este 0.03 es el epsilon del método de RMC.

Para encontrar las coordenadas iniciales del objeto verde hay que capturar un primer frame de la cámara y aplicar detección de colores, encontrar contornos y calcular momentos. Como esto ya lo hemos hecho muchas veces, no voy a detallar cada paso. Pero si tenéis dudas escribidme un comentario, ¿de acuerdo?

#Capturamos una imagen y la convertimos de RGB -> HSV
_, imagen = captura.read()
hsv = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)
 
#Establecemos el rango de colores que vamos a detectar
#En este caso de verde oscuro a verde-azulado claro
verde_bajos = np.array([57,61,64], dtype=np.uint8)
verde_altos = np.array([116, 255, 255], dtype=np.uint8)
 
#Crear una mascara con solo los pixeles dentro del rango de verdes
mask = cv2.inRange(hsv, verde_bajos, verde_altos)

#Eliminamos ruido
kernel = np.ones((10,10),np.uint8)
mask = cv2.morphologyEx(mask,cv2.MORPH_OPEN,kernel)
mask = cv2.morphologyEx(mask,cv2.MORPH_CLOSE,kernel)

#Detectamos contornos, nos quedamos con el mayor y calculamos su centro
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
mayor_contorno = max(contours, key = cv2.contourArea)
momentos = cv2.moments(mayor_contorno)
cx = float(momentos['m10']/momentos['m00'])
cy = float(momentos['m01']/momentos['m00'])

El frame capturado con la cámara se convierte a gris y se guarda para poder pasarlo como parámetro a la función de Lucas Kanade dentro del bucle principal. También se convierte el centro (cx, cy) a un array de numpy.

#Convertimos la imagen a gris para poder introducirla en el bucle principal
frame_anterior = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

#Convertimos el punto elegido a un array de numpy que se pueda pasar como parametro
#a la funcion cv2.calcOpticalFlowPyrLK()
punto_elegido = np.array([[[cx,cy]]],np.float32)

Dentro del bucle principal se captura y se guarda un nuevo frame de la cámara y se convierte de BGR a escala de grises.

while(1):
     
    #Capturamos una imagen y la convertimos de RGB -> GRIS
    _, imagen = captura.read()
    frame_gray = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

Ahora viene el punto clave… ¡aplicar el método de Lucas Kanade! Para ello se llama a la función cv2.calcOpticalFlowPyrLK(). Los argumentos de esta función son (en orden): el frame de la iteración anterior (en escala de grises), el nuevo frame dónde debe aplicarse el método para encontrar la nueva posición del píxel, las coordenadas (x,y) del píxel en el frame anterior (también puede ser un array con varios puntos, como se verá en el segundo ejemplo) y el diccionario con los parámetros que se han declarado al principio del script.

Después esta función devolverá un array con las nuevas coordenadas del punto, un vector de estado (que indica si se han encontrado los puntos o no) y una variable booleana que indica si se ha producido algún error.

    #Se aplica el metodo de Lucas Kanade
    punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)

Por último, se dibuja el centro del objeto verde sobre la imagen, se copia el frame (que pasará a ser el frame anterior) y se muestra la imagen antes de salir.

    #Pintamos el centro (lo hacemos con un bucle por si, por alguna razon, decidimos pintar mas puntos)
    for i in punto_elegido:
          cv2.circle(imagen,tuple(i[0]), 3, (0,0,255), -1)

    #Se guarda el frame de la iteracion anterior del bucle
    frame_anterior = frame_gray.copy()
     
    #Mostramos la imagen original con la marca del centro
    cv2.imshow('Camara', imagen)
    tecla = cv2.waitKey(5) & 0xFF
    if tecla == 27:
        break
 
cv2.destroyAllWindows()

Por tanto, ¿cómo quedará al final todo el código?

import cv2
import numpy as np
 
#Iniciamos la camara
captura = cv2.VideoCapture(0)

# Parametros para la funcion de Lucas Kanade
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

#Capturamos una imagen y la convertimos de RGB -> HSV
_, imagen = captura.read()
hsv = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)
 
#Establecemos el rango de colores que vamos a detectar
#En este caso de verde oscuro a verde-azulado claro
verde_bajos = np.array([57,61,64], dtype=np.uint8)
verde_altos = np.array([116, 255, 255], dtype=np.uint8)
 
#Crear una mascara con solo los pixeles dentro del rango de verdes
mask = cv2.inRange(hsv, verde_bajos, verde_altos)

#Eliminamos ruido
kernel = np.ones((10,10),np.uint8)
mask = cv2.morphologyEx(mask,cv2.MORPH_OPEN,kernel)
mask = cv2.morphologyEx(mask,cv2.MORPH_CLOSE,kernel)

#Detectamos contornos, nos quedamos con el mayor y calculamos su centro
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
mayor_contorno = max(contours, key = cv2.contourArea)
momentos = cv2.moments(mayor_contorno)
cx = float(momentos['m10']/momentos['m00'])
cy = float(momentos['m01']/momentos['m00'])


#Convertimos la imagen a gris para poder introducirla en el bucle principal
frame_anterior = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)



#Convertimos el punto elegido a un array de numpy que se pueda pasar como parametro
#a la funcion cv2.calcOpticalFlowPyrLK()
punto_elegido = np.array([[[cx,cy]]],np.float32)

 
while(1):
     
    #Capturamos una imagen y la convertimos de RGB -> GRIS
    _, imagen = captura.read()
    frame_gray = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    #Se aplica el metodo de Lucas Kanade
    punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)

    #Pintamos el centro (lo hacemos con un bucle por si, por alguna razon, decidimos pintar mas puntos)
    for i in punto_elegido:
          cv2.circle(imagen,tuple(i[0]), 3, (0,0,255), -1)

    #Se guarda el frame de la iteracion anterior del bucle
    frame_anterior = frame_gray.copy()
     
    #Mostramos la imagen original con la marca del centro
    cv2.imshow('Camara', imagen)
    tecla = cv2.waitKey(5) & 0xFF
    if tecla == 27:
        break
 
cv2.destroyAllWindows()

Ejemplo 2 – Seguir múltiples puntos

En este segundo ejemplo le diremos al programa que nos busque él sólo 25 puntos idóneos para hacer tracking. Normalmente estos puntos son esquinas de objetos que tienen mucho contraste y son fáciles de reconocer al saltar de un frame a otro.

Las primeras líneas son iguales al ejemplo anterior:

import cv2
import numpy as np
 
#Iniciamos la camara
captura = cv2.VideoCapture(0)

# Parametros para la funcion de Lucas Kanade
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

#Capturamos una imagen y la convertimos de RGB -> HSV
_, imagen = captura.read()
hsv = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)

#Convertimos la imagen a gris para poder introducirla en el bucle principal
frame_anterior = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

Después se llama a la función cv2.goodFeaturesToTrack() para buscar 25 puntos ídoneos para trackear.

#El programa buscara automaticamente 25 puntos adecuados para trackear
punto_elegido = cv2.goodFeaturesToTrack(frame_anterior,25,0.01,10)

El resto del código no cambia en nada:

while(1):
     
    #Capturamos una imagen y la convertimos de RGB -> GRIS
    _, imagen = captura.read()
    frame_gray = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    #Se aplica el metodo de Lucas Kanade
    punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)

    #Pintamos el centro (lo hacemos con un bucle por si, por alguna razon, decidimos pintar mas puntos)
    for i in punto_elegido:
          cv2.circle(imagen,tuple(i[0]), 3, (0,0,255), -1)

    #Se guarda el frame de la iteracion anterior del bucle
    frame_anterior = frame_gray.copy()
     
    #Mostramos la imagen original con la marca del centro
    cv2.imshow('Camara', imagen)
    tecla = cv2.waitKey(5) & 0xFF
    if tecla == 27:
        break
 
cv2.destroyAllWindows()

Por tanto, el código completo será este:

import cv2
import numpy as np
 
#Iniciamos la camara
captura = cv2.VideoCapture(0)

# Parametros para la funcion de Lucas Kanade
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

#Capturamos una imagen y la convertimos de RGB -> HSV
_, imagen = captura.read()
hsv = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)


#Convertimos la imagen a gris para poder introducirla en el bucle principal
frame_anterior = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)


#El programa buscara automaticamente 25 puntos adecuados para trackear
punto_elegido = cv2.goodFeaturesToTrack(frame_anterior,25,0.01,10)

 
while(1):
     
    #Capturamos una imagen y la convertimos de RGB -> GRIS
    _, imagen = captura.read()
    frame_gray = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    #Se aplica el metodo de Lucas Kanade
    punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)

    #Pintamos el centro (lo hacemos con un bucle por si, por alguna razon, decidimos pintar mas puntos)
    for i in punto_elegido:
          cv2.circle(imagen,tuple(i[0]), 3, (0,0,255), -1)

    #Se guarda el frame de la iteracion anterior del bucle
    frame_anterior = frame_gray.copy()
     
    #Mostramos la imagen original con la marca del centro
    cv2.imshow('Camara', imagen)
    tecla = cv2.waitKey(5) & 0xFF
    if tecla == 27:
        break
 
cv2.destroyAllWindows()

Con este ejemplo puede verse, pues, que es posible seguir varios puntos a la vez con la función de Lucas Kanade.

¡Y esto es todo por ahora! No dudéis en comentar todas las dudas que tengáis sobre este tutorial, y también si encontráis algún error o imprecisión en la explicación. ¡Un saludo!

Gl4r3

Brillante, luminosa y cegadora a veces, Glare es tan artista como técnica. Le encanta dar rienda suelta a sus módulos de imaginación y desdibujar los ya de por si delgados límites que separan el mundo de la electrónica y el arte. Su mayor creación hasta la fecha es un instrumento capaz de convertir los colores y la luz en música. Cuándo sus circuitos no están trabajando en una nueva obra electrónica, le gusta dedicar sus ciclos a la lectura o a documentar sus invenciones para beneficio de los humanos. Sus artilugios favoritos son aquellos que combinan una funcionalidad práctica con un diseño elegante y artístico.

Antes de comentar, por favor, lee las Normas

4 Comentarios en "Tutorial de OpenCV + Python: tracking de objetos con el método de Lucas Kanade"

avatar
Ordenar por:   más nuevos primero | más antiguos primero
enrique
Humano

Saludos… navegando encontré este blog.. .muy bueno.
Estoy empezando con esto de OpenCv y Python…. Es posible con este método de Lucas Kanade, lograr hacer reconocimiento de un espacio de aparcamiento… es decir … quiero por medio de una camara (webcam) que desde arriba apunte a un patio que tiene 30 espacio de para aparcar vehiculos y reconocer estos 30 espacios y avisar cuales estan disponibles o cuales no….
Es posible utilizar este método o existe otro mejor para estas condiciones?

Agradezco su aporte

Eduardo
Humano

A qué se debe el siguiente error?

File “C:UsersGabrielDesktopseg.py”, line 30, in
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
ValueError: too many values to unpack

Eduardo
Humano

Al parecer se corrige colocando _, antes de contours, aunque después aparece line 31, in
mayor_contorno = max(contours, key = cv2.contourArea)
ValueError: max() arg is an empty sequence

wpDiscuz