Отслеживание является одним из наиболее важных компонентов обнаружения объектов, когда дело доходит до реальных приложений. Такие приложения, как видеонаблюдение в режиме реального времени и системы автономного вождения, не могут полностью раскрыть свой потенциал без отслеживания. Отслеживание объектов, будь то люди или транспортные средства, играет важную роль. Однако тестирование множества моделей обнаружения и повторной идентификации является трудоемким. Для этого мы собираемся упростить процесс отслеживания в режиме реального времени с помощью глубокой сортировки с помощью Torch vision Detectors.

Рисунок 1. Пример отслеживания глубокой сортировки с использованием модели Torchvision Faster RCNN MobileNetV3.
Рисунок 1. Пример отслеживания глубокой сортировки с использованием модели Torchvision Faster RCNN MobileNetV3.

В этой статье мы создадим небольшую кодовую базу, которая позволит нам протестировать любую модель обнаружения объектов из Torchvision. Мы объединим это с библиотекой глубокой сортировки в реальном времени, которая предоставит нам доступ к ряду моделей Re-IDENT. Кроме того, мы также проведем качественный и количественный анализ FPS и результатов для различных комбинаций детекторов и моделей Re-ID.

Что такое модели Re-ID и зачем они нам нужны?

Прежде чем мы углубимся в часть кодирования, давайте обсудим модели повторной идентификации (сокращенно Re-ID).

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

Но что, если мы хотим отслеживать что-то другое, кроме людей?

Хотя при отслеживании людей рекомендуется использовать модель повторной идентификации человека, мы можем использовать любую большую предварительно подготовленную модель, например, если мы хотим отслеживать и повторно идентифицировать автомобили по видеокадрам. Для такого случая у нас нет модели повторной идентификации, обученной для автомобилей. Однако для этого мы можем использовать предварительно обученную модель ImageNet. Поскольку модель уже обучена работе с миллионами изображений, она сможет легко извлекать особенности автомобилей.

Аналогично, мы также можем использовать модели базовых изображений (например, CLIP ResNet50) для повторной идентификации. Мы собираемся использовать такие модели в этой статье.

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

Зачем нам нужны модели с повторной идентификацией?

Модели Re-IDENT обладают множеством преимуществ, особенно в системах с несколькими камерами, где безопасность и точность являются главными приоритетами.

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

Клип 1. Пример идентификации по окклюзии в режиме мультикамеры.

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

Взаимосвязь между перекрытиями: перекрытия могут возникать, когда люди или транспортные средства перемещаются в видеокадрах. Если человек оказывается закрытым объектом на несколько кадров и появляется снова, то модель повторного идентификатора может связать тот же идентификатор, что и до закрытия.

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

Клип 2. Повторная идентификация людей с помощью нескольких камер в условиях низкой освещенности.

Настройка глубокой сортировки в режиме реального времени

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

Наиболее важной из них является deep-sort-realtime библиотека. Она предоставляет нам доступ к алгоритму глубокой сортировки через вызовы API. Наряду с этим, есть возможность выбора из нескольких моделей повторной идентификации, которые были предварительно обучены на огромных базовых наборах данных, таких как ImageNet. Эти модели включают в себя множество моделей OpenAI CLIP image, а также torchreid модели.

Перед выполнением следующих шагов, пожалуйста, убедитесь, что вы установили PyTorch вместе с CUDA.
Чтобы установить deep-sort-realtime библиотеку, выполните следующую команду в выбранной среде:

pip install deep-sort-realtime

Это дает нам доступ к алгоритму глубокой сортировки и одному встроенному в mobilenet встраивателю повторной идентификации.

Но если мы хотим получить доступ к OpenAI CLIP Re-ID и torchreid встраивателям, то нам нужно выполнить дополнительные шаги.

Чтобы использовать средства встраивания клипов, мы установим библиотеку клипов OpenAI с помощью следующей команды:

pip install git+https://github.com/openai/CLIP.git

Это позволяет нам использовать несколько моделей CLIP ResNet и Vision Transformer в качестве встраивающих устройств.

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

Сначала нам нужно клонировать репозиторий и сделать его текущим рабочим каталогом. Вы можете клонировать его в каталог, отличный от каталога проекта.

