Programar fácil con Arduino

Programar fácil con Arduino, entiende cómo funciona el mundo.

  • Blog
  • ¿Quién soy?
  • Podcast
  • Curso Arduino [GRATIS]
  • Curso Domótica [GRATIS]
  • Acceder
Usted está aquí: Inicio / Blog / Vision Artificial / Detección de movimiento con OpenCV y Python

Detección de movimiento con OpenCV y Python

Comentarios(10)
Luis del Valle Hernández

En este artículo voy a explicar cómo podemos hacer la detección de movimiento con OpenCV y Python. Antes de comenzar debes tener claro que ventajas nos aporta la visión artificial y cómo podemos empezar a programar y desarrollar nuestras propias aplicaciones de visión artificial. El primer paso es preparar el sistema. Yo utilizo una plataforma donde tienes todo disponible en el mismo instalador, Anaconda. También hay que instalar la librería OpenCV para Python. Entiendo que a partir de ahora ya tienes todo dispuesto para empezar a programar así que comencemos.

Indice de contenidos

  • 1 Detección de movimiento con OpenCV y Python
  • 2 Substracción de fondo
  • 3 Fases del proceso de detección de movimiento
  • 4 Código
  • 5 Conclusión

Detección de movimiento con OpenCV y Python

En muchas aplicaciones basadas en la visión artificial, se utiliza la detección de movimiento. Por ejemplo, cuando queremos contar las personas que pasan por un determinado lugar o cuántos coches han pasado por un peaje. En todos estos casos, lo primero que tenemos que hacer es extraer las personas o vehículos que hay en la escena.

Existen diferentes técnicas, métodos o algoritmos que posibilitan la detección de movimiento. Al igual que en otras materias, en la visión artificial no hay casos genéricos. Dependerá de cada situación usar uno u otro. En este artículo te voy a mostrar 4 métodos para hacer la detección de movimiento con OpenCV y Python.

Los métodos que te voy a explicar a continuación están basados en la substracción del fondo, una técnica muy común en la detección de movimiento.

El objeto de este artículo no es entrar en detalle en la base matemática y científica que hay detrás de esta técnica. En el mundo en el que vivimos, el tiempo es un bien preciado. Voy a intentar ser práctico y te voy a dar los fundamentos de la sustracción de fondo y como utilizar esta técnica para la detección de movimiento con OpenCV y Python.

Substracción de fondo

La substracción de fondo consiste en tomar una imagen de la escena sin movimiento y restar los sucesivos fotogramas que vamos obteniendo de un vídeo. A la imagen sin movimiento se le llama fondo o segundo plano (background en inglés). El fotograma que vamos a analizar sería el primer plano (foreground en inglés). Por lo tanto, tenemos un fondo al que vamos restando los diferentes fotogramas.

Sustraccion de fondo

El resultado, como ves en la imagen, es una escena con fondo negro. Donde se detecta movimiento, el color es diferente. Es una técnica muy sencilla. No requiere que el sujeto u objeto que se está intentando detectar deba tener algo que lo identifique como un sensor, baliza o traje especial. Por el contrario, la substracción de fondo es muy sensible a los cambios de iluminación como las sombras o los cambios producidos por la luz natural. Otra desventaja es que si el sujeto u objeto tiene un color parecido al del fondo, o no se detecta el movimiento o se detecta mal.

Dentro de la técnica de la substracción de fondo, existen dos modalidades. Dependerá de cómo se obtiene el fondo o segundo plano, con imagen de referencia o con fotogramas anteriores.

Substracción con imagen de referencia

Esta modalidad consiste en tener una imagen de referencia donde no haya ningún objeto en movimiento. A partir de esta imagen se obtienen los elementos en movimiento restando cada fotograma con la imagen de referencia. Normalmente se coge el primer fotograma de una secuencia de vídeo.

Es muy sensible a los cambios de luz. Imagínate que tomas la imagen de referencia en una habitación con luz natural. A las 10:00 de la mañana habrá unas condiciones de luz. Pero a las 18:00 de la tarde habrá otras. También es muy sensible a los movimientos de la cámara. Un movimiento muy pequeño puede hacer que se detecten falsos positivos en la escena. Por el contrario, este método funciona muy bien en entornos con iluminación controlada y detecta perfectamente la silueta de los objetos en movimiento.

