63 lines
2.0 KiB
Python
63 lines
2.0 KiB
Python
"""跨摄像头关联 — 基于「同类别 + 时间窗口 + 空间邻近摄像头」的轻量启发式匹配"""
|
||
import logging
|
||
import threading
|
||
import time
|
||
|
||
logger = logging.getLogger("services.cross_camera")
|
||
|
||
# 最近结束的目标:list of dict(stream_id, track_id, label, global_track_id, ended_at)
|
||
_recent_ended = []
|
||
_lock = threading.Lock()
|
||
_WINDOW_SEC = 60.0
|
||
|
||
|
||
def register_ended(stream_id, track_id, label, global_track_id):
|
||
with _lock:
|
||
_recent_ended.append({
|
||
"stream_id": stream_id,
|
||
"track_id": track_id,
|
||
"label": label,
|
||
"global_track_id": global_track_id,
|
||
"ended_at": time.time(),
|
||
})
|
||
cutoff = time.time() - _WINDOW_SEC * 2
|
||
while _recent_ended and _recent_ended[0]["ended_at"] < cutoff:
|
||
_recent_ended.pop(0)
|
||
|
||
|
||
def try_link(stream_id, track_id, label):
|
||
"""新目标出现时尝试关联到其他摄像头刚消失的同类别目标。
|
||
|
||
返回 (global_track_id, linked_from) ;linked_from 为 dict 或 None。
|
||
"""
|
||
now = time.time()
|
||
best = None
|
||
with _lock:
|
||
for item in reversed(_recent_ended):
|
||
if item["stream_id"] == stream_id:
|
||
continue
|
||
if item["label"] != label:
|
||
continue
|
||
if now - item["ended_at"] > _WINDOW_SEC:
|
||
continue
|
||
best = item
|
||
break
|
||
if not best:
|
||
return "cam%d-%d" % (stream_id, track_id), None
|
||
return best["global_track_id"], best
|
||
|
||
|
||
def make_cross_camera_event(from_info, to_stream_id, to_track_id, label):
|
||
return {
|
||
"type": "cross_camera",
|
||
"stream_id": to_stream_id,
|
||
"track_id": to_track_id,
|
||
"label": label,
|
||
"from_stream_id": from_info.get("stream_id"),
|
||
"from_track_id": from_info.get("track_id"),
|
||
"global_track_id": from_info.get("global_track_id"),
|
||
"timestamp": time.time(),
|
||
"description": "cross camera: %s -> stream#%s" % (
|
||
from_info.get("stream_id"), to_stream_id),
|
||
}
|