mustafa2ak commited on
Commit
9c944d2
Β·
verified Β·
1 Parent(s): 3c2b613

Update reid.py

Browse files
Files changed (1) hide show
  1. reid.py +86 -193
reid.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Enhanced ReID with MegaDescriptor-B-224 and adaptive strategies
3
  """
4
  import numpy as np
5
  import cv2
@@ -20,89 +20,63 @@ class DogFeatures:
20
  bbox: List[float] = field(default_factory=list)
21
  confidence: float = 0.5
22
  frame_num: int = 0
23
- track_id: int = 0 # Add track ID for continuity
24
 
25
 
26
  class MegaDescriptorReID:
27
  """
28
- Enhanced ReID with adaptive strategies and debugging
29
  """
30
 
31
- def __init__(self, device: str = 'cuda', max_expected_dogs: int = 10):
32
  self.device = device if torch.cuda.is_available() else 'cpu'
33
- self.base_threshold = 0.40
34
- self.max_expected_dogs = max_expected_dogs
35
 
36
  # Dog database (temporary only)
37
  self.dog_database = {} # dog_id -> list of DogFeatures
38
  self.next_dog_id = 1
39
  self.current_frame = 0
40
 
41
- # Track continuity mapping
42
- self.track_to_dog = {} # track_id -> dog_id
43
- self.dog_last_track = {} # dog_id -> last_track_id
44
-
45
  # Statistics for debugging
46
  self.match_stats = {
47
- 'new_dogs_created': 0,
48
- 'successful_matches': 0,
49
- 'threshold_adjustments': 0,
50
- 'track_continuity_matches': 0,
51
- 'similarity_scores': []
52
  }
53
 
54
- # Initialize MegaDescriptor
55
  self._initialize_megadescriptor()
56
 
57
- print(f"βœ… Enhanced MegaDescriptor ReID initialized")
58
- print(f" Device: {self.device}")
59
- print(f" Max expected dogs: {self.max_expected_dogs}")
60
 
61
  def _initialize_megadescriptor(self):
62
- """Initialize MegaDescriptor-B-224"""
63
  try:
 
 
64
  self.model = timm.create_model(
65
- 'hf-hub:BVRA/MegaDescriptor-B-224',
66
  pretrained=True
67
  )
68
  self.model.to(self.device).eval()
69
 
70
- # Get the preprocessing config
71
- data_config = timm.data.resolve_model_data_config(self.model)
72
- self.transform = timm.data.create_transform(**data_config, is_training=False)
 
 
 
 
73
 
74
- print("βœ… MegaDescriptor-B-224 loaded successfully")
 
 
75
 
76
  except Exception as e:
77
  print(f"❌ MegaDescriptor initialization error: {e}")
78
  self.model = None
79
 
80
- def get_adaptive_threshold(self):
81
- """Calculate adaptive threshold based on current dog count"""
82
- num_dogs = len(self.dog_database)
83
- original_threshold = self.base_threshold
84
-
85
- if num_dogs >= self.max_expected_dogs * 1.5:
86
- # Way too many dogs, be very lenient
87
- adapted = 0.25
88
- elif num_dogs >= self.max_expected_dogs:
89
- # Too many dogs, lower threshold
90
- adapted = 0.30
91
- elif num_dogs >= self.max_expected_dogs * 0.8:
92
- # Approaching limit, start lowering
93
- adapted = 0.35
94
- else:
95
- # Normal range
96
- adapted = self.base_threshold
97
-
98
- if adapted != original_threshold:
99
- self.match_stats['threshold_adjustments'] += 1
100
- print(f"πŸ“Š Adaptive threshold: {original_threshold:.2f} β†’ {adapted:.2f} (dogs: {num_dogs})")
101
-
102
- return adapted
103
-
104
- def extract_features(self, image: np.ndarray, bbox: List[float] = None, track_id: int = None) -> Optional[DogFeatures]:
105
- """Extract features using MegaDescriptor"""
106
  if image is None or image.size == 0 or self.model is None:
107
  return None
108
 
@@ -114,7 +88,7 @@ class MegaDescriptorReID:
114
  from PIL import Image
115
  pil_img = Image.fromarray(img_rgb)
116
 
117
- # Apply MegaDescriptor transforms
118
  img_tensor = self.transform(pil_img).unsqueeze(0).to(self.device)
119
 
120
  # Extract features
@@ -127,8 +101,7 @@ class MegaDescriptorReID:
127
  return DogFeatures(
128
  features=features,
129
  bbox=bbox if bbox else [0, 0, 100, 100],
130
- frame_num=self.current_frame,
131
- track_id=track_id if track_id else 0
132
  )
133
 
134
  except Exception as e:
@@ -136,28 +109,9 @@ class MegaDescriptorReID:
136
  return None
137
 
138
  def match_or_register(self, track, image_crop=None) -> Tuple[int, float]:
139
- """Match or register a dog with enhanced strategies"""
140
  self.current_frame += 1
141
 
142
- # Get track ID for continuity
143
- track_id = track.track_id if hasattr(track, 'track_id') else 0
144
-
145
- # Check if this track already has a dog ID (continuity)
146
- if track_id in self.track_to_dog:
147
- dog_id = self.track_to_dog[track_id]
148
- self.match_stats['track_continuity_matches'] += 1
149
- print(f" πŸ”„ Track continuity: Track {track_id} β†’ Dog {dog_id}")
150
-
151
- # Still extract and store features for future matching
152
- for det in reversed(track.detections[-3:]):
153
- if det.image_crop is not None:
154
- features = self.extract_features(det.image_crop, det.bbox, track_id)
155
- if features:
156
- self._update_dog_features(dog_id, features)
157
- break
158
-
159
- return dog_id, 0.95 # High confidence for track continuity
160
-
161
  # Get detection with image
162
  detection = None
163
  for det in reversed(track.detections[-3:]):
@@ -172,8 +126,7 @@ class MegaDescriptorReID:
172
  # Extract features
173
  features = self.extract_features(
174
  image_crop,
175
- detection.bbox if hasattr(detection, 'bbox') else None,
176
- track_id
177
  )
178
 
179
  if features is None:
@@ -181,18 +134,15 @@ class MegaDescriptorReID:
181
 
182
  features.confidence = detection.confidence if hasattr(detection, 'confidence') else 0.5
183
 
184
- # Get adaptive threshold
185
- threshold = self.get_adaptive_threshold()
186
-
187
  # Find best match
188
  best_dog_id = None
189
  best_score = -1.0
190
- all_scores = [] # For debugging
191
 
192
  for dog_id, dog_features_list in self.dog_database.items():
193
  # Calculate similarity with stored features
194
  similarities = []
195
- for stored_feat in dog_features_list[-10:]: # Use last 10 features
196
  sim = cosine_similarity(
197
  features.features.reshape(1, -1),
198
  stored_feat.features.reshape(1, -1)
@@ -200,104 +150,53 @@ class MegaDescriptorReID:
200
  similarities.append(sim)
201
 
202
  if similarities:
203
- avg_similarity = np.mean(similarities)
204
- max_similarity = np.max(similarities)
205
- # Use weighted combination
206
- weighted_sim = 0.7 * avg_similarity + 0.3 * max_similarity
 
207
 
208
- all_scores.append((dog_id, weighted_sim))
209
 
210
- if weighted_sim > best_score:
211
- best_score = weighted_sim
212
  best_dog_id = dog_id
213
 
214
  # Debug output
215
- if all_scores:
216
- self.match_stats['similarity_scores'].extend([s[1] for s in all_scores])
217
- top_matches = sorted(all_scores, key=lambda x: x[1], reverse=True)[:3]
218
- print(f" πŸ” Top matches: {[(f'Dog{d}', f'{s:.3f}') for d, s in top_matches]}")
 
 
 
 
 
 
219
 
220
- # Decision: match or new dog
221
  if best_dog_id is not None and best_score >= threshold:
222
  # Match found
223
- self.match_stats['successful_matches'] += 1
224
- print(f" βœ… Matched to Dog {best_dog_id} (score: {best_score:.3f}, threshold: {threshold:.3f})")
225
-
226
- # Update track mapping
227
- self.track_to_dog[track_id] = best_dog_id
228
- self.dog_last_track[best_dog_id] = track_id
229
 
230
  # Update features
231
- self._update_dog_features(best_dog_id, features)
 
 
 
232
 
233
  return best_dog_id, best_score
234
  else:
235
- # Check if we should be more aggressive due to dog count
236
- if len(self.dog_database) >= self.max_expected_dogs and best_score > 0.2:
237
- # Force match if we have too many dogs and score is reasonable
238
- print(f" ⚠️ Forced match to Dog {best_dog_id} (too many dogs, score: {best_score:.3f})")
239
- self.track_to_dog[track_id] = best_dog_id
240
- self._update_dog_features(best_dog_id, features)
241
- return best_dog_id, best_score
242
-
243
  # New dog
244
- new_dog_id = self._register_new_dog(features, track_id)
 
 
 
 
 
 
245
  return new_dog_id, 1.0
246
 
247
- def _update_dog_features(self, dog_id: int, features: DogFeatures):
248
- """Update dog features database"""
249
- self.dog_database[dog_id].append(features)
250
- # Keep more features for better matching
251
- if len(self.dog_database[dog_id]) > 30:
252
- self.dog_database[dog_id] = self.dog_database[dog_id][-30:]
253
-
254
- def _register_new_dog(self, features: DogFeatures, track_id: int) -> int:
255
- """Register a new dog"""
256
- new_dog_id = self.next_dog_id
257
- self.next_dog_id += 1
258
- self.match_stats['new_dogs_created'] += 1
259
-
260
- self.dog_database[new_dog_id] = [features]
261
- self.track_to_dog[track_id] = new_dog_id
262
- self.dog_last_track[new_dog_id] = track_id
263
-
264
- print(f" πŸ†• New dog registered: Dog {new_dog_id} (Total: {len(self.dog_database)})")
265
-
266
- return new_dog_id
267
-
268
- def post_process_merge(self, merge_threshold: float = 0.7):
269
- """Post-process to merge similar dogs"""
270
- print("\nπŸ”„ Post-processing: Checking for similar dogs to merge...")
271
-
272
- merged_count = 0
273
- dog_ids = list(self.dog_database.keys())
274
-
275
- for i, dog1_id in enumerate(dog_ids):
276
- if dog1_id not in self.dog_database:
277
- continue
278
-
279
- for dog2_id in dog_ids[i+1:]:
280
- if dog2_id not in self.dog_database:
281
- continue
282
-
283
- # Compare average features
284
- feat1 = np.mean([f.features for f in self.dog_database[dog1_id]], axis=0)
285
- feat2 = np.mean([f.features for f in self.dog_database[dog2_id]], axis=0)
286
-
287
- similarity = cosine_similarity(feat1.reshape(1, -1), feat2.reshape(1, -1))[0, 0]
288
-
289
- if similarity > merge_threshold:
290
- # Merge dog2 into dog1
291
- print(f" πŸ”— Merging Dog {dog2_id} into Dog {dog1_id} (similarity: {similarity:.3f})")
292
- self.dog_database[dog1_id].extend(self.dog_database[dog2_id])
293
- del self.dog_database[dog2_id]
294
- merged_count += 1
295
-
296
- if merged_count > 0:
297
- print(f" βœ… Merged {merged_count} duplicate dogs. Final count: {len(self.dog_database)}")
298
-
299
- return merged_count
300
-
301
  def match_or_register_all(self, track) -> Dict:
302
  """Compatible interface"""
303
  dog_id, confidence = self.match_or_register(track)
@@ -309,36 +208,39 @@ class MegaDescriptorReID:
309
  }
310
 
311
  def set_all_thresholds(self, threshold: float):
312
- """Update base threshold"""
313
  self.base_threshold = max(0.15, min(0.95, threshold))
314
- print(f"πŸ“Š Base ReID threshold set to: {self.base_threshold:.2f}")
315
 
316
  def reset_all(self):
317
  """Reset for new video"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  self.dog_database.clear()