git clone https://github.com/KaiyangZhou/deep-person-reid.git
cd deep-person-reid/

Затем проверьте requirements.txt файл и установите зависимости в соответствии с вашими потребностями. Как только это будет сделано, установите библиотеку в режиме разработки.

python setup.py develop

Выполнив все этапы установки, мы можем перейти к части кодирования.

Код для глубокой сортировки в режиме реального времени с помощью Torchvision

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

Детали отслеживания будут обрабатываться библиотекой deep-sort-realtime внутри компании. Мы стремимся создать модульную кодовую базу для быстрого создания прототипов нескольких моделей обнаружения и повторной идентификации.

Два основных файла Python, которые нам нужны, - это deep_sort_tracking.py и utils.py.Содержимое coco_classes.py файла, который содержит список всех классов набора данных COCO, выглядит следующим образом:

COCO_91_CLASSES = [
    '__background__',
    'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
    'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A', 'stop sign',
    'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
    'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack', 'umbrella', 'N/A', 'N/A',
    'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
    'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
    'bottle', 'N/A', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
    'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
    'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table',
    'N/A', 'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
    'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A', 'book',
    'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'
]

Это будет использоваться для сопоставления индексов классов и имен классов.

Код отслеживания глубокой сортировки

deep_sort_tracking.py - это исполняемый скрипт, который мы запустим из командной строки.

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

Код пояснит это подробнее. Давайте начнем с операторов импорта и анализаторов аргументов.

import torch
import torchvision
import cv2
import os
import time
import argparse
import numpy as np
 
 
from torchvision.transforms import ToTensor
from deep_sort_realtime.deepsort_tracker import DeepSort
from utils import convert_detections, annotate
from coco_classes import COCO_91_CLASSES
 
 
parser = argparse.ArgumentParser()
parser.add_argument(
    '--input',
    default='input/mvmhat_1_1.mp4',
    help='path to input video',
)
parser.add_argument(
    '--imgsz',
    default=None,
    help='image resize, 640 will resize images to 640x640',
    type=int
)
parser.add_argument(
    '--model',
    default='fasterrcnn_resnet50_fpn_v2',
    help='model name',
    choices=[
        'fasterrcnn_resnet50_fpn_v2',
        'fasterrcnn_resnet50_fpn',
        'fasterrcnn_mobilenet_v3_large_fpn',
        'fasterrcnn_mobilenet_v3_large_320_fpn',
        'fcos_resnet50_fpn',
        'ssd300_vgg16',
        'ssdlite320_mobilenet_v3_large',
        'retinanet_resnet50_fpn',
        'retinanet_resnet50_fpn_v2'
    ]
)
parser.add_argument(
    '--threshold',
    default=0.8,
    help='score threshold to filter out detections',
    type=float
)
parser.add_argument(
    '--embedder',
    default='mobilenet',
    help='type of feature extractor to use',
    choices=[
        "mobilenet",
        "torchreid",
        "clip_RN50",
        "clip_RN101",
        "clip_RN50x4",
        "clip_RN50x16",
        "clip_ViT-B/32",
        "clip_ViT-B/16"
    ]
)
parser.add_argument(
    '--show',
    action='store_true',
    help='visualize results in real-time on screen'
)
parser.add_argument(
    '--cls',
    nargs='+',
    default=[1],
    help='which classes to track',
    type=int
)
args = parser.parse_args()

Мы импортируем класс трекера DeepSort из deep_sort_realtime пакета, который позже будем использовать для инициализации трекера. Мы также импортируем функции convert_detections и annotate из пакета utils. На данный момент нам не нужно вдаваться в подробности двух вышеупомянутых функций. Давайте обсудим их при написании кода для utils.py файла.

Описание всех анализаторов аргументов, которые мы создали выше:

  • --input: Путь к входному видеофайлу.

  • --imgsz: Принимается единственное целое число, указывающее квадратную форму, в которую следует изменить размер изображения.

  • --model: Это перечисление моделей Torchvision. Мы можем выбрать любую из моделей обнаружения объектов в Torchvision.

  • --threshold: Пороговое значение, ниже которого все обнаружения будут отброшены.

  • --embedder: Модель встраивания Re-ID, которую мы хотим использовать.

  • --show: Логический аргумент, указывающий, хотим ли мы визуализировать выходные данные в режиме реального времени или нет.

  • --cls: Она принимает индексы классов, которые мы хотим отслеживать. По умолчанию она отслеживает только людей. Если мы хотим отслеживать людей и велосипеды, мы должны предоставить --cls 1 2.