Substracción con fotogramas anteriores

En esta modalidad, el fondo o segundo plano se obtiene de los fotogramas anteriores. La técnica consiste en tomar una imagen de referencia, dejar pasar un tiempo aplicando un retardo y empezar a comparar con los fotogramas que vamos obteniendo. Este retardo dependerá de factores como la velocidad de los objetos.

Una de las mayores desventajas es que si el objeto o la persona en movimiento se quedan quietos, no se detecta. No es capaz de detectar siluetas. Sin embargo, es un método bastante robusto a los cambios de iluminación y a los movimientos de cámara. Consigue estabilizarse pasado un tiempo.

Fases del proceso de detección de movimiento

Ahora veremos cuales son las fases que debemos seguir para crear un algoritmo que nos permita la detección de movimiento con OpenCV. El proceso realizará varias tareas.

  • Conversión a escala de grises y eliminación de ruido.
  • Operación de substracción entre el segundo plano y el primer plano.
  • Aplicar un umbral a la imagen resultado de la resta.
  • Detección de contornos o blobs.

Algo muy común en las ciencias de la computación, en particular en la visión artificial, son los parámetros. Cada parámetro puede tener un rango de valores. El valor correcto dependerá de muchos factores. Está en nuestras manos adaptar cada valor a una situación concreta.

Existen diferentes técnicas que nos permiten estimar cual es el conjunto de valores que nos da mejor resultados. Una de ellas es Simulated Annealing. No te voy a hablar de esta técnica ya que es muy compleja y no es el objeto de este artículo, pero debes tenerlo en cuenta cuando tu objetivo es hacer una aplicación profesional. Es la única manera de ajustar los parámetros para obtener unos resultados aceptables.

Conversión a escala de grises y eliminación de ruido

Antes de realizar ninguna operación con las imágenes, es conveniente convertir a escala de grises. Resulta menos complejo y más óptimo trabajar con este tipo de imágenes. Por otro lado hay que minimizar el ruido provocado por la propia cámara y por la iluminación. Esto se hace a través de promediar cada píxel con sus vecinos. Se conoce comúnmente como suavizado.

OpenCV proporciona los métodos que nos permiten convertir a escala de grises y suavizar la imagen.

1
2
# Convertir la imagen a escala de grises
gris = cv2.cvtColor(fotograma, cv2.COLOR_BGR2GRAY)
1
2
# Suavizado de la imagen
gris = cv2.GaussianBlur(gris, (21, 21), 0)

Substracción entre el segundo plano y el primer plano

A la hora de restar una imagen con otra, debemos tener en cuenta que podemos obtener valores negativos. Para evitar esto lo que haremos será restar y obtener los valores absolutos de cada píxel. OpenCV nos proporciona un método para hacer esto.

1
2
# Resta absoluta
resta = cv2.absdiff(fondo, gris)

Aplicación de umbral

En esta parte del proceso lo que hacemos es quedarnos con aquellos píxeles que superen un umbral. El objetivo es binarizar la imagen es decir, tener dos posibles valores. Todos aquellos que superen el umbral serán píxeles blancos y los que no lo superen serán píxeles negros. Esto nos servirá para seleccionar el objeto en movimiento.

En OpenCV existe un método para aplicar un umbral.

1
2
# Aplicamos el umbral a la imagen
umbral = cv2.threshold(resta, 25, 255, cv2.THRESH_BINARY)

Detección de contornos o blobs

Una vez que tenemos la imagen con píxeles blancos o negros, tenemos que detectar los contornos o blobs. Un blob es un conjunto de píxeles que están conectados entre si es decir, tiene vecinos con el mismo valor. Cuando hablamos de vecinos es que estén al lado. En la siguiente imagen te muestro un ejemplo.

Detectar contornos

Se han detectado 3 blobs o contornos. Todos ellos tienen píxeles que están conectados entre si de alguna manera.

En OpenCV hay un método que busca los contornos de una imagen.

