- 상세 설명: - 군중 위험 감지 로직 추가 (거리 기반·속도 기반 위험도 산출 및 Optical Flow 적용) - OC-SORT 트래킹 알고리즘 연동 - 얼굴 인식, 이상 행동 감지, 차량 번호판 인식 결과를 card 형태의 메타데이터로 구성 - 테스트 사항: - Node.js 연동을 통한 실시간 시각화 테스트 완료main
parent
a0e204e3d3
commit
aa853c4f26
@ -0,0 +1,71 @@
|
|||||||
|
from collections import defaultdict, deque
|
||||||
|
|
||||||
|
|
||||||
|
def distance_risk(items, thr=80):
|
||||||
|
centers = [((it["x1"]+it["x2"])/2, (it["y1"]+it["y2"])/2) for it in items]
|
||||||
|
n = len(centers)
|
||||||
|
if n < 2:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
close_pairs = 0
|
||||||
|
for i in range(n):
|
||||||
|
cx1, cy1 = centers[i]
|
||||||
|
for j in range(i+1, n):
|
||||||
|
cx2, cy2 = centers[j]
|
||||||
|
dist = ((cx1-cx2)**2 + (cy1-cy2)**2)**0.5
|
||||||
|
if dist < thr:
|
||||||
|
close_pairs += 1
|
||||||
|
|
||||||
|
return min(close_pairs / 30.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
prev_centers = {}
|
||||||
|
trajectories = defaultdict(lambda: deque(maxlen=10))
|
||||||
|
last_seen = {}
|
||||||
|
KEEP_FRAMES = 15
|
||||||
|
frame_counter = 0
|
||||||
|
|
||||||
|
|
||||||
|
def motion_risk_and_path(items):
|
||||||
|
global prev_centers, trajectories, last_seen, frame_counter
|
||||||
|
|
||||||
|
frame_counter += 1
|
||||||
|
fid = frame_counter
|
||||||
|
|
||||||
|
curr = {}
|
||||||
|
speeds = []
|
||||||
|
|
||||||
|
for it in items:
|
||||||
|
|
||||||
|
if it["tid"] == 65535:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tid = it["tid"]
|
||||||
|
|
||||||
|
cx = (it["x1"] + it["x2"]) / 2
|
||||||
|
cy = (it["y1"] + it["y2"]) / 2
|
||||||
|
|
||||||
|
curr[tid] = (cx, cy)
|
||||||
|
trajectories[tid].append((cx, cy))
|
||||||
|
|
||||||
|
last_seen[tid] = fid
|
||||||
|
|
||||||
|
if tid in prev_centers:
|
||||||
|
px, py = prev_centers[tid]
|
||||||
|
spd = ((cx - px)**2 + (cy - py)**2)**0.5
|
||||||
|
speeds.append(spd)
|
||||||
|
|
||||||
|
prev_centers = curr
|
||||||
|
|
||||||
|
for tid in list(trajectories.keys()):
|
||||||
|
if tid not in curr:
|
||||||
|
if fid - last_seen.get(tid, fid) > KEEP_FRAMES:
|
||||||
|
del trajectories[tid]
|
||||||
|
del last_seen[tid]
|
||||||
|
|
||||||
|
if not speeds:
|
||||||
|
risk = 0.0
|
||||||
|
else:
|
||||||
|
risk = min((sum(speeds) / len(speeds)) / 20.0, 1.0)
|
||||||
|
|
||||||
|
return risk, trajectories
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BASE_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
CLI="python3 $BASE_DIR/ctrl_cli.py"
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "Usage: $0 {IN|OUT|SET value|STATUS}" >&2
|
||||||
|
echo " 예) $0 IN"
|
||||||
|
echo " 예) $0 OUT"
|
||||||
|
echo " 예) $0 SET 3"
|
||||||
|
echo " 예) $0 STATUS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CMD="$1"
|
||||||
|
|
||||||
|
case "$CMD" in
|
||||||
|
IN)
|
||||||
|
$CLI "ZOOM_IN"
|
||||||
|
;;
|
||||||
|
OUT)
|
||||||
|
$CLI "ZOOM_OUT"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERR: unknown command '$CMD'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
; config file for yolov8s_coco_640x384_apache6sr250_aiw4939.aiwbin
|
||||||
|
; author:
|
||||||
|
; date:
|
||||||
|
; network input size:
|
||||||
|
|
||||||
|
[detection] ; network
|
||||||
|
|
||||||
|
class_num = 12 ; number of class
|
||||||
|
class_name = shirt, t-shirt, sweater, cardigan, jacket, vest, pants, shorts, skirt, coat, dress, bag
|
||||||
|
|
||||||
|
class_color = {255, 255, 255},{0, 0, 255},{255, 0, 0},{0, 255, 255},{55, 255, 0},{0, 255, 0},{255, 255, 255},{0, 0, 255},{255, 0, 0},{0, 255, 255},{55, 255, 0},{0, 255, 0}
|
||||||
|
|
||||||
|
CONFIDENCE_DETECTION_THRESHOLD = 0.4 ; detection threshold
|
||||||
|
CONFIDENCE_NMS_THRESHOLD = 0.3 ; nms threshold
|
||||||
|
|
||||||
|
; caution
|
||||||
|
; 1. Write the value of each key on one line
|
||||||
|
; 2. class_num = number of class_name = number of class_color
|
||||||
|
; 3. range of threshold -> 0 < threshold < 1
|
||||||
|
; 4. Class IDs are determined in the order of class_name. (0,1,2,3 ...)
|
||||||
Binary file not shown.
@ -0,0 +1,379 @@
|
|||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def iou_batch(bboxes1, bboxes2):
|
||||||
|
"""
|
||||||
|
From SORT: Computes IOU between two bboxes in the form [x1,y1,x2,y2]
|
||||||
|
"""
|
||||||
|
bboxes2 = np.expand_dims(bboxes2, 0)
|
||||||
|
bboxes1 = np.expand_dims(bboxes1, 1)
|
||||||
|
|
||||||
|
xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])
|
||||||
|
yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])
|
||||||
|
xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])
|
||||||
|
yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])
|
||||||
|
w = np.maximum(0., xx2 - xx1)
|
||||||
|
h = np.maximum(0., yy2 - yy1)
|
||||||
|
wh = w * h
|
||||||
|
o = wh / ((bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])
|
||||||
|
+ (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1]) - wh)
|
||||||
|
return(o)
|
||||||
|
|
||||||
|
|
||||||
|
def giou_batch(bboxes1, bboxes2):
|
||||||
|
"""
|
||||||
|
:param bbox_p: predict of bbox(N,4)(x1,y1,x2,y2)
|
||||||
|
:param bbox_g: groundtruth of bbox(N,4)(x1,y1,x2,y2)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# for details should go to https://arxiv.org/pdf/1902.09630.pdf
|
||||||
|
# ensure predict's bbox form
|
||||||
|
bboxes2 = np.expand_dims(bboxes2, 0)
|
||||||
|
bboxes1 = np.expand_dims(bboxes1, 1)
|
||||||
|
|
||||||
|
xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])
|
||||||
|
yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])
|
||||||
|
xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])
|
||||||
|
yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])
|
||||||
|
w = np.maximum(0., xx2 - xx1)
|
||||||
|
h = np.maximum(0., yy2 - yy1)
|
||||||
|
wh = w * h
|
||||||
|
union = ((bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])
|
||||||
|
+ (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1]) - wh)
|
||||||
|
iou = wh / union
|
||||||
|
|
||||||
|
xxc1 = np.minimum(bboxes1[..., 0], bboxes2[..., 0])
|
||||||
|
yyc1 = np.minimum(bboxes1[..., 1], bboxes2[..., 1])
|
||||||
|
xxc2 = np.maximum(bboxes1[..., 2], bboxes2[..., 2])
|
||||||
|
yyc2 = np.maximum(bboxes1[..., 3], bboxes2[..., 3])
|
||||||
|
wc = xxc2 - xxc1
|
||||||
|
hc = yyc2 - yyc1
|
||||||
|
assert((wc > 0).all() and (hc > 0).all())
|
||||||
|
area_enclose = wc * hc
|
||||||
|
giou = iou - (area_enclose - union) / area_enclose
|
||||||
|
giou = (giou + 1.)/2.0 # resize from (-1,1) to (0,1)
|
||||||
|
return giou
|
||||||
|
|
||||||
|
|
||||||
|
def diou_batch(bboxes1, bboxes2):
|
||||||
|
"""
|
||||||
|
:param bbox_p: predict of bbox(N,4)(x1,y1,x2,y2)
|
||||||
|
:param bbox_g: groundtruth of bbox(N,4)(x1,y1,x2,y2)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# for details should go to https://arxiv.org/pdf/1902.09630.pdf
|
||||||
|
# ensure predict's bbox form
|
||||||
|
bboxes2 = np.expand_dims(bboxes2, 0)
|
||||||
|
bboxes1 = np.expand_dims(bboxes1, 1)
|
||||||
|
|
||||||
|
# calculate the intersection box
|
||||||
|
xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])
|
||||||
|
yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])
|
||||||
|
xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])
|
||||||
|
yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])
|
||||||
|
w = np.maximum(0., xx2 - xx1)
|
||||||
|
h = np.maximum(0., yy2 - yy1)
|
||||||
|
wh = w * h
|
||||||
|
union = ((bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])
|
||||||
|
+ (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1]) - wh)
|
||||||
|
iou = wh / union
|
||||||
|
centerx1 = (bboxes1[..., 0] + bboxes1[..., 2]) / 2.0
|
||||||
|
centery1 = (bboxes1[..., 1] + bboxes1[..., 3]) / 2.0
|
||||||
|
centerx2 = (bboxes2[..., 0] + bboxes2[..., 2]) / 2.0
|
||||||
|
centery2 = (bboxes2[..., 1] + bboxes2[..., 3]) / 2.0
|
||||||
|
|
||||||
|
inner_diag = (centerx1 - centerx2) ** 2 + (centery1 - centery2) ** 2
|
||||||
|
|
||||||
|
xxc1 = np.minimum(bboxes1[..., 0], bboxes2[..., 0])
|
||||||
|
yyc1 = np.minimum(bboxes1[..., 1], bboxes2[..., 1])
|
||||||
|
xxc2 = np.maximum(bboxes1[..., 2], bboxes2[..., 2])
|
||||||
|
yyc2 = np.maximum(bboxes1[..., 3], bboxes2[..., 3])
|
||||||
|
|
||||||
|
outer_diag = (xxc2 - xxc1) ** 2 + (yyc2 - yyc1) ** 2
|
||||||
|
diou = iou - inner_diag / outer_diag
|
||||||
|
|
||||||
|
return (diou + 1) / 2.0 # resize from (-1,1) to (0,1)
|
||||||
|
|
||||||
|
def ciou_batch(bboxes1, bboxes2):
|
||||||
|
"""
|
||||||
|
:param bbox_p: predict of bbox(N,4)(x1,y1,x2,y2)
|
||||||
|
:param bbox_g: groundtruth of bbox(N,4)(x1,y1,x2,y2)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# for details should go to https://arxiv.org/pdf/1902.09630.pdf
|
||||||
|
# ensure predict's bbox form
|
||||||
|
bboxes2 = np.expand_dims(bboxes2, 0)
|
||||||
|
bboxes1 = np.expand_dims(bboxes1, 1)
|
||||||
|
|
||||||
|
# calculate the intersection box
|
||||||
|
xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])
|
||||||
|
yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])
|
||||||
|
xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])
|
||||||
|
yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])
|
||||||
|
w = np.maximum(0., xx2 - xx1)
|
||||||
|
h = np.maximum(0., yy2 - yy1)
|
||||||
|
wh = w * h
|
||||||
|
union = ((bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])
|
||||||
|
+ (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1]) - wh)
|
||||||
|
iou = wh / union
|
||||||
|
|
||||||
|
centerx1 = (bboxes1[..., 0] + bboxes1[..., 2]) / 2.0
|
||||||
|
centery1 = (bboxes1[..., 1] + bboxes1[..., 3]) / 2.0
|
||||||
|
centerx2 = (bboxes2[..., 0] + bboxes2[..., 2]) / 2.0
|
||||||
|
centery2 = (bboxes2[..., 1] + bboxes2[..., 3]) / 2.0
|
||||||
|
|
||||||
|
inner_diag = (centerx1 - centerx2) ** 2 + (centery1 - centery2) ** 2
|
||||||
|
|
||||||
|
xxc1 = np.minimum(bboxes1[..., 0], bboxes2[..., 0])
|
||||||
|
yyc1 = np.minimum(bboxes1[..., 1], bboxes2[..., 1])
|
||||||
|
xxc2 = np.maximum(bboxes1[..., 2], bboxes2[..., 2])
|
||||||
|
yyc2 = np.maximum(bboxes1[..., 3], bboxes2[..., 3])
|
||||||
|
|
||||||
|
outer_diag = (xxc2 - xxc1) ** 2 + (yyc2 - yyc1) ** 2
|
||||||
|
|
||||||
|
w1 = bboxes1[..., 2] - bboxes1[..., 0]
|
||||||
|
h1 = bboxes1[..., 3] - bboxes1[..., 1]
|
||||||
|
w2 = bboxes2[..., 2] - bboxes2[..., 0]
|
||||||
|
h2 = bboxes2[..., 3] - bboxes2[..., 1]
|
||||||
|
|
||||||
|
# prevent dividing over zero. add one pixel shift
|
||||||
|
h2 = h2 + 1.
|
||||||
|
h1 = h1 + 1.
|
||||||
|
arctan = np.arctan(w2/h2) - np.arctan(w1/h1)
|
||||||
|
v = (4 / (np.pi ** 2)) * (arctan ** 2)
|
||||||
|
S = 1 - iou
|
||||||
|
alpha = v / (S+v)
|
||||||
|
ciou = iou - inner_diag / outer_diag - alpha * v
|
||||||
|
|
||||||
|
return (ciou + 1) / 2.0 # resize from (-1,1) to (0,1)
|
||||||
|
|
||||||
|
|
||||||
|
def ct_dist(bboxes1, bboxes2):
|
||||||
|
"""
|
||||||
|
Measure the center distance between two sets of bounding boxes,
|
||||||
|
this is a coarse implementation, we don't recommend using it only
|
||||||
|
for association, which can be unstable and sensitive to frame rate
|
||||||
|
and object speed.
|
||||||
|
"""
|
||||||
|
bboxes2 = np.expand_dims(bboxes2, 0)
|
||||||
|
bboxes1 = np.expand_dims(bboxes1, 1)
|
||||||
|
|
||||||
|
centerx1 = (bboxes1[..., 0] + bboxes1[..., 2]) / 2.0
|
||||||
|
centery1 = (bboxes1[..., 1] + bboxes1[..., 3]) / 2.0
|
||||||
|
centerx2 = (bboxes2[..., 0] + bboxes2[..., 2]) / 2.0
|
||||||
|
centery2 = (bboxes2[..., 1] + bboxes2[..., 3]) / 2.0
|
||||||
|
|
||||||
|
ct_dist2 = (centerx1 - centerx2) ** 2 + (centery1 - centery2) ** 2
|
||||||
|
|
||||||
|
ct_dist = np.sqrt(ct_dist2)
|
||||||
|
|
||||||
|
# The linear rescaling is a naive version and needs more study
|
||||||
|
ct_dist = ct_dist / ct_dist.max()
|
||||||
|
return ct_dist.max() - ct_dist # resize to (0,1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def speed_direction_batch(dets, tracks):
|
||||||
|
tracks = tracks[..., np.newaxis]
|
||||||
|
CX1, CY1 = (dets[:,0] + dets[:,2])/2.0, (dets[:,1]+dets[:,3])/2.0
|
||||||
|
CX2, CY2 = (tracks[:,0] + tracks[:,2]) /2.0, (tracks[:,1]+tracks[:,3])/2.0
|
||||||
|
dx = CX1 - CX2
|
||||||
|
dy = CY1 - CY2
|
||||||
|
norm = np.sqrt(dx**2 + dy**2) + 1e-6
|
||||||
|
dx = dx / norm
|
||||||
|
dy = dy / norm
|
||||||
|
return dy, dx # size: num_track x num_det
|
||||||
|
|
||||||
|
|
||||||
|
def linear_assignment(cost_matrix):
|
||||||
|
try:
|
||||||
|
import lap
|
||||||
|
_, x, y = lap.lapjv(cost_matrix, extend_cost=True)
|
||||||
|
return np.array([[y[i],i] for i in x if i >= 0]) #
|
||||||
|
except ImportError:
|
||||||
|
from scipy.optimize import linear_sum_assignment
|
||||||
|
x, y = linear_sum_assignment(cost_matrix)
|
||||||
|
return np.array(list(zip(x, y)))
|
||||||
|
|
||||||
|
|
||||||
|
def associate_detections_to_trackers(detections,trackers,iou_threshold = 0.3):
|
||||||
|
"""
|
||||||
|
Assigns detections to tracked object (both represented as bounding boxes)
|
||||||
|
Returns 3 lists of matches, unmatched_detections and unmatched_trackers
|
||||||
|
"""
|
||||||
|
if(len(trackers)==0):
|
||||||
|
return np.empty((0,2),dtype=int), np.arange(len(detections)), np.empty((0,5),dtype=int)
|
||||||
|
|
||||||
|
iou_matrix = iou_batch(detections, trackers)
|
||||||
|
|
||||||
|
if min(iou_matrix.shape) > 0:
|
||||||
|
a = (iou_matrix > iou_threshold).astype(np.int32)
|
||||||
|
if a.sum(1).max() == 1 and a.sum(0).max() == 1:
|
||||||
|
matched_indices = np.stack(np.where(a), axis=1)
|
||||||
|
else:
|
||||||
|
matched_indices = linear_assignment(-iou_matrix)
|
||||||
|
else:
|
||||||
|
matched_indices = np.empty(shape=(0,2))
|
||||||
|
|
||||||
|
unmatched_detections = []
|
||||||
|
for d, det in enumerate(detections):
|
||||||
|
if(d not in matched_indices[:,0]):
|
||||||
|
unmatched_detections.append(d)
|
||||||
|
unmatched_trackers = []
|
||||||
|
for t, trk in enumerate(trackers):
|
||||||
|
if(t not in matched_indices[:,1]):
|
||||||
|
unmatched_trackers.append(t)
|
||||||
|
|
||||||
|
#filter out matched with low IOU
|
||||||
|
matches = []
|
||||||
|
for m in matched_indices:
|
||||||
|
if(iou_matrix[m[0], m[1]]<iou_threshold):
|
||||||
|
unmatched_detections.append(m[0])
|
||||||
|
unmatched_trackers.append(m[1])
|
||||||
|
else:
|
||||||
|
matches.append(m.reshape(1,2))
|
||||||
|
if(len(matches)==0):
|
||||||
|
matches = np.empty((0,2),dtype=int)
|
||||||
|
else:
|
||||||
|
matches = np.concatenate(matches,axis=0)
|
||||||
|
|
||||||
|
return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
|
||||||
|
|
||||||
|
|
||||||
|
def associate(detections, trackers, iou_threshold, velocities, previous_obs, vdc_weight):
|
||||||
|
if(len(trackers)==0):
|
||||||
|
return np.empty((0,2),dtype=int), np.arange(len(detections)), np.empty((0,5),dtype=int)
|
||||||
|
|
||||||
|
Y, X = speed_direction_batch(detections, previous_obs)
|
||||||
|
inertia_Y, inertia_X = velocities[:,0], velocities[:,1]
|
||||||
|
inertia_Y = np.repeat(inertia_Y[:, np.newaxis], Y.shape[1], axis=1)
|
||||||
|
inertia_X = np.repeat(inertia_X[:, np.newaxis], X.shape[1], axis=1)
|
||||||
|
diff_angle_cos = inertia_X * X + inertia_Y * Y
|
||||||
|
diff_angle_cos = np.clip(diff_angle_cos, a_min=-1, a_max=1)
|
||||||
|
diff_angle = np.arccos(diff_angle_cos)
|
||||||
|
diff_angle = (np.pi /2.0 - np.abs(diff_angle)) / np.pi
|
||||||
|
|
||||||
|
valid_mask = np.ones(previous_obs.shape[0])
|
||||||
|
valid_mask[np.where(previous_obs[:,4]<0)] = 0
|
||||||
|
|
||||||
|
iou_matrix = iou_batch(detections, trackers)
|
||||||
|
scores = np.repeat(detections[:,-1][:, np.newaxis], trackers.shape[0], axis=1)
|
||||||
|
# iou_matrix = iou_matrix * scores # a trick sometiems works, we don't encourage this
|
||||||
|
valid_mask = np.repeat(valid_mask[:, np.newaxis], X.shape[1], axis=1)
|
||||||
|
|
||||||
|
angle_diff_cost = (valid_mask * diff_angle) * vdc_weight
|
||||||
|
angle_diff_cost = angle_diff_cost.T
|
||||||
|
angle_diff_cost = angle_diff_cost * scores
|
||||||
|
|
||||||
|
if min(iou_matrix.shape) > 0:
|
||||||
|
a = (iou_matrix > iou_threshold).astype(np.int32)
|
||||||
|
if a.sum(1).max() == 1 and a.sum(0).max() == 1:
|
||||||
|
matched_indices = np.stack(np.where(a), axis=1)
|
||||||
|
else:
|
||||||
|
matched_indices = linear_assignment(-(iou_matrix+angle_diff_cost))
|
||||||
|
else:
|
||||||
|
matched_indices = np.empty(shape=(0,2))
|
||||||
|
|
||||||
|
unmatched_detections = []
|
||||||
|
for d, det in enumerate(detections):
|
||||||
|
if(d not in matched_indices[:,0]):
|
||||||
|
unmatched_detections.append(d)
|
||||||
|
unmatched_trackers = []
|
||||||
|
for t, trk in enumerate(trackers):
|
||||||
|
if(t not in matched_indices[:,1]):
|
||||||
|
unmatched_trackers.append(t)
|
||||||
|
|
||||||
|
# filter out matched with low IOU
|
||||||
|
matches = []
|
||||||
|
for m in matched_indices:
|
||||||
|
if(iou_matrix[m[0], m[1]]<iou_threshold):
|
||||||
|
unmatched_detections.append(m[0])
|
||||||
|
unmatched_trackers.append(m[1])
|
||||||
|
else:
|
||||||
|
matches.append(m.reshape(1,2))
|
||||||
|
if(len(matches)==0):
|
||||||
|
matches = np.empty((0,2),dtype=int)
|
||||||
|
else:
|
||||||
|
matches = np.concatenate(matches,axis=0)
|
||||||
|
|
||||||
|
return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
|
||||||
|
|
||||||
|
|
||||||
|
def associate_kitti(detections, trackers, det_cates, iou_threshold,
|
||||||
|
velocities, previous_obs, vdc_weight):
|
||||||
|
if(len(trackers)==0):
|
||||||
|
return np.empty((0,2),dtype=int), np.arange(len(detections)), np.empty((0,5),dtype=int)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cost from the velocity direction consistency
|
||||||
|
"""
|
||||||
|
Y, X = speed_direction_batch(detections, previous_obs)
|
||||||
|
inertia_Y, inertia_X = velocities[:,0], velocities[:,1]
|
||||||
|
inertia_Y = np.repeat(inertia_Y[:, np.newaxis], Y.shape[1], axis=1)
|
||||||
|
inertia_X = np.repeat(inertia_X[:, np.newaxis], X.shape[1], axis=1)
|
||||||
|
diff_angle_cos = inertia_X * X + inertia_Y * Y
|
||||||
|
diff_angle_cos = np.clip(diff_angle_cos, a_min=-1, a_max=1)
|
||||||
|
diff_angle = np.arccos(diff_angle_cos)
|
||||||
|
diff_angle = (np.pi /2.0 - np.abs(diff_angle)) / np.pi
|
||||||
|
|
||||||
|
valid_mask = np.ones(previous_obs.shape[0])
|
||||||
|
valid_mask[np.where(previous_obs[:,4]<0)]=0
|
||||||
|
valid_mask = np.repeat(valid_mask[:, np.newaxis], X.shape[1], axis=1)
|
||||||
|
|
||||||
|
scores = np.repeat(detections[:,-1][:, np.newaxis], trackers.shape[0], axis=1)
|
||||||
|
angle_diff_cost = (valid_mask * diff_angle) * vdc_weight
|
||||||
|
angle_diff_cost = angle_diff_cost.T
|
||||||
|
angle_diff_cost = angle_diff_cost * scores
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cost from IoU
|
||||||
|
"""
|
||||||
|
iou_matrix = iou_batch(detections, trackers)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
With multiple categories, generate the cost for catgory mismatch
|
||||||
|
"""
|
||||||
|
num_dets = detections.shape[0]
|
||||||
|
num_trk = trackers.shape[0]
|
||||||
|
cate_matrix = np.zeros((num_dets, num_trk))
|
||||||
|
for i in range(num_dets):
|
||||||
|
for j in range(num_trk):
|
||||||
|
if det_cates[i] != trackers[j, 4]:
|
||||||
|
cate_matrix[i][j] = -1e6
|
||||||
|
|
||||||
|
cost_matrix = - iou_matrix -angle_diff_cost - cate_matrix
|
||||||
|
|
||||||
|
if min(iou_matrix.shape) > 0:
|
||||||
|
a = (iou_matrix > iou_threshold).astype(np.int32)
|
||||||
|
if a.sum(1).max() == 1 and a.sum(0).max() == 1:
|
||||||
|
matched_indices = np.stack(np.where(a), axis=1)
|
||||||
|
else:
|
||||||
|
matched_indices = linear_assignment(cost_matrix)
|
||||||
|
else:
|
||||||
|
matched_indices = np.empty(shape=(0,2))
|
||||||
|
|
||||||
|
unmatched_detections = []
|
||||||
|
for d, det in enumerate(detections):
|
||||||
|
if(d not in matched_indices[:,0]):
|
||||||
|
unmatched_detections.append(d)
|
||||||
|
unmatched_trackers = []
|
||||||
|
for t, trk in enumerate(trackers):
|
||||||
|
if(t not in matched_indices[:,1]):
|
||||||
|
unmatched_trackers.append(t)
|
||||||
|
|
||||||
|
#filter out matched with low IOU
|
||||||
|
matches = []
|
||||||
|
for m in matched_indices:
|
||||||
|
if(iou_matrix[m[0], m[1]]<iou_threshold):
|
||||||
|
unmatched_detections.append(m[0])
|
||||||
|
unmatched_trackers.append(m[1])
|
||||||
|
else:
|
||||||
|
matches.append(m.reshape(1,2))
|
||||||
|
if(len(matches)==0):
|
||||||
|
matches = np.empty((0,2),dtype=int)
|
||||||
|
else:
|
||||||
|
matches = np.concatenate(matches,axis=0)
|
||||||
|
|
||||||
|
return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,431 @@
|
|||||||
|
"""
|
||||||
|
This script is adopted from the SORT script by Alex Bewley alex@bewley.ai
|
||||||
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from .association import *
|
||||||
|
|
||||||
|
|
||||||
|
def k_previous_obs(observations, cur_age, k):
|
||||||
|
if len(observations) == 0:
|
||||||
|
return [-1, -1, -1, -1, -1]
|
||||||
|
for i in range(k):
|
||||||
|
dt = k - i
|
||||||
|
if cur_age - dt in observations:
|
||||||
|
return observations[cur_age-dt]
|
||||||
|
max_age = max(observations.keys())
|
||||||
|
return observations[max_age]
|
||||||
|
|
||||||
|
|
||||||
|
def convert_bbox_to_z(bbox):
|
||||||
|
"""
|
||||||
|
Takes a bounding box in the form [x1,y1,x2,y2] and returns z in the form
|
||||||
|
[x,y,s,r] where x,y is the centre of the box and s is the scale/area and r is
|
||||||
|
the aspect ratio
|
||||||
|
"""
|
||||||
|
w = bbox[2] - bbox[0]
|
||||||
|
h = bbox[3] - bbox[1]
|
||||||
|
x = bbox[0] + w/2.
|
||||||
|
y = bbox[1] + h/2.
|
||||||
|
s = w * h # scale is just area
|
||||||
|
r = w / float(h+1e-6)
|
||||||
|
return np.array([x, y, s, r]).reshape((4, 1))
|
||||||
|
|
||||||
|
|
||||||
|
def convert_x_to_bbox(x, score=None):
|
||||||
|
"""
|
||||||
|
Takes a bounding box in the centre form [x,y,s,r] and returns it in the form
|
||||||
|
[x1,y1,x2,y2] where x1,y1 is the top left and x2,y2 is the bottom right
|
||||||
|
"""
|
||||||
|
w = np.sqrt(x[2] * x[3])
|
||||||
|
h = x[2] / w
|
||||||
|
if(score == None):
|
||||||
|
return np.array([x[0]-w/2., x[1]-h/2., x[0]+w/2., x[1]+h/2.]).reshape((1, 4))
|
||||||
|
else:
|
||||||
|
return np.array([x[0]-w/2., x[1]-h/2., x[0]+w/2., x[1]+h/2., score]).reshape((1, 5))
|
||||||
|
|
||||||
|
|
||||||
|
def speed_direction(bbox1, bbox2):
|
||||||
|
cx1, cy1 = (bbox1[0]+bbox1[2]) / 2.0, (bbox1[1]+bbox1[3])/2.0
|
||||||
|
cx2, cy2 = (bbox2[0]+bbox2[2]) / 2.0, (bbox2[1]+bbox2[3])/2.0
|
||||||
|
speed = np.array([cy2-cy1, cx2-cx1])
|
||||||
|
norm = np.sqrt((cy2-cy1)**2 + (cx2-cx1)**2) + 1e-6
|
||||||
|
return speed / norm
|
||||||
|
|
||||||
|
|
||||||
|
class KalmanBoxTracker(object):
|
||||||
|
"""
|
||||||
|
This class represents the internal state of individual tracked objects observed as bbox.
|
||||||
|
"""
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
def __init__(self, bbox, delta_t=3, orig=False):
|
||||||
|
"""
|
||||||
|
Initialises a tracker using initial bounding box.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# define constant velocity model
|
||||||
|
if not orig:
|
||||||
|
from .kalmanfilter import KalmanFilterNew as KalmanFilter
|
||||||
|
self.kf = KalmanFilter(dim_x=7, dim_z=4)
|
||||||
|
else:
|
||||||
|
from filterpy.kalman import KalmanFilter
|
||||||
|
self.kf = KalmanFilter(dim_x=7, dim_z=4)
|
||||||
|
self.kf.F = np.array([[1, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0, 1], [
|
||||||
|
0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 1]])
|
||||||
|
self.kf.H = np.array([[1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]])
|
||||||
|
|
||||||
|
self.kf.R[2:, 2:] *= 10.
|
||||||
|
self.kf.P[4:, 4:] *= 1000. # give high uncertainty to the unobservable initial velocities
|
||||||
|
self.kf.P *= 10.
|
||||||
|
self.kf.Q[-1, -1] *= 0.01
|
||||||
|
self.kf.Q[4:, 4:] *= 0.01
|
||||||
|
|
||||||
|
self.kf.x[:4] = convert_bbox_to_z(bbox)
|
||||||
|
self.time_since_update = 0
|
||||||
|
self.id = KalmanBoxTracker.count
|
||||||
|
KalmanBoxTracker.count += 1
|
||||||
|
self.history = []
|
||||||
|
self.hits = 0
|
||||||
|
self.hit_streak = 0
|
||||||
|
self.age = 0
|
||||||
|
"""
|
||||||
|
NOTE: [-1,-1,-1,-1,-1] is a compromising placeholder for non-observation status, the same for the return of
|
||||||
|
function k_previous_obs. It is ugly and I do not like it. But to support generate observation array in a
|
||||||
|
fast and unified way, which you would see below k_observations = np.array([k_previous_obs(...]]), let's bear it for now.
|
||||||
|
"""
|
||||||
|
self.last_observation = np.array([-1, -1, -1, -1, -1]) # placeholder
|
||||||
|
self.observations = dict()
|
||||||
|
self.history_observations = []
|
||||||
|
self.velocity = None
|
||||||
|
self.delta_t = delta_t
|
||||||
|
|
||||||
|
def update(self, bbox):
|
||||||
|
"""
|
||||||
|
Updates the state vector with observed bbox.
|
||||||
|
"""
|
||||||
|
if bbox is not None:
|
||||||
|
if self.last_observation.sum() >= 0: # no previous observation
|
||||||
|
previous_box = None
|
||||||
|
for i in range(self.delta_t):
|
||||||
|
dt = self.delta_t - i
|
||||||
|
if self.age - dt in self.observations:
|
||||||
|
previous_box = self.observations[self.age-dt]
|
||||||
|
break
|
||||||
|
if previous_box is None:
|
||||||
|
previous_box = self.last_observation
|
||||||
|
"""
|
||||||
|
Estimate the track speed direction with observations \Delta t steps away
|
||||||
|
"""
|
||||||
|
self.velocity = speed_direction(previous_box, bbox)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Insert new observations. This is a ugly way to maintain both self.observations
|
||||||
|
and self.history_observations. Bear it for the moment.
|
||||||
|
"""
|
||||||
|
self.last_observation = bbox
|
||||||
|
self.observations[self.age] = bbox
|
||||||
|
self.history_observations.append(bbox)
|
||||||
|
|
||||||
|
self.time_since_update = 0
|
||||||
|
self.history = []
|
||||||
|
self.hits += 1
|
||||||
|
self.hit_streak += 1
|
||||||
|
self.kf.update(convert_bbox_to_z(bbox))
|
||||||
|
else:
|
||||||
|
self.kf.update(bbox)
|
||||||
|
|
||||||
|
def predict(self):
|
||||||
|
"""
|
||||||
|
Advances the state vector and returns the predicted bounding box estimate.
|
||||||
|
"""
|
||||||
|
if((self.kf.x[6]+self.kf.x[2]) <= 0):
|
||||||
|
self.kf.x[6] *= 0.0
|
||||||
|
|
||||||
|
self.kf.predict()
|
||||||
|
self.age += 1
|
||||||
|
if(self.time_since_update > 0):
|
||||||
|
self.hit_streak = 0
|
||||||
|
self.time_since_update += 1
|
||||||
|
self.history.append(convert_x_to_bbox(self.kf.x))
|
||||||
|
return self.history[-1]
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
"""
|
||||||
|
Returns the current bounding box estimate.
|
||||||
|
"""
|
||||||
|
return convert_x_to_bbox(self.kf.x)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
We support multiple ways for association cost calculation, by default
|
||||||
|
we use IoU. GIoU may have better performance in some situations. We note
|
||||||
|
that we hardly normalize the cost by all methods to (0,1) which may not be
|
||||||
|
the best practice.
|
||||||
|
"""
|
||||||
|
ASSO_FUNCS = { "iou": iou_batch,
|
||||||
|
"giou": giou_batch,
|
||||||
|
"ciou": ciou_batch,
|
||||||
|
"diou": diou_batch,
|
||||||
|
"ct_dist": ct_dist}
|
||||||
|
|
||||||
|
|
||||||
|
class OCSort(object):
|
||||||
|
def __init__(self, det_thresh, max_age=30, min_hits=3,
|
||||||
|
iou_threshold=0.3, delta_t=3, asso_func="iou", inertia=0.2, use_byte=False):
|
||||||
|
"""
|
||||||
|
Sets key parameters for SORT
|
||||||
|
"""
|
||||||
|
self.max_age = max_age
|
||||||
|
self.min_hits = min_hits
|
||||||
|
self.iou_threshold = iou_threshold
|
||||||
|
self.trackers = []
|
||||||
|
self.frame_count = 0
|
||||||
|
self.det_thresh = det_thresh
|
||||||
|
self.delta_t = delta_t
|
||||||
|
self.asso_func = ASSO_FUNCS[asso_func]
|
||||||
|
self.inertia = inertia
|
||||||
|
self.use_byte = use_byte
|
||||||
|
KalmanBoxTracker.count = 0
|
||||||
|
|
||||||
|
def update(self, output_results, img_info, img_size):
|
||||||
|
"""
|
||||||
|
Params:
|
||||||
|
dets - a numpy array of detections in the format [[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]
|
||||||
|
Requires: this method must be called once for each frame even with empty detections (use np.empty((0, 5)) for frames without detections).
|
||||||
|
Returns the a similar array, where the last column is the object ID.
|
||||||
|
NOTE: The number of objects returned may differ from the number of detections provided.
|
||||||
|
"""
|
||||||
|
if output_results is None:
|
||||||
|
return np.empty((0, 5))
|
||||||
|
|
||||||
|
self.frame_count += 1
|
||||||
|
# post_process detections
|
||||||
|
if output_results.shape[1] == 5:
|
||||||
|
scores = output_results[:, 4]
|
||||||
|
bboxes = output_results[:, :4]
|
||||||
|
else:
|
||||||
|
output_results = output_results.cpu().numpy()
|
||||||
|
scores = output_results[:, 4] * output_results[:, 5]
|
||||||
|
bboxes = output_results[:, :4] # x1y1x2y2
|
||||||
|
img_h, img_w = img_info[0], img_info[1]
|
||||||
|
scale = min(img_size[0] / float(img_h), img_size[1] / float(img_w))
|
||||||
|
bboxes /= scale
|
||||||
|
dets = np.concatenate((bboxes, np.expand_dims(scores, axis=-1)), axis=1)
|
||||||
|
inds_low = scores > 0.1
|
||||||
|
inds_high = scores < self.det_thresh
|
||||||
|
inds_second = np.logical_and(inds_low, inds_high) # self.det_thresh > score > 0.1, for second matching
|
||||||
|
dets_second = dets[inds_second] # detections for second matching
|
||||||
|
remain_inds = scores > self.det_thresh
|
||||||
|
dets = dets[remain_inds]
|
||||||
|
|
||||||
|
# get predicted locations from existing trackers.
|
||||||
|
trks = np.zeros((len(self.trackers), 5))
|
||||||
|
to_del = []
|
||||||
|
ret = []
|
||||||
|
for t, trk in enumerate(trks):
|
||||||
|
pos = self.trackers[t].predict()[0]
|
||||||
|
trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
|
||||||
|
if np.any(np.isnan(pos)):
|
||||||
|
to_del.append(t)
|
||||||
|
trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
|
||||||
|
for t in reversed(to_del):
|
||||||
|
self.trackers.pop(t)
|
||||||
|
|
||||||
|
velocities = np.array(
|
||||||
|
[trk.velocity if trk.velocity is not None else np.array((0, 0)) for trk in self.trackers])
|
||||||
|
last_boxes = np.array([trk.last_observation for trk in self.trackers])
|
||||||
|
k_observations = np.array(
|
||||||
|
[k_previous_obs(trk.observations, trk.age, self.delta_t) for trk in self.trackers])
|
||||||
|
|
||||||
|
"""
|
||||||
|
First round of association
|
||||||
|
"""
|
||||||
|
matched, unmatched_dets, unmatched_trks = associate(
|
||||||
|
dets, trks, self.iou_threshold, velocities, k_observations, self.inertia)
|
||||||
|
for m in matched:
|
||||||
|
self.trackers[m[1]].update(dets[m[0], :])
|
||||||
|
|
||||||
|
"""
|
||||||
|
Second round of associaton by OCR
|
||||||
|
"""
|
||||||
|
# BYTE association
|
||||||
|
if self.use_byte and len(dets_second) > 0 and unmatched_trks.shape[0] > 0:
|
||||||
|
u_trks = trks[unmatched_trks]
|
||||||
|
iou_left = self.asso_func(dets_second, u_trks) # iou between low score detections and unmatched tracks
|
||||||
|
iou_left = np.array(iou_left)
|
||||||
|
if iou_left.max() > self.iou_threshold:
|
||||||
|
"""
|
||||||
|
NOTE: by using a lower threshold, e.g., self.iou_threshold - 0.1, you may
|
||||||
|
get a higher performance especially on MOT17/MOT20 datasets. But we keep it
|
||||||
|
uniform here for simplicity
|
||||||
|
"""
|
||||||
|
matched_indices = linear_assignment(-iou_left)
|
||||||
|
to_remove_trk_indices = []
|
||||||
|
for m in matched_indices:
|
||||||
|
det_ind, trk_ind = m[0], unmatched_trks[m[1]]
|
||||||
|
if iou_left[m[0], m[1]] < self.iou_threshold:
|
||||||
|
continue
|
||||||
|
self.trackers[trk_ind].update(dets_second[det_ind, :])
|
||||||
|
to_remove_trk_indices.append(trk_ind)
|
||||||
|
unmatched_trks = np.setdiff1d(unmatched_trks, np.array(to_remove_trk_indices))
|
||||||
|
|
||||||
|
if unmatched_dets.shape[0] > 0 and unmatched_trks.shape[0] > 0:
|
||||||
|
left_dets = dets[unmatched_dets]
|
||||||
|
left_trks = last_boxes[unmatched_trks]
|
||||||
|
iou_left = self.asso_func(left_dets, left_trks)
|
||||||
|
iou_left = np.array(iou_left)
|
||||||
|
if iou_left.max() > self.iou_threshold:
|
||||||
|
"""
|
||||||
|
NOTE: by using a lower threshold, e.g., self.iou_threshold - 0.1, you may
|
||||||
|
get a higher performance especially on MOT17/MOT20 datasets. But we keep it
|
||||||
|
uniform here for simplicity
|
||||||
|
"""
|
||||||
|
rematched_indices = linear_assignment(-iou_left)
|
||||||
|
to_remove_det_indices = []
|
||||||
|
to_remove_trk_indices = []
|
||||||
|
for m in rematched_indices:
|
||||||
|
det_ind, trk_ind = unmatched_dets[m[0]], unmatched_trks[m[1]]
|
||||||
|
if iou_left[m[0], m[1]] < self.iou_threshold:
|
||||||
|
continue
|
||||||
|
self.trackers[trk_ind].update(dets[det_ind, :])
|
||||||
|
to_remove_det_indices.append(det_ind)
|
||||||
|
to_remove_trk_indices.append(trk_ind)
|
||||||
|
unmatched_dets = np.setdiff1d(unmatched_dets, np.array(to_remove_det_indices))
|
||||||
|
unmatched_trks = np.setdiff1d(unmatched_trks, np.array(to_remove_trk_indices))
|
||||||
|
|
||||||
|
for m in unmatched_trks:
|
||||||
|
self.trackers[m].update(None)
|
||||||
|
|
||||||
|
# create and initialise new trackers for unmatched detections
|
||||||
|
for i in unmatched_dets:
|
||||||
|
trk = KalmanBoxTracker(dets[i, :], delta_t=self.delta_t)
|
||||||
|
self.trackers.append(trk)
|
||||||
|
i = len(self.trackers)
|
||||||
|
for trk in reversed(self.trackers):
|
||||||
|
if trk.last_observation.sum() < 0:
|
||||||
|
d = trk.get_state()[0]
|
||||||
|
else:
|
||||||
|
"""
|
||||||
|
this is optional to use the recent observation or the kalman filter prediction,
|
||||||
|
we didn't notice significant difference here
|
||||||
|
"""
|
||||||
|
d = trk.last_observation[:4]
|
||||||
|
if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
|
||||||
|
# +1 as MOT benchmark requires positive
|
||||||
|
ret.append(np.concatenate((d, [trk.id+1])).reshape(1, -1))
|
||||||
|
i -= 1
|
||||||
|
# remove dead tracklet
|
||||||
|
if(trk.time_since_update > self.max_age):
|
||||||
|
self.trackers.pop(i)
|
||||||
|
if(len(ret) > 0):
|
||||||
|
return np.concatenate(ret)
|
||||||
|
return np.empty((0, 5))
|
||||||
|
|
||||||
|
def update_public(self, dets, cates, scores):
|
||||||
|
self.frame_count += 1
|
||||||
|
|
||||||
|
det_scores = np.ones((dets.shape[0], 1))
|
||||||
|
dets = np.concatenate((dets, det_scores), axis=1)
|
||||||
|
|
||||||
|
remain_inds = scores > self.det_thresh
|
||||||
|
|
||||||
|
cates = cates[remain_inds]
|
||||||
|
dets = dets[remain_inds]
|
||||||
|
|
||||||
|
trks = np.zeros((len(self.trackers), 5))
|
||||||
|
to_del = []
|
||||||
|
ret = []
|
||||||
|
for t, trk in enumerate(trks):
|
||||||
|
pos = self.trackers[t].predict()[0]
|
||||||
|
cat = self.trackers[t].cate
|
||||||
|
trk[:] = [pos[0], pos[1], pos[2], pos[3], cat]
|
||||||
|
if np.any(np.isnan(pos)):
|
||||||
|
to_del.append(t)
|
||||||
|
trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
|
||||||
|
for t in reversed(to_del):
|
||||||
|
self.trackers.pop(t)
|
||||||
|
|
||||||
|
velocities = np.array([trk.velocity if trk.velocity is not None else np.array((0,0)) for trk in self.trackers])
|
||||||
|
last_boxes = np.array([trk.last_observation for trk in self.trackers])
|
||||||
|
k_observations = np.array([k_previous_obs(trk.observations, trk.age, self.delta_t) for trk in self.trackers])
|
||||||
|
|
||||||
|
matched, unmatched_dets, unmatched_trks = associate_kitti\
|
||||||
|
(dets, trks, cates, self.iou_threshold, velocities, k_observations, self.inertia)
|
||||||
|
|
||||||
|
for m in matched:
|
||||||
|
self.trackers[m[1]].update(dets[m[0], :])
|
||||||
|
|
||||||
|
if unmatched_dets.shape[0] > 0 and unmatched_trks.shape[0] > 0:
|
||||||
|
"""
|
||||||
|
The re-association stage by OCR.
|
||||||
|
NOTE: at this stage, adding other strategy might be able to continue improve
|
||||||
|
the performance, such as BYTE association by ByteTrack.
|
||||||
|
"""
|
||||||
|
left_dets = dets[unmatched_dets]
|
||||||
|
left_trks = last_boxes[unmatched_trks]
|
||||||
|
left_dets_c = left_dets.copy()
|
||||||
|
left_trks_c = left_trks.copy()
|
||||||
|
|
||||||
|
iou_left = self.asso_func(left_dets_c, left_trks_c)
|
||||||
|
iou_left = np.array(iou_left)
|
||||||
|
det_cates_left = cates[unmatched_dets]
|
||||||
|
trk_cates_left = trks[unmatched_trks][:,4]
|
||||||
|
num_dets = unmatched_dets.shape[0]
|
||||||
|
num_trks = unmatched_trks.shape[0]
|
||||||
|
cate_matrix = np.zeros((num_dets, num_trks))
|
||||||
|
for i in range(num_dets):
|
||||||
|
for j in range(num_trks):
|
||||||
|
if det_cates_left[i] != trk_cates_left[j]:
|
||||||
|
"""
|
||||||
|
For some datasets, such as KITTI, there are different categories,
|
||||||
|
we have to avoid associate them together.
|
||||||
|
"""
|
||||||
|
cate_matrix[i][j] = -1e6
|
||||||
|
iou_left = iou_left + cate_matrix
|
||||||
|
if iou_left.max() > self.iou_threshold - 0.1:
|
||||||
|
rematched_indices = linear_assignment(-iou_left)
|
||||||
|
to_remove_det_indices = []
|
||||||
|
to_remove_trk_indices = []
|
||||||
|
for m in rematched_indices:
|
||||||
|
det_ind, trk_ind = unmatched_dets[m[0]], unmatched_trks[m[1]]
|
||||||
|
if iou_left[m[0], m[1]] < self.iou_threshold - 0.1:
|
||||||
|
continue
|
||||||
|
self.trackers[trk_ind].update(dets[det_ind, :])
|
||||||
|
to_remove_det_indices.append(det_ind)
|
||||||
|
to_remove_trk_indices.append(trk_ind)
|
||||||
|
unmatched_dets = np.setdiff1d(unmatched_dets, np.array(to_remove_det_indices))
|
||||||
|
unmatched_trks = np.setdiff1d(unmatched_trks, np.array(to_remove_trk_indices))
|
||||||
|
|
||||||
|
for i in unmatched_dets:
|
||||||
|
trk = KalmanBoxTracker(dets[i,:])
|
||||||
|
trk.cate = cates[i]
|
||||||
|
self.trackers.append(trk)
|
||||||
|
i = len(self.trackers)
|
||||||
|
|
||||||
|
for trk in reversed(self.trackers):
|
||||||
|
if trk.last_observation.sum() > 0:
|
||||||
|
d = trk.last_observation[:4]
|
||||||
|
else:
|
||||||
|
d = trk.get_state()[0]
|
||||||
|
if (trk.time_since_update < 1):
|
||||||
|
if (self.frame_count <= self.min_hits) or (trk.hit_streak >= self.min_hits):
|
||||||
|
# id+1 as MOT benchmark requires positive
|
||||||
|
ret.append(np.concatenate((d, [trk.id+1], [trk.cate], [0])).reshape(1,-1))
|
||||||
|
if trk.hit_streak == self.min_hits:
|
||||||
|
# Head Padding (HP): recover the lost steps during initializing the track
|
||||||
|
for prev_i in range(self.min_hits - 1):
|
||||||
|
prev_observation = trk.history_observations[-(prev_i+2)]
|
||||||
|
ret.append((np.concatenate((prev_observation[:4], [trk.id+1], [trk.cate],
|
||||||
|
[-(prev_i+1)]))).reshape(1,-1))
|
||||||
|
i -= 1
|
||||||
|
if (trk.time_since_update > self.max_age):
|
||||||
|
self.trackers.pop(i)
|
||||||
|
|
||||||
|
if(len(ret)>0):
|
||||||
|
return np.concatenate(ret)
|
||||||
|
return np.empty((0, 7))
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in new issue