Contando los autos OpenCV + Python Issue

He estado tratando de contar los carros al cruzar la línea y funciona, pero el problema es que cuenta un carro muchas veces, lo cual es ridículo porque debería contarse una vez.

Aquí está el código que estoy usando:

import cv2 import numpy as np bgsMOG = cv2.BackgroundSubtractorMOG() cap = cv2.VideoCapture("traffic.avi") counter = 0 if cap: while True: ret, frame = cap.read() if ret: fgmask = bgsMOG.apply(frame, None, 0.01) cv2.line(frame,(0,60),(160,60),(255,255,0),1) # To find the countours of the Cars contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) try: hierarchy = hierarchy[0] except: hierarchy = [] for contour, hier in zip(contours, hierarchy): (x, y, w, h) = cv2.boundingRect(contour) if w > 20 and h > 20: cv2.rectangle(frame, (x,y), (x+w,y+h), (255, 0, 0), 1) #To find centroid of the Car x1 = w/2 y1 = h/2 cx = x+x1 cy = y+y1 ## print "cy=", cy ## print "cx=", cx centroid = (cx,cy) ## print "centoid=", centroid # Draw the circle of Centroid cv2.circle(frame,(int(cx),int(cy)),2,(0,0,255),-1) # To make sure the Car crosses the line ## dy = cy-108 ## print "dy", dy if centroid > (27, 38) and centroid < (134, 108): ## if (cx = 20): counter +=1 ## print "counter=", counter ## if cy > 10 and cy < 160: cv2.putText(frame, str(counter), (x,y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2) ## cv2.namedWindow('Output',cv2.cv.CV_WINDOW_NORMAL) cv2.imshow('Output', frame) ## cv2.imshow('FGMASK', fgmask) key = cv2.waitKey(60) if key == 27: break cap.release() cv2.destroyAllWindows() 

y el video está en mi página de github @ https://github.com/Tes3awy/MatLab-Tutoriales llamados traffic.avi, y también es un video incorporado en la biblioteca de Matlab

¿Alguna ayuda que cada coche se cuenta una vez?


EDITAR: Los cuadros individuales del video tienen el siguiente aspecto:

Preparación

Para entender lo que está sucediendo y, finalmente, resolver nuestro problema, primero debemos mejorar un poco el script.

He agregado el registro de los pasos importantes de su algoritmo, he refactorizado un poco el código, he agregado el guardado de la máscara y las imágenes procesadas, he agregado la capacidad de ejecutar el script utilizando las imágenes de marcos individuales, junto con algunas otras modificaciones.

