ByteTrack, BoT-SORT, TrackTrack으로 객체 추적하기
모든 프레임에서 각 객체에 안정적인 ID를 부여하고, 추적기를 선택한 다음, 탐지기에 맞춰 임계값을 조정합니다.
모든 개수, 체류 시간, 속도 추정은 한 가지 질문에 달려 있습니다. 이 프레임의 ID 17은 이전 프레임의 ID 17과 동일한 객체일까요? 약 30명의 보행자가 등장하는 동일한 62프레임 클립과 동일한 탐지기를 사용했을 때 ByteTrack은 ID 32개를 찾았고, 기본 TrackTrack은 9개를 찾았습니다. 추적기를 선택하고 조정하는 과정은 모델의 일부이며, 잘못 선택하면 이후의 모든 수치가 눈에 띄지 않게 틀어집니다.
실습형
추적기가 추가하는 기능#

이 프레임의 레이블과 궤적은 추적기의 출력입니다. 각 ID는 실제 객체 하나에 대한 가설인 트랙이며, 궤적은 해당 객체의 최근 이력입니다. 각 프레임에서 추적기는 칼만 필터로 각 트랙의 위치를 예측한 다음, 박스 중첩률(및 선택적으로 외형)을 기준으로 새 탐지 결과를 예측값과 연결합니다.
track_buffer 프레임 동안 매칭되지 않은 트랙은 삭제됩니다. 이것이 전체 메커니즘이며, 임계값이 이후의 모든 결과를 좌우합니다.
추적기 선택하기#
추적 모드에는 BoT-SORT, ByteTrack, OC-SORT, Deep OC-SORT, FastTracker, TrackTrack 등 추적기 6개가 포함되어 있습니다(tracker을 전달하지 않았을 때의 기본값은 TrackTrack입니다). 대부분의 요구 사항에는 다음 세 가지 추적기가 적합합니다.
| 추적기 | 매칭 기준(기본 구성) | 다음 상황에 적합합니다 |
|---|---|---|
| ByteTrack | 모션과 박스 중첩률을 사용하며, 신뢰도가 낮은 박스를 복구하는 두 번째 매칭 단계를 포함합니다. | 고정 카메라, 작거나 불확실한 탐지기, 처리량이 중요한 경우 |
| BoT-SORT | ByteTrack 방식의 매칭에 카메라 모션 보정(gmc_method: sparseOptFlow)을 더합니다. | 움직이거나 흔들리는 카메라를 사용하는 경우입니다. 군중과 장시간 가림에 대비해 with_reid: True을 설정하세요. |
| TrackTrack | 다중 기준 비용(박스 형태, 신뢰도, 각도), 트랙을 고려한 초기화, 카메라 모션 보정 | 강력하고 잘 학습된 모델의 신뢰도 높은 탐지 결과 |
BoT-SORT의 외형 매칭(재식별)은 기본적으로 비활성화되어 있습니다. model: auto을 사용하면 탐지기 자체의 특징을 재사용하므로, 활성화해도 오버헤드가 거의 추가되지 않습니다.
하나의 클립에서 추적기 비교하기#
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
for tracker in ("bytetrack.yaml", "botsort.yaml", "tracktrack.yaml"):
ids, frames = set(), 0
for r in model.track(video, stream=True, tracker=tracker, classes=[0], verbose=False):
frames += 1
if r.boxes.is_track:
ids.update(r.boxes.id.int().tolist())
print(f"{tracker:16s} {frames} frames, {len(ids)} unique person IDs")bytetrack.yaml 62 frames, 32 unique person IDs
botsort.yaml 62 frames, 33 unique person IDs
tracktrack.yaml 62 frames, 9 unique person IDs이 클립에는 위에서 내려다본 광장을 가로지르는 약 30명의 사람이 나옵니다. 따라서 ByteTrack과 BoT-SORT의 결과가 실제 개수에 가깝습니다. TrackTrack에 문제가 있는 것은 아닙니다. new_track_thresh: 0.7보다 신뢰도가 높을 때만 트랙을 시작하고, track_high_thresh: 0.6보다 높을 때 첫 매칭을 수행합니다. 하지만 이 클립에서 YOLO26n의 사람 탐지 신뢰도 중앙값은 0.53이며, 탐지 결과 중 17%만 0.7에 도달합니다. 대부분의 사람은 ID를 받지 못합니다. 의사 결정 원칙은 다음과 같습니다. 개수를 신뢰하기 전에 추적기의 임계값과 탐지기의 신뢰도 분포를 비교하세요.
파이프라인에서 ID 사용하기#
from collections import defaultdict
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
counts = defaultdict(set)
for r in model.track(video, stream=True, tracker="bytetrack.yaml", verbose=False):
if not r.boxes.is_track: # no confirmed tracks in this frame yet
continue
for cls, tid in zip(r.boxes.cls.int().tolist(), r.boxes.id.int().tolist()):
counts[r.names[cls]].add(tid)
for cls, ids in counts.items():
print(f"{cls}: {len(ids)} unique objects")person: 32 unique objects클래스별 집합을 사용하면 각 ID가 몇 개 프레임에 나타나든 한 번만 계산하므로 탐지 수가 아니라 객체 수를 얻게 됩니다. stream=True은 동영상 전체를 메모리에 보관하지 않고 프레임별로 결과를 반환합니다. 영역 계수, 히트맵, 거리 계산은 이 루프 위에 얹는 간단한 계층입니다.
직접 읽어 들인 프레임 추적하기#
경로 또는 URL을 전달하면 추적기 상태를 자동으로 관리합니다. 자체 디코더, 웹캠 또는 RTSP 카메라에서 프레임을 가져오는 경우, 각 호출이 동일한 시퀀스를 이어 가도록 persist=True을 전달하세요(예측 모드 문서에 모든 소스 유형이 나와 있습니다).
import cv2
from ultralytics import YOLO
from ultralytics.utils.downloads import safe_download
model = YOLO("yolo26n.pt")
video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video)) # or 0 for a webcam, or an rtsp:// URL
seen = set()
while True:
ok, frame = cap.read()
if not ok:
break
r = model.track(frame, persist=True, tracker="bytetrack.yaml", classes=[0], verbose=False)[0]
if r.boxes.is_track:
seen.update(r.boxes.id.int().tolist()) # your logic: dwell time, trajectories, alerts
cap.release()
print(f"{len(seen)} unique IDs")32 unique IDspersist=True이 62회 호출 전체에서 추적기 하나를 계속 유지했으므로 경로를 전달했을 때와 결과가 같습니다.
탐지기에 맞게 추적기 조정하기#
패키지에 포함된 구성을 복사하고, 탐지기에 맞지 않는 임계값을 낮춘 다음, 새 파일을 전달합니다.
from pathlib import Path
from ultralytics import YOLO
from ultralytics.utils import ROOT
cfg = (ROOT / "cfg/trackers/tracktrack.yaml").read_text() # packaged defaults
for old, new in (
("track_high_thresh: 0.6", "track_high_thresh: 0.3"),
("track_low_thresh: 0.25", "track_low_thresh: 0.1"),
("new_track_thresh: 0.7", "new_track_thresh: 0.35"),
):
cfg = cfg.replace(old, new)
Path("tracktrack_tuned.yaml").write_text(cfg)
model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
for tracker in ("tracktrack.yaml", "tracktrack_tuned.yaml"):
ids = set()
for r in model.track(video, stream=True, tracker=tracker, classes=[0], verbose=False):
if r.boxes.is_track:
ids.update(r.boxes.id.int().tolist())
print(f"{tracker:22s} {len(ids)} unique person IDs")tracktrack.yaml 9 unique person IDs
tracktrack_tuned.yaml 27 unique person IDs임계값을 탐지기 신뢰도 중앙값 바로 아래로 설정하자 TrackTrack의 ID 수가 9개에서 27개로 증가해 직접 센 개수에 가까워졌습니다. 모든 추적기에 공통으로 적용되는 조정 항목은 다음과 같습니다.
| 설정 항목 | ByteTrack 기본값 | 값을 높이는 경우 | 값을 낮추는 경우 |
|---|---|---|---|
track_high_thresh | 0.25 | 첫 매칭에서 신뢰도가 낮은 탐지 결과 무시 | 신뢰도가 낮은 탐지기를 추적 |
new_track_thresh | 0.25 | 수명이 짧은 오탐 트랙 줄이기 | 희미하게 보이는 객체의 트랙 시작 |
track_buffer | 30프레임 | 장시간 가림을 견디도록 설정 | 사라진 객체의 ID를 더 빨리 해제 |
match_thresh | 0.8 | 더 유연하게 매칭(빠른 움직임, 흔들리는 박스) | 더 엄격하게 매칭(군중) |
주의할 점#
증상: 특히 작은 모델에서 객체 수보다 ID 수가 훨씬 적습니다. 해결 방법: 탐지 신뢰도의 중앙값을 출력하고(해답 참조), new_track_thresh과 track_high_thresh을 그 값보다 약간 낮게 설정합니다.
증상: 직접 model.track(frame)을 호출하면 매 프레임마다 ID가 초기화되거나, 두 카메라가 하나의 모델에 입력을 제공할 때 ID가 객체 사이에서 바뀝니다. 해결 방법: 하나의 스트림에서 연속된 프레임에 persist=True을 전달하고, 카메라마다 별도의 모델 인스턴스를 사용합니다(또는 스트림별로 추적기를 하나씩 유지하는 .streams 소스를 사용합니다).
증상: 주차된 차량의 ID가 몇 프레임마다 바뀝니다. 해결 방법: 추적기가 기존 트랙과 매칭하는 대신 새 트랙을 시작하고 있습니다. 먼저 해당 객체에 대한 탐지기의 신뢰도를 확인한 다음 match_thresh과 track_buffer을 높입니다.
정답 보기
먼저 탐지기의 신뢰도를 측정한 다음, 그보다 약간 낮게 임계값을 설정하세요.
import torch
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
conf = torch.cat([r.boxes.conf for r in model.predict(video, stream=True, classes=[0], verbose=False)])
print(f"median {conf.median():.2f}, share >= 0.7: {(conf >= 0.7).float().mean():.0%}")median 0.53, share >= 0.7: 17%광장 클립의 표에는 ByteTrack 32개, BoT-SORT 33개, TrackTrack 9개, 조정된 TrackTrack 27개, 직접 센 개수 약 30개로 나옵니다. 탐지 결과 중 17%만 TrackTrack의 new_track_thresh: 0.7을 넘으므로, 해당 값을 0.35로 낮추는 것이 적절한 수정입니다(track_high_thresh: 0.3도 함께 적용). 이 고정된 오버헤드 카메라에서는 일반 ByteTrack이 더 간단한 선택입니다.