? DETECCIÓN DE MOVIMIENTO (Con sustracción de imágenes) – OpenCV y Python
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:
- Leer un video o realizar video streaming
- Transformar de BGR a escala de grises
- Conseguir la imagen del fondo y exterior, para restarlas con cv2.absdiff
- Aplicar umbralización simple
- Encontrar los contornos
- 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.
Buen trabajo!! espero que sigas mucho tiempo enseñandonos asi de bien!
Muchas gracias Fran, gracias por tomarte el tiempo de dejar un comentario. ¡Cuídate mucho :)!
Como podría hacerse para que en vez de restarse con una imagen inicial se reste con el frame anterior? Muchas gracias n.n
Hola Camila, podrías usar una variable auxiliar para que realices el proceso de sustracción. Te dejo también otros métodos que posee OpenCV para la sustracción de fondo: https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_video/py_bg_subtraction/py_bg_subtraction.html
Creo que te podrían ayudar bastante 🙂
Muchas gracias n.n lo he intentado con esos métodos que da openCV, pero he tenido problemas por que en cierto punto uno mismo termina siendo considerado fondo, entonces cada vez se detecta menos movimiento.
Hola, si la versión de cv2 es 4.2 si funciona?
Hola Diego, lo acabo de probar y si funciona. 🙂
Hola. Excelente tutorial. Me gustaría saber si se puede agregar un contador de personas. Mil gracias!
Hola Jonatan, si podrías contar las personas que se muestren en la imagen. Sin embargo ten en cuenta que como estos san tratadas como contornos, si dos personas están juntas por ejemplo, se va a obtener un solo contorno, por lo que se contaría una sola persona.
Muchas gracias, me gusta mucho tu canal y tus tutoriales. Podríamos hacer uno de conteo de personas, que por ejemplo, ingresan y salen de un centro comercial, por favor? (Pdta: Eres muy buena explicando)
Hola Gaby, muy bien explicado todo.
Solo con una solicitud: se puede grabar en otro video y/o en fotografias, solo el movimiento? y cuál serían las lineas de comado para ello.
mil gracias
Hola Ludwing, si desear grabar un video te recomiendo que visites este post: http://omes-va.com/basicvideo/ allí puedes encontrar los pasos para guardar un video.
Gracias. pero mi problema es que quiero guardar en imágenes solo las que son diferentes al fondo de un archivo de video.
hollllllaaaa necesito de tu ayuda …como te puedo contactar para conversar es muy importante sobre trabajo
Hola Jonathan, puedes escribirme a [email protected]
HOLA , MUCHO GUSTO ,QUE BUENO ENCONTRAR ESTE TIPO DE CONTENIDO, QUISIER SACARME DE DUDAS ALGUNAS COSILLAS, YA QUE SOY NUEVO EN ESTE MUNDO, PERO POR AHORA, ESTE PROGRAMA A QUE SE LE PUEDE IMPLEMENTAR? COMO QUE TITULO ADEMAS DE SOLO DETECCION, SI NO PARA QUE FIN REALIZAR ESTA DETECCION, COMO SEGURIDAD?
Hola Franco, podrías usarlo para algunas aplicaciones, puedes encontrar algunos videos en el que uso la sustracción de imágenes, como estos: https://youtu.be/nVkX7wC25g4 https://youtu.be/fR7IQQJJAnk
Hola gaby, Me a servido el video, pero me gustaria saber que tengo que agregar al codigo para que si una persona se agrega a la imagen aparezca como otra persona diferente (con otro marco y otro texto) algo asi como un contador de personas
espero me puedas ayudar
ya me suscribí y le di like a los vídeos
Hola ARAM, muchas gracias. Tendrías que añadir texto con cv2.putText, aunque si quieres realizar un contador de personas, tendrías que aplicar también tracking o seguimiento de objetos ya que cada persona va a tener una ID. Ahora si vas a contar el número de personas presentes en la imagen, no sería necesario el tracking.
Exactamente eso es lo que quiero, contar el numero de personas presentes en la imagen que da la cámara de la computadora, con el programa actual si se agrega otra persona lo encierra en el mismo rectángulo y con el mismo texto, lo que quiero lograr es que si se agrega otra persona aparezca con el rectángulo de otro color y otro texto (en su caso numero), agregué esta linea de texto: cv2.putText(frame,’individuo1′,(x,y),font,0.75,(255,0,0),2,cv2.LINE_AA) pero es lo mismo, si se agrega otra persona sale igual como ‘individuo1’, espero me puedas ayudar . te lo agradecería infinitamente. gracias , eres la mejor 🙂
Exactamente eso es lo que quiero, contar el numero de personas presentes en la imagen que da la cámara de la computadora, con el programa actual si se agrega otra persona lo encierra en el mismo rectángulo y con el mismo texto, lo que quiero lograr es que si se agrega otra persona aparezca con el rectángulo de otro color y otro texto (en su caso numero), agregué esta linea de texto: cv2.putText(frame,’individuo1′,(x,y),font,0.75,(255,0,0),2,cv2.LINE_AA) pero es lo mismo, si se agrega otra persona sale igual como ‘individuo1’, espero me puedas ayudar . te lo agradecería infinitamente. gracias , eres la mejor ?
Hola buenas días, quisiera saber si se podrían tomar el valor de los ángulos formados con el movimiento de miembros inferiores y superiores. Todo esto en tiempo real.
Hola Dani, tal vez puedas usar los defectos de convexidad. http://omes-va.com/contando-dedos-defectos-de-convexidad-python-opencv/
Hola gaby
Al igual que una persona que comento en este post, me gustaría saber que se debe de agregar para contar el numero de personas en la cámara, si se agrega otra persona que salga otro nombre, se que se tiene que agregar un puttext, pero ya lo intente pero sale con el mismo nombre: texto: cv2.putText(frame,’persona 1′,(x,y),font,1,(0,255,0),2,cv2.LINE_AA), me gustaría saber que debo de agregar (si un ciclo, o algún parámetro que tengo en la estructura del puttex esta mal) para si se agrega otra persona salga persona 2, si se agrega otra persona salga persona 3 y así sucesivamente.
espero me puedas apoyar.
muchas gracias
Hola Diana, tendrías que usar un contador y este se actualice para que lo puedas visualizar con cv2.putText. Si quisieras agregarle una ID a cada persona tendrías que usar tracking de objetos, esta parte no está incluida en este tutorial. Lo que básicamente se tendría que hacer es almacenar la posición de las personas dentro de la imagen para poder seguirlas.
Hola Gaby! 😀 solo pasaba a comentarte y felicitarte por este gran proyecto. Muy pocos canales en YouTube cuentan con tanta dedicación y cariño como tú lo haces. Felicitaciones porque veo que estás creciendo bastante. Hace poco descubrí tu canal y me ha parecido una maravilla, explicas todo de una manera excelente y dinámica. Espero que sigas creciendo, aprendiendo y logrando muchos triunfos más por tanta dedicación. Un saludo desde Colombia 😀
Oooh Yohanna, qué hermoso comentario!. De verdad muchas gracias por tomarte el tiempo de escribirme, significa mucho para mí. Me alegra tanto que te guste el contenido y que lo encuentres útil. Espero poder seguir haciendo este tipo de contenido que lo hago con muchísimo cariño para ustedes. ¡Un saludo y un abrazo enoorme para ti también desde Ecuador! 🙂
Great tremendous things here. I’m very glad to look your article. Thank you so much and i am taking a look ahead to contact you. Will you please drop me a mail?
Hola! Creo que falta el método cv2.destroyAllWindows() al final del código. Buen contenido, gracias
Muchas gracias Leo, en ese momento estaba usando ubuntu y por alguna razón cerraba correctamente la imagen jiji. Muchas gracias por la observación. 🙂
Graciaaaaaaaaaaaaaaaas por la aclaración, soy nuevo en esto y tenia ese problema twt
Saludos, te recomiendo usar una fuenta y color mas sencillas.
Las letras alargadas, finas y de color girs dificultan un poco la lectura.
Exelente trabajo, gracias
Hola Jesus, muchas gracias por la sugerencia. Es en cuanto a los posts en esta web o en los videos?
disculpa puedo imprimir las diferencias que se presentan en el video al final en una sola imagen si me ayudas con eso gracias
hola que tal me gustan tus videos son muy explicativos, una consulta puedo realizar suma de varias imágenes o en una imagen almacenar las diferencias entre un video, te agradecería que me ayudes con esta duda gracias
Hola Gaby, excelente trabajo, te saludo desde Perú y me ayudo bastante el codigo, espero que sigas asi, salu2 pimpolla.
Hola me gusto mucho tu articulo!
Probe el codigo y me funciono a la perfeccion, muchas gracias !!
Solo tegno un problema y es que cuando trato de usar la camara web usando cv2.VideoCapture(0), esta no me abre ninguna ventana 🙁
Sabes a que se deba este posible problema??
Excelentes tus lecciones. me enseñan muchisimo todos los dias, Gracias!
Te felicito por tu blog, ando aprendiendo de aqui, de allá sobre opencv y python, pero me quedare por aqui por largo tiempo, tienes mucho contendido del cual puedo aprender, me gustaría saber cómo detectar el color cyan(éste está en movimiento) y sus coordenadas en la pantalla y mover el mouse hacia esa coordenada y darle Click. Podrías hacer algo sobre eso?, Gaby