Далее мы установим начальное значение, определим выходной каталог и распечатаем информацию об эксперименте.

np.random.seed(42)
 
 
OUT_DIR = 'outputs'
os.makedirs(OUT_DIR, exist_ok=True)
 
 
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
COLORS = np.random.randint(0, 255, size=(len(COCO_91_CLASSES), 3))
 
 
print(f"Tracking: {[COCO_91_CLASSES[idx] for idx in args.cls]}")
print(f"Detector: {args.model}")
print(f"Re-ID embedder: {args.embedder}")

Идя дальше, нам нужно загрузить модель обнаружения, модель повторной идентификации и видеофайл.

# Load model.
model = getattr(torchvision.models.detection, args.model)(weights='DEFAULT')
# Set model to evaluation mode.
model.eval().to(device)
 
 
# Initialize a SORT tracker object.
tracker = DeepSort(max_age=30, embedder=args.embedder)
 
 
VIDEO_PATH = args.input
cap = cv2.VideoCapture(VIDEO_PATH)
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
frame_fps = int(cap.get(5))
frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
save_name = VIDEO_PATH.split(os.path.sep)[-1].split('.')[0]
# Define codec and create VideoWriter object.
out = cv2.VideoWriter(
    f"{OUT_DIR}/{save_name}_{args.model}_{args.embedder}.mp4",
    cv2.VideoWriter_fourcc(*'mp4v'), frame_fps,
    (frame_width, frame_height)
)
 
 
frame_count = 0 # To count total frames.
total_fps = 0 # To get the final frames per second.

Как вы можете видеть, мы также определяем видеоинформацию, которую используем для определения имени выходного файла. frame_count и total_fps помогут нам отслеживать количество повторенных кадров и частоту кадров в секунду, с которой происходит вывод.
Заключительная часть этого файла кода включает в себя большой while блок для перебора видеокадров и выполнения вывода обнаружения и отслеживания.

while cap.isOpened():
    # Read a frame
    ret, frame = cap.read()
    if ret:
        if args.imgsz != None:
            resized_frame = cv2.resize(
                cv2.cvtColor(frame, cv2.COLOR_BGR2RGB),
                (args.imgsz, args.imgsz)
            )
        else:
            resized_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        # Convert frame to tensor and send it to device (cpu or cuda).
        frame_tensor = ToTensor()(resized_frame).to(device)
 
 
        start_time = time.time()
        # Feed frame to model and get detections.
        det_start_time = time.time()
        with torch.no_grad():
            detections = model([frame_tensor])[0]
        det_end_time = time.time()
 
 
        det_fps = 1 / (det_end_time - det_start_time)
    
        # Convert detections to Deep SORT format.
        detections = convert_detections(detections, args.threshold, args.cls)
    
        # Update tracker with detections.
        track_start_time = time.time()
        tracks = tracker.update_tracks(detections, frame=frame)
        track_end_time = time.time()
        track_fps = 1 / (track_end_time - track_start_time)
 
 
        end_time = time.time()
        fps = 1 / (end_time - start_time)
        # Add `fps` to `total_fps`.
        total_fps += fps
        # Increment frame count.
        frame_count += 1
 
 
        print(f"Frame {frame_count}/{frames}",
              f"Detection FPS: {det_fps:.1f},",
              f"Tracking FPS: {track_fps:.1f}, Total FPS: {fps:.1f}")
        # Draw bounding boxes and labels on frame.
        if len(tracks) > 0:
            frame = annotate(
                tracks,
                frame,
                resized_frame,
                frame_width,
                frame_height,
                COLORS
            )
        cv2.putText(
            frame,
            f"FPS: {fps:.1f}",
            (int(20), int(40)),
            fontFace=cv2.FONT_HERSHEY_SIMPLEX,
            fontScale=1,
            color=(0, 0, 255),
            thickness=2,
            lineType=cv2.LINE_AA
        )
        out.write(frame)
        if args.show:
            # Display or save output frame.
            cv2.imshow("Output", frame)
            # Press q to quit.
            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
    else:
        break
    