319
- self.track_to_dog.clear()
320
- self.dog_last_track.clear()
321
  self.next_dog_id = 1
322
  self.current_frame = 0
323
 
324
- # Print debug statistics before reset
325
- if self.match_stats['new_dogs_created'] > 0:
326
- print("\nπŸ“ˆ Session Statistics:")
327
- print(f" β€’ New dogs created: {self.match_stats['new_dogs_created']}")
328
- print(f" β€’ Successful matches: {self.match_stats['successful_matches']}")
329
- print(f" β€’ Track continuity matches: {self.match_stats['track_continuity_matches']}")
330
- print(f" β€’ Threshold adjustments: {self.match_stats['threshold_adjustments']}")
331
- if self.match_stats['similarity_scores']:
332
- scores = self.match_stats['similarity_scores']
333
- print(f" β€’ Avg similarity: {np.mean(scores):.3f} (min: {np.min(scores):.3f}, max: {np.max(scores):.3f})")
334
-
335
  # Reset statistics
336
  self.match_stats = {
337
- 'new_dogs_created': 0,
338
- 'successful_matches': 0,
339
- 'threshold_adjustments': 0,
340
- 'track_continuity_matches': 0,
341
- 'similarity_scores': []
342
  }
343
 
344
  print("πŸ”„ ReID reset\n")