Así es como se ve el script en este punto:

 import logging import logging.handlers import os import time import sys import cv2 import numpy as np from vehicle_counter import VehicleCounter # ============================================================================ IMAGE_DIR = "images" IMAGE_FILENAME_FORMAT = IMAGE_DIR + "/frame_%04d.png" # Support either video file or individual frames CAPTURE_FROM_VIDEO = False if CAPTURE_FROM_VIDEO: IMAGE_SOURCE = "traffic.avi" # Video file else: IMAGE_SOURCE = IMAGE_FILENAME_FORMAT # Image sequence # Time to wait between frames, 0=forever WAIT_TIME = 1 # 250 # ms LOG_TO_FILE = True # Colours for drawing on processed frames DIVIDER_COLOUR = (255, 255, 0) BOUNDING_BOX_COLOUR = (255, 0, 0) CENTROID_COLOUR = (0, 0, 255) # ============================================================================ def init_logging(): main_logger = logging.getLogger() formatter = logging.Formatter( fmt='%(asctime)s.%(msecs)03d %(levelname)-8s [%(name)s] %(message)s' , datefmt='%Y-%m-%d %H:%M:%S') handler_stream = logging.StreamHandler(sys.stdout) handler_stream.setFormatter(formatter) main_logger.addHandler(handler_stream) if LOG_TO_FILE: handler_file = logging.handlers.RotatingFileHandler("debug.log" , maxBytes = 2**24 , backupCount = 10) handler_file.setFormatter(formatter) main_logger.addHandler(handler_file) main_logger.setLevel(logging.DEBUG) return main_logger # ============================================================================ def save_frame(file_name_format, frame_number, frame, label_format): file_name = file_name_format % frame_number label = label_format % frame_number log.debug("Saving %s as '%s'", label, file_name) cv2.imwrite(file_name, frame) # ============================================================================ def get_centroid(x, y, w, h): x1 = int(w / 2) y1 = int(h / 2) cx = x + x1 cy = y + y1 return (cx, cy) # ============================================================================ def detect_vehicles(fg_mask): log = logging.getLogger("detect_vehicles") MIN_CONTOUR_WIDTH = 21 MIN_CONTOUR_HEIGHT = 21 # Find the contours of any vehicles in the image contours, hierarchy = cv2.findContours(fg_mask , cv2.RETR_EXTERNAL , cv2.CHAIN_APPROX_SIMPLE) log.debug("Found %d vehicle contours.", len(contours)) matches = [] for (i, contour) in enumerate(contours): (x, y, w, h) = cv2.boundingRect(contour) contour_valid = (w >= MIN_CONTOUR_WIDTH) and (h >= MIN_CONTOUR_HEIGHT) log.debug("Contour #%d: pos=(x=%d, y=%d) size=(w=%d, h=%d) valid=%s" , i, x, y, w, h, contour_valid) if not contour_valid: continue centroid = get_centroid(x, y, w, h) matches.append(((x, y, w, h), centroid)) return matches # ============================================================================ def filter_mask(fg_mask): kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # Fill any small holes closing = cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, kernel) # Remove noise opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel) # Dilate to merge adjacent blobs dilation = cv2.dilate(opening, kernel, iterations = 2) return dilation # ============================================================================ def process_frame(frame_number, frame, bg_subtractor, car_counter): log = logging.getLogger("process_frame") # Create a copy of source frame to draw into processed = frame.copy() # Draw dividing line -- we count cars as they cross this line. cv2.line(processed, (0, car_counter.divider), (frame.shape[1], car_counter.divider), DIVIDER_COLOUR, 1) # Remove the background fg_mask = bg_subtractor.apply(frame, None, 0.01) fg_mask = filter_mask(fg_mask) save_frame(IMAGE_DIR + "/mask_%04d.png" , frame_number, fg_mask, "foreground mask for frame #%d") matches = detect_vehicles(fg_mask) log.debug("Found %d valid vehicle contours.", len(matches)) for (i, match) in enumerate(matches): contour, centroid = match log.debug("Valid vehicle contour #%d: centroid=%s, bounding_box=%s", i, centroid, contour) x, y, w, h = contour # Mark the bounding box and the centroid on the processed frame # NB: Fixed the off-by one in the bottom right corner cv2.rectangle(processed, (x, y), (x + w - 1, y + h - 1), BOUNDING_BOX_COLOUR, 1) cv2.circle(processed, centroid, 2, CENTROID_COLOUR, -1) log.debug("Updating vehicle count...") car_counter.update_count(matches, processed) return processed # ============================================================================ def main(): log = logging.getLogger("main") log.debug("Creating background subtractor...") bg_subtractor = cv2.BackgroundSubtractorMOG() log.debug("Pre-training the background subtractor...") default_bg = cv2.imread(IMAGE_FILENAME_FORMAT % 119) bg_subtractor.apply(default_bg, None, 1.0) car_counter = None # Will be created after first frame is captured # Set up image source log.debug("Initializing video capture device #%s...", IMAGE_SOURCE) cap = cv2.VideoCapture(IMAGE_SOURCE) frame_width = cap.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH) frame_height = cap.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT) log.debug("Video capture frame size=(w=%d, h=%d)", frame_width, frame_height) log.debug("Starting capture loop...") frame_number = -1 while True: frame_number += 1 log.debug("Capturing frame #%d...", frame_number) ret, frame = cap.read() if not ret: log.error("Frame capture failed, stopping...") break log.debug("Got frame #%d: shape=%s", frame_number, frame.shape) if car_counter is None: # We do this here, so that we can initialize with actual frame size log.debug("Creating vehicle counter...") car_counter = VehicleCounter(frame.shape[:2], frame.shape[0] / 2) # Archive raw frames from video to disk for later inspection/testing if CAPTURE_FROM_VIDEO: save_frame(IMAGE_FILENAME_FORMAT , frame_number, frame, "source frame #%d") log.debug("Processing frame #%d...", frame_number) processed = process_frame(frame_number, frame, bg_subtractor, car_counter) save_frame(IMAGE_DIR + "/processed_%04d.png" , frame_number, processed, "processed frame #%d") cv2.imshow('Source Image', frame) cv2.imshow('Processed Image', processed) log.debug("Frame #%d processed.", frame_number) c = cv2.waitKey(WAIT_TIME) if c == 27: log.debug("ESC detected, stopping...") break log.debug("Closing video capture device...") cap.release() cv2.destroyAllWindows() log.debug("Done.") # ============================================================================ if __name__ == "__main__": log = init_logging() if not os.path.exists(IMAGE_DIR): log.debug("Creating image directory `%s`...", IMAGE_DIR) os.makedirs(IMAGE_DIR) main() 

