hiro1997 commited on
Commit
66840d1
·
verified ·
1 Parent(s): 5288900

Upload engines/trackers/person_tracker.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. engines/trackers/person_tracker.py +114 -2
engines/trackers/person_tracker.py CHANGED
@@ -15,6 +15,14 @@ try:
15
  except ImportError:
16
  logger.warning("deep-sort-realtime 未安装,人物追踪功能将不可用。安装方式: pip install deep-sort-realtime")
17
 
 
 
 
 
 
 
 
 
18
 
19
  def _is_gpu_available() -> bool:
20
  try:
@@ -40,7 +48,7 @@ class PersonTracker:
40
  self.sample_fps = sample_fps
41
  self.gpu = _is_gpu_available()
42
 
43
- # 追踪结果:{track_id: {frames: [frame_indices], bbox_avg: [cx, cy], embeddings: [emb1, emb2, ...]}}
44
  self.tracks: Dict[int, Dict[str, Any]] = {}
45
 
46
  # 视频信息
@@ -86,6 +94,85 @@ class PersonTracker:
86
  logger.warning(f"X-CLIP 模型加载失败: {e}")
87
  return None, None
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  def _encode_person_crop(self, frame: np.ndarray, bbox: List[float],
90
  xclip_model, xclip_processor) -> Optional[np.ndarray]:
91
  """裁剪人物区域并用 X-CLIP 视觉编码器提取嵌入
@@ -186,6 +273,14 @@ class PersonTracker:
186
  # 初始化 DeepSort 追踪器
187
  tracker = DeepSort(max_age=30, n_init=3, nn_budget=100)
188
 
 
 
 
 
 
 
 
 
189
  cap = cv2.VideoCapture(self.video_path)
190
  if not cap.isOpened():
191
  logger.error("无法打开视频文件")
@@ -243,6 +338,7 @@ class PersonTracker:
243
  "frames": [],
244
  "bbox_avg": [0.0, 0.0],
245
  "embeddings": [],
 
246
  "_cx_sum": 0.0,
247
  "_cy_sum": 0.0,
248
  }
@@ -265,6 +361,14 @@ class PersonTracker:
265
  if embedding is not None:
266
  self.tracks[track_id]["embeddings"].append(embedding)
267
 
 
 
 
 
 
 
 
 
268
  except Exception as e:
269
  logger.warning(f"DeepSort 追踪失败 (frame {frame_idx}): {e}")
270
 
@@ -284,10 +388,18 @@ class PersonTracker:
284
 
285
  cap.release()
286
 
287
- # 清理临时字段
288
  for track_id in self.tracks:
289
  self.tracks[track_id].pop("_cx_sum", None)
290
  self.tracks[track_id].pop("_cy_sum", None)
 
 
 
 
 
 
 
 
291
 
292
  # 过滤掉出现次数太少的追踪(少于 5 帧)
293
  self.tracks = {
 
15
  except ImportError:
16
  logger.warning("deep-sort-realtime 未安装,人物追踪功能将不可用。安装方式: pip install deep-sort-realtime")
17
 
18
+ # 尝试导入 PaddleOCR,不可用时优雅降级
19
+ _paddleocr_available = False
20
+ try:
21
+ from paddleocr import PaddleOCR
22
+ _paddleocr_available = True
23
+ except ImportError:
24
+ logger.debug("paddleocr 未安装,球衣号码识别功能将不可用。安装方式: pip install paddleocr")
25
+
26
 
27
  def _is_gpu_available() -> bool:
28
  try:
 
48
  self.sample_fps = sample_fps
49
  self.gpu = _is_gpu_available()
50
 
51
+ # 追踪结果:{track_id: {frames: [frame_indices], bbox_avg: [cx, cy], embeddings: [emb1, emb2, ...], jersey_numbers: [str, ...]}}
52
  self.tracks: Dict[int, Dict[str, Any]] = {}
53
 
54
  # 视频信息
 
94
  logger.warning(f"X-CLIP 模型加载失败: {e}")
95
  return None, None
96
 
97
+ def _get_ocr_engine(self):
98
+ """获取 PaddleOCR 引擎(懒加载)"""
99
+ if not _paddleocr_available:
100
+ return None
101
+ try:
102
+ ocr = PaddleOCR(
103
+ use_angle_cls=False,
104
+ lang="en",
105
+ use_gpu=self.gpu,
106
+ show_log=False,
107
+ det_model_dir=None, # 使用默认模型
108
+ rec_model_dir=None,
109
+ )
110
+ return ocr
111
+ except Exception as e:
112
+ logger.warning(f"PaddleOCR 初始化失败: {e}")
113
+ return None
114
+
115
+ def _recognize_jersey_number(self, frame: np.ndarray, bbox: List[float], ocr_engine) -> Optional[str]:
116
+ """识别人物球衣号码
117
+
118
+ 从人物上半身区域裁剪,用 PaddleOCR 识别数字。
119
+
120
+ Args:
121
+ frame: 原始帧 (H, W, 3) BGR
122
+ bbox: [left, top, width, height]
123
+ ocr_engine: PaddleOCR 实例
124
+
125
+ Returns:
126
+ 识别到的号码字符串 或 None
127
+ """
128
+ if ocr_engine is None:
129
+ return None
130
+
131
+ try:
132
+ h, w = frame.shape[:2]
133
+ left = max(0, int(bbox[0]))
134
+ top = max(0, int(bbox[1]))
135
+ right = min(w, int(bbox[0] + bbox[2]))
136
+ bottom = min(h, int(bbox[1] + bbox[3]))
137
+
138
+ if right - left < 20 or bottom - top < 40:
139
+ return None
140
+
141
+ # 裁剪上半身区域(球衣号码通常在胸部位置)
142
+ torso_top = top
143
+ torso_bottom = top + int((bottom - top) * 0.6) # 上 60%
144
+ torso_left = left + int((right - left) * 0.15) # 去掉边缘
145
+ torso_right = right - int((right - left) * 0.15)
146
+
147
+ if torso_right - torso_left < 10 or torso_bottom - torso_top < 10:
148
+ return None
149
+
150
+ crop = frame[torso_top:torso_bottom, torso_left:torso_right]
151
+ if crop.size == 0:
152
+ return None
153
+
154
+ # PaddleOCR 识别
155
+ result = ocr_engine.ocr(crop, cls=False)
156
+ if not result or not result[0]:
157
+ return None
158
+
159
+ # 从识别结果中提取纯数字文本
160
+ for line in result[0]:
161
+ text = line[1][0].strip()
162
+ confidence = line[1][1]
163
+ # 过滤:只接受 1-2 位纯数字,置信度 > 0.5
164
+ if confidence < 0.5:
165
+ continue
166
+ digits = "".join(c for c in text if c.isdigit())
167
+ if 1 <= len(digits) <= 2 and digits != "0":
168
+ return digits
169
+
170
+ return None
171
+
172
+ except Exception as e:
173
+ logger.debug(f"球衣号码识别失败: {e}")
174
+ return None
175
+
176
  def _encode_person_crop(self, frame: np.ndarray, bbox: List[float],
177
  xclip_model, xclip_processor) -> Optional[np.ndarray]:
178
  """裁剪人物区域并用 X-CLIP 视觉编码器提取嵌入
 