@@ -348,17 +250,8 @@ class MegaDescriptorReID:
348
  return {
349
  'total_dogs': len(self.dog_database),
350
  'threshold': self.base_threshold,
351
- 'stats': self.match_stats
352
  }
353
- def aggressive_merge(self):
354
- """Keep merging until no more merges possible"""
355
- total_merged = 0
356
- while True:
357
- merged = self.post_process_merge(merge_threshold=0.5)
358
- if merged == 0:
359
- break
360
- total_merged += merged
361
- return total_merged
362
 
363
 
364
  # Compatibility aliases
 
1
  """
2
+ Simplified ReID with MegaDescriptor-L-384 (Largest Model)
3
  """
4
  import numpy as np
5
  import cv2
 
20
  bbox: List[float] = field(default_factory=list)
21
  confidence: float = 0.5
22
  frame_num: int = 0
 
23
 
24
 
25
  class MegaDescriptorReID:
26
  """
27
+ Simplified ReID using MegaDescriptor-L-384
28
  """
29
 
30
+ def __init__(self, device: str = 'cuda'):
31
  self.device = device if torch.cuda.is_available() else 'cpu'
32
+ self.base_threshold = 0.35 # Lower default for L model
 
33
 
34
  # Dog database (temporary only)
35
  self.dog_database = {} # dog_id -> list of DogFeatures