1
2
# Buscamos contorno en la imagen
im, contornos, hierarchy = cv2.findContours(umbral,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

Por último y dentro de esta fase, tenemos que descartar los contornos más pequeños. Son aquellos que han pasado del suavizado y de la aplicación del umbral. Eso lo hacemos determinando un área mínima por el cual decidimos que son susceptibles de ser analizados.

Código

Ahora toca programar. Con todo lo que hemos visto podemos llegar a crear una aplicación para la detección de movimiento con OpenCV y Python. En este caso vamos a empezar con uno muy ligero, básico. Esto nos permitirá utilizarlo en aplicaciones donde los requerimientos son mínimos. Una posible aplicación sería con una Raspberry Pi que nos permita detectar la presencia de personas. Esto será otro artículo del blog.

Algoritmo básico de la detección de movimiento con OpenCV y Python

A continuación te dejo el código de este algoritmo basado en todo lo anteriormente explicado. Nos servirá para la detección de movimiento con OpenCV y Python básico.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# Importamos las librerías necesarias
import numpy as np
import cv2
import time
 
# Cargamos el vídeo
camara = cv2.VideoCapture("detector-movimiento-opencv.mp4")
 
# Inicializamos el primer frame a vacío.
# Nos servirá para obtener el fondo
fondo = None
 
# Recorremos todos los frames
while True:
# Obtenemos el frame
(grabbed, frame) = camara.read()
 
# Si hemos llegado al final del vídeo salimos
if not grabbed:
break
 
# Convertimos a escala de grises
gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 
# Aplicamos suavizado para eliminar ruido
gris = cv2.GaussianBlur(gris, (21, 21), 0)
 
# Si todavía no hemos obtenido el fondo, lo obtenemos
# Será el primer frame que obtengamos
if fondo is None:
fondo = gris
continue
 
# Calculo de la diferencia entre el fondo y el frame actual
resta = cv2.absdiff(fondo, gris)
 
# Aplicamos un umbral
umbral = cv2.threshold(resta, 25, 255, cv2.THRESH_BINARY)[1]
 
# Dilatamos el umbral para tapar agujeros
umbral = cv2.dilate(umbral, None, iterations=2)
 
# Copiamos el umbral para detectar los contornos
contornosimg = umbral.copy()
 
# Buscamos contorno en la imagen
im, contornos, hierarchy = cv2.findContours(contornosimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
 
# Recorremos todos los contornos encontrados
for c in contornos:
# Eliminamos los contornos más pequeños
if cv2.contourArea(c) < 500:
continue
 
# Obtenemos el bounds del contorno, el rectángulo mayor que engloba al contorno
(x, y, w, h) = cv2.boundingRect(c)
# Dibujamos el rectángulo del bounds
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
 
# Mostramos las imágenes de la cámara, el umbral y la resta
cv2.imshow("Camara", frame)
cv2.imshow("Umbral", umbral)
cv2.imshow("Resta", resta)
cv2.imshow("Contorno", contornosimg)
 
# Capturamos una tecla para salir
key = cv2.waitKey(1) & 0xFF
 
# Tiempo de espera para que se vea bien
time.sleep(0.015)
 
# Si ha pulsado la letra s, salimos
if key == ord("s"):
break
 
# Liberamos la cámara y cerramos todas las ventanas
camara.release()
cv2.destroyAllWindows()

Puedes ver el resultado a continuación.

Métodos para la detección de movimiento con OpenCV y Python

Y como todo en la vida, existe un camino corto y en principio, «más sencillo». Este camino consiste en utilizar los métodos que nos proporciona OpenCV para la detección de movimiento. Están basados en trabajos realizados por investigadores y que se han incorporado al core de OpenCV. Dentro de la detección de movimiento con OpenCV hay varios algoritmos. Los tres que veremos aquí son:

  • BackgroundSubtractorMOG.
  • BackgroundSubtractorMOG2.
  • BackgroundSubtractorKNN

BackgroundSubtractorMOG

Detectar la detección de movimiento con OpenCV y Python utilizando este método, puede ser sencillo con respecto a la programación. Lo realmente complejo es dar con los parámetros idóneos para tu proyecto. Aquí no queda otra que leer el paper o documento científico en el que se basó.

1
cv2.bgsegm.createBackgroundSubtractorMOG(history=200, nmixtures=5, backgroundRatio=0.7, noiseSigma=0)

Donde:

  • history: tamaño del histórico.
  • nmistures: número de mezclas Gaussianas.
  • backgroundRatio: relación de fondo.
  • noiseSigma: potencia del ruido (desviación estándar de la luminosidad o de cada canal de color). 0 significa que obtiene un valor automático.

A continuación te dejo el código de ejemplo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Importación de librerías
import numpy as np
import cv2
 
# Capturamos el vídeo
cap = cv2.VideoCapture('detector-movimiento-opencv.mp4')
 
# Llamada al método
fgbg = cv2.bgsegm.createBackgroundSubtractorMOG(history=200, nmixtures=5, backgroundRatio=0.7, noiseSigma=0)
 
# Deshabilitamos OpenCL, si no hacemos esto no funciona
cv2.ocl.setUseOpenCL(False)
 
while(1):
# Leemos el siguiente frame
ret, frame = cap.read()
 
# Si hemos llegado al final del vídeo salimos
if not ret:
break
 
# Aplicamos el algoritmo
fgmask = fgbg.apply(frame)
 
# Copiamos el umbral para detectar los contornos
contornosimg = fgmask.copy()
 
# Buscamos contorno en la imagen
im, contornos, hierarchy = cv2.findContours(contornosimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
 
# Recorremos todos los contornos encontrados
for c in contornos:
# Eliminamos los contornos más pequeños
if cv2.contourArea(c) < 500:
continue
 
# Obtenemos el bounds del contorno, el rectángulo mayor que engloba al contorno
(x, y, w, h) = cv2.boundingRect(c)
# Dibujamos el rectángulo del bounds
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
 
# Mostramos las capturas
cv2.imshow('Camara',frame)
cv2.imshow('Umbral',fgmask)
cv2.imshow('Contornos',contornosimg)
 
# Sentencias para salir, pulsa 's' y sale
k = cv2.waitKey(30) & 0xff
if k == ord("s"):
break
 
# Liberamos la cámara y cerramos todas las ventanas
cap.release()
cv2.destroyAllWindows()

Puedes ver el resultado a continuación.

BackgroundSubtractorMOG2

Es una variante del método anterior y está basado en la misma idea. Ya te he comentado que en la detección de movimiento con OpenCV es muy importante ajustar los parámetros. En el método que vamos a ver ahora no puede ser de otra manera. Para profundizar más en cómo funciona este método, puedes leer la documentación científica en la que se basó. Documento 1 y documento 2.

1
cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=50, detectShadows=False)

Donde:

  • history: tamaño del histórico.
  • varThreshold: umbral de la distancia de Mahalanobis al cuadrado entre el píxel y el modelo para decidir si un píxel está bien descrito por el fondo.
  • detectShadows: con un valor verdadero (True) detecta las sombras. Esto reduce la velocidad así que si no se utiliza ponerlo a falso.

A continuación te dejo el código de ejemplo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Importación de librerías
import numpy as np
import cv2
 
# Capturamos el vídeo
cap = cv2.VideoCapture('detector-movimiento-opencv.mp4')
 
# Llamada al método
fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=50, detectShadows=False)
 
# Deshabilitamos OpenCL, si no hacemos esto no funciona
cv2.ocl.setUseOpenCL(False)
 
while(1):
# Leemos el siguiente frame
ret, frame = cap.read()
 
# Si hemos llegado al final del vídeo salimos
if not ret:
break
 
# Aplicamos el algoritmo
fgmask = fgbg.apply(frame)
 
# Copiamos el umbral para detectar los contornos
contornosimg = fgmask.copy()
 
# Buscamos contorno en la imagen
im, contornos, hierarchy = cv2.findContours(contornosimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
 
# Recorremos todos los contornos encontrados
for c in contornos:
# Eliminamos los contornos más pequeños
if cv2.contourArea(c) < 500:
continue
 
# Obtenemos el bounds del contorno, el rectángulo mayor que engloba al contorno
(x, y, w, h) = cv2.boundingRect(c)
# Dibujamos el rectángulo del bounds
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
 
# Mostramos las capturas
cv2.imshow('Camara',frame)
cv2.imshow('Umbral',fgmask)
cv2.imshow('Contornos',contornosimg)
 
# Sentencias para salir, pulsa 's' y sale
k = cv2.waitKey(30) & 0xff
if k == ord("s"):
break
 
# Liberamos la cámara y cerramos todas las ventanas
cap.release()
cv2.destroyAllWindows()

Puedes ver el resultado a continuación.

BackgroundSubtractorKNN

Este método se basa en el algoritmo K-nearest neigbours. Igual que los otros dos, tenemos que ser capaces de buscar los parámetros más adecuados para nuestro proyecto. Esto nos permitirá la detección de movimiento con OpenCV de una manera óptima.

1
cv2.createBackgroundSubtractorKNN(history=500, dist2Threshold=400, detectShadows=False)

Donde:

  • history: tamaño del histórico.
  • dist2Threshold: umbral de la distancia al cuadrado entre el píxel y la muestra para decidir si un píxel está cerca de esa muestra.
  • detectShadows: con un valor verdadero (True) detecta las sombras. Esto reduce la velocidad así que si no se utiliza ponerlo a falso.

A continuación te dejo el código de ejemplo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Importación de librerías
import numpy as np
import cv2
 
# Capturamos el vídeo
cap = cv2.VideoCapture('detector-movimiento-opencv.mp4')
 
# Llamada al método
fgbg = cv2.createBackgroundSubtractorKNN(history=500, dist2Threshold=400, detectShadows=False)
 
# Deshabilitamos OpenCL, si no hacemos esto no funciona
cv2.ocl.setUseOpenCL(False)
 
while(1):
# Leemos el siguiente frame
ret, frame = cap.read()
 
# Si hemos llegado al final del vídeo salimos
if not ret:
break
 
# Aplicamos el algoritmo
fgmask = fgbg.apply(frame)
 
# Copiamos el umbral para detectar los contornos
contornosimg = fgmask.copy()
 
# Buscamos contorno en la imagen
im, contornos, hierarchy = cv2.findContours(contornosimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
 
# Recorremos todos los contornos encontrados
for c in contornos:
# Eliminamos los contornos más pequeños
if cv2.contourArea(c) < 500:
continue
 
# Obtenemos el bounds del contorno, el rectángulo mayor que engloba al contorno
(x, y, w, h) = cv2.boundingRect(c)
# Dibujamos el rectángulo del bounds
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
 
# Mostramos las capturas
cv2.imshow('Camara',frame)
cv2.imshow('Umbral',fgmask)
cv2.imshow('Contornos',contornosimg)
 
# Sentencias para salir, pulsa 's' y sale
k = cv2.waitKey(30) & 0xff
if k == ord("s"):
break
 
# Liberamos la cámara y cerramos todas las ventanas
cap.release()
cv2.destroyAllWindows()

Puedes ver el resultado a continuación.

Conclusión

La detección de movimiento con OpenCV es una tarea que se puede complicar todo lo que queramos. Dependerá del objetivo de nuestro proyecto. Mi consejo es que si estás interesado te suscribas al curso de Introducción a la Visión Artificial con OpenCV y Python. La única manera de hacer bien las cosas en entenderlas.

Tinkercad simulador Arduino

Seguro que has utilizado más de una vez algún tipo de juego de vídeo que simula situaciones de la vida real. Tal vez hayas jugado con alguno que … [+ info...]

Generador de funciones

Si te gusta entender la tecnología en profundidad, el tema que vamos a abordar hoy te va a interesar. Analizaremos las bases de funcionamiento de uno … [+ info...]

esp8266 esp32

ESP8266 ESP32 y la domótica libre

Mucha gente habla de los ESP8266 ESP32 y la domótica libre pero muy pocos saben porqué, a día de hoy, hasta mi hija de 10 años puede programar el … [+ info...]

Copyright © 2023 · Programar Fácil · Aviso legal

Utilizamos cookies propios y de terceros para mejorar nuestros servicios y experiencia de usuario. Si continua navegando, consideramos que acepta su uso.Aceptar Política de privacidad y cookies
Política de cookies

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Siempre activado
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Non-necessary
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.
GUARDAR Y ACEPTAR