mustafa2ak commited on
Commit
f6982b8
·
verified ·
1 Parent(s): b67bf7a

Update reid.py

Browse files
Files changed (1) hide show
  1. reid.py +159 -54
reid.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- reid.py - Simplified Single-Model Dog Re-Identification System
3
- Stable ResNet50 ReID without overengineering
4
  """
5
  import numpy as np
6
  import cv2
@@ -21,20 +21,30 @@ class DogFeatures:
21
  features: np.ndarray
22
  confidence: float = 0
23
  quality_score: float = 0.5
 
24
 
25
  class SingleModelReID:
26
- """Simplified ReID focused on stability"""
27
 
28
  def __init__(self, device: str = 'cuda'):
29
  self.device = device if torch.cuda.is_available() else 'cpu'
30
- self.similarity_threshold = 0.40 # Default threshold
31
 
32
- # In-memory dog database (no persistence for temp processing)
 
 
 
 
 
33
  self.dog_database = {} # dog_id -> list of features
34
  self.next_dog_id = 1
35
 
36
- # Track to dog mapping
37
  self.track_to_dog = {}
 
 
 
 
 
38
 
39
  try:
40
  # Initialize ResNet50
@@ -42,26 +52,33 @@ class SingleModelReID:
42
  self.model = nn.Sequential(*list(self.model.children())[:-1])
43
  self.model.to(self.device).eval()
44
 
45
- # Image preprocessing
46
  self.transform = transforms.Compose([
47
  transforms.ToPILImage(),
48
- transforms.Resize((224, 224)),
 
49
  transforms.ToTensor(),
50
  transforms.Normalize(
51
  mean=[0.485, 0.456, 0.406],
52
  std=[0.229, 0.224, 0.225]
53
  )
54
  ])
55
- print("ResNet50 ReID initialized successfully")
56
  except Exception as e:
57
  print(f"ResNet50 init error: {e}")
58
  self.model = None
59
 
60
  def extract_features(self, image: np.ndarray) -> Optional[np.ndarray]:
61
- """Extract ResNet50 features"""
62
  if self.model is None or image is None or image.size == 0:
63
  return None
64
 
 
 
 
 
 
 
65
  try:
66
  img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
67
  img_tensor = self.transform(img_rgb).unsqueeze(0).to(self.device)
@@ -77,10 +94,12 @@ class SingleModelReID:
77
  return None
78
 
79
  def match_or_register(self, track) -> Tuple[int, float]:
80
- """Simple matching without overengineering"""
81
- # Get latest detection with image
 
 
82
  detection = None
83
- for det in reversed(track.detections):
84
  if det.image_crop is not None:
85
  detection = det
86
  break
@@ -88,72 +107,151 @@ class SingleModelReID:
88
  if detection is None:
89
  return 0, 0.0
90
 
91
- # Check if track already has dog ID
 
 
 
 
92
  if track.track_id in self.track_to_dog:
93
- # Track continuity - keep same dog ID
94
  existing_dog_id = self.track_to_dog[track.track_id]
95
 
96
- # Update features if good quality
97
- features = self.extract_features(detection.image_crop)
98
- if features is not None:
99
- if existing_dog_id in self.dog_database:
100
- self.dog_database[existing_dog_id].append(
101
- DogFeatures(features=features, confidence=detection.confidence)
102
- )
103
- # Keep only last 10 features
104
- if len(self.dog_database[existing_dog_id]) > 10:
105
- self.dog_database[existing_dog_id] = self.dog_database[existing_dog_id][-10:]
 
 
 
 
 
 
 
 
106
 
107
- return existing_dog_id, detection.confidence
 
 
108
 
109
- # Extract features for new/lost track
110
  features = self.extract_features(detection.image_crop)
111
  if features is None:
112
  return 0, 0.0
113
 
114
- # Find best match using simple average similarity
115
  best_dog_id = None
116
  best_score = -1.0
 
117
 
118
  for dog_id, feature_list in self.dog_database.items():
119
  if not feature_list:
120
  continue
121
 
122
- # Simple average of similarities to recent features
 
 
 
 
 
 
 
123
  similarities = []
124
- for dog_feat in feature_list[-5:]: # Use last 5 features only
 
 
125
  sim = cosine_similarity(
126
  features.reshape(1, -1),
127
  dog_feat.features.reshape(1, -1)
128
  )[0, 0]
 
 
 
 
 
 
 
129
  similarities.append(sim)
 
130
 
131
- avg_similarity = np.mean(similarities)
 
 
 
 
 
 
 
 
132
 
133
  if avg_similarity > best_score:
134
  best_score = avg_similarity
135
  best_dog_id = dog_id
136
 
137
- # Decide if match or new dog
138
- if best_dog_id is not None and best_score >= self.similarity_threshold:
139
- # Match found
140
- self.dog_database[best_dog_id].append(
141
- DogFeatures(features=features, confidence=detection.confidence)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  )
143
- if len(self.dog_database[best_dog_id]) > 10:
144
- self.dog_database[best_dog_id] = self.dog_database[best_dog_id][-10:]
145
-
146
- self.track_to_dog[track.track_id] = best_dog_id
147
- return best_dog_id, best_score
148
- else:
149
- # Register new dog
150
- new_dog_id = self.next_dog_id
151
- self.next_dog_id += 1
152
- self.dog_database[new_dog_id] = [
153
- DogFeatures(features=features, confidence=detection.confidence)
154
- ]
155
- self.track_to_dog[track.track_id] = new_dog_id
156
- return new_dog_id, best_score
 
 
 
 
157
 
158
  def match_or_register_all(self, track) -> Dict:
159
  """Compatible interface"""
@@ -170,11 +268,16 @@ class SingleModelReID:
170
  """Reset all temporary data"""
171
  self.dog_database.clear()
172
  self.track_to_dog.clear()
 
 
173
  self.next_dog_id = 1
 
174
 
175
  def set_all_thresholds(self, threshold: float):
176
- """Set similarity threshold"""
177
- self.similarity_threshold = max(0.3, min(0.9, threshold))
 
 
178
 
179
  def get_statistics(self) -> Dict:
180
  """Get statistics"""
@@ -182,8 +285,10 @@ class SingleModelReID:
182
  'ResNet50': {
183
  'total_dogs': self.next_dog_id - 1,
184
  'dogs_in_database': len(self.dog_database),
185
- 'avg_processing_time': 0,
186
- 'threshold': self.similarity_threshold
 
 
187
  }
188
  }
189
 
 
1
  """