273
  # 初始化 DeepSort 追踪器
274
  tracker = DeepSort(max_age=30, n_init=3, nn_budget=100)
275
 
276
+ # 初始化 PaddleOCR(球衣号码识别)
277
+ ocr_engine = None
278
+ if _paddleocr_available:
279
+ _report(0.12, "初始化 PaddleOCR 球衣号码识别...")
280
+ ocr_engine = self._get_ocr_engine()
281
+ if ocr_engine is None:
282
+ logger.info("PaddleOCR 不可用,跳过球衣号码识别")
283
+
284
  cap = cv2.VideoCapture(self.video_path)
285
  if not cap.isOpened():
286
  logger.error("无法打开视频文件")
 
338
  "frames": [],
339
  "bbox_avg": [0.0, 0.0],
340
  "embeddings": [],
341
+ "jersey_numbers": [],
342
  "_cx_sum": 0.0,
343
  "_cy_sum": 0.0,
344
  }
 
361
  if embedding is not None:
362
  self.tracks[track_id]["embeddings"].append(embedding)
363
 
364
+ # 球衣号码识别(每 10 帧采样一次,OCR 较慢)
365
+ if ocr_engine is not None and n % 10 == 1:
366
+ jersey_num = self._recognize_jersey_number(
367
+ frame, [ltrb[0], ltrb[1], bbox_w, bbox_h], ocr_engine,
368
+ )
369
+ if jersey_num is not None:
370
+ self.tracks[track_id]["jersey_numbers"].append(jersey_num)
371
+
372
  except Exception as e:
373
  logger.warning(f"DeepSort 追踪失败 (frame {frame_idx}): {e}")
374
 
 
388
 
389
  cap.release()
390
 
391
+ # 清理临时字段,去重球衣号码
392
  for track_id in self.tracks:
393
  self.tracks[track_id].pop("_cx_sum", None)
394
  self.tracks[track_id].pop("_cy_sum", None)
395
+ # 球衣号码去重,保留出现次数最多的
396
+ jns = self.tracks[track_id].get("jersey_numbers", [])
397
+ if jns:
398
+ from collections import Counter
399
+ most_common = Counter(jns).most_common(1)[0][0]
400
+ self.tracks[track_id]["jersey_number"] = most_common
401
+ else:
402
+ self.tracks[track_id]["jersey_number"] = None
403
 
404
  # 过滤掉出现次数太少的追踪(少于 5 帧)
405
  self.tracks = {