Este script es responsable del procesamiento del flujo de imágenes y de la identificación de todos los vehículos en cada fotogtwig; me refiero a ellos como matches en el código.


La tarea de contar los vehículos detectados se delega a la clase VehicleCounter . La razón por la que elegí hacer de esto una clase se hará evidente a medida que avanzamos. No implementé el algoritmo de conteo de su vehículo, porque no funcionará por razones que se volverán evidentes a medida que profundizamos en esto.

El archivo vehicle_counter.py contiene el siguiente código:

 import logging # ============================================================================ class VehicleCounter(object): def __init__(self, shape, divider): self.log = logging.getLogger("vehicle_counter") self.height, self.width = shape self.divider = divider self.vehicle_count = 0 def update_count(self, matches, output_image = None): self.log.debug("Updating count using %d matches...", len(matches)) # ============================================================================ 

Finalmente, escribí un guión que unirá todas las imágenes generadas, para que sea más fácil inspeccionarlas:

 import cv2 import numpy as np # ============================================================================ INPUT_WIDTH = 160 INPUT_HEIGHT = 120 OUTPUT_TILE_WIDTH = 10 OUTPUT_TILE_HEIGHT = 12 TILE_COUNT = OUTPUT_TILE_WIDTH * OUTPUT_TILE_HEIGHT # ============================================================================ def stitch_images(input_format, output_filename): output_shape = (INPUT_HEIGHT * OUTPUT_TILE_HEIGHT , INPUT_WIDTH * OUTPUT_TILE_WIDTH , 3) output = np.zeros(output_shape, np.uint8) for i in range(TILE_COUNT): img = cv2.imread(input_format % i) cv2.rectangle(img, (0, 0), (INPUT_WIDTH - 1, INPUT_HEIGHT - 1), (0, 0, 255), 1) # Draw the frame number cv2.putText(img, str(i), (2, 10) , cv2.FONT_HERSHEY_PLAIN, 0.7, (255, 255, 255), 1) x = i % OUTPUT_TILE_WIDTH * INPUT_WIDTH y = i / OUTPUT_TILE_WIDTH * INPUT_HEIGHT output[y:y+INPUT_HEIGHT, x:x+INPUT_WIDTH,:] = img cv2.imwrite(output_filename, output) # ============================================================================ stitch_images("images/frame_%04d.png", "stitched_frames.png") stitch_images("images/mask_%04d.png", "stitched_masks.png") stitch_images("images/processed_%04d.png", "stitched_processed.png") 

Análisis

Para resolver este problema, deberíamos tener alguna idea sobre qué resultados esperamos obtener. También deberíamos etiquetar todos los distintos autos en el video, para que sea más fácil hablar de ellos.

Los 10 vehículos del video.

Si ejecutamos nuestro script y unimos las imágenes, obtendremos una serie de archivos útiles que nos ayudarán a analizar el problema:

  • Imagen que contiene un mosaico de marcos de entrada.
  • Imagen que contiene un mosaico de máscaras de primer plano :

  • Imagen que contiene un mosaico de marcos procesados.

  • El registro de depuración para la ejecución.

Al inspeccionarlos, una serie de problemas se hacen evidentes:

  • Las máscaras de primer plano suelen ser ruidosas. Deberíamos hacer un poco de filtrado (¿erosionar / dilatar?) Para eliminar el ruido y los espacios estrechos.
  • A veces echamos de menos los vehículos (los grises).
  • Algunos vehículos se detectan dos veces en el cuadro único.
  • Los vehículos rara vez se detectan en las regiones superiores del bastidor.
  • El mismo vehículo se detecta a menudo en cuadros consecutivos. Necesitamos encontrar una manera de rastrear el mismo vehículo en cuadros consecutivos y contarlo solo una vez.

Solución

1. Pre-Siembra del Subtractor de Fondo

Nuestro video es bastante corto, solo 120 cuadros. Con una tasa de aprendizaje de 0.01 , tomará una parte sustancial del video para que el detector de fondo se estabilice.

Afortunadamente, el último fotogtwig del video (número de fotogtwig 119) carece por completo de vehículos y, por lo tanto, podemos usarlo como nuestra imagen de fondo inicial. (Otras notas para obtener una imagen adecuada se mencionan en notas y comentarios).