36
  self.next_dog_id = 1
37
  self.current_frame = 0
38
 
 
 
 
 
39
  # Statistics for debugging
40
  self.match_stats = {
41
+ 'new_dogs': [],
42
+ 'matches': [],
43
+ 'all_scores': []
 
 
44
  }
45
 
46
+ # Initialize MegaDescriptor-L
47
  self._initialize_megadescriptor()
48
 
49
+ print(f"βœ… MegaDescriptor-L-384 ReID initialized on {self.device}")
 
 
50
 
51
  def _initialize_megadescriptor(self):
52
+ """Initialize MegaDescriptor-L-384 (Largest model)"""
53
  try:
54
+ # Load the largest MegaDescriptor model
55
+ print("πŸ“₯ Loading MegaDescriptor-L-384 (this may take a moment)...")
56
  self.model = timm.create_model(
57
+ 'hf-hub:BVRA/MegaDescriptor-L-384',
58
  pretrained=True
59
  )
60
  self.model.to(self.device).eval()
61
 
62
+ # L model uses 384x384 input
63
+ self.transform = timm.data.create_transform(
64
+ input_size=(384, 384),
65
+ is_training=False,
66
+ mean=[0.5, 0.5, 0.5],
67
+ std=[0.5, 0.5, 0.5]
68
+ )
69
 
70
+ print("βœ… MegaDescriptor-L-384 loaded successfully")
71
+ print(" β€’ Model: Large (384x384 input)")
72
+ print(" β€’ Features: 1024-dim")
73
 
74
  except Exception as e:
75
  print(f"❌ MegaDescriptor initialization error: {e}")
76
  self.model = None
77
 
