Roboflow の trackers ライブラリで YOLO + ByteTrack による動画マルチオブジェクト追跡をする

初めに

trackers は、Roboflow が提供するマルチオブジェクト追跡(MOTアルゴリズムクリーンルーム実装ライブラリです。Apache 2.0 ライセンスで公開されており、任意の検出モデルと組み合わせて使えます。

今回は YOLO で物体を検出し、ByteTrack で追跡、supervision で可視化する一連のパイプラインを動かします。ByteTrack は二段階アソシエーション(高信頼度 → 低信頼度の順でマッチング)を行うアルゴリズムで、低信頼度の検出も活用することでオクルージョンに強い追跡が可能です。

結果

入力

出力

開発環境

項目 バージョン
OS Windows 11
Python 3.13
パッケージマネージャ uv
trackers 2.2.0rc0
YOLO モデル yolo11m.pt (Ultralytics)

環境構築

リポジトリをクローンして依存パッケージをインストールします。

git clone https://github.com/roboflow/trackers.git
cd trackers
uv sync

trackers は ultralyticsopencv-python を依存に含んでいるため、追加インストールは不要です。

テスト用動画のダウンロード

supervision ライブラリの download_assets を使うとサンプル動画を簡単に取得できます。

from supervision.assets import download_assets, VideoAssets

video_path = download_assets(VideoAssets.PEOPLE_WALKING)

実行すると people-walking.mp4 がカレントディレクトリにダウンロードされます。

デモスクリプトの解説

スクリプトexamples/demo_bytetrack.py にあります。

引数

引数 デフォルト 説明
--source (必須) 入力動画のパス
--output output.mp4 出力動画のパス
--model yolo11m.pt YOLO モデル名またはパス

検出

YOLO で各フレームの物体を検出し、sv.Detections.from_ultralytics() で supervision の形式に変換します。

results = model(frame, verbose=False)[0]
detections = sv.Detections.from_ultralytics(results)

追跡

ByteTrackTracker().update(detections) を呼ぶだけでトラッカーID が付与されます。tracker_id-1 の場合は、まだ連続フレーム数の閾値(デフォルト2フレーム)に達していない未成熟なトラックです。

tracker = ByteTrackTracker()
detections = tracker.update(detections)

可視化

sv.BoxAnnotatorsv.LabelAnnotatorバウンディングボックスとラベルを描画します。

box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()

annotated = box_annotator.annotate(scene=frame.copy(), detections=detections)
annotated = label_annotator.annotate(
    scene=annotated, detections=detections, labels=labels
)

ソースコード全文

import argparse
import time

import cv2
import supervision as sv
from ultralytics import YOLO

from trackers import ByteTrackTracker


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="YOLO + ByteTrack object tracking demo"
    )
    parser.add_argument("--source", type=str, required=True)
    parser.add_argument("--output", type=str, default="output.mp4")
    parser.add_argument("--model", type=str, default="yolo11m.pt")
    return parser.parse_args()


def main() -> None:
    args = parse_args()

    model = YOLO(args.model)
    tracker = ByteTrackTracker()
    box_annotator = sv.BoxAnnotator()
    label_annotator = sv.LabelAnnotator()

    cap = cv2.VideoCapture(args.source)
    if not cap.isOpened():
        print(f"Error: cannot open video '{args.source}'")
        return

    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    writer = cv2.VideoWriter(args.output, fourcc, fps, (width, height))

    frame_idx = 0
    prev_time = time.time()

    print(f"Processing '{args.source}' ({total_frames} frames, {fps:.1f} FPS)")
    print(f"Output: '{args.output}'")
    print("Press 'q' to stop early.\n")

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        results = model(frame, verbose=False)[0]
        detections = sv.Detections.from_ultralytics(results)
        detections = tracker.update(detections)

        tracker_ids = (
            detections.tracker_id
            if detections.tracker_id is not None
            else []
        )
        labels = [
            f"#{int(tid)}" if tid >= 0 else "?"
            for tid in tracker_ids
        ]

        annotated = box_annotator.annotate(
            scene=frame.copy(), detections=detections
        )
        annotated = label_annotator.annotate(
            scene=annotated, detections=detections, labels=labels
        )

        now = time.time()
        processing_fps = 1.0 / max(now - prev_time, 1e-9)
        prev_time = now

        frame_idx += 1
        n_tracks = (
            int((detections.tracker_id >= 0).sum())
            if detections.tracker_id is not None
            else 0
        )
        print(
            f"\rFrame {frame_idx}/{total_frames}"
            f"  FPS: {processing_fps:.1f}"
            f"  Tracks: {n_tracks}",
            end="", flush=True,
        )

        writer.write(annotated)
        cv2.imshow("ByteTrack Demo", annotated)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            print("\nStopped by user.")
            break

    cap.release()
    writer.release()
    cv2.destroyAllWindows()
    print(f"\n\nDone. Output saved to '{args.output}'")


if __name__ == "__main__":
    main()

実行

以下のコマンドで実行します。

uv run python examples/demo_bytetrack.py --source people-walking.mp4

ターミナルに処理状況が表示されます。

Processing 'people-walking.mp4' (210 frames, 30.0 FPS)
Output: 'output.mp4'
Press 'q' to stop early.

Frame 210/210  FPS: 15.2  Tracks: 7

ウィンドウが開いてリアルタイムで追跡結果が表示されます。途中で止めたい場合は q キーを押してください。完了すると output.mp4 が出力されます。