# Release resources.
cap.release()
cv2.destroyAllWindows()

После обработки каждого кадра мы пропускаем тензор через модель обнаружения для получения обнаружений. detections Перед передачей его трекеру они должны быть в формате обнаружения. Для этого мы вызываем convert_detections() функцию. Вместе с обнаружениями ей также передаются порог обнаружения и индексы класса.

После того, как мы получим обнаружения в надлежащем формате, мы вызываем update_tracks() метод tracker объекта.

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

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

Служебные скрипты для обнаружения и аннотирования

В utils.py файле есть две функции. Давайте начнем с импорта и convert_detections() функции.

import cv2
import numpy as np
 
 
# Define a function to convert detections to SORT format.
def convert_detections(detections, threshold, classes):
    # Get the bounding boxes, labels and scores from the detections dictionary.
    boxes = detections["boxes"].cpu().numpy()
    labels = detections["labels"].cpu().numpy()
    scores = detections["scores"].cpu().numpy()
    lbl_mask = np.isin(labels, classes)
    scores = scores[lbl_mask]
    # Filter out low confidence scores and non-person classes.
    mask = scores > threshold
    boxes = boxes[lbl_mask][mask]
    scores = scores[mask]
    labels = labels[lbl_mask][mask]
 
 
    # Convert boxes to [x1, y1, w, h, score] format.
    final_boxes = []
    for i, box in enumerate(boxes):
        # Append ([x, y, w, h], score, label_string).
        final_boxes.append(
            (
                [box[0], box[1], box[2] - box[0], box[3] - box[1]],
                scores[i],
                str(labels[i])
            )
        )
 
 
    return final_boxes

convert_detections() Функция принимает выходные данные модели и возвращает только те поля классов и метки, которые мы хотим отслеживать. Для каждого объекта библиотека отслеживания ожидает кортеж, содержащий ограничивающие рамки в x, y, w, h формате, оценки и индексы меток. Мы сохраняем его в final_boxes списке и возвращаем в конце.
annotate() Функция принимает выходные данные трекера и информацию о кадре.

# Function for bounding box and ID annotation.
def annotate(tracks, frame, resized_frame, frame_width, frame_height, colors):
    for track in tracks:
        if not track.is_confirmed():
            continue
        track_id = track.track_id
        track_class = track.det_class
        x1, y1, x2, y2 = track.to_ltrb()
        p1 = (int(x1/resized_frame.shape[1]*frame_width), int(y1/resized_frame.shape[0]*frame_height))
        p2 = (int(x2/resized_frame.shape[1]*frame_width), int(y2/resized_frame.shape[0]*frame_height))
        # Annotate boxes.
        color = colors[int(track_class)]
        cv2.rectangle(
            frame,
            p1,
            p2,
            color=(int(color[0]), int(color[1]), int(color[2])),
            thickness=2
        )
        # Annotate ID.
        cv2.putText(
            frame, f"ID: {track_id}",
            (p1[0], p1[1] - 10),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.5,
            (0, 255, 0),
            2,
            lineType=cv2.LINE_AA
        )
    return frame

Она извлекает идентификатор отслеживания объекта, используя track_id атрибут, и метку класса, используя det_class атрибут. Затем мы помечаем рамку ограничивающей рамкой и идентификатором и возвращаем ее.

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

Отслеживание глубокой сортировки с использованием модели обнаружения Torchvision – эксперименты

Примечание: Все эксперименты с выводом данных проводились на ноутбуке с графическим процессором GTX 1060, процессором i7 8-го поколения и 16 ГБ оперативной памяти.

Давайте выполним первый вывод глубокой сортировки, используя модель обнаружения Torchvision по умолчанию и средство встраивания повторного идентификатора.

python deep_sort_tracking.py --input input/video_traffic_1.mp4 --show

