- ✅ Time-based gallery cleaning (timeout 30s)
- ✅ Hungarian algorithm dla optymalnego matchingu
- ✅ Stabilniejsze embeddingi (alpha=0.3)
- ✅ Tracking statystyk (last_seen, appearances)
- ✅ Threshold obniżony do 0.5
Problem: ID może migać gdy osoba jest częściowo zasłonięta lub w trudnym kącie
Rozwiązanie:
class TemporalTracker:
def __init__(self, smoothing_window=5):
self.id_history = {} # {track_id: [last 5 IDs]}
def smooth_id(self, bbox_center, proposed_id):
# Znajdź najbliższy track po pozycji
# Jeśli ID jest spójne z historią → zachowaj
# Jeśli nowe ID → wymagaj 3/5 głosów
passEfekt: Stabilniejsze ID nawet przy ocklusion
Problem: Osoba przeskakuje przez ekran z ID 1 na ID 2
Rozwiązanie:
def spatial_constrained_match(prev_bboxes, curr_bboxes, prev_ids):
# Oblicz IoU między prev i curr bboxes
# Jeśli IoU > 0.3 → prawdopodobnie ta sama osoba
# Użyj spatial prior w Hungarian matching
iou_matrix = compute_iou_matrix(prev_bboxes, curr_bboxes)
combined_score = 0.7 * similarity + 0.3 * iouEfekt: Fizycznie bliska detekcja dostaje ten sam ID
Problem: Osoba idzie z kamery 1 na kamerę 2 → nowe ID
Rozwiązanie:
class GlobalGallery:
def __init__(self):
self.global_embeddings = {} # Wspólna galeria dla wszystkich kamer
def match_across_cameras(self, emb, camera_id, timestamp):
# Szukaj w global gallery
# Uwzględnij czas i topologię kamer
passEfekt: Konsystentne ID między kamerami
Problem: Threshold 0.5 może być za wysoki/niski w różnych warunkach
Rozwiązanie:
def adaptive_threshold(base_threshold, detection_confidence, lighting):
# Wysoka confidence YOLO → niższy threshold (więcej ufności)
# Niska confidence → wyższy threshold (ostrożniej)
threshold = base_threshold * (2 - detection_confidence)
return np.clip(threshold, 0.3, 0.7)Efekt: Dynamiczne dostosowanie do warunków
Problem: CPU daje ~7 FPS, potrzeba więcej dla realtime
Rozwiązanie:
# Batch inference dla OSNet
def extract_features_batch(images):
batch = torch.stack([transform(img) for img in images])
with torch.no_grad():
features = model(batch.to('cuda'))
return features
# Użyj CUDA streams dla parallel processingEfekt: 30-50 FPS na GPU
Problem: osnet_x1_0 jest duży (10.9MB), wolny inference
Rozwiązanie:
# Użyj osnet_x0_25 lub osnet_ain_x0_5
model = models.build_model(
name='osnet_x0_25', # 1/4 rozmiaru, 2x szybszy
pretrained=True
)Efekt: 12-15 FPS na CPU (przy minimalnej stracie accuracy)
Problem: Ta sama detekcja w podobnych klatkach → redundantne obliczenia
Rozwiązanie:
class FeatureCache:
def __init__(self, max_age=5):
self.cache = {} # {bbox_hash: (features, timestamp)}
def get_or_compute(self, bbox, image):
bbox_hash = hash_bbox(bbox)
if bbox_hash in cache and age < max_age:
return cache[bbox_hash]
features = extract_features(crop_bbox(image, bbox))
cache[bbox_hash] = (features, time.time())
return featuresEfekt: 20-30% przyspieszenie dla statycznych osób
Problem: Osoba w różnych pozach (stoi/siedzi) może mieć różne embeddingi
Rozwiązanie:
# Użyj pose estimation (np. MediaPipe Pose)
def extract_pose_normalized_features(image, keypoints):
# Znormalizuj crop do standardowej pozy
aligned = align_by_keypoints(image, keypoints)
features = model(aligned)
return featuresEfekt: Robustness na zmiany pozy
Problem: OSNet traktuje całe ciało równo, ale głowa/torso ważniejsze
Rozwiązanie:
class AttentionOSNet(nn.Module):
def __init__(self):
self.osnet = osnet_x1_0()
self.attention = nn.Sequential(
nn.Conv2d(512, 1, 1),
nn.Sigmoid()
)
def forward(self, x):
features = self.osnet.conv5(x) # (B, 512, H, W)
attention_map = self.attention(features) # (B, 1, H, W)
weighted = features * attention_map
pooled = F.adaptive_avg_pool2d(weighted, 1)
return pooledEfekt: Lepsze fokusowanie na istotnych regionach
Problem: Zasłonięta osoba (za słupem) → trudne rozpoznanie
Rozwiązanie:
def extract_part_features(image):
# Podziel na 3 części: głowa, torso, nogi
h, w = image.shape[:2]
head = image[0:h//3, :]
torso = image[h//3:2*h//3, :]
legs = image[2*h//3:, :]
head_emb = model(head)
torso_emb = model(torso)
legs_emb = model(legs)
return [head_emb, torso_emb, legs_emb]
def match_with_occlusion(part_embs1, part_embs2):
# Porównaj tylko widoczne części
similarities = []
for p1, p2 in zip(part_embs1, part_embs2):
if p1 is not None and p2 is not None:
similarities.append(cosine_sim(p1, p2))
return np.mean(similarities)Efekt: Robustness przy zasłonięciach
config = {
'similarity_threshold': 0.6, # Wyższy - mniej false positives
'alpha': 0.2, # Wolniejsza adaptacja
'inactive_timeout': 60.0, # Dłuższa pamięć
'min_appearances': 3, # Potwierdź ID po 3 widzeniach
'use_face': True, # Zawsze używaj face
'yolo_confidence': 0.7 # Wyższa pewność YOLO
}config = {
'similarity_threshold': 0.45, # Niższy - więcej true positives
'alpha': 0.3, # Szybsza adaptacja
'inactive_timeout': 20.0, # Szybkie czyszczenie
'yolo_imgsz': 320, # Mniejszy rozmiar
'skip_frames': 3, # Co 3 klatka
'use_face': False, # Tylko body dla szybkości
}config = {
'similarity_threshold': 0.55,
'use_hungarian': True, # Zawsze Hungarian
'max_gallery_size': 50, # Limit galerii
'spatial_constraint': True, # IoU filtering
'temporal_smoothing': True, # Kalman filter
'batch_inference': True # Batch processing dla GPU
}# Wejdź → znikij → wróć po 10s → sprawdź ID
def test_persistence():
id1 = first_appearance_id()
time.sleep(10)
id2 = reappearance_id()
assert id1 == id2, "ID should persist"# 3 osoby w kadrze → każda unikalne ID
def test_uniqueness():
ids = detect_all_ids_in_frame()
assert len(ids) == len(set(ids)), "All IDs should be unique"# Osoba zmienia kurtkę → sprawdź czy ID się utrzymuje
# (wymaga face detection lub pose consistency)Przyczyna: Threshold zbyt blisko embedding similarity
Fix: Obniż threshold o 0.05 lub zwiększ alpha
Przyczyna: Embeddingi zbyt różne (zły lighting, blur)
Fix: Sprawdź jakość obrazu, zwiększ YOLO imgsz, użyj denoising
Przyczyna: CPU bottleneck
Fix: Zwiększ skip_frames, zmniejsz yolo_imgsz, użyj GPU, użyj osnet_x0_25
Przyczyna: clean_inactive_persons nie działa
Fix: Sprawdź czy clean jest wywoływany, zmniejsz inactive_timeout
- "Omni-Scale Feature Learning for Person Re-Identification" - OSNet paper
- "Deep Learning for Person Re-identification: A Survey" - Comprehensive overview
- "Multi-Object Tracking with Multiple Cues and Switcher-Aware Classification" - Tracking theory
- "Simple Online and Realtime Tracking with a Deep Association Metric (Deep SORT)" - Association logic
- ✅ Temporal smoothing (Kalman/voting)
- ✅ Spatial constraint (IoU filtering)
- ✅ GPU batch inference
- Adaptive threshold
- OSNet lightweight model
- Feature caching
- Multi-camera fusion
- Pose-aware ReID
- Attention mechanism
- Part-based matching
- Zawsze monitoruj FPS - jeśli <5 FPS, użytkownicy będą niezadowoleni
- Log wszystkie ID transitions - pomaga w debugging (ID 1→2, dlaczego?)
- Zapisuj edge cases - gdy similarity jest blisko threshold (0.48-0.52)
- Test na różnych kamerach - każda ma inne lighting/resolution
- Używaj confusion matrix - track false positives i false negatives
class ReIDMetrics:
def __init__(self):
self.id_switches = 0 # Ile razy ID się zmieniło niepotrzebnie
self.id_persistence = [] # Ile sekund ID się utrzymuje
self.false_merges = 0 # Różne osoby z tym samym ID
self.false_splits = 0 # Ta sama osoba z różnymi ID
def report(self):
print(f"ID Switches: {self.id_switches}")
print(f"Avg Persistence: {np.mean(self.id_persistence):.1f}s")
print(f"False Merges: {self.false_merges}")
print(f"False Splits: {self.false_splits}")Target Values:
- ID Switches: <5% of total detections
- Persistence: >30s average
- False Merges: 0 (critical!)
- False Splits: <10% (akceptowalne dla powracających osób)