Imagen de fondo

Para usar esta imagen de fondo inicial, simplemente la cargamos y la apply en el restador de fondo con el factor de aprendizaje 1.0 :

 bg_subtractor = cv2.BackgroundSubtractorMOG() default_bg = cv2.imread(IMAGE_FILENAME_FORMAT % 119) bg_subtractor.apply(default_bg, None, 1.0) 

Cuando observamos el nuevo mosaico de máscaras , podemos ver que tenemos menos ruido y que la detección del vehículo funciona mejor en los primeros cuadros.

2. Limpiando la máscara de primer plano

Un enfoque simple para mejorar nuestra máscara de primer plano es aplicar algunas transformaciones morfológicas .

 def filter_mask(fg_mask): kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # Fill any small holes closing = cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, kernel) # Remove noise opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel) # Dilate to merge adjacent blobs dilation = cv2.dilate(opening, kernel, iterations = 2) return dilation 

Al inspeccionar las máscaras , los marcos procesados y el archivo de registro generado con el filtrado, podemos ver que ahora detectamos los vehículos de manera más confiable y hemos mitigado el problema de la detección de diferentes partes de un vehículo como objetos separados.

3. Seguimiento de vehículos entre marcos

En este punto, debemos revisar nuestro archivo de registro y recostackr todas las coordenadas del centroide para cada vehículo. Esto nos permitirá trazar e inspeccionar la ruta que cada vehículo traza a través de la imagen, y desarrollar un algoritmo para hacerlo automáticamente. Para facilitar este proceso, podemos crear un registro reducido mediante la grep de las entradas relevantes.

Las listas de coordenadas centroides:

 traces = { 'A': [(112, 36), (112, 45), (112, 52), (112, 54), (112, 63), (111, 73), (111, 86), (111, 91), (111, 97), (110, 105)] , 'B': [(119, 37), (120, 42), (121, 54), (121, 55), (123, 64), (124, 74), (125, 87), (127, 94), (125, 100), (126, 108)] , 'C': [(93, 23), (91, 27), (89, 31), (87, 36), (85, 42), (82, 49), (79, 59), (74, 71), (70, 82), (62, 86), (61, 92), (55, 101)] , 'D': [(118, 30), (124, 83), (125, 90), (116, 101), (122, 100)] , 'E': [(77, 27), (75, 30), (73, 33), (70, 37), (67, 42), (63, 47), (59, 53), (55, 59), (49, 67), (43, 75), (36, 85), (27, 92), (24, 97), (20, 102)] , 'F': [(119, 30), (120, 34), (120, 39), (122, 59), (123, 60), (124, 70), (125, 82), (127, 91), (126, 97), (128, 104)] , 'G': [(88, 37), (87, 41), (85, 48), (82, 55), (79, 63), (76, 74), (72, 87), (67, 92), (65, 98), (60, 106)] , 'H': [(124, 35), (123, 40), (125, 45), (127, 59), (126, 59), (128, 67), (130, 78), (132, 88), (134, 93), (135, 99), (135, 107)] , 'I': [(98, 26), (97, 30), (96, 34), (94, 40), (92, 47), (90, 55), (87, 64), (84, 77), (79, 87), (74, 93), (73, 102)] , 'J': [(123, 60), (125, 63), (125, 81), (127, 93), (126, 98), (125, 100)] } 

Rastros de vehículos individuales trazados en el fondo:

Rastros de los vehículos detectados.

Imagen ampliada combinada de todas las trazas del vehículo:

Todos los rastros juntos en un fondo escalado

Vectores

Para analizar el movimiento, necesitamos trabajar con vectores (es decir, la distancia y la dirección que se mueven). El siguiente diagtwig muestra cómo los angularjs corresponden al movimiento de los vehículos en la imagen.

Podemos usar la siguiente función para calcular el vector entre dos puntos:

 def get_vector(a, b): """Calculate vector (distance, angle in degrees) from point a to point b. Angle ranges from -180 to 180 degrees. Vector with angle 0 points straight down on the image. Values increase in clockwise direction. """ dx = float(b[0] - a[0]) dy = float(b[1] - a[1]) distance = math.sqrt(dx**2 + dy**2) if dy > 0: angle = math.degrees(math.atan(-dx/dy)) elif dy == 0: if dx < 0: angle = 90.0 elif dx > 0: angle = -90.0 else: angle = 0.0 else: if dx < 0: angle = 180 - math.degrees(math.atan(dx/dy)) elif dx > 0: angle = -180 - math.degrees(math.atan(dx/dy)) else: angle = 180.0 return distance, angle 

