SORT : Simple Online Real Time Tracking
SORT가 쓰이는 영역들
교통관제에서 차량의 움직임을 추적할때 사용될 수 있다.
스포츠에서 공의 움직임을 추적하여 파울이나 득점을 결정하는 용도로 사용될 수 있다.
다중카메라감시시스템에서 한 카메라에 잡힌 사람이 다른 카메라에 잡혔을때 동일인물인지 여부를 확인하는 용도로 사용될 수 있다.
SORT가 가진 문제는 Occulusion, Different View Point, ID switching이다.
이 문제를 해결하기 위해 DeepSORT가 사용된다.



SORT 는 칼만필터와 헝가리안 알고리즘으로 구성된다.
1단계 | 각기 객체들을 YOLO를 사용해서 탐지한다. |
2단계 | 이전 프레임과 다음 프레임 사이의 위치를 가우스 분포로 측정한다 |
3단계 | 비용함수로 매트릭스를 구성한다 |
4단계 | 프레임간에 객체 아이디를 할당한다 |
DeepSORT를 사용하면...

차량 Bbox에 흐름선이 남는다.
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
에러가 있어서 위의 옵션을 주고
import torch
import torch.backends.cudnn as cudnn
from numpy import random
Bbox의 색상을 랜덤하게 주기 위해서 random을 임포트한다. 컬러팔레트의 색상을 랜덤하게 고른다.
from ultralytics.yolo.engine.predictor import BasePredictor
from ultralytics.yolo.utils import DEFAULT_CONFIG, ROOT, ops
from ultralytics.yolo.utils.checks import check_imgsz
from ultralytics.yolo.utils.plotting import Annotator, colors, save_one_box
import cv2
객체탐지와 트랙킹을 위해 cv2를 임포트한다.
DeepSORT를 임포트한다.
from deep_sort_pytorch.utils.parser import get_config
from deep_sort_pytorch.deep_sort import DeepSort
double ended queue를 생성해서 objectID를 관리. 화면에서 사라지는 ID와 나타나는 ID를 관리한다.
from collections import deque
팔레트를 정의하고 이름을 정의하고
palette = (2 ** 11 - 1, 2 ** 15 - 1, 2 ** 20 - 1)
data_deque = {}
deepsort = None
트래커를 이니셜라이징 한다
def init_tracker():
global deepsort
cfg_deep = get_config()
cfg_deep.merge_from_file("deep_sort_pytorch/configs/deep_sort.yaml")
deepsort= DeepSort(cfg_deep.DEEPSORT.REID_CKPT,
max_dist=cfg_deep.DEEPSORT.MAX_DIST, min_confidence=cfg_deep.DEEPSORT.MIN_CONFIDENCE,
nms_max_overlap=cfg_deep.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg_deep.DEEPSORT.MAX_IOU_DISTANCE,
max_age=cfg_deep.DEEPSORT.MAX_AGE, n_init=cfg_deep.DEEPSORT.N_INIT, nn_budget=cfg_deep.DEEPSORT.NN_BUDGET,
use_cuda=True)
SORT는 코너 좌표를 사용하나 DeepSORT는 센터 좌표를 사용한다.
YOLO처럼 top left corner 와 bottom right corner 코디네이트를 사용하지 않고, x센터, y센터, width, height를 사용한느 방식이다.
def xyxy_to_xywh(*xyxy):
"""" Calculates the relative bounding box from absolute pixel values. """
bbox_left = min([xyxy[0].item(), xyxy[2].item()])
bbox_top = min([xyxy[1].item(), xyxy[3].item()])
bbox_w = abs(xyxy[0].item() - xyxy[2].item())
bbox_h = abs(xyxy[1].item() - xyxy[3].item())
x_c = (bbox_left + bbox_w / 2)
y_c = (bbox_top + bbox_h / 2)
w = bbox_w
h = bbox_h
return x_c, y_c, w, h
클래스별로 색상을 할당
def compute_color_for_labels(label):
"""
Simple function that adds fixed color depending on the class
"""
if label == 0: #person
color = (85,45,255)
elif label == 2: # Car
color = (222,82,175)
elif label == 3: # Motobike
color = (0, 204, 255)
elif label == 5: # Bus
color = (0, 149, 255)
else:
color = [int((p * (label ** 2 - label + 1)) % 255) for p in palette]
return tuple(color)
21:truck과 같은 레이블을 그리는 함수
def draw_border(img, pt1, pt2, color, thickness, r, d):
x1,y1 = pt1
x2,y2 = pt2
# Top left
cv2.line(img, (x1 + r, y1), (x1 + r + d, y1), color, thickness)
cv2.line(img, (x1, y1 + r), (x1, y1 + r + d), color, thickness)
cv2.ellipse(img, (x1 + r, y1 + r), (r, r), 180, 0, 90, color, thickness)
# Top right
cv2.line(img, (x2 - r, y1), (x2 - r - d, y1), color, thickness)
cv2.line(img, (x2, y1 + r), (x2, y1 + r + d), color, thickness)
cv2.ellipse(img, (x2 - r, y1 + r), (r, r), 270, 0, 90, color, thickness)
# Bottom left
cv2.line(img, (x1 + r, y2), (x1 + r + d, y2), color, thickness)
cv2.line(img, (x1, y2 - r), (x1, y2 - r - d), color, thickness)
cv2.ellipse(img, (x1 + r, y2 - r), (r, r), 90, 0, 90, color, thickness)
# Bottom right
cv2.line(img, (x2 - r, y2), (x2 - r - d, y2), color, thickness)
cv2.line(img, (x2, y2 - r), (x2, y2 - r - d), color, thickness)
cv2.ellipse(img, (x2 - r, y2 - r), (r, r), 0, 0, 90, color, thickness)
cv2.rectangle(img, (x1 + r, y1), (x2 - r, y2), color, -1, cv2.LINE_AA)
cv2.rectangle(img, (x1, y1 + r), (x2, y2 - r - d), color, -1, cv2.LINE_AA)
cv2.circle(img, (x1 +r, y1+r), 2, color, 12)
cv2.circle(img, (x2 -r, y1+r), 2, color, 12)
cv2.circle(img, (x1 +r, y2-r), 2, color, 12)
cv2.circle(img, (x2 -r, y2-r), 2, color, 12)
return img
cv2.rectangle로 Bbox를 생성
def UI_box(x, img, color=None, label=None, line_thickness=None):
# Plots one bounding box on image img
tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness
color = color or [random.randint(0, 255) for _ in range(3)]
c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
if label:
tf = max(tl - 1, 1) # font thickness
t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
img = draw_border(img, (c1[0], c1[1] - t_size[1] -3), (c1[0] + t_size[0], c1[1]+3), color, 1, 8, 2)
cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
data_deque에서 각기 Bbox의 bottom center가 기록된다 64개의 length를 두었기에 trail 형태가 된다. cv2.line으로 그려진다.
def draw_boxes(img, bbox, names,object_id, identities=None, offset=(0, 0)):
#cv2.line(img, line[0], line[1], (46,162,112), 3)
height, width, _ = img.shape
# remove tracked point from buffer if object is lost
for key in list(data_deque):
if key not in identities:
data_deque.pop(key)
for i, box in enumerate(bbox):
x1, y1, x2, y2 = [int(i) for i in box]
x1 += offset[0]
x2 += offset[0]
y1 += offset[1]
y2 += offset[1]
# code to find center of bottom edge
center = (int((x2+x1)/ 2), int((y2+y2)/2))
# get ID of object
id = int(identities[i]) if identities is not None else 0
# create new buffer for new object
if id not in data_deque:
data_deque[id] = deque(maxlen= 64)
color = compute_color_for_labels(object_id[i])
obj_name = names[object_id[i]]
label = '{}{:d}'.format("", id) + ":"+ '%s' % (obj_name)
# add center to buffer
data_deque[id].appendleft(center)
UI_box(box, img, label=label, color=color, line_thickness=2)
# draw trail
for i in range(1, len(data_deque[id])):
# check if on buffer value is none
if data_deque[id][i - 1] is None or data_deque[id][i] is None:
continue
# generate dynamic thickness of trails
thickness = int(np.sqrt(64 / float(i + i)) * 1.5)
# draw trails
cv2.line(img, data_deque[id][i - 1], data_deque[id][i], color, thickness)
return img
car12, car20 같은 ID는 incremental이 아니라 random이다.