28

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!


-RESOLUCIÓN DE ERRORES-

(1) Si usáis algunas versiones recientes de OpenCV puede aparecer este error:

Traceback (most recent call last):
File "mi_script.py", line 30, in <module>
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
ValueError: too many values to unpack (expected 2)

Esto se debe a que la función cv2.findContours() devuelve un tercer valor. Cambiad la línea

contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

por

_, contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

(2) Otro error que puede aparecer es:

Traceback (most recent call last):
File “mi_script.py”, line 54, in
punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido,**lk_params)
TypeError: Required argument ‘nextPts’ (pos 4) not found

En este caso, hay que sustituir la línea

punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)

por

punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, None, **lk_params)

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.

28
Deja un comentario

avatar
15 Hilos iniciados
13 Respuestas a hilos
0 Followers
 
Most reacted comment
Hottest comment thread
15 Nº autores comentarios
MikaelaGl4r3Jose CarlosAlejandroAlberto Guille Autores de comentarios recientes
más nuevos primero más antiguos primero
Mikaela
Humano
Mikaela

Hola, tengo un problema, necesito hacer un código que me detecte la boca y una vez detectada haga el tracking. Tengo un código que hace la detección y otro que hace el trackin pero tengo problemas para vincularlos. Me podrían ayudar? Gracias. ***Detección*** import cv2 import numpy as np #Buscamos los archivos HAAR que permiten detectar el rostro y la boca en OpenCV face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’) mouth_cascade = cv2.CascadeClassifier(‘haarcascade_mouth.xml’) #Iniciamos la camara video = cv2.VideoCapture(0) while (True): #leemos un frame y lo guardamos ret, frame = video.read() if frame is not None: #convertimos la imagen a blanco y negro gray… Leer más »

Jose Carlos
Humano
Jose Carlos

Se que es una pregunta muy tonta, pero soy nuevo en esto y me gustaria saber si puedo obtener las coordenadas x,y del objeto que estos detectando.

Alejandro
Humano
Alejandro

Hola, te felicito por el tutorial!!! al ejecutar el codigome tira este error…… yo uso el IDE de SPYDER.
Gracias

File “C:/Python27/Practicas/seguir_punto.py”, line 21, in
hsv = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)

Alberto Guille
Humano
Alberto Guille

Hola para los que les sale este error:

File “C:\Users\Manu\Documents\Vision\TareaOpenCVTracking.py”, line 54, in
punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)
TypeError: Required argument ‘nextPts’ (pos 4) not found

Searregla con lo que nos dejo en guia de soluciones:

Traceback (most recent call last):
File “mi_script.py”, line 34, in
punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido,**lk_params)
TypeError: Required argument ‘nextPts’ (pos 4) not found
En este caso, hay que sustituir la línea

punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)
por

punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, None, **lk_params)

¡¡##solo que obviamente lo cambian en su linea 54 y no en la linea 34##!!

Manuel Guillermo
Humano
Manuel Guillermo

Hola a mi tambien me aparece un error, me podrias ayudar por favor
Traceback (most recent call last):
File “C:\Users\Manu\Documents\Vision\TareaOpenCVTracking.py”, line 54, in
punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)
TypeError: Required argument ‘nextPts’ (pos 4) not found
>>>

HHHH
Humano
HHHH

Hola me aparece un error, me podrias ayudar, por favor,
Traceback (most recent call last):
File “C:\Users\the best\Documents\Vision\TareaOpenCVTracking.py”, line 32, in
punto_elegido,st,err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido, **lk_params)
TypeError: Required argument ‘nextPts’ (pos 4) not found
>>>
saludos me gusta tu proyecto

Lucia
Humano
Lucia

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

John Perez
Humano
John Perez

Tengo un error ->
Traceback (most recent call last):
File “mult-objects.py”, line 34, in
punto_elegido, st, err = cv2.calcOpticalFlowPyrLK(frame_anterior, frame_gray, punto_elegido,**lk_params)
TypeError: Required argument ‘nextPts’ (pos 4) not found

Diego
Humano
Diego

Hola! Excelente tu blog. Queria hacer una consulta, a ver si me puedes orientar. Queria hacer un proyecto donde identificar en una tapa plastica cuadrada de una caja, el logo de una marca. El problema con el que me encuentro es que, el logo esta en sobrerrelieve, por lo tanto no tengo practicamente diferencia de color entre el fondo de la tapa y el logo. la finalidad del proyecto es detectar la orientacion del logo, si esta mal orientado descartar la tapa. Que metodo se te ocurre que podria utilizar?
Desde ya te agradezco tu tiempo.
Saludos

serato
Humano
serato

que tal, a que se debe el error en la linea 53 no me reconoce el “for”, me dice:
for i in punto elegido:
(no reconoce el for)
identation error: unexpected error

por cierto, que rola mas… medieval, me hizo recordar muchas peliculas… 🙂

Enzo Peña
Humano
Enzo Peña

Quisiera detectar solo el objeto que este en el centro de la camara , se podría hacer algo asi?

manuel
Humano
manuel

Hola, estoy realizando un programa para detectar patrones específicos en una imagen pero hasta ahora no e encontrado nada parecido, (Solo e encontrado detección de esquinas y coincidencias en 2 imágenes). Mi pregunta es que método de investigación debo ocupar en opencv para lograr dicho programa?

Juan Carlos
Humano
Juan Carlos

Hola Eduardo quisiera saber si es posible seguir la punta de un lápiz, y luego poder generar o determinar la dirección del trazo realizado. Tal vez podrías guiarme un poco. Gracias

enrique
Humano
enrique

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

Miguel
Humano
Miguel

Buenas amigo, estoy realizando un proyecto bastante similar!!! lograste terminarlo?
me podria ayudar? le agradeceria muchisimo si logramos ponernos en contacto!

Eduardo
Humano
Eduardo

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
Eduardo

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