Deep SORT의 코사인 거리는 box 좌표가 아니라 검출 객체의 appearance feature와 기존 target이 저장한 feature gallery를 비교해 같은 ID인지 판단하는 데 쓰입니다. 위치 예측이 가능한 후보를 만들고 외형 거리가 그 연결을 보강하므로, feature 파일이 없거나 잘못 정렬되면 이름만 Deep SORT여도 재식별 이점을 얻지 못합니다. Threshold와 nn_budget은 ID switch와 track fragmentation 사이의 trade-off로 평가해야 합니다.
Deep SORT의 코사인 거리는 어디에 쓰일까: Feature Gallery와 추적 코드 흐름
실행 전에 데이터와 Feature 파일을 구분하기
원문은 논문과 cosine metric learning 코드를 바탕으로 Market-1501에서 appearance model 학습 흐름을 보고, MOT16 sequence에서 추적 예제를 읽습니다. ReID 데이터로 MARS와 Market-1501을 제시하지만 원문 실습은 Market-1501을 선택했습니다.
학습 호출은 dataset, loss, log, run id를 명시합니다.
1
2
3
4
5
python train_market1501.py \
--dataset_dir=./Market-1501-v15.09.15/ \
--loss_mode=cosine-softmax \
--log_dir=./output/market1501/ \
--run_id=cosine-softmax
추적 호출은 MOT16 frame sequence와 미리 계산된 detection, feature 배열을 입력으로 받습니다.
1
python deep_sort_app.py --sequence_dir=./MOT16/test/MOT16-06 --detection_file=./resources/detections/MOT16_POI_test/MOT16-06.npy --min_confidence=0.3 --nn_budget=100 --display=True
두 명령은 저장소 clone, 의존성 설치, 데이터 다운로드를 포함한 완전한 설치법이 아닙니다. 지정한 디렉터리와 .npy 파일이 실제로 준비된 환경에서의 핵심 실행 호출이며, 세부 준비는 저장소의 README를 함께 봐야 합니다.
한 frame에서 Detection이 Track으로 바뀌는 순서
run 함수는 sequence 정보를 읽고 cosine distance metric과 Tracker를 만든 뒤 frame마다 callback을 호출합니다. callback 내부의 흐름은 다음과 같습니다.
- 현재 frame index에 해당하는 detection을 만듭니다.
min_detection_height보다 작은 box를 제외합니다.min_confidence보다 낮은 detection을 제외합니다.nms_max_overlap을 기준으로 NMS를 적용합니다.- tracker가 기존 track을
predict합니다. - 남은 detection으로
update합니다. - 확정된 최신 track만 결과와 화면에 보냅니다.
gather_sequence_info는 img1 폴더의 frame 파일, gt/gt.txt, detection 배열, image size, 첫 frame과 마지막 frame의 index, feature 차원을 모읍니다. detection 배열의 처음 10개 열은 MOTChallenge 형식이고 그 뒤 열이 각 detection의 feature입니다.
1
2
bbox, confidence, feature = row[2:6], row[6], row[10:]
detection_list.append(Detection(bbox, confidence, feature))
즉, box만 있는 detector 출력은 이 코드가 기대하는 appearance feature까지 자동으로 만들어 주지 않습니다. detection_file의 열 구조가 맞는지 먼저 확인해야 합니다.
Cosine metric과 nn_budget이 ID에 미치는 영향
Euclidean distance는 vector 사이의 직선 거리를 보고, cosine distance는 크기보다 방향의 차이를 봅니다. 원문 구현은 입력 feature를 정규화한 뒤 1 - dot product를 반환합니다.
1
2
3
4
5
def _cosine_distance(a, b, data_is_normalized=False):
if not data_is_normalized:
a = np.asarray(a) / np.linalg.norm(a, axis=1, keepdims=True)
b = np.asarray(b) / np.linalg.norm(b, axis=1, keepdims=True)
return 1. - np.dot(a, b.T)
target 하나에는 지금까지 관측한 feature가 여러 개 저장됩니다. 새 detection과 비교할 때 그 gallery에서 가장 가까운 cosine distance를 사용합니다. matching_threshold보다 거리가 크면 잘못된 일치로 보고, nn_budget은 target마다 보관할 sample 수를 제한해 오래된 sample부터 제거합니다.
따라서 두 값은 다른 문제를 조절합니다.
max_cosine_distance: 외형이 얼마나 달라도 같은 target으로 허용할지 정합니다.nn_budget: 한 target의 과거 외형을 얼마나 많이 기억할지 정합니다.min_confidence: tracker에 들어오기 전 detector의 낮은 신뢰도 box를 거릅니다.nms_max_overlap: 심하게 겹치는 detection을 update 전에 줄입니다.min_detection_height: 너무 작은 box를 입력에서 제외합니다.
threshold를 느슨하게 하면 다른 사람을 같은 ID로 붙일 수 있고, 엄격하게 하면 외형 변화가 있는 같은 사람을 끊을 수 있습니다. nn_budget을 크게 한다고 항상 좋아지는 것도 아닙니다. gallery가 커지는 만큼 오래된 모습도 비교 대상에 남습니다.
코드 일부를 완성 앱으로 오해하지 않기
원문의 발췌는 run, sequence parser, cosine metric, detection 생성, NMS의 핵심을 보여 주지만 tracker 내부의 전체 association과 appearance feature 생성 과정까지 모두 담지는 않습니다. NMS 구현도 np.float, frame parser는 np.int 표기를 사용하는 당시 코드입니다.
결과를 저장할 때는 확정되지 않은 track이나 한 frame 이상 update되지 않은 track을 건너뛰고, frame_idx, track_id, x, y, width, height 형식으로 기록합니다. 화면 표시를 끄면 NoVisualization 경로를 사용하지만 tracker의 predict와 update는 계속 실행됩니다.
이 글을 실제 코드와 대조할 때는 다음 질문이 유용합니다.
- detection 배열 뒤쪽에 appearance feature가 정말 들어 있는가
- confidence와 height 필터가 필요한 객체까지 지우고 있지 않은가
- NMS가 가까이 선 서로 다른 객체를 하나로 줄이고 있지 않은가
- cosine threshold가 ID switch와 track fragmentation 중 어느 쪽을 늘리는가
- target별 sample 수가
nn_budget으로 잘 제한되는가
Deep SORT를 이해하는 핵심은 “코사인 거리로 위치를 찾는다”가 아닙니다. 위치 예측으로 후보를 만들고, 각 검출의 appearance vector를 target의 과거 vector들과 비교해 연관성을 보강하는 흐름입니다.
Appearance 매칭이 실패했는지 어떻게 확인하나
먼저 detection 행과 feature 행이 같은 객체 순서인지 확인합니다. Box 좌표가 맞더라도 vector가 한 행씩 밀리면 코사인 거리는 엉뚱한 사람을 비교합니다. Feature 차원과 정규화 여부, 빈 detection frame의 처리도 별도로 출력해 봅니다. 추적기 파라미터를 바꾸기 전에 입력 데이터 계약을 검증해야 합니다.
다음에는 같은 ID의 거리와 다른 ID의 거리를 장면별로 기록합니다. 조명 변화, 회전, 가림에서 같은 객체의 거리가 임계값을 넘는다면 threshold가 너무 엄격하거나 feature 표현이 장면을 충분히 견디지 못하는 것입니다. 서로 비슷한 옷을 입은 객체의 거리가 모두 작다면 threshold만으로는 구분하기 어려우므로 motion 정보의 역할이 커집니다.
마지막으로 nn_budget을 메모리 제한으로만 보지 않습니다. Gallery가 너무 짧으면 최근의 가려진 모습만 남고, 너무 길면 오래된 특징이 계속 후보가 됩니다. ID switch, 끊어진 track 수, 처리 시간을 함께 비교해 실제 장면에 필요한 과거 길이를 고르는 편이 좋습니다.
함께 읽으면 이해가 이어지는 글
- SORT가 빠른 대신 ID를 놓치는 이유: Kalman Filter와 Hungarian 매칭 — SORT가 검출 box만으로 다중 객체를 실시간 추적하는 전체 흐름을 설명합니다. Kalman Filter의 상태 예측, IoU 비용행렬, Hungarian assignment, track 생성, 삭제 조건을 코드 구조와 연결하고…
- Darknet NMS는 Class별로 해야 할까? do_nms_obj와 do_nms_sort 차이 — Darknet box.c의 objectness 기준 NMS와 class별 NMS를 비교하고, IoU 계산, stride box 변환, encode/decode, 비활성 diou 미분 코드의 주의점을 코드 흐름으로 설명합니다.
- CenterNet은 Anchor와 NMS 없이 어떻게 물체를 찾을까: 중심점, 크기, Offset 해설 — CenterNet이 object를 bounding box 후보가 아닌 중심점 하나로 표현하는 방식을 설명합니다. Heatmap peak, box 크기, stride offset의 C+4 출력과 focal, L1 loss를 연결하고…
자주 묻는 질문
Deep SORT의 코사인 거리는 box 위치를 비교하나요?
아닙니다. 검출에서 추출한 appearance vector와 각 target이 저장한 과거 vector를 비교합니다. 위치 후보는 motion 예측과 별도로 다뤄집니다.
nn_budget을 크게 하면 ID가 항상 더 안정적인가요?
항상 그렇지는 않습니다. 더 오래된 다양한 특징을 남길 수 있지만 메모리와 비교 비용이 늘고, 외형이 크게 변한 과거 표본이 현재 매칭을 방해할 수도 있습니다.
코사인 threshold를 낮추면 어떤 trade-off가 생기나요?
더 비슷한 appearance만 허용하므로 잘못된 ID 연결은 줄 수 있지만 같은 객체의 외형 변화도 거부해 track fragmentation과 새 ID 생성을 늘릴 수 있습니다.
←→ 키와 좌우 스와이프를 지원합니다. 읽던 페이지는 이 기기에 저장됩니다.