with instalar gui python numpy tracking opencv traffic image-processing

instalar - python 3.7 opencv windows



Contando coches OpenCV+Python Issue (1)

He estado tratando de contar autos al cruzar la línea y funciona, pero el problema es que cuenta un auto muchas veces, lo cual es ridículo porque debe 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 <= 132)and(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-Tutorials llamado traffic.avi, y también es un video incorporado en la biblioteca de Matlab

¿Alguna ayuda de que cada auto se cuente una vez?

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


Preparación

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

Agregué el registro de los pasos importantes de su algoritmo, modifiqué un poco el código y agregué el guardado de la máscara y las imágenes procesadas, agregué la capacidad de ejecutar el script usando las imágenes de cuadros 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 cuadro; 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 progresamos. No implementé el algoritmo de conteo de vehículos, porque no funcionará por razones que nuevamente serán evidentes a medida que profundicemos 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 script que unirá todas las imágenes generadas juntas, por lo que es 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 los resultados que esperamos obtener. También debemos etiquetar todos los autos distintos en el video, para que sea más fácil hablar de ellos.

Si ejecutamos nuestro script y unimos las imágenes, obtenemos una cantidad de archivos útiles para ayudarnos a analizar el problema:

Al inspeccionarlos, se hacen evidentes varios problemas:

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

Solución

1. Sembrar previamente el sustractor 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 fotograma del video (número de fotograma 119) está completamente desprovisto de vehículos y, por lo tanto, podemos usarlo como nuestra imagen de fondo inicial. (Otras opciones para obtener una imagen adecuada se mencionan en notas y comentarios).

Para usar esta imagen de fondo inicial, simplemente la cargamos y la apply en el sustractor 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 obtenemos menos ruido y la detección del vehículo funciona mejor en los primeros cuadros.

2. Limpieza de 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 masks , los marcos procesados y el archivo de registro generado con el filtrado, podemos ver que ahora detectamos vehículos de manera más confiable y hemos mitigado el problema de que diferentes partes de un vehículo sean detectadas como objetos separados.

masks

3. Seguimiento de vehículos entre cuadros

En este punto, debemos revisar nuestro archivo de registro y recopilar 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 seleccionando 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:

Imagen ampliada ampliada de todos los rastros del vehículo:

Vectores

Para analizar el movimiento, necesitamos trabajar con vectores (es decir, la distancia y la dirección movidas). El siguiente diagrama muestra cómo los ángulos 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 clasificar los movimientos como válidos / inválidos es hacer un diagrama de dispersión (ángulo vs. distancia):

  • Los puntos verdes representan un movimiento válido, que determinamos usando las listas de puntos para cada vehículo.
  • Los puntos rojos representan movimientos inválidos: vectores entre puntos en carriles de tráfico adyacentes.
  • Tracé 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 clasificar 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 nuestra pérdida de seguimiento del vehículo D en los cuadros 43..48.

Algoritmo

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

  • Algún tipo de identificador
  • Lista de puestos, más reciente al frente
  • Contador visto por última vez: número de cuadros desde la última vez que vimos este vehículo
  • Marca para marcar si el vehículo se contó o no

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

  1. Actualizar los Vehicle actualmente rastreados:
    • Para cada vehículo
      • Si hay alguna 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, aumente el último contador visto para ese vehículo.
  2. Crea nuevos Vehicle para los partidos restantes
  3. Actualizar recuento de vehículos
    • Para cada vehículo
      • Si el vehículo ha pasado el divisor y aún no se ha contado, actualice el recuento total y marque el vehículo como contado
  4. Eliminar vehículos que ya no son visibles
    • Para cada vehículo
      • Si el último contador visto excede 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 programa ahora dibuja las rutas históricas de todos los vehículos actualmente rastreados en la imagen de salida, junto con el recuento 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, solo se cuenta una vez, ya que perdemos la pista antes de cruzar el divisor. Las ideas sobre cómo resolver esto se mencionan en el apéndice.

Basado en el último marco procesado generado por el script

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:

A. Mejoras potenciales

  • Refactorizar, agregar pruebas unitarias.
  • Mejora el filtrado / preprocesamiento de la máscara de primer plano
    • Múltiples iteraciones de filtrado, rellenar agujeros usando 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 vehículos (y solo se conoce una posición) ... para poder
    • Use el cambio de dirección en lugar de la dirección sola (creo que esto agruparía los ángulos de los vectores de movimiento válidos cercanos a cero).
  • Mejora el seguimiento del vehículo
    • Predecir la posición de los cuadros 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 mezcla mediana .