Подтвердить что ты не робот

Подсчет автомобилей OpenCV + Python Issue

Я старался подсчитывать автомобили при пересечении линии, и это работает, но проблема в том, что он считает один автомобиль много раз, что смешно, потому что его следует считать раз

Вот код, который я использую:

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()

и видео находится на моей странице github @https://github.com/Tes3awy/MatLab-Tutorials называемый traffic.avi, а также встроенное видео в библиотеке Matlab.

Любая помощь, которую каждый автомобиль подсчитывает один раз?


EDIT: отдельные кадры видео выглядят следующим образом:

R5O1yYD.png

4b9b3361

Ответ 1

Подготовка

Чтобы понять, что происходит, и в конечном итоге решить нашу проблему, нам сначала нужно немного улучшить script.

Я добавил регистрацию важных шагов вашего алгоритма, немного отредактировал код и добавил сохранение маски и обработанных изображений, добавил возможность запускать script с использованием отдельных кадров, а также некоторые другие модификаций.

Вот что выглядит script:

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()

Этот script отвечает за обработку потока изображений и идентифицирует все транспортные средства в каждом кадре - я называю их как matches в коде.


Задача подсчета обнаруженных транспортных средств делегируется классу VehicleCounter. Причина, по которой я выбрал этот класс, станет очевидной по мере нашего прогресса. Я не реализовал алгоритм подсчета вашего автомобиля, потому что он не будет работать по причинам, которые снова станут очевидными, когда мы углубимся в это глубже.

Файл vehicle_counter.py содержит следующий код:

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))

# ============================================================================

Наконец, я написал script, который будет сшивать все сгенерированные изображения вместе, поэтому их проще проверить:

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")

Анализ

Чтобы решить эту проблему, мы должны иметь некоторое представление о том, какие результаты мы ожидаем получить. Мы также должны обозначить все отдельные автомобили в видео, чтобы было легче говорить о них.

All 10 vehicles from the video

Если мы запустим наш script и сшиваем изображения вместе, мы получим несколько полезных файлов, которые помогут нам проанализировать проблему:

zXKlmBN.png

4ceMiT2.png

При проверке этих проблем становится очевидным ряд проблем:

  • Маски переднего плана имеют тенденцию быть шумными. Мы должны сделать некоторую фильтрацию (erode/dilate?), Чтобы избавиться от шума и узких промежутков.
  • Иногда мы пропускаем транспортные средства (серые).
  • Некоторые транспортные средства обнаруживаются дважды в одном кадре.
  • Транспортные средства редко обнаруживаются в верхних областях кадра.
  • Тот же самый автомобиль часто обнаруживается в последовательных кадрах. Нам нужно выяснить способ отслеживания одного и того же транспортного средства в последовательных кадрах и подсчета его только один раз.

Решение

1. Предварительное седирование фонового субтрактора

Наше видео довольно короткое, всего 120 кадров. При скорости обучения 0.01 для стабилизации фонового детектора потребуется значительная часть видео.

К счастью, последний кадр видео (номер кадра 119) полностью лишен транспортных средств, и поэтому мы можем использовать его в качестве исходного фонового изображения. (Другие варианты получения подходящего изображения указаны в примечаниях и комментариях.)

Background Image

Чтобы использовать это исходное фоновое изображение, мы просто загружаем его, а apply на фоновом вычитателе с коэффициентом обучения 1.0:

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

Когда мы смотрим на новую мозаику масок мы можем видеть, что мы получаем меньше шума, и обнаружение автомобиля улучшается в ранних кадрах.

vcQ66Zb.png

2. Очистка маски переднего плана

Простой подход для улучшения нашей маски переднего плана заключается в применении нескольких морфологических преобразований.

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

Проверка масок, обработанных кадров и файл журнала, сгенерированный с фильтрацией, мы можем видеть, что мы теперь более надежно обнаруживаем транспортные средства и смягчили вопрос о том, что различные части одного транспортного средства были обнаружены как отдельные объекты.

iavT8I1.png

ZKqDpfU.png

3. Отслеживание транспортных средств между кадрами

На этом этапе нам нужно пройти через наш файл журнала и собрать все координаты центра тяжести для каждого автомобиля. Это позволит нам строить и проверять траекторию каждой дорожки автомобиля по изображению и разрабатывать алгоритм для этого автоматически. Чтобы облегчить этот процесс, мы можем создать сокращенный журнал, выполнив соответствующие записи.

Списки координат центра тяжести:

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)]
}

На заднем плане изображены отдельные дорожки:

Traces of the detected vehicles

