Friday, October 26, 2018

Detalles del código, uso de threads

Dentro de la librerías de opencv y imutils para python tenemos un gran surtido de funciones para leer el vídeo de un stream, probablemente la más utilizada para estas cosas está dentro de la librería cv2.VideoCapture. La función .read es la que se usa para este cometido, pero tiene un problema, es una función propiamente bloqueante, ya que no solo lee el stream, si no que lo tiene que decodificar, no importa en el formato en el que esté, todo este proceso lo hace en el thread principal, con lo cual mientras no acabe de leer y decodificar no puede hacer otra acción.

Esto no es un problema si disponemos de un hardware suficientemente potente, pero en una RPi necesitamos algo más eficiente si queremos tener resultados aceptables. La solución es usar threads, un thread que se encarge de la lectura y procesado del vídeo y el principal que se encarga de hacer la detección de movimiento. La diferencia entre hacerlo así o hacer ambas tareas en el mismo thread es significativa, (se verá la diferencia en el siguiente post). Existe una librería en imutils que hace la lectura del vídeo en otro thread, FileVideoStream.

Veamos un ejemplo de código.

# start the file video stream thread and allow the buffer to
# start to fill
print("[INFO] starting video file thread...")
fvs = FileVideoStream(args["video"]).start()
time.sleep(1.0) 


El código es bastante sencillo, se pasa la dirección del video como argumento y se inicia el thread con start(), se le da un segundo para que llene el buffer con frames del stream, finalmente leemos en el bucle principal.

while (1):
    frame = fvs.read()


En este caso concreto se utiliza también una cola a modo de buffer para frames, esto lo utiliza internamente la librería FileVideoStream, se puede configurar el tamaño de la misma, por defecto está a 200 frames. Se puede ver el uso de la misma con Q.qsize().

Lo siguiente es toda la lógica de detección del movimiento, irá en el thread principal dentro del bucle.

# grab the frame from the threaded video file stream, resize
# it, and convert it to grayscale (while still retaining 3
# channels)
frame = fvs.read()
frame = imutils.resize(frame, width=450)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = np.dstack([frame, frame, frame]) 


Ahora toca aplicar la función de la que hablamos en el anterior post, definimos un fondo fuera del bucle.

bg = cv2.bgsegm.createBackgroundSubtractorMOG()  

Dentro del bucle.

motion = bg.apply(frame, learningRate=0.005) 
kernel = np.ones((3, 3), np.uint8) 
motion = cv2.morphologyEx(motion, cv2.MORPH_CLOSE, kernel, iterations=1) 
motion = cv2.morphologyEx(motion, cv2.MORPH_OPEN, kernel, iterations=1) 
motion = cv2.dilate(motion,kernel,iterations = 1) 

Extrae una imagen en blanco y negro sin el fondo.
Crea una matriz de 3x3.
Las líneas siguientes eliminan ruído de la imagen con bloques de 3x3.

El siguiente paso es encontrar los contornos con cambios (movimiento) en el frame y pintar un rectangulo para identificarlos.

contours = cv2.findContours(motion, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = contours[0] if imutils.is_cv2() else contours[1]

for c in cnts:
   # if the contour is too small, ignore it
   if cv2.contourArea(c) < 5000:
      continue
   # compute the bounding box for the contour, draw it on the frame,
   # and update the text
   (x, y, w, h) = cv2.boundingRect(c)
   cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)


Finalmente mostramos el frame con los recuadros que identifican el movimiento en el vídeo

cv2.imshow("Frame", frame)


Este es el funcionamiento básico del algoritmo, podeis encontrar el código completo en github. En el siguiente post analizaremos los resultados obtenidos, principalmente analizaremos como afecta nuestro algoritmo al rendimiento del procesado del vídeo y cual es el fps óptimo para tener el vídeo en tiempo real.

No comments:

Post a Comment