? CONTADOR DE AUTOS (Fácil) | OpenCV con Python
En el tutorial de hoy te mostraré como crear un contador de autos sencillo con ayuda de OpenCV y Python que actuará sobre un carril de una autopista (esto para mantener el tutorial sencillo, pero si te animas a aplicarlo a toda la autopista sería genial). Para ello usaremos el contenido que hemos visto en tutoriales anteriores, en especial la sustracción de fondo. ¡Vamos a empezar!.
Conteo de autos dada cierta sección de una autopista/calle con OpenCV y Python
Para esta aplicación nos vamos a ayudar de la sustracción de fondo, de la cual ya había hecho un post en donde detectábamos la presencia de movimiento sobre cierta área dada. Como en ese post, vamos a determinar cierta porción de la autopista para detectar a los autos en movimiento. Así que para empezar vamos a crear un script llamándolo conteo_autos.py
import cv2 import numpy as np import imutils cap = cv2.VideoCapture('autos.mp4') fgbg = cv2.bgsegm.createBackgroundSubtractorMOG() kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) car_counter = 0 while True: ret, frame = cap.read() if ret == False: break frame = imutils.resize(frame, width=640) # Especificamos los puntos extremos del área a analizar area_pts = np.array([[330, 216], [frame.shape[1]-80, 216], [frame.shape[1]-80, 271], [330, 271]])
Línea 1 a 3: Importamos OpenCV, numpy e imutils.
Línea 5: Especificamos el video que va a ser leído. El video de entrada puedes encontrarlo aquí.
Línea 7 y 8: Indicamos que vamos a usar background sustractor mog, luego especificamos kernel
el cual usaremos más adelante para mejorar la imagen binaria obtenida luego de aplicar la sustracción de fondo.
Línea 9: Declaramos en 0 a car_counter
, que será nuestro contador de autos.
Línea 13 a 15: Leemos los fotogramas y luego con ayuda de imutils los redimensionamos a 640 pixeles de ancho. Esto ya que no necesitamos de fotogramas tan grandes con los cuales trabajar.
Línea 18: Especificamos los 4 puntos extremos que rodean la porción del quito carril de la autopista. Estos los he ordenado de forma horaria.
Más adelante también dibujaremos una línea amarilla aproximadamente en la mitad del área seleccionada. El área de interés dibujada y la línea amarilla que dibujaremos más adelante se verían de la siguiente manera:
La siguiente sección del código será similar a la del tutorial anterior ? DETECCIÓN DE MOVIMIENTO en CIERTA ÁREA | Python – OpenCV. Estaremos creando una imagen auxiliar de ceros, para luego dibujar en blanco nuestra área de interés.
# Con ayuda de una imagen auxiliar, determinamos el área # sobre la cual actuará el detector de movimiento imAux = np.zeros(shape=(frame.shape[:2]), dtype=np.uint8) imAux = cv2.drawContours(imAux, [area_pts], -1, (255), -1) image_area = cv2.bitwise_and(frame, frame, mask=imAux)
Línea 22: Creamos una imagen del mismo tamaño que frame
, esta será una matriz de ceros que crearemos gracias a np.zeros
.
Línea 23: Dibujamos el contorno que forman los puntos que habíamos especificado en area_pts
, en blanco.
Línea 24: Ahora haremos que en vez de esa porción blanca aparezca frame
. Veamos como se vería:
Procederemos aplicar la sustracción de fondo a la imagen recientemente obtenida, image_area
.
# Obtendremos la imagen binaria donde la región en blanco representa # la existencia de movimiento fgmask = fgbg.apply(image_area) fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel) fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_CLOSE, kernel) fgmask = cv2.dilate(fgmask, None, iterations=5)
Línea 28: Aplicamos la sustracción de fondo a image_area
, de donde en negro veremos el fondo de la escena, mientras que en blanco al objeto en movimiento (auto).
Línea 29 a 31: Usaremos transformaciones morfológicas para mejorar la imagen binaria. Cabe destacar que en la línea 31 he especificado 5 iteraciones para la dilatación, esto lo hago para incrementar y conectar las áreas blancas que representan el auto y que pueden estar desconectadas, de este modo podemos obtener un gran área blanca.
# Encontramos los contornos presentes de fgmask, para luego basándonos # en su área poder determinar si existe movimiento (autos) cnts = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
Línea 35: Empleamos cv2.findContours
para encontrar los contornos presentes en fgmask
.
Una vez que tenemos los contornos, vamos a tratar de analizar cada uno de ellos para asegurarnos que se trate de los autos.
for cnt in cnts: if cv2.contourArea(cnt) > 1500: x, y, w, h = cv2.boundingRect(cnt) cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,255), 1)
Línea 37 y 38: Procedemos a analizar cada contorno contenido en cnts
. Luego comparamos si el área del contorno es lo suficientemente grande como para ser considerada un auto. Para ello comparamos el área en pixeles del contorno con 1500
, de tal modo que si el contorno es mayor a este valor, seguiremos con el proceso, caso contrario lo descartamos.
Línea 39 y 40: Con ayuda de cv2.boundingRect
, obtenemos los puntos x
, y
, w
que corresponde al ancho y h
al alto del contorno. Luego en la línea 40 dibujaremos un rectángulo que rodee al contorno.
Ahora que tenemos el recuadro que rodea al contorno, procederemos a usar el punto superior derecho, para que cuando pase por cierta sección en el eje x, el auto sea contado.
# Si el auto ha cruzado entre 440 y 460 abierto, se incrementará # en 1 el contador de autos if 440 < (x + w) < 460: car_counter = car_counter + 1 cv2.line(frame, (450, 216), (450, 271), (0, 255, 0), 3)
Línea 44: Si el punto superior derecho del contorno está entre 440 y 460 se procederá a incrementar en 1 el contador de autos, además que se dibujará una línea verde en el mismo lugar donde teníamos la línea amarilla. Puedes echarle un ojo a la figura 7.
Pero te estarás preguntando de donde sale este rango (entre 440 y 460 en el eje x). Pues bien, la línea amarilla central que pusimos de referencia está ubicada en el eje x en 450 (lo podremos ver más adelante en la línea 50), por lo que he restado y sumado 10 pixeles a cada lado. Esto junto a la velocidad a la que se presentan los autos nos permite contar cada auto, ya que solo una vez pasará entre 440 y 460.
NOTA: El contador podría fallar si un auto va demasiado lento o se queda parado en ese lugar (entre 440 y 460 en x), en ese caso el contador de autos se incrementaría y tendríamos fallos, por lo que para este tutorial he asumido que los autos siempre van a la velocidad que se encuentra en el video.
Finalmente vamos a la visualización del área de interés, la línea amarilla central, el contador y frame
.
# Visualización del conteo de autos cv2.drawContours(frame, [area_pts], -1, (255, 0, 255), 2) cv2.line(frame, (450, 216), (450, 271), (0, 255, 255), 1) cv2.rectangle(frame, (frame.shape[1]-70, 215), (frame.shape[1]-5, 270), (0, 255, 0), 2) cv2.putText(frame, str(car_counter), (frame.shape[1]-55, 250), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0,255,0), 2) cv2.imshow('frame', frame) k = cv2.waitKey(70) & 0xFF if k ==27: break cap.release() cv2.destroyAllWindows()
Línea 49 y 50: Dibujamos el contorno dados los puntos de area_pts
, y luego vamos a especificar una línea en la mitad de esta área, para que los autos una vez pasen por ese lugar sean contados.
Línea 51 a 52: Visualizamos un rectángulo verde en la parte derecha del carril que estamos analizando, para dentro de él realizar el conteo de los autos según la información que tengamos en car_counter
. Visualizamos frame
, y si se presiona la tecla ESC el proceso se rompe y se cierran las ventanas de visualización.
Como podemos ver, el conteo se realiza de una forma correcta y se han avanzado a contar un total de 12 autos (puedes verlo en el video tutorial) que son los que han pasado por el quinto carril. Si quieres profundizar un poco, podrías realizar este análisis en el resto de carriles y puedes dejarme en los comentarios que te pareció. Y bien esto fue todo por el tutorial de hoy. ¡Cuídate mucho, nos vemos en un siguiente tutorial! chao chao.
Hola Gabriela, muy bueno ¿Se puede hacer con videostreaming en tiempo real?
Hola Abel, si podrías probarlo Abel. 🙂 Hay que tomar en cuenta que pueden presentarse ciertas limitaciones de acuerdo al ambiente donde se tome el video.
Hola Gabriela, se podría detectar varios tamaños de vehículos? A manera de que se pueda realizar una clasificación vehículos tipo A (vehículos), B (autobús) y C (carga).
Gracias!.
Hola Victor si, podrías tomar en cuenta el área del contorno para ello, puedes darle un vistazo a cv2.contourArea.
Buenas noches, intente recrear el proyecto pero me aparece este error:
fgmask = fgbg.apply(image_area)
AttributeError: ‘numpy.ndarray’ object has no attribute ‘apply’
Sabe el por que o la solución que podría tener ante esto
Especificaste fgbg = cv2.bgsegm.createBackgroundSubtractorMOG()?
hola, muy buen programa, mi duda es si el area de interez abarca toda la pantalla y 2 carros a la vez pasan por los puntos detecta los dos o solamente como si fueran uno?
En la parte de
if k ==27:
break
me arroja este error
«break» can be used only within a loop
Hola Luis, dale un tab en el break. 🙂
Como un tap?
dice que no se puede usar fuera de un loop, un if es un loop y ahi hay un if pero el break esta fuerapara ponerlo dentro basta con ponerlo asi:
if k == 27:
break
Como puedo hacer la linea amarilla de forma horizontal?
Hola gracias por contribuir con el aprendizaje de este lenguaje.
Me gustaría entender mejor cómo establecer un área de detección?
Coordenadas del recuadro de interés.
Saludos
He probado y me aparece esto, ayudaa
imAux = np.zeros(shape=(frame.shape[:2]), dtype=np.uint8)
AttributeError: ‘NoneType’ object has no attribute ‘shape’
Hola Matias, puede que no se esté leyendo correctamente la imagen.
Como puedo corregir o verificar eso?
Hola,
que genial… que programa utilizas?
Hola Karen, a qué te refieres? 🙂
Hola, quizás seamos familia 🙂
Si pudieras comunicarte conmigo via e-mail sería genial para hacerte un par de preguntas y dudas al respecto sobre un proyecto que estoy trabajando.
Gracias Gabi.
Muy buenas tardes, tengo una duda, porque al reproducir un video con openvc – python el video se me reproduce como si le estuviera haciendo zoom, hay algun limite de resolucion con que trabaje opencv y python ? Muchas gracias.
Hola, soy estudiante y me encanta tu canal, muy didáctico realmente muy bueno, estoy tratando de correr el código de este video para ir aprendiendo, pero hice todo tal cual está con tu video , pero no me aparece error sino solo: «Process finished with exit code 0» , es decir no levanta el video ni nada, es como que faltar algo.
Me puedes ayudar o es mucha molestia?
desde ya muchas gracias
Hola Rocio, muchas gracias eres muy amable. Puedes especificarme un poco más con qué estás trabajando? Sistema Operativo, versión de Python y OpenCV. A ver que puede estar pasando.
Gracias por tu respuesta, la versión de py_3.11.4 y Opencv=4.8.0 , me avisas cualquier cosa o versión que deba instalar, gracias!
Me comentabas que no se leía el video. ¿Puede ser tal vez que estés ubicándolo en alguna carpeta cuyo nombre contenga un carácter especial, o en sí el nombre de este tenga un carácter especial?
Hola buenas tengo una duda al intentar hacer el contador de forma horizontal me complica el hecho que al pasar un auto no se muy bien como ubicarlo dentro de los ejes x h que aparece en
for cnt2 in cnts2:
if cv2.contourArea(cnt2) > 1500:
x, y, w, h = cv2.boundingRect(cnt2)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 255), 2)
if 410 < (x + w) < 449:
car_counter = car_counter – 1
cv2.line(frame, (470, 350), (280, 350), (0, 255, 0), 3)
como podria cambiarlo para que lo detecte bien?