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:
- Imagen que contiene un
- Imagen que contiene un mosaico de máscaras de primer plano :
- Imagen que contiene un mosaico de cuadros procesados
- El registro de depuración para la ejecución.
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.
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
:
-
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.
-
Para cada vehículo
-
Crea nuevos
Vehicle
para los partidos restantes -
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
-
Para cada vehículo
-
Eliminar vehículos que ya no son visibles
-
Para cada vehículo
- Si el último contador visto excede el umbral, retire el vehículo
-
Para cada 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:
- Registro de depuración completo
- Registro del contador del vehículo filtrado
- Un mosaico de los marcos procesados:
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
conCV_FILLED
? - Algoritmo de cuenca?
-
Múltiples iteraciones de filtrado, rellenar agujeros usando
-
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 .