2
+ reid.py - Improved Single-Model Dog Re-Identification System
3
+ Enhanced with better feature matching and temporal consistency
4
  """
5
  import numpy as np
6
  import cv2
 
21
  features: np.ndarray
22
  confidence: float = 0
23
  quality_score: float = 0.5
24
+ frame_num: int = 0
25
 
26
  class SingleModelReID:
27
+ """Improved ReID with better matching strategies"""
28
 
29
  def __init__(self, device: str = 'cuda'):
30
  self.device = device if torch.cuda.is_available() else 'cpu'
 
31
 
32
+ # Adaptive thresholds
33
+ self.primary_threshold = 0.45 # Main matching threshold
34
+ self.secondary_threshold = 0.35 # Lower threshold for recent tracks
35
+ self.new_dog_threshold = 0.55 # Higher threshold to create new dog
36
+
37
+ # In-memory dog database
38
  self.dog_database = {} # dog_id -> list of features
39
  self.next_dog_id = 1
40
 
41
+ # Track to dog mapping with confidence history
42
  self.track_to_dog = {}
43
+ self.track_confidence = {} # track_id -> list of confidences
44
+
45
+ # Temporal consistency
46
+ self.recent_matches = {} # dog_id -> last_frame_seen
47
+ self.current_frame = 0
48
 
49
  try:
50
  # Initialize ResNet50
 
52
  self.model = nn.Sequential(*list(self.model.children())[:-1])
53
  self.model.to(self.device).eval()
54
 
55
+ # Enhanced preprocessing with augmentation options
56
  self.transform = transforms.Compose([
57
  transforms.ToPILImage(),
58
+ transforms.Resize((256, 256)), # Slightly larger for better features
59
+ transforms.CenterCrop(224),
60
  transforms.ToTensor(),
61
  transforms.Normalize(
62
  mean=[0.485, 0.456, 0.406],
63
  std=[0.229, 0.224, 0.225]
64
  )
65
  ])
66
+ print("Enhanced ResNet50 ReID initialized")
67
  except Exception as e:
68
  print(f"ResNet50 init error: {e}")
69
  self.model = None
70
 
71
  def extract_features(self, image: np.ndarray) -> Optional[np.ndarray]:
72
+ """Extract ResNet50 features with quality check"""
73
  if self.model is None or image is None or image.size == 0:
74
  return None
75
 
76
+ # Quality check - skip blurry images
77
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
78
+ laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
79
+ if laplacian_var < 50: # Too blurry
80
+ return None
81
+
82
  try:
83
  img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
84
  img_tensor = self.transform(img_rgb).unsqueeze(0).to(self.device)
 
94
  return None
95
 
96
  def match_or_register(self, track) -> Tuple[int, float]:
97
+ """Enhanced matching with temporal consistency"""
98
+ self.current_frame += 1
99
+
100
+ # Get latest good quality detection
101
  detection = None
102
+ for det in reversed(track.detections[-5:]): # Check last 5 detections
103
  if det.image_crop is not None:
104
  detection = det
105
  break
 
107
  if detection is None:
108
  return 0, 0.0
109
 
110
+ # Check track confidence history
111
+ if track.track_id not in self.track_confidence:
112
+ self.track_confidence[track.track_id] = []
113
+
114
+ # If track already has consistent dog ID
115
  if track.track_id in self.track_to_dog:
 
116
  existing_dog_id = self.track_to_dog[track.track_id]
117
 
118
+ # Update features periodically (not every frame to save memory)
119
+ if self.current_frame % 3 == 0:
120
+ features = self.extract_features(detection.image_crop)
121
+ if features is not None:
122
+ if existing_dog_id in self.dog_database:
123
+ # Add with frame number for temporal reference
124
+ self.dog_database[existing_dog_id].append(
125
+ DogFeatures(
126
+ features=features,
127
+ confidence=detection.confidence,
128
+ frame_num=self.current_frame
129
+ )
130
+ )
131
+ # Keep only recent and high-quality features
132
+ self._prune_features(existing_dog_id)
133
+
134
+ # Update recent matches
135
+ self.recent_matches[existing_dog_id] = self.current_frame
136
 
137
+ # Calculate running confidence
138
+ recent_conf = np.mean(self.track_confidence[track.track_id][-10:]) if self.track_confidence[track.track_id] else detection.confidence
139
+ return existing_dog_id, recent_conf
140
 
141
+ # Extract features for matching
142
  features = self.extract_features(detection.image_crop)
143
  if features is None:
144
  return 0, 0.0
145
 
146
+ # Find best match with adaptive thresholds
147
  best_dog_id = None
148
  best_score = -1.0
149
+ match_details = {}
150
 
151
  for dog_id, feature_list in self.dog_database.items():
152
  if not feature_list:
153
  continue
154
 
155
+ # Check temporal proximity (bonus for recently seen dogs)
156
+ recency_bonus = 0.0
157
+ if dog_id in self.recent_matches:
158
+ frames_since = self.current_frame - self.recent_matches[dog_id]
159
+ if frames_since < 30: # Within 1 second at 30fps
160
+ recency_bonus = 0.05 * (1 - frames_since / 30)
161
+
162
+ # Weighted similarity based on feature quality and recency
163
  similarities = []
164
+ weights = []
165
+
166
+ for dog_feat in feature_list[-8:]: # Use last 8 features
167
  sim = cosine_similarity(
168
  features.reshape(1, -1),
169
  dog_feat.features.reshape(1, -1)
170
  )[0, 0]
171
+
172
+ # Weight by confidence and recency
173
+ weight = dog_feat.confidence
174
+ if hasattr(dog_feat, 'frame_num'):
175
+ age = self.current_frame - dog_feat.frame_num
176
+ weight *= np.exp(-age / 100) # Exponential decay
177
+
178
  similarities.append(sim)
179
+ weights.append(weight)
180
 
181
+ # Weighted average
182
+ if weights:
183
+ weights = np.array(weights)
184
+ weights = weights / weights.sum()
185
+ avg_similarity = np.average(similarities, weights=weights) + recency_bonus
186
+ else:
187
+ avg_similarity = np.mean(similarities) + recency_bonus
188
+
189
+ match_details[dog_id] = avg_similarity
190
 
191
  if avg_similarity > best_score:
192
  best_score = avg_similarity
193
  best_dog_id = dog_id
194
 
195
+ # Adaptive threshold based on context
196
+ threshold = self.primary_threshold
197
+ if best_dog_id and best_dog_id in self.recent_matches:
198
+ # Lower threshold for recently seen dogs
199
+ if self.current_frame - self.recent_matches[best_dog_id] < 60:
200
+ threshold = self.secondary_threshold
201
+
202
+ # Decision logic
203
+ if best_dog_id is not None and best_score >= threshold:
204
+ # Match found - but verify it's not too different
205
+ if best_score < self.new_dog_threshold or len(match_details) < 3:
206
+ # Accept match
207
+ self.dog_database[best_dog_id].append(
208
+ DogFeatures(
209
+ features=features,
210
+ confidence=detection.confidence,
211
+ frame_num=self.current_frame
212
+ )
213
+ )
214
+ self._prune_features(best_dog_id)
215
+
216
+ self.track_to_dog[track.track_id] = best_dog_id
217
+ self.track_confidence[track.track_id].append(best_score)
218
+ self.recent_matches[best_dog_id] = self.current_frame
219
+
220
+ return best_dog_id, best_score
221
+ else:
222
+ # Score in ambiguous range - check if we should create new dog
223
+ second_best_score = sorted(match_details.values(), reverse=True)[1] if len(match_details) > 1 else 0
224
+ if best_score - second_best_score < 0.1:
225
+ # Too similar to multiple dogs - likely new dog
226
+ pass # Fall through to create new dog
227
+
228
+ # Register new dog
229
+ new_dog_id = self.next_dog_id
230
+ self.next_dog_id += 1
231
+ self.dog_database[new_dog_id] = [
232
+ DogFeatures(
233
+ features=features,
234
+ confidence=detection.confidence,
235
+ frame_num=self.current_frame
236
  )
237
+ ]
238
+ self.track_to_dog[track.track_id] = new_dog_id
239
+ self.track_confidence[track.track_id] = [1.0]
240
+ self.recent_matches[new_dog_id] = self.current_frame
241
+
242
+ return new_dog_id, 1.0
243
+
244
+ def _prune_features(self, dog_id: int):
245
+ """Keep only best recent features to save memory"""
246
+ if dog_id not in self.dog_database:
247
+ return
248
+
249
+ features = self.dog_database[dog_id]
250
+ if len(features) > 15:
251
+ # Sort by confidence and recency
252
+ features.sort(key=lambda x: x.confidence + (0.001 * x.frame_num), reverse=True)
253
+ # Keep top 10
254
+ self.dog_database[dog_id] = features[:10]
255
 
256
  def match_or_register_all(self, track) -> Dict:
257
  """Compatible interface"""
 
268
  """Reset all temporary data"""
269
  self.dog_database.clear()
270
  self.track_to_dog.clear()
271
+ self.track_confidence.clear()
272
+ self.recent_matches.clear()
273
  self.next_dog_id = 1
274
+ self.current_frame = 0
275
 
276
  def set_all_thresholds(self, threshold: float):
277
+ """Set similarity thresholds adaptively"""
278
+ self.primary_threshold = max(0.3, min(0.9, threshold))
279
+ self.secondary_threshold = max(0.25, self.primary_threshold - 0.1)
280
+ self.new_dog_threshold = min(0.9, self.primary_threshold + 0.1)
281
 
282
  def get_statistics(self) -> Dict:
283
  """Get statistics"""
 
285
  'ResNet50': {
286
  'total_dogs': self.next_dog_id - 1,
287
  'dogs_in_database': len(self.dog_database),
288
+ 'active_dogs': len([d for d, f in self.recent_matches.items()
289
+ if self.current_frame - f < 150]),
290
+ 'avg_features_per_dog': np.mean([len(f) for f in self.dog_database.values()]) if self.dog_database else 0,
291
+ 'threshold': self.primary_threshold
292
  }
293
  }
294