====== OpenCV ======
OpenCV (Open Source Computer Vision) es una biblioteca de funciones centradas en la visión artificial en tiempo real. Creada por Intel en 1999. Aunque esté programada en C++, hay bindings en Python.
* [[https://opencv.org/|Web oficial]]
En Python el módulo se instala ''pip install opencv-python''
===== Imágenes en Jupyter =====
Hasta que se diga lo contrario, el código que se muestra en este apartado es para abrir imágenes y mostrarlas en un cuaderno de [[informatica:software:jupyterlab|Jupyter]].
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# Importamos el módulo de OpenCV
import cv2
# Lectura de una imagen
img = cv2.imread('ruta/imagen.png')
type(img)
# numpy.ndarray
# Mostrar la imagen:
plt.imshow(img)
El módulo de ''OpenCV'' nos ahorra la creación del array de NumPy. Sencillamente cargamos la imagen y la habrá creado como un array de NumPy
La imagen que mostramos mediante Matplotlib se verá rara porque OpenCV lee los canales en un orden diferente:
* Matplotlib: Rojo, Verde y Azul
* OpenCV: Azul, Verde y Rojo.
Para solucionarlo:
# Convertir Blue-Green-Red en Red-Green-Blue
fix_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Lo mostramos:
plt.imshow(fix_img)
Podemos leer una imagen en escala de grises:
img_gray = cv2.imread(''ruta/imagen.png'', cv2.IMREAD_GRAYSCALE)
img_gray.shape
# Ya no tendremos la parte de los canales de color
# (1080,1920)
# Elegimos representarla con el colormap de escala de grises
plt.imshow(img_gray,cmap='gray')
# Elegimos representarla con el colormap de magma
plt.imshow(img_gray,cmap='magma')
==== Redimensionar ====
# 720x480 serán las nuevas dimensiones
new_img = cv2.resize(fix_img, (720,480))
Podemos redimensionar por ratio:
w_ratio = 0.5 # ratio de la anchura, 50 % del original
h_ratio = 0.5 # ratio de la altura, 50 % del original
new_img = cv2.resize(fix_img, (0,0), fix_img, w_ratio, h_ratio)
new_img.shape
# (540, 960, 3)
==== Girar ====
Ponerla al revés:
new_img = cv2.flip(fix_img, 0)
plt.imshow(new_img)
Girarla sobre el eje Y (espejo):
new_img = cv2.flip(fix_img, 1)
plt.imshow(new_img)
Para hacer los dos giros a la vez:
new_img = cv2.flip(fix_img, -1)
plt.imshow(new_img)
==== Guardar ====
cv2.imwrite('/ruta/nueva_imagen.png', fix_img)
===== Definir tamaño lienzo Matplotlib =====
# Dimensiones del lienzo/canvas en pulgadas
fig = plt.figure(figsize=(10,8))
# El lienzo afectará a 1 imagen
ax = fig.add_subplot(111)
# Mostramos una imagen en el nuevo lienzo
ax.imshow(fix_img)
Podemos convertirlo en una función para utilizarlo más cómodamente:
def display(img):
dig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111)
new_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
ax.imgshow(new_img)
# Para usarla:
img = cv2.imread("imagen.jpg")
display(img)
===== Imágenes en OpenCV =====
import cv2
img = cv2.imread('../101/foo.png')
# 'Foobar' será el título de la ventana que mostrará la imagen
cv2.imshow('Foobar', img)
cv2.waitKey()
Se abrirá una ventana con la imagen. La imagen se abrirá en su tamaño original. No se puede redimensionar la ventana, solo cerrarla.
Si el código de arriba genera una ventana que no responde, podemos arreglarlo con el siguiente código modificado:
import cv2
img = cv2.imread('../101/foo.png')
while True:
cv2.imshow('Foobar', img)
// Si hemos esperado al menos 1 milisegundo Y pulsamos la tecla 'q'
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
La ventana se cerrará cuando pulsemos q
===== Dibujar en imágenes =====
Los siguientes ejemplos son para realizar en un cuaderno de Jupyter
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# Creamos una imagen de 512x512 rellenando el array NumPy de ceros para obtener una imagen en negro
blank_img = np.zeros(shape=(512,512,3), dtype=np.int16)
# La mostramos:
plt.imshow(blank_img)
==== Rectángulos ====
Para dibujar un rectángulo sobre esa imagen, utilizamos la función ''rectangle()'':
cv2.rectangle(blank_img, pt1=(384, 10), pt2=(500, 150), color=(0, 255,0), thickness=10)
* ''pt1'': coordenadas de partida del rectángulo, esquina superior izquierda.
* ''pt2'': coordenadas finales, esquina inferior derecha.
* ''color'': color del rectángulo en RGB
* ''thickness'': grosor (en pixels) de la línea del rectángulo
Si quisiéramos hacer un rectángulo justo en el medio para el ejemplo de arriba:
cv2.rectangle(blank_img, pt1=(200, 200), pt2=(300, 300), color=(0, 0, 255), thickness=10)
==== Círculos ====
Para dibujar círculos se utiliza la función ''circle()'':
cv2.circle(img = blank_img, center=(100, 100), radius=50, color=(255,0,0), thickness=8)
* ''center'': coordenadas que marcan el centro del círculo
* ''radius'': radio del círculo en píxeles.
* ''color'': color del círculo en RGB
* ''thickness'': grosor (en pixels) de la línea del círculo.
Si queremos que las figuras tengan relleno, el argumento ''thickness'' tiene que valer -1:
cv2.circle(img = blank_img, center=(400, 400), radius=50, color=(255,0,0), thickness=-1)
El código anterior dibujará un círculo totalmente rojo, tanto en su línea como relleno.
==== Líneas ====
Para dibujar líneas se usa el método ''line()'':
cv2.line(blank_img, pt1=(0, 0), pt2=(512, 512), color=(102,255,255), thickness=5)
==== Texto ====
Primero cargamos una tipografía:
font = cv2.FONT_HERSHEY_SIMPLEX
Dibujamos el texto:
cv2.putText(blank_img, text = 'Hola', org=(10, 500), fontFace = font, fontScale=4, color=(255,255,255), thickness=3, lineType=cv2.LINE_AA)
==== Polígonos ====
Partimos de una imagen nueva:
blank_img = np.zeros(shape=(512,512,3), dtype=np.int32)
Creamos los vértices del polígono:
# Coordenadas del polígono
vertices = np.array([ [100,300], [200,200], [300,300], [200,400] ], dtype=np.int32)
# Como OpenCV espera un array de 3 dimensiones
vertices.shape #(4, 2)
# Recreamos el array para que tenga 3 dimensiones:
pts = vertices.reshape((-1, 1, 2))
pts.shape # (4, 1, 2)
Finalmente creamos el polígono:
cv2.polylines(blank_img, [pts], isClosed=True, color=(255,0,0), thickness=5)
===== Dibujar en imágenes con el ratón =====
Podemos usar //CallBacks// para conectar imágenes con funciones de eventos mediante OpenCV. Esto nos permite interactuar directamente con imágenes o vídeos.
import cv2
import numpy as np
# Función
def draw_circle(event, x, y, flags, param):
# Los argumentos son cubiertos por el método setMouseCallback()
# Si se recibe el evento de pulsación de botón del ratón,
# dibujaremos en la imagen un círculo
if event == cv2.EVENT_LBUTTONDOWN:
cv2.circle(img, (x,y), 100, (0,255,0), -1)
# Si pulsamos con el botón derecho, dibujaremos un
# círculo de otro color
elif event == cv2.EVENT_RBUTTONDOWN:
cv2.circle(img, (x,y), 100, (255, 0, 0), -1)
# Conectaremos la función anterior con la imagen gracias al
# nombre de la ventana y la siguiente función de Callback
cv2.namedWindow(winname = 'foobar')
cv2.setMouseCallback('foobar', draw_circle)
# Creamos una imagen
img = np.zeros(shape=(512,512,3), dtype=np.int8)
# Mostramos la imagen con OpenCV
while True:
cv2.imshow('foobar', img)
# Mostramos la imagen mientras no se pulse 'q'
if cv2.waitKey(20) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
En el siguiente código se muestra cómo dibujar rectángulos sobre una imagen:
import cv2
import numpy as np
# Variables
# True cuando estemos pulsando el botón
# False cuando lo dejemos de pulsar
dibujando = False
ix = -1
iy = -1
# Función
def draw_rectangle(event, x, y, flags, params):
global ix, iy, dibujando
# Si pulsamos el botón izquierdo del ratón
if event == cv2.EVENT_LBUTTONDOWN:
dibujando = True
ix, iy = x, y
# Si movemos el ratón
elif event == cv2.EVENT_MOUSEMOVE:
if dibujando == True:
cv2.rectangle(img, (ix, iy), (x,y), (0,255,0), -1)
# Soltamos el botón izquierdo del ratón
elif event == cv2.EVENT_LBUTTONUP:
dibujando = False
cv2.rectangle(img, (ix,iy), (x,y), (0,255,0), -1)
# Mostrar imagen
# Imagen en negro
img = np.zeros((512,512,3))
cv2.namedWindow(winname='foobar')
cv2.setMouseCallback('foobar', draw_rectangle)
while True:
cv2.imshow('foobar', img)
# Mostramos la imagen mientras no se pulse 'q'
if cv2.waitKey(20) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
===== Procesamiento de imágenes =====
==== Espacio de colores ====
Un espacio de color es un sistema de interpretación del color, es decir, una organización específica de los colores en una imagen o vídeo.
Un modelo de color es un modelo matemático abstracto que describe la forma en la que los colores pueden representarse como tuplas de números. Por ejemplo, en el modelo **RGB** los colores son modelados como combinación de rojo (//red//), verde (//green//) y azul (//blue//).
Otras alternativas son **HSL** (hue, saturation, lightness) y **HSV** (hue, saturation, value). Son más cercanos a cómo la visión humana realmente percibe el color.
El modelo RGB se representa como un cubo y HSL y HSV como un cilindro.
Conversión de espacio de colores con OpenCV:
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
img = cv2.imread(r'D:\apps\python\WPy64-3850\scripts\opencv\lenna.png')
# Si ahora la mostramos con Matplotlib, se verá azulada porque
# OpenCV espera las imágenes en formato azul, verde, rojo
plt.imshow(img)
# Para cambiar el espacio de color:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
Si queremos pasar al modelo HSL o HSV:
# RGB -> HSV
img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
# RGV -> HSL
img = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
==== Umbrales ====
//Thresholding// es un método que permite segmentar una imagen en diferentes partes. Una imagen se convierte en un conjunto de dos valores: blanco o negro.
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
# Leemos una imagen en escala de grises:
img = cv2.imread("/ruta/imagen.png", 0)
# Para mostrarla correctamente con Matplotlib, indicamos el mapa de colores:
plt.imshow(img, cmap='gray')
# Threshold: con el segundo parámetro indicamos el umbral, es decir,
# los que estén por debajo de ese valor serán 0 (negro), y por encima 1 (blanco)
# El último argumento es el tipo de threshold
ret, thresh1 = cv2.threshold(img, 127, 255, cv.THRESH_BINARY)
# Mostramos:
plt.imshow(thresh1, cmap = 'gray')
===== Vídeo =====
==== Conexión con webcam ====
import cv2
# Cogemos la cámara por defecto
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Mostramos la imagen
while True:
# El frame será cada una de las imágenes
# que va capturando la cámara
ret, frame = cap.read()
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Dejamos de capturar el vídeo
cap.release()
cv2.destroyAllWindows()
Si queremos guardar el stream en un vídeo, primero crearemos el fichero indicando su ruta, el códec que usaremos, los frames por segundo y las dimensiones del vídeo:
# Guardar el stream en un fichero de vídeo
# Windows -> *'DIVX'
# MacOS, Linux -> *'XVID'
writer = cv2.VideoWriter("video-cap.mp4", cv2.VideoWriter_fourcc(*'DIVX'), 20, (width, height))
Luego tendremos que ir guardando los frames que vayamos capturando de la cámara:
# Mostramos la imagen
while True:
# El frame será cada una de las imágenes
# que va capturando la cámara
ret, frame = cap.read()
# Operaciones
writer.write(frame)
Código final:
import cv2
# Cogemos la cámara por defecto
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Guardar el stream en un fichero de vídeo
# Windows -> *'DIVX'
# MacOS, Linux -> *'XVID'
writer = cv2.VideoWriter("video-cap.mp4", cv2.VideoWriter_fourcc(*'DIVX'), 20, (width, height))
# Mostramos la imagen
while True:
# El frame será cada una de las imágenes
# que va capturando la cámara
ret, frame = cap.read()
# Operaciones
writer.write(frame)
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Dejamos de capturar el vídeo
cap.release()
cv2.destroyAllWindows()
* [[https://docs.opencv.org/master/dd/d43/tutorial_py_video_display.html|
Getting Started with Videos]]
==== Reproducir vídeos ====
import cv2
import time
cap = cv2.VideoCapture("video-cap.mp4")
if cap.isOpened() == False:
print("ERROR: Archivo no encontrado o codec incorrecto")
while cap.isOpened():
ret, frame = cap.read()
if ret == True:
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
cap.release()
cv2.destroyAllWindows()
La reproducción será muy rápida ya que no está pensado para ser visto por humanos, sino que la función hace que el ordenador lea todos los frames del vídeo a la velocidad que sea capaz. Esto es útil si queremos manipular los vídeos sin esperar que se reproduzcan a velocidad humana.
Si queremos reproducirlo a la misma velocidad que fue grabado, añadimos un par de instrucciones:
import cv2
import time
cap = cv2.VideoCapture("video-cap.mp4")
if cap.isOpened() == False:
print("ERROR: Archivo no encontrado o codec incorrecto")
while cap.isOpened():
ret, frame = cap.read()
if ret == True:
# Según sea el FPS del vídeo. Para este ejemplo,
# Hacemos que se muestren 20 imágenes por segundo
time.sleep(1/20)
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
cap.release()
cv2.destroyAllWindows()
==== Dibujando en streaming ====
El procedimiento es similar a dibujar sobre una imagen, solo que lo hacemos sobre un frame.
import cv2
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Esquina superior izquierda
x = width // 2 # Nos aseguramos de obtener un entero
y = height // 2
# anchura y altura del rectángulo
w = width // 4
h = height // 4
# Esquina inferior derecha: x + w, y + h
while True:
ret, frame = cap.read()
cv2.rectangle(frame, (x,y), (x+w,y+h), color=(0,0,255), thickness=4)
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
Si queremos dibujar mientras el vídeo se reproduce:
import cv2
# Función callback
def draw_rectangle(event, x, y, flags, params):
global pt1, pt2, topLeft_clicked, botRight_clicked
if event == cv2.EVENT_LBUTTONDOWN:
# Reiniciamos / borramos el rectángulo cuando
# ya hayamos pulsado en dos puntos para hacer
# el rectángulo
if topLeft_clicked == True and botRight_clicked == True:
pt1 = (0,0)
pt2 = (0,0)
topLeft_clicked = False
botRight_clicked = False
if topLeft_clicked == False:
pt1 = (x,y)
topLeft_clicked = True
elif botRight_clicked == False:
pt2 = (x,y)
botRight_clicked = True
# Variables globales
pt1 = (0,0)
pt2 = (0,0)
# Seguimiento de las pulsaciones del ratón
topLeft_clicked = False
botRight_clicked = False
# Conexión con la función de callback
cap = cv2.VideoCapture(0)
cv2.namedWindow('Test')
cv2.setMouseCallback('Test', draw_rectangle)
while True:
ret, frame = cap.read()
# Dibujamos sobre el frame basado en las variables globales
if topLeft_clicked:
cv2.circle(frame, center=pt1, radius=5, color=(0,0,255), thickness=-1)
if topLeft_clicked and botRight_clicked:
cv2.rectangle(frame, pt1, pt2, color=(0,0,255), thickness=3)
cv2.imshow('Test', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
===== Detección de objetos =====
Varios métodos:
* Coincidencias con modelos (//template matching//): busca una copia exacta de una imagen en otra imagen.
* Detección de esquinas: búsqueda de esquinas en las imágenes
* Detección de filos (//edge detection//): búsqueda de los contornos de los objetos.
* Detección de rejillas (//grid detection//): combinación de los dos anteriores métodos.
* Detección de contornos (//contour detection//):
* Coincidencia de características (//feature matching//)
* Algoritmo Watershed:
* Haar cascades: para la detección de caras y ojos.
==== Template matching ====
Es la forma más simple de detección de objetos. Escanea una imagen más grande con una plantilla para comprobar si hay coincidencia.
for m in metodos:
# Creamos una copia
full_copy = full.copy()
metodo = eval(m)
# Template matching
res = cv2.matchTemplate(full_copy, face, metodo)
# A través del mapa de calor (que indica la coincidencia)
# obtenemos varios valores
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# Para estos dos métodos, el mapa de calor funciona diferente
if metodo in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc # (x, y)
else:
top_left = max_loc
height, width, channels = face.shape
bottom_right = (top_left[0]+width, top_left[1]+height)
cv2.rectangle(full_copy, top_left, bottom_right, (255,0,0), 10)
# Mostramos un par de imágenes:
# el mapa de calor de la detección y la detección
plt.subplot(121)
plt.imshow(res)
plt.title('Mapa de calor')
plt.subplot(122)
plt.imshow(full_copy)
plt.title("Detección de plantilla")
# Título del método usado:
plt.suptitle('Método ' + m)
plt.show()
print("\n")
print("\n")
==== Detección de caras ====
Haar Cascades, componente clave del framwork de detección de objetos Viola-Jones.
Con este método detectaremos si existe alguna cara en una imagen, aunque no a quién pertenece ya que eso es parte de deep learning.
El algoritomo Haar cascades en lugar de recorrer la imagen en busca de una imagen, pasa una cascada de clasificadores.
Pasos:
* Imagen con una cara mirando hacia la cámara
* Convertir la imagen a escala de grises
* Comienza la búsqueda de características Haar Cascade
* Primero los ojos
* Luego el tabique nasal
* ...
La búsqueda se hace con la idea de características de línea y de filo/margen
Cuando la imagen pasa todos los clasificadores, se puede concluir que se ha detectado una cara.
El problema de este método es que necesita un conjunto muy grande de información. La parte buena es que OpenCV [[https://github.com/opencv/opencv/tree/master/data/haarcascades|viene con ficheros XML pre-entrenados para Haar]].
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# Leemos una imagen en escala de grises
nadia = cv2.imread("nadia.jpg", 0)
plt.imshow(nadia, cmap='gray')
# El siguiente clasificador es una lista de 6000 características que definen una cara de frente
face_cascade = cv2.CascadeClassifier("../101/DATA/haarcascades/haarcascade_frontalface_default.xml")
def detect_face(img):
face_img = img.copy()
face_rects = face_cascade.detectMultiScale(face_img)
# El método anterior devuelve un objeto que utilizaremos
# para dibujar un rectángulo alrededor de la cara
for (x,y,w,h) in face_rects:
cv2.rectangle(face_img,(x,y),(x+w,y+h), (255,255,255), 10)
return face_img
result = detect_face(nadia)
# Mostramos la imagen resultante con el rectángulo sobre la cara
plt.imshow(result, cmap='gray')
Si no se detectasen las caras correctamente o hubiese duplicidades, se puede jugar con el factor de escala o el parámetro de vecinos:
# Mejora
def adj_detect_face(img):
face_img = img.copy()
face_rects = face_cascade.detectMultiScale(face_img, scaleFactor = 1.2, minNeighbors=5)
# El método anterior devuelve un objeto que utilizaremos
# para dibujar un rectángulo alrededor de la cara
for (x,y,w,h) in face_rects:
cv2.rectangle(face_img,(x,y),(x+w,y+h), (255,255,255), 10)
return face_img
==== Detección de ojos ====
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
nadia = cv2.imread("../101/DATA/Nadia_Murad.jpg", 0)
# Utilizamos otro clasificador
eye_cascade = cv2.CascadeClassifier("../101/DATA/haarcascades/haarcascade_eye.xml")
# Función para detectar los ojos y pintar un recuadro
def detect_eyes(img):
face_img = img.copy()
eyes_rects = eye_cascade.detectMultiScale(face_img)
# El método anterior devuelve un objeto que utilizaremos
# para dibujar un rectángulo alrededor de los ojos
for (x,y,w,h) in eyes_rects:
cv2.rectangle(face_img,(x,y),(x+w,y+h), (255,255,255), 10)
return face_img
result = detect_eyes(nadia)
plt.imshow(result, cmap='gray')
Igual que en la detección de caras, podemos jugar con los argumentos del método ''detectMultiScale'' para mejorar los resultados
==== Detección de caras en vídeo ====
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# Capturamos vídeo de la primera fuente del PC
cap = cv2.VideoCapture(0)
# Función de detección de imágenes
def detect_face(img):
face_img = img.copy()
face_rects = face_cascade.detectMultiScale(face_img)
for (x,y,w,h) in face_rects:
cv2.rectangle(face_img,(x,y),(x+w,y+h), (255,255,255), 10)
return face_img
while True:
ret, frame = cap.read(0)
frame = detect_face(frame)
cv2.imshow('Video Face Detector', frame)
k = cv2.waitKey(1)
if k == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
===== Seguimiento de objetos =====
Técnicas:
* Optical Flow
* MeanShift y CamShift
==== Optical Flow ====
Los métodos de OpenCV parten de un conjunto de puntos y un frame y luego intenta encontrar esos puntos en el siguiente frame.
Dos algoritmos:
* Lucas-Kanade
* Gunner Farneback: si queremos seguir todos los puntos de un vídeo.
cap = cv2.VideoCapture(0)
ret, prev_frame = cap.read()
# Primer frame
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2RGB)
# Puntos a seguir
prevPts = cv2.goodFeaturesToTrack(prev_gray, mask = None, **corner_track_params)
mask = np.zeros_like(prev_frame)
while True:
ret, frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Calculamos el optical flow entre frames
nextPts, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, frame_gray, prevPts, None, **lk_params)
good_new = nextPts[status == 1]
good_prev = prevPts[status == 1]
for i, (new, prev) in enumerate(zip(good_new, good_prev)):
x_new, y_new = new.ravel()
x_prev, y_prev = prev.ravel()
mask = cv2.line(mask, (x_new, y_new), (x_prev, y_prev), (0,255,0),3)
frame = cv2.circle(frame, (x_new, y_new), 8, (0,0,255), -1)
img = csv2.add(frame, mask)
cv2.imshow('tracking', img)
k = cv2.waitKey(30) & 0xFF
if k == ord('q'):
break;
prev_grey = frame_gray.copy()
prevPts = good_new.reshape(-1, 1, 2)
cv2.destroyAllWindows()
cap.release()