78
+ def extract_features(self, image: np.ndarray, bbox: List[float] = None) -> Optional[DogFeatures]:
79
+ """Extract features using MegaDescriptor-L"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  if image is None or image.size == 0 or self.model is None:
81
  return None
82
 
 
88
  from PIL import Image
89
  pil_img = Image.fromarray(img_rgb)
90
 
91
+ # Apply transforms (384x384 for L model)
92
  img_tensor = self.transform(pil_img).unsqueeze(0).to(self.device)
93
 
94
  # Extract features
 
101
  return DogFeatures(
102
  features=features,
103
  bbox=bbox if bbox else [0, 0, 100, 100],
104
+ frame_num=self.current_frame
 
105
  )
106
 
107
  except Exception as e:
 
109
  return None
110
 
111
  def match_or_register(self, track, image_crop=None) -> Tuple[int, float]:
112
+ """Simple match or register without complex strategies"""
113
  self.current_frame += 1
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  # Get detection with image
116
  detection = None
117
  for det in reversed(track.detections[-3:]):
 
126
  # Extract features
127
  features = self.extract_features(
128
  image_crop,
129
+ detection.bbox if hasattr(detection, 'bbox') else None
 
130
  )
131
 
132
  if features is None:
 
134
 
135
  features.confidence = detection.confidence if hasattr(detection, 'confidence') else 0.5
136
 
 
 
 
137
  # Find best match
138
  best_dog_id = None
139
  best_score = -1.0
140
+ debug_scores = []
141
 
142
  for dog_id, dog_features_list in self.dog_database.items():
143
  # Calculate similarity with stored features
144
  similarities = []
145
+ for stored_feat in dog_features_list[-20:]: # Use last 20 features
146
  sim = cosine_similarity(
147
  features.features.reshape(1, -1),
148
  stored_feat.features.reshape(1, -1)
 
150
  similarities.append(sim)
151
 
152
  if similarities:
153
+ # Use max similarity for L model (more discriminative)
154
+ max_sim = np.max(similarities)
155
+ avg_sim = np.mean(similarities)
156
+ # Weight max more for L model
157
+ final_score = 0.6 * max_sim + 0.4 * avg_sim
158
 
159
+ debug_scores.append((dog_id, final_score, max_sim, avg_sim))
160
 
161
+ if final_score > best_score:
162
+ best_score = final_score
163
  best_dog_id = dog_id
164
 
165
  # Debug output
166
+ if debug_scores:
167
+ self.match_stats['all_scores'].append(best_score)
168
+ top_matches = sorted(debug_scores, key=lambda x: x[1], reverse=True)[:3]
169
+ print(f" πŸ” Frame {self.current_frame} matches:")
170
+ for dog_id, final, max_s, avg_s in top_matches[:3]:
171
+ print(f" Dog {dog_id}: {final:.3f} (max:{max_s:.3f}, avg:{avg_s:.3f})")
172
+
173
+ # Simple decision with threshold
174
+ threshold = self.base_threshold
175
+ print(f" πŸ“Š Best score: {best_score:.3f}, Threshold: {threshold:.3f}")
176
 
 
177
  if best_dog_id is not None and best_score >= threshold:
178
  # Match found
179
+ self.match_stats['matches'].append((best_dog_id, best_score))
180
+ print(f" βœ… Matched to Dog {best_dog_id}")
 
 
 
 
181
 
182
  # Update features
183
+ self.dog_database[best_dog_id].append(features)
184
+ # Keep last 30 features
185
+ if len(self.dog_database[best_dog_id]) > 30:
186
+ self.dog_database[best_dog_id] = self.dog_database[best_dog_id][-30:]
187
 
188
  return best_dog_id, best_score
189
  else:
 
 
 
 
 
 
 
 
190
  # New dog
191
+ new_dog_id = self.next_dog_id
192
+ self.next_dog_id += 1
193
+ self.match_stats['new_dogs'].append(new_dog_id)
194
+
195
+ self.dog_database[new_dog_id] = [features]
196
+ print(f" πŸ†• New dog: Dog {new_dog_id} (Total: {len(self.dog_database)})")
197
+
198
  return new_dog_id, 1.0
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  def match_or_register_all(self, track) -> Dict:
201
  """Compatible interface"""
202
  dog_id, confidence = self.match_or_register(track)
 
208
  }
209
 
210
  def set_all_thresholds(self, threshold: float):
211
+ """Update threshold"""
212
  self.base_threshold = max(0.15, min(0.95, threshold))
213
+ print(f"πŸ“Š ReID threshold set to: {self.base_threshold:.2f}")
214
 
215
  def reset_all(self):
216
  """Reset for new video"""
217
+ # Print final statistics
218
+ if self.dog_database:
219
+ print("\n" + "="*50)
220
+ print("πŸ“ˆ Final Session Statistics:")
221
+ print(f" β€’ Total dogs detected: {len(self.dog_database)}")
222
+ print(f" β€’ New dog creations: {len(self.match_stats['new_dogs'])}")
223
+ print(f" β€’ Successful matches: {len(self.match_stats['matches'])}")
224
+
225
+ if self.match_stats['all_scores']:
226
+ scores = self.match_stats['all_scores']
227
+ print(f" β€’ Match scores - Avg: {np.mean(scores):.3f}, Min: {np.min(scores):.3f}, Max: {np.max(scores):.3f}")
228
+
229
+ print("\n Dogs summary:")
230
+ for dog_id, features_list in self.dog_database.items():
231
+ print(f" Dog {dog_id}: {len(features_list)} features stored")
232
+ print("="*50 + "\n")
233
+
234
+ # Clear everything
235
  self.dog_database.clear()
 
 
236
  self.next_dog_id = 1
237
  self.current_frame = 0
238
 
 
 
 
 
 
 
 
 
 
 
 
239
  # Reset statistics
240
  self.match_stats = {
241
+ 'new_dogs': [],
242
+ 'matches': [],
243
+ 'all_scores': []
 
 
244
  }
245
 
246
  print("πŸ”„ ReID reset\n")
 
250
  return {
251
  'total_dogs': len(self.dog_database),
252
  'threshold': self.base_threshold,
253
+ 'model': 'MegaDescriptor-L-384'
254
  }
 
 
 
 
 
 
 
 
 
255
 
256
 
257
  # Compatibility aliases