Приведенная выше команда запустит скрипт с более быстрой моделью RCNN ResNet50 FPN V2 вместе с моделью встраивания MobileNet Re-ID. Кроме того, по умолчанию он будет отслеживать только людей.

Вот видео-результат.

Клип 3. Результат отслеживания человека с использованием более быстрого RCNN ResNet50 FPN V2 и встроенного устройства повторной идентификации MobileNet.

Результаты хороши даже при средней частоте 2,5 кадра в секунду. Модель может корректно отслеживать людей. Примечательно отметить, что благодаря надежности более быстрой модели RCNN даже человек внутри автомобиля распознается на последних нескольких кадрах.

Но можем ли мы сделать вывод еще быстрее? Да, мы можем использовать более быструю модель RCNN MobileNetV3, которая является детектором легкого веса. Мы можем объединить это с моделью MobileNet Re-ID для получения превосходных результатов.

python deep_sort_tracking.py --input input/video_traffic_1.mp4 --model fasterrcnn_mobilenet_v3_large_fpn --embedder mobilenet --cls 1 3 --show

На этот раз мы предоставляем--cls 1 3, которые соответствуют классовым индексам человека и автомобиля в наборе данных COCO.

Клип 4. Более быстрая модель RCNN MobileNetV3 наряду с моделью MobileNetV3 обеспечивает больше кадров в секунду при отслеживании нескольких категорий в видео.

Отслеживание глубокой сортировки выполняется почти со скоростью 8 кадров в секунду. В основном это связано с более быстрой моделью RCNN MobileNetV3. Результаты также хороши. Обнаруживаются все автомобили, и меньше переключений между идентификаторами.

Далее мы будем использовать встраиваемый модуль OpenAI CLIP ResNet50 в качестве модели Re-ID и детектор Torchvision RetinaNet. Здесь мы используем гораздо более плотную дорожную обстановку, где мы будем отслеживать легковые и грузовые автомобили.

python deep_sort_tracking.py --input input/video_traffic_2.mp4 --model retinanet_resnet50_fpn_v2 --embedder clip_RN50 --cls 3 8 --show --threshold 0.7

Клип 5. Использование детектора RetinaNet и встроенного устройства Clip ResNet50 в качестве модели повторной идентификации для отслеживания плотного трафика.

Результаты неплохие. Детектор способен обнаруживать почти все легковые и грузовые автомобили, а трекер глубокой сортировки отслеживает почти все из них. Однако также есть несколько переключателей идентификаторов. Следует отметить одну интересную вещь: детектор иногда распознает удаленные грузовики как легковые. Это корректируется по мере приближения грузовика. Но идентификатор не переключается. Это показывает еще одну полезность использования модели повторной идентификации.

Для заключительного эксперимента мы будем использовать torchreid библиотеку в очень сложных условиях. По умолчанию в torchreid модели используется osnet_ain_x1_0 модель повторной идентификации предварительно обученного человека. Наряду с этим мы будем использовать модель обнаружения RetinaNet.

python deep_sort_tracking.py --input input/mvmhat_1_1.mp4 --model retinanet_resnet50_fpn_v2
 --embedder torchreid --cls 1 --show --threshold 0.7

Клип 6. Отслеживание людей в сложной сцене, где часто происходит внутриполушарная окклюзия.

Хотя частота кадров в секунду немного ниже из-за модели RetinaNet, результаты очень хорошие. Несмотря на то, что несколько человек пересекаются друг с другом, мы видим только два переключения идентификаторов.

Заключение

В этой статье мы создали простую кодовую базу для объединения различных моделей обнаружения Torchvision с моделями повторной идентификации для выполнения отслеживания глубокой сортировки. Результаты не были идеальными, но опробование различных комбинаций встраивателей повторной идентификации и детекторов объектов может оказаться полезным. Такое решение может быть использовано в дальнейшем для отслеживания трафика в режиме реального времени с использованием легких детекторов, которые настроены только на транспортные средства.

P.S. код на GitHub

Комментарии (1)


  1. ivankudryavtsev
    21.08.2023 14:48

    Печалька. Переводить Deep SORT (Simple Online Realtime Tracking) как Глубокая сортировка… ну в общем, no comments… не тратьте свою жизнь на такие глупости…