Categorización

Una forma en que podemos buscar patrones que podrían usarse para categorizar los movimientos como válidos / no válidos es hacer un diagtwig de dispersión (ángulo frente a distancia):

Parcela de ángulo vs distancia

  • Los puntos verdes representan un movimiento válido, que determinamos utilizando las listas de puntos para cada vehículo.
  • Los puntos rojos representan movimientos no válidos, vectores entre puntos en los carriles de tráfico adyacentes.
  • Trazé dos curvas azules, que podemos usar para separar los dos tipos de movimientos. Cualquier punto que se encuentre debajo de cualquiera de las curvas puede considerarse válido. Las curvas son:
    • distance = -0.008 * angle**2 + 0.4 * angle + 25.0
    • distance = 10.0

Podemos usar la siguiente función para categorizar los vectores de movimiento:

 def is_valid_vector(a): distance, angle = a threshold_distance = max(10.0, -0.008 * angle**2 + 0.4 * angle + 25.0) return (distance <= threshold_distance) 

NB: Hay un valor atípico, que se produce debido a que perdemos la pista del vehículo D en los cuadros 43..48.

Algoritmo

Usaremos Vehicle clase para almacenar información sobre cada vehículo rastreado:

  • Algún tipo de identificador
  • Listado de posiciones, las más recientes al frente.
  • Contador visto por última vez: número de cuadros desde la última vez que vimos este vehículo
  • Bandera para marcar si el vehículo fue contado o no

Class VehicleCounter almacenará una lista de los vehículos actualmente rastreados y hará un seguimiento del conteo total. En cada cuadro, utilizaremos la lista de cuadros delimitadores y las posiciones de los vehículos identificados (lista de candidatos) para actualizar el estado de VehicleCounter :

  1. Actualización de Vehicle actualmente rastreados:
    • Para cada vehiculo
      • Si existe una coincidencia válida para un vehículo determinado, actualice la posición del vehículo y restablezca su último contador visto. Eliminar el partido de la lista de candidatos.
      • De lo contrario, aumenta el contador visto por última vez para ese vehículo.
  2. Crear nuevos Vehicle para las coincidencias restantes
  3. Actualizar el recuento de vehículos
    • Para cada vehiculo
      • Si el vehículo ha pasado el divisor y aún no se ha contado, actualice el conteo total y marque el vehículo como contado
  4. Retire los vehículos que ya no son visibles
    • Para cada vehiculo
      • Si el último contador visto supera el umbral, retire el vehículo.

4. Solución

Podemos reutilizar el script principal con la versión final de vehicle_counter.py , que contiene la implementación de nuestro algoritmo de conteo:

 import logging import math import cv2 import numpy as np # ============================================================================ CAR_COLOURS = [ (0,0,255), (0,106,255), (0,216,255), (0,255,182), (0,255,76) , (144,255,0), (255,255,0), (255,148,0), (255,0,178), (220,0,255) ] # ============================================================================ class Vehicle(object): def __init__(self, id, position): self.id = id self.positions = [position] self.frames_since_seen = 0 self.counted = False @property def last_position(self): return self.positions[-1] def add_position(self, new_position): self.positions.append(new_position) self.frames_since_seen = 0 def draw(self, output_image): car_colour = CAR_COLOURS[self.id % len(CAR_COLOURS)] for point in self.positions: cv2.circle(output_image, point, 2, car_colour, -1) cv2.polylines(output_image, [np.int32(self.positions)] , False, car_colour, 1) # ============================================================================ class VehicleCounter(object): def __init__(self, shape, divider): self.log = logging.getLogger("vehicle_counter") self.height, self.width = shape self.divider = divider self.vehicles = [] self.next_vehicle_id = 0 self.vehicle_count = 0 self.max_unseen_frames = 7 @staticmethod def get_vector(a, b): """Calculate vector (distance, angle in degrees) from point a to point b. Angle ranges from -180 to 180 degrees. Vector with angle 0 points straight down on the image. Values increase in clockwise direction. """ dx = float(b[0] - a[0]) dy = float(b[1] - a[1]) distance = math.sqrt(dx**2 + dy**2) if dy > 0: angle = math.degrees(math.atan(-dx/dy)) elif dy == 0: if dx < 0: angle = 90.0 elif dx > 0: angle = -90.0 else: angle = 0.0 else: if dx < 0: angle = 180 - math.degrees(math.atan(dx/dy)) elif dx > 0: angle = -180 - math.degrees(math.atan(dx/dy)) else: angle = 180.0 return distance, angle @staticmethod def is_valid_vector(a): distance, angle = a threshold_distance = max(10.0, -0.008 * angle**2 + 0.4 * angle + 25.0) return (distance <= threshold_distance) def update_vehicle(self, vehicle, matches): # Find if any of the matches fits this vehicle for i, match in enumerate(matches): contour, centroid = match vector = self.get_vector(vehicle.last_position, centroid) if self.is_valid_vector(vector): vehicle.add_position(centroid) self.log.debug("Added match (%d, %d) to vehicle #%d. vector=(%0.2f,%0.2f)" , centroid[0], centroid[1], vehicle.id, vector[0], vector[1]) return i # No matches fit... vehicle.frames_since_seen += 1 self.log.debug("No match for vehicle #%d. frames_since_seen=%d" , vehicle.id, vehicle.frames_since_seen) return None def update_count(self, matches, output_image = None): self.log.debug("Updating count using %d matches...", len(matches)) # First update all the existing vehicles for vehicle in self.vehicles: i = self.update_vehicle(vehicle, matches) if i is not None: del matches[i] # Add new vehicles based on the remaining matches for match in matches: contour, centroid = match new_vehicle = Vehicle(self.next_vehicle_id, centroid) self.next_vehicle_id += 1 self.vehicles.append(new_vehicle) self.log.debug("Created new vehicle #%d from match (%d, %d)." , new_vehicle.id, centroid[0], centroid[1]) # Count any uncounted vehicles that are past the divider for vehicle in self.vehicles: if not vehicle.counted and (vehicle.last_position[1] > self.divider): self.vehicle_count += 1 vehicle.counted = True self.log.debug("Counted vehicle #%d (total count=%d)." , vehicle.id, self.vehicle_count) # Optionally draw the vehicles on an image if output_image is not None: for vehicle in self.vehicles: vehicle.draw(output_image) cv2.putText(output_image, ("%02d" % self.vehicle_count), (142, 10) , cv2.FONT_HERSHEY_PLAIN, 0.7, (127, 255, 255), 1) # Remove vehicles that have not been seen long enough removed = [ v.id for v in self.vehicles if v.frames_since_seen >= self.max_unseen_frames ] self.vehicles[:] = [ v for v in self.vehicles if not v.frames_since_seen >= self.max_unseen_frames ] for id in removed: self.log.debug("Removed vehicle #%d.", id) self.log.debug("Count updated, tracking %d vehicles.", len(self.vehicles)) # ============================================================================ 

El progtwig ahora dibuja las rutas históricas de todos los vehículos actualmente rastreados en la imagen de salida, junto con el conteo de vehículos. A cada vehículo se le asigna 1 de 10 colores.

Observe que el vehículo D termina siendo rastreado dos veces, sin embargo, se cuenta solo una vez, ya que perdemos la pista de él antes de cruzar el divisor. Las ideas sobre cómo resolver esto se mencionan en el apéndice.

Basado en el último fotogtwig procesado generado por el script.

Último marco procesado

El recuento total de vehículos es 10 . Este es un resultado correcto.

Se pueden encontrar más detalles en la salida que generó el script:

  • Registro de depuración completo
  • Filtrado de registro de contador de vehículo
  • Un mosaico de los marcos procesados:


A. Mejoras potenciales

  • Refactor, añadir pruebas unitarias.
  • Mejora el filtrado / preprocesamiento de la máscara de primer plano
    • Varias iteraciones de filtrado, rellene los orificios con cv2.drawContours con CV_FILLED ?
    • ¿Algoritmo de cuenca?
  • Mejorar la categorización de los vectores de movimiento.
    • Cree un predictor para estimar el ángulo de movimiento inicial cuando se crean los vehículos (y solo se conoce una posición) ... para poder
    • Utilice el cambio de dirección en lugar de la dirección sola (creo que esto agruparía los angularjs de los vectores de movimiento válidos cerca de cero).
  • Mejorar el seguimiento del vehículo.
    • Prediga la posición de los marcos donde no se ve el vehículo.

B. Notas

  • Parece que no es posible extraer directamente la imagen de fondo actual de BackgroundSubtractorMOG en Python (al menos en OpenCV 2.4.x), pero hay una manera de hacerlo con un poco de trabajo.
  • Según lo sugerido por Henrik , podemos obtener una buena estimación del fondo utilizando la combinación de la mediana .