? DETECCIÓN DE MOVIMIENTO (Con sustracción de imágenes) – OpenCV y Python

Por Administrador

Bienvenido y bienvenida a un nuevo post. ¿Quisieras aprender como realizar un detector de movimiento bastante sencillo?, pues has llegado a la web correcta. En este post veremos como realizar detección de movimiento aplicando sustracción de imágenes usando OpenCV y Python.

Importante: Los programas realizados en estos posts y en los videos de youtube también los puedes encontrar en mi repositorio en gitHub. Así que si quieres usar el mismo video que yo usé te recomiendo que visites el repositorio.

El proceso que vamos a realizar será el siguiente:

  1. Leer un video o realizar video streaming
  2. Transformar de BGR a escala de grises
  3. Conseguir la imagen del fondo y exterior, para restarlas con cv2.absdiff
  4. Aplicar umbralización simple
  5. Encontrar los contornos
  6. Discriminar los contornos encontrados de acuerdo a su tamaño y encerrar en un rectángulo a los que superen cierta área

Leer un video o realizar un video streaming

Para realizar esta aplicación he preparado un video (revisa mi repositorio en gitHub), sin embargo también podrías realizarlo en tiempo real. Es decir aplicar el detector en este momento sin tener que cargar ningún otro video. Para realizar este proceso (en caso de que esté un poco confuso), te recomiendo revisar mi post sobre: Capturar, guardar y leer un video en OpenCV y Python.

import cv2
import numpy as np

video = cv2.VideoCapture('Video.mp4')

i = 0
while True:
  ret, frame = video.read()
  if ret == False: break

Línea 1 y 2: Importamos los paquetes necesarios que vamos a usar: OpenCV y numpy.

Línea 4: Especificamos si vamos a leer un video (especifindo el nombre del video, en este caso Video.mp4), si quisieramos emplear video streming, en vez del nombre del video, debemos digitar cv2.VideoCapture(0).

Línea 6: Iniciamos un contador  con i=0. Este nos servirá para captar el fondo de la imagen, pero ya lo veremos más adelante.

Línea 7 a 9: Leemos las imagenes del video que se almacenarán en frame, y en caso de que no se haya podido tomar una imagen, es decir si ret == false, se rompe el ciclo while.

Transformar de BGR a escala de grises

Para tranasformar de BGR a escala de grises unsamos la función cvcv2.cvtColor.

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

Conseguir la imagen del fondo y exterior, para restarlas con cv2.absdiff

Ahora usaremos el contador que habíamos declarado en la línea 6, este nos permitirá guardar una imagen del fondo de la escena a la veinteava iteración con el objetivo de asegurar que se encienda correctamente la cámara y evitar tomar una imagen oscura en un principio. Cuando la iteración es mayor a 20 en cambio podremos seguir con el proceso de sustracción de imágenes.

if i == 20:
  bgGray = gray
if i > 20:
  dif = cv2.absdiff(gray, bgGray)

Línea 11 y 12: Cuando el contador llega a 20, entonces se graba la imagen gray en bgGray, que será la imagen del fondo de la escena. Esta nos servirá para restarla de la imagen actual.

Línea 13 y 14: Una vez que el contador es mayor a 20, se procede a usar cv2.absdiff, para poder restar la imagen actual y la del fondo. Recuerda que esta función realiza la sustracción y a su resultado se le aplica valor absoluto. Para más información puedes visitar este post.

Aplicar umbralización simple

Tenemos la imagen dif, a esta debemos transformarla a binaria, es decir a una imagen en blanco y negro, para ello usaremos cv2.threshold.

_, th = cv2.threshold(dif, 40, 255, cv2.THRESH_BINARY)

Esta función con cv2.THRESH_BINARY nos indica que los pixeles con un valor mayor a 40 se les asignará 255. De este modo obtenemos la imagen binaria, donde el área que se muestre en blanco representará el movimiento. Ahora solo nos queda encerrar dicha área.

Encontrar los contornos

Para encontrar los contornos usaremos cv2.findContours.

# Para OpenCV 3
#_, cnts, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Para OpenCV 4
cnts, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#cv2.drawContours(frame, cnts, -1, (0,0,255),2)	

Recuerda que puedes usar las líneas 17 o 19 dependiendo de la versión de OpenCV que tengas instalado. Además si quisieras dibujar los contornos encontrados podrías aplicar la línea 20.

Discriminar los contornos encontrados de acuerdo a su tamaño y encerrar en un rectángulo a los que superen cierta área

Una vez que tenemos todos los contornos encontrados, es necesario descartar aquellos que sean muy pequeños y que no representen movimiento. Para ello es necesario estudiar cada uno de estos contornos por lo que emplearemos el bucle for.

for c in cnts:
  area = cv2.contourArea(c)
  if area > 9000:
    x,y,w,h = cv2.boundingRect(c)
    cv2.rectangle(frame, (x,y), (x+w,y+h),(0,255,0),2)

Línea 22: Analizaremos cada contorno dentro de cnts.

Línea 23: Se emplea la función cv2.contourArea, para determinar el área en pixeles del contorno.

Línea 24 a 26: Si el área es mayor a 9000, entonces se aplica cv2.boundingRect que devuelve las coordenadas x e y, a más del ancho y alto del contorno. Luego para dibujar este contorno usaremos la información de la línea 25 y dibujaremos un rectángulo con cv2.rectangle.

Para finalizar…

  cv2.imshow('Frame',frame)

  i = i+1
  if cv2.waitKey(30) & 0xFF == ord ('q'):
    break
video.release()

Línea 28: Visualizamos el proceso realizado en frame.

Línea 30: Aumentamos en 1 el contador.

Línea 31 a 33: Si se preciosa la tecla ‘q’, se sale del proceso y finaliza el video. (Si estas leyendo un video te recomiendo que pongas 30 en vez de 1 en cv2.waitKey en caso de que este se vea muy rápido).

Programación completa

import cv2
import numpy as np

video = cv2.VideoCapture('Video.mp4')

i = 0
while True:
  ret, frame = video.read()
  if ret == False: break
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  if i == 20:
    bgGray = gray
  if i > 20:
    dif = cv2.absdiff(gray, bgGray)
    _, th = cv2.threshold(dif, 40, 255, cv2.THRESH_BINARY)
    # Para OpenCV 3
    #_, cnts, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # Para OpenCV 4
    cnts, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    #cv2.drawContours(frame, cnts, -1, (0,0,255),2)		
    
    for c in cnts:
      area = cv2.contourArea(c)
      if area > 9000:
        x,y,w,h = cv2.boundingRect(c)
        cv2.rectangle(frame, (x,y), (x+w,y+h),(0,255,0),2)

  cv2.imshow('Frame',frame)

  i = i+1
  if cv2.waitKey(30) & 0xFF == ord ('q'):
    break
video.release()

Resultado

Recomendaciones

Hay dos aspectos muy importantes que deben tomarse en cuenta en este pequeño proyecto.

El primero es el movimiento de la cámara, una vez que sea almacenada la imagen del fondo de la escena, se restará con la imagen actual, por lo tanto no es recomendable mover la cámara una vez que se emplee esta aplicación.

La iluminación también es un factor muy importante, ya que como se restan los valores de los pixeles puede que a distinta iluminación se perciba como movimiento, por lo tanto es mejor emplear esta en un ambiente controlado.