Tutorial: Estudio del movimiento con Optical Flow
Para identificar el movimiento de los elementos en una secuencia de imágenes analizamos el flujo óptico. Las técnicas para identificar el flujo optico nos permite rastrear las posiciones de un conjunto de puntos de interés en dos imágene sucesivas y con ello, estimar la trayectoria seguida por cada punto, lo que nos da una aproximación del desplazamiento ocurrido entre una imagen y la siguiente.
El algoritmo Lucas-Kanade se usa para estimar las posiciones a la que se desplaza un conjunto de puntos dados. Es decir, no se analizan todos los pixeles de una imagen sino un subconjunto de ellos seleccionados con un extractor de atributos como SIFT o SURF.
En las imágenes anteriores se observan dos frames sucesivos de un timelapse.
Para realizar una primera aproximación al estudio del movimiento, usamos una adaptación del algoritmo LK sobre los puntos de una reticula rectángular de dimensiones fijas. El siguiente procedimiento muestra los pasos básico del procedimiento.
# 1. importar módulos
import cv2, math
import numpy as np
from glob import glob
# 2. obtener una lista de nombres de archivos
in_fns = glob("./TL9/*.png")
# 3. carga una imagen para usar como referencia
old_frame = cv2.imread(in_fns[0], cv2.IMREAD_UNCHANGED)
# 4. convertir a escala de grises
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# canny edge detection
#old_gray = cv2.Canny(old_gray, 30, 30)
# 5. crear la reticula con los puntos a observar
h,w,c = old_frame.shape
pts = []
for i in range (0, w, 50):
for j in range (0, h, 50):
pts.append([[i, j]])
p0 = np.array(pts, dtype="float32")
# 6. parámetros para algoritmo LK
lk_params = dict( winSize = (15,15),
maxLevel = 4,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# 7. carga y convierte una nueva imagen
frame = cv2.imread(in_fns[2], cv2.IMREAD_UNCHANGED)
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# canny
#frame_gray = cv2.Canny(frame_gray, 30, 30)
# 8. crear nueva imagen para dibujar líneas de dirección
vfield = np.zeros_like(old_frame)
# canny
#vfield = np.zeros_like(frame_gray)
# 9. calcular el flujo óptico sobre las imágenes usando los puntos de la retícula
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 10. filtrar aquellos puntos que registraron movimiento
good_new = p1[st==1]
good_old = p0[st==1]
# 11. dibujar líneas desde las posiciones iniciales a las posiciones finales
for i,(new,old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
cv2.line (vfield, (a,b),(c,d), (127, 125, 125), 1)
# 12. mezclar la imagen original con las líneas trazadas
img = cv2.add(frame, vfield)
# canny
#img = cv2.add(frame_gray, vfield)
# 13. muestra la imagen resultante (y espera la tecla ESC para terminar)
while (True):
cv2.imshow('frame', img)
k = cv2.waitKey(10) & 0xff
if k == 27:
break
cv2.destroyAllWindows()
En las imágenes se muestran las líneas de desplazamiento sobre la retícula en la imágen original y aplicando el detector de bordes. Para usar el detector de bordes en el ejemplo anterior, activar las instrucciones comentadas en los pasos 4, 7, 8 y reemplazar la instrucción en 12.