Комбинированное увеличенное изображение всех трасс автомобиля:

All traces together on a scaled up background

Векторы

Чтобы проанализировать движение, нам нужно работать с векторами (т.е. перемещением и перемещением). Следующая диаграмма показывает, как углы соответствуют перемещению транспортных средств на изображении.

Мы можем использовать следующую функцию для вычисления вектора между двумя точками:

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

категоризации

Один из способов поиска шаблонов, которые можно использовать для категоризации движений как допустимых/недействительных, заключается в создании графика рассеяния (угол по отношению к расстоянию):

Plot of angle vs distance

  • Зеленые точки представляют собой действительное движение, которое мы определили с использованием списков точек для каждого транспортного средства.
  • Красные точки представляют собой недействительные движения - векторы между точками в смежных полосах движения.
  • Я построил две синие кривые, которые мы можем использовать для разделения двух типов движений. Любая точка, лежащая ниже любой кривой, может считаться действительной. Кривые:
    • distance = -0.008 * angle**2 + 0.4 * angle + 25.0
    • distance = 10.0

Мы можем использовать следующую функцию для категоризации векторов движения:

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: Существует один выброс, который происходит из-за нашей потери дорожки автомобиля D в кадрах 43..48.

Алгоритм

Мы будем использовать класс Vehicle для хранения информации о каждом гусеничном автомобиле:

  • Какой-то идентификатор
  • Список позиций, самый последний на передней панели
  • Последний видимый счетчик - количество кадров, так как мы в последний раз видели этот автомобиль
  • Флаг, чтобы отметить, было ли подсчитано транспортное средство или нет

Класс VehicleCounter будет хранить список отслеживаемых в настоящее время транспортных средств и отслеживать общее количество. В каждом кадре мы будем использовать список ограничивающих полей и позиций идентифицированных транспортных средств (список кандидатов) для обновления состояния VehicleCounter:

  • В настоящее время отслеживается обновление Vehicle s:
    • Для каждого автомобиля
      • Если есть какое-либо действительное соответствие для данного автомобиля, обновите позицию автомобиля и reset его последний увиденный счетчик. Удалите совпадение из списка кандидатов.
      • В противном случае увеличьте счетчик последних просмотров для этого автомобиля.
  • Создайте новый Vehicle для любых оставшихся совпадений
  • Обновить количество автомобилей
    • Для каждого автомобиля
      • Если автомобиль прошел разделитель и еще не был подсчитан, обновите общее количество и отметьте транспортное средство в расчете
  • Снимите транспортные средства, которые больше не видны
    • Для каждого автомобиля
      • Если показанный счетчик превышает пороговое значение, удалите автомобиль

4. Решение

Мы можем повторно использовать основной script с окончательной версией vehicle_counter.py, содержащий реализацию нашего алгоритма подсчета:

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))

# ============================================================================

Теперь программа рисует исторические пути всех отслеживаемых в настоящее время транспортных средств на выходное изображение вместе с количеством транспортных средств. Каждому автомобилю присваивается 1 из 10 цветов.

Обратите внимание, что транспортное средство D заканчивается дважды, но оно подсчитывается только один раз, так как мы теряем его, прежде чем пересекать делитель. Идеи о том, как разрешить это, упоминаются в приложении.

На основе последнего обработанного кадра, сгенерированного с помощью script

<Т411 >

общее количество транспортных средств 10. Это правильный результат.

Более подробную информацию можно найти на выходе script сгенерировано:

Ipi0vkB.png


A. Потенциальные улучшения

  • Рефакторинг, добавьте модульные тесты.
  • Улучшить фильтрацию/предварительную обработку маски переднего плана
    • Несколько итераций фильтрации, заполнения отверстий с помощью cv2.drawContours с помощью CV_FILLED?
    • Алгоритм Watershed?
  • Улучшить категоризацию векторов движения
    • Создайте предиктор, чтобы оценить начальный угол перемещения при создании транспортных средств (и только одна позиция известна)... чтобы иметь возможность
    • Используйте изменение направления, а не направление в одиночку (я думаю, что это скроет углы действительных векторов движения, близких к нулю).
  • Улучшение отслеживания транспортных средств
    • Предсказать положение для фреймов, где транспортное средство не видно.

В. Примечания

  • Невозможно напрямую извлечь текущее фоновое изображение из BackgroundSubtractorMOG в Python (по крайней мере, в OpenCV 2.4.x), но есть способ сделать это с небольшой работой.
  • Как было предложено Henrik, мы можем получить хорошую оценку фона